github.com/MetalBlockchain/metalgo@v1.11.9/snow/networking/sender/sender_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package sender
     5  
     6  import (
     7  	"context"
     8  	"math/rand"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/stretchr/testify/require"
    15  	"go.uber.org/mock/gomock"
    16  
    17  	"github.com/MetalBlockchain/metalgo/ids"
    18  	"github.com/MetalBlockchain/metalgo/message"
    19  	"github.com/MetalBlockchain/metalgo/network/p2p"
    20  	"github.com/MetalBlockchain/metalgo/snow"
    21  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    22  	"github.com/MetalBlockchain/metalgo/snow/networking/benchlist"
    23  	"github.com/MetalBlockchain/metalgo/snow/networking/handler"
    24  	"github.com/MetalBlockchain/metalgo/snow/networking/router"
    25  	"github.com/MetalBlockchain/metalgo/snow/networking/timeout"
    26  	"github.com/MetalBlockchain/metalgo/snow/networking/tracker"
    27  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    28  	"github.com/MetalBlockchain/metalgo/snow/validators"
    29  	"github.com/MetalBlockchain/metalgo/subnets"
    30  	"github.com/MetalBlockchain/metalgo/utils/constants"
    31  	"github.com/MetalBlockchain/metalgo/utils/logging"
    32  	"github.com/MetalBlockchain/metalgo/utils/math/meter"
    33  	"github.com/MetalBlockchain/metalgo/utils/resource"
    34  	"github.com/MetalBlockchain/metalgo/utils/set"
    35  	"github.com/MetalBlockchain/metalgo/utils/timer"
    36  	"github.com/MetalBlockchain/metalgo/version"
    37  
    38  	p2ppb "github.com/MetalBlockchain/metalgo/proto/pb/p2p"
    39  	commontracker "github.com/MetalBlockchain/metalgo/snow/engine/common/tracker"
    40  )
    41  
    42  const testThreadPoolSize = 2
    43  
    44  func TestTimeout(t *testing.T) {
    45  	require := require.New(t)
    46  
    47  	snowCtx := snowtest.Context(t, snowtest.CChainID)
    48  	ctx := snowtest.ConsensusContext(snowCtx)
    49  	vdrs := validators.NewManager()
    50  	require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1))
    51  	benchlist := benchlist.NewNoBenchlist()
    52  	tm, err := timeout.NewManager(
    53  		&timer.AdaptiveTimeoutConfig{
    54  			InitialTimeout:     time.Millisecond,
    55  			MinimumTimeout:     time.Millisecond,
    56  			MaximumTimeout:     10 * time.Second,
    57  			TimeoutHalflife:    5 * time.Minute,
    58  			TimeoutCoefficient: 1.25,
    59  		},
    60  		benchlist,
    61  		prometheus.NewRegistry(),
    62  		prometheus.NewRegistry(),
    63  	)
    64  	require.NoError(err)
    65  	go tm.Dispatch()
    66  
    67  	chainRouter := router.ChainRouter{}
    68  
    69  	metrics := prometheus.NewRegistry()
    70  	mc, err := message.NewCreator(
    71  		logging.NoLog{},
    72  		metrics,
    73  		constants.DefaultNetworkCompressionType,
    74  		10*time.Second,
    75  	)
    76  	require.NoError(err)
    77  
    78  	require.NoError(chainRouter.Initialize(
    79  		ids.EmptyNodeID,
    80  		logging.NoLog{},
    81  		tm,
    82  		time.Second,
    83  		set.Set[ids.ID]{},
    84  		true,
    85  		set.Set[ids.ID]{},
    86  		nil,
    87  		router.HealthConfig{},
    88  		prometheus.NewRegistry(),
    89  	))
    90  
    91  	externalSender := &ExternalSenderTest{TB: t}
    92  	externalSender.Default(false)
    93  
    94  	sender, err := New(
    95  		ctx,
    96  		mc,
    97  		externalSender,
    98  		&chainRouter,
    99  		tm,
   100  		p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   101  		subnets.New(ctx.NodeID, subnets.Config{}),
   102  		prometheus.NewRegistry(),
   103  	)
   104  	require.NoError(err)
   105  
   106  	ctx2 := snowtest.ConsensusContext(snowCtx)
   107  	resourceTracker, err := tracker.NewResourceTracker(
   108  		prometheus.NewRegistry(),
   109  		resource.NoUsage,
   110  		meter.ContinuousFactory{},
   111  		time.Second,
   112  	)
   113  	require.NoError(err)
   114  
   115  	p2pTracker, err := p2p.NewPeerTracker(
   116  		logging.NoLog{},
   117  		"",
   118  		prometheus.NewRegistry(),
   119  		nil,
   120  		version.CurrentApp,
   121  	)
   122  	require.NoError(err)
   123  
   124  	h, err := handler.New(
   125  		ctx2,
   126  		vdrs,
   127  		nil,
   128  		time.Hour,
   129  		testThreadPoolSize,
   130  		resourceTracker,
   131  		validators.UnhandledSubnetConnector,
   132  		subnets.New(ctx.NodeID, subnets.Config{}),
   133  		commontracker.NewPeers(),
   134  		p2pTracker,
   135  		prometheus.NewRegistry(),
   136  	)
   137  	require.NoError(err)
   138  
   139  	bootstrapper := &common.BootstrapperTest{
   140  		EngineTest: common.EngineTest{
   141  			T: t,
   142  		},
   143  	}
   144  	bootstrapper.Default(true)
   145  	bootstrapper.CantGossip = false
   146  	bootstrapper.ContextF = func() *snow.ConsensusContext {
   147  		return ctx
   148  	}
   149  	bootstrapper.ConnectedF = func(context.Context, ids.NodeID, *version.Application) error {
   150  		return nil
   151  	}
   152  	h.SetEngineManager(&handler.EngineManager{
   153  		Avalanche: &handler.Engine{
   154  			StateSyncer:  nil,
   155  			Bootstrapper: bootstrapper,
   156  			Consensus:    nil,
   157  		},
   158  		Snowman: &handler.Engine{
   159  			StateSyncer:  nil,
   160  			Bootstrapper: bootstrapper,
   161  			Consensus:    nil,
   162  		},
   163  	})
   164  	ctx2.State.Set(snow.EngineState{
   165  		Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   166  		State: snow.Bootstrapping, // assumed bootstrap is ongoing
   167  	})
   168  
   169  	chainRouter.AddChain(context.Background(), h)
   170  
   171  	bootstrapper.StartF = func(context.Context, uint32) error {
   172  		return nil
   173  	}
   174  	h.Start(context.Background(), false)
   175  
   176  	var (
   177  		wg           = sync.WaitGroup{}
   178  		vdrIDs       = set.Set[ids.NodeID]{}
   179  		chains       = set.Set[ids.ID]{}
   180  		requestID    uint32
   181  		failedLock   sync.Mutex
   182  		failedVDRs   = set.Set[ids.NodeID]{}
   183  		failedChains = set.Set[ids.ID]{}
   184  	)
   185  
   186  	cancelledCtx, cancel := context.WithCancel(context.Background())
   187  	cancel()
   188  
   189  	failed := func(ctx context.Context, nodeID ids.NodeID, _ uint32) error {
   190  		require.NoError(ctx.Err())
   191  
   192  		failedLock.Lock()
   193  		defer failedLock.Unlock()
   194  
   195  		failedVDRs.Add(nodeID)
   196  		wg.Done()
   197  		return nil
   198  	}
   199  
   200  	bootstrapper.GetStateSummaryFrontierFailedF = failed
   201  	bootstrapper.GetAcceptedStateSummaryFailedF = failed
   202  	bootstrapper.GetAcceptedFrontierFailedF = failed
   203  	bootstrapper.GetAcceptedFailedF = failed
   204  	bootstrapper.GetAncestorsFailedF = failed
   205  	bootstrapper.GetFailedF = failed
   206  	bootstrapper.QueryFailedF = failed
   207  	bootstrapper.AppRequestFailedF = func(ctx context.Context, nodeID ids.NodeID, _ uint32, _ *common.AppError) error {
   208  		require.NoError(ctx.Err())
   209  
   210  		failedLock.Lock()
   211  		defer failedLock.Unlock()
   212  
   213  		failedVDRs.Add(nodeID)
   214  		wg.Done()
   215  		return nil
   216  	}
   217  
   218  	bootstrapper.CrossChainAppRequestFailedF = func(ctx context.Context, chainID ids.ID, _ uint32, _ *common.AppError) error {
   219  		require.NoError(ctx.Err())
   220  
   221  		failedLock.Lock()
   222  		defer failedLock.Unlock()
   223  
   224  		failedChains.Add(chainID)
   225  		wg.Done()
   226  		return nil
   227  	}
   228  
   229  	sendAll := func() {
   230  		{
   231  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   232  			vdrIDs.Union(nodeIDs)
   233  			wg.Add(1)
   234  			requestID++
   235  			sender.SendGetStateSummaryFrontier(cancelledCtx, nodeIDs, requestID)
   236  		}
   237  		{
   238  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   239  			vdrIDs.Union(nodeIDs)
   240  			wg.Add(1)
   241  			requestID++
   242  			sender.SendGetAcceptedStateSummary(cancelledCtx, nodeIDs, requestID, nil)
   243  		}
   244  		{
   245  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   246  			vdrIDs.Union(nodeIDs)
   247  			wg.Add(1)
   248  			requestID++
   249  			sender.SendGetAcceptedFrontier(cancelledCtx, nodeIDs, requestID)
   250  		}
   251  		{
   252  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   253  			vdrIDs.Union(nodeIDs)
   254  			wg.Add(1)
   255  			requestID++
   256  			sender.SendGetAccepted(cancelledCtx, nodeIDs, requestID, nil)
   257  		}
   258  		{
   259  			nodeID := ids.GenerateTestNodeID()
   260  			vdrIDs.Add(nodeID)
   261  			wg.Add(1)
   262  			requestID++
   263  			sender.SendGetAncestors(cancelledCtx, nodeID, requestID, ids.Empty)
   264  		}
   265  		{
   266  			nodeID := ids.GenerateTestNodeID()
   267  			vdrIDs.Add(nodeID)
   268  			wg.Add(1)
   269  			requestID++
   270  			sender.SendGet(cancelledCtx, nodeID, requestID, ids.Empty)
   271  		}
   272  		{
   273  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   274  			vdrIDs.Union(nodeIDs)
   275  			wg.Add(1)
   276  			requestID++
   277  			sender.SendPullQuery(cancelledCtx, nodeIDs, requestID, ids.Empty, 0)
   278  		}
   279  		{
   280  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   281  			vdrIDs.Union(nodeIDs)
   282  			wg.Add(1)
   283  			requestID++
   284  			sender.SendPushQuery(cancelledCtx, nodeIDs, requestID, nil, 0)
   285  		}
   286  		{
   287  			nodeIDs := set.Of(ids.GenerateTestNodeID())
   288  			vdrIDs.Union(nodeIDs)
   289  			wg.Add(1)
   290  			requestID++
   291  			require.NoError(sender.SendAppRequest(cancelledCtx, nodeIDs, requestID, nil))
   292  		}
   293  		{
   294  			chainID := ids.GenerateTestID()
   295  			chains.Add(chainID)
   296  			wg.Add(1)
   297  			requestID++
   298  			require.NoError(sender.SendCrossChainAppRequest(cancelledCtx, chainID, requestID, nil))
   299  		}
   300  	}
   301  
   302  	// Send messages to disconnected peers
   303  	externalSender.SendF = func(message.OutboundMessage, common.SendConfig, ids.ID, subnets.Allower) set.Set[ids.NodeID] {
   304  		return nil
   305  	}
   306  	sendAll()
   307  
   308  	// Send messages to connected peers
   309  	externalSender.SendF = func(_ message.OutboundMessage, config common.SendConfig, _ ids.ID, _ subnets.Allower) set.Set[ids.NodeID] {
   310  		return config.NodeIDs
   311  	}
   312  	sendAll()
   313  
   314  	wg.Wait()
   315  
   316  	require.Equal(vdrIDs, failedVDRs)
   317  	require.Equal(chains, failedChains)
   318  }
   319  
   320  func TestReliableMessages(t *testing.T) {
   321  	require := require.New(t)
   322  
   323  	snowCtx := snowtest.Context(t, snowtest.CChainID)
   324  	ctx := snowtest.ConsensusContext(snowCtx)
   325  	vdrs := validators.NewManager()
   326  	require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.BuildTestNodeID([]byte{1}), nil, ids.Empty, 1))
   327  	benchlist := benchlist.NewNoBenchlist()
   328  	tm, err := timeout.NewManager(
   329  		&timer.AdaptiveTimeoutConfig{
   330  			InitialTimeout:     time.Millisecond,
   331  			MinimumTimeout:     time.Millisecond,
   332  			MaximumTimeout:     time.Millisecond,
   333  			TimeoutHalflife:    5 * time.Minute,
   334  			TimeoutCoefficient: 1.25,
   335  		},
   336  		benchlist,
   337  		prometheus.NewRegistry(),
   338  		prometheus.NewRegistry(),
   339  	)
   340  	require.NoError(err)
   341  
   342  	go tm.Dispatch()
   343  
   344  	chainRouter := router.ChainRouter{}
   345  
   346  	metrics := prometheus.NewRegistry()
   347  	mc, err := message.NewCreator(
   348  		logging.NoLog{},
   349  		metrics,
   350  		constants.DefaultNetworkCompressionType,
   351  		10*time.Second,
   352  	)
   353  	require.NoError(err)
   354  
   355  	require.NoError(chainRouter.Initialize(
   356  		ids.EmptyNodeID,
   357  		logging.NoLog{},
   358  		tm,
   359  		time.Second,
   360  		set.Set[ids.ID]{},
   361  		true,
   362  		set.Set[ids.ID]{},
   363  		nil,
   364  		router.HealthConfig{},
   365  		prometheus.NewRegistry(),
   366  	))
   367  
   368  	externalSender := &ExternalSenderTest{TB: t}
   369  	externalSender.Default(false)
   370  
   371  	sender, err := New(
   372  		ctx,
   373  		mc,
   374  		externalSender,
   375  		&chainRouter,
   376  		tm,
   377  		p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   378  		subnets.New(ctx.NodeID, subnets.Config{}),
   379  		prometheus.NewRegistry(),
   380  	)
   381  	require.NoError(err)
   382  
   383  	ctx2 := snowtest.ConsensusContext(snowCtx)
   384  	resourceTracker, err := tracker.NewResourceTracker(
   385  		prometheus.NewRegistry(),
   386  		resource.NoUsage,
   387  		meter.ContinuousFactory{},
   388  		time.Second,
   389  	)
   390  	require.NoError(err)
   391  
   392  	p2pTracker, err := p2p.NewPeerTracker(
   393  		logging.NoLog{},
   394  		"",
   395  		prometheus.NewRegistry(),
   396  		nil,
   397  		version.CurrentApp,
   398  	)
   399  	require.NoError(err)
   400  
   401  	h, err := handler.New(
   402  		ctx2,
   403  		vdrs,
   404  		nil,
   405  		1,
   406  		testThreadPoolSize,
   407  		resourceTracker,
   408  		validators.UnhandledSubnetConnector,
   409  		subnets.New(ctx.NodeID, subnets.Config{}),
   410  		commontracker.NewPeers(),
   411  		p2pTracker,
   412  		prometheus.NewRegistry(),
   413  	)
   414  	require.NoError(err)
   415  
   416  	bootstrapper := &common.BootstrapperTest{
   417  		EngineTest: common.EngineTest{
   418  			T: t,
   419  		},
   420  	}
   421  	bootstrapper.Default(true)
   422  	bootstrapper.CantGossip = false
   423  	bootstrapper.ContextF = func() *snow.ConsensusContext {
   424  		return ctx2
   425  	}
   426  	bootstrapper.ConnectedF = func(context.Context, ids.NodeID, *version.Application) error {
   427  		return nil
   428  	}
   429  	queriesToSend := 1000
   430  	awaiting := make([]chan struct{}, queriesToSend)
   431  	for i := 0; i < queriesToSend; i++ {
   432  		awaiting[i] = make(chan struct{}, 1)
   433  	}
   434  	bootstrapper.QueryFailedF = func(_ context.Context, _ ids.NodeID, reqID uint32) error {
   435  		close(awaiting[int(reqID)])
   436  		return nil
   437  	}
   438  	bootstrapper.CantGossip = false
   439  	h.SetEngineManager(&handler.EngineManager{
   440  		Avalanche: &handler.Engine{
   441  			StateSyncer:  nil,
   442  			Bootstrapper: bootstrapper,
   443  			Consensus:    nil,
   444  		},
   445  		Snowman: &handler.Engine{
   446  			StateSyncer:  nil,
   447  			Bootstrapper: bootstrapper,
   448  			Consensus:    nil,
   449  		},
   450  	})
   451  	ctx2.State.Set(snow.EngineState{
   452  		Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   453  		State: snow.Bootstrapping, // assumed bootstrap is ongoing
   454  	})
   455  
   456  	chainRouter.AddChain(context.Background(), h)
   457  
   458  	bootstrapper.StartF = func(context.Context, uint32) error {
   459  		return nil
   460  	}
   461  	h.Start(context.Background(), false)
   462  
   463  	go func() {
   464  		for i := 0; i < queriesToSend; i++ {
   465  			vdrIDs := set.Of(ids.BuildTestNodeID([]byte{1}))
   466  
   467  			sender.SendPullQuery(context.Background(), vdrIDs, uint32(i), ids.Empty, 0)
   468  			time.Sleep(time.Duration(rand.Float64() * float64(time.Microsecond))) // #nosec G404
   469  		}
   470  	}()
   471  
   472  	for _, await := range awaiting {
   473  		<-await
   474  	}
   475  }
   476  
   477  func TestReliableMessagesToMyself(t *testing.T) {
   478  	require := require.New(t)
   479  
   480  	benchlist := benchlist.NewNoBenchlist()
   481  	snowCtx := snowtest.Context(t, snowtest.CChainID)
   482  	ctx := snowtest.ConsensusContext(snowCtx)
   483  	vdrs := validators.NewManager()
   484  	require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1))
   485  	tm, err := timeout.NewManager(
   486  		&timer.AdaptiveTimeoutConfig{
   487  			InitialTimeout:     10 * time.Millisecond,
   488  			MinimumTimeout:     10 * time.Millisecond,
   489  			MaximumTimeout:     10 * time.Millisecond, // Timeout fires immediately
   490  			TimeoutHalflife:    5 * time.Minute,
   491  			TimeoutCoefficient: 1.25,
   492  		},
   493  		benchlist,
   494  		prometheus.NewRegistry(),
   495  		prometheus.NewRegistry(),
   496  	)
   497  	require.NoError(err)
   498  
   499  	go tm.Dispatch()
   500  
   501  	chainRouter := router.ChainRouter{}
   502  
   503  	metrics := prometheus.NewRegistry()
   504  	mc, err := message.NewCreator(
   505  		logging.NoLog{},
   506  		metrics,
   507  		constants.DefaultNetworkCompressionType,
   508  		10*time.Second,
   509  	)
   510  	require.NoError(err)
   511  
   512  	require.NoError(chainRouter.Initialize(
   513  		ids.EmptyNodeID,
   514  		logging.NoLog{},
   515  		tm,
   516  		time.Second,
   517  		set.Set[ids.ID]{},
   518  		true,
   519  		set.Set[ids.ID]{},
   520  		nil,
   521  		router.HealthConfig{},
   522  		prometheus.NewRegistry(),
   523  	))
   524  
   525  	externalSender := &ExternalSenderTest{TB: t}
   526  	externalSender.Default(false)
   527  
   528  	sender, err := New(
   529  		ctx,
   530  		mc,
   531  		externalSender,
   532  		&chainRouter,
   533  		tm,
   534  		p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   535  		subnets.New(ctx.NodeID, subnets.Config{}),
   536  		prometheus.NewRegistry(),
   537  	)
   538  	require.NoError(err)
   539  
   540  	ctx2 := snowtest.ConsensusContext(snowCtx)
   541  	resourceTracker, err := tracker.NewResourceTracker(
   542  		prometheus.NewRegistry(),
   543  		resource.NoUsage,
   544  		meter.ContinuousFactory{},
   545  		time.Second,
   546  	)
   547  	require.NoError(err)
   548  
   549  	p2pTracker, err := p2p.NewPeerTracker(
   550  		logging.NoLog{},
   551  		"",
   552  		prometheus.NewRegistry(),
   553  		nil,
   554  		version.CurrentApp,
   555  	)
   556  	require.NoError(err)
   557  
   558  	h, err := handler.New(
   559  		ctx2,
   560  		vdrs,
   561  		nil,
   562  		time.Second,
   563  		testThreadPoolSize,
   564  		resourceTracker,
   565  		validators.UnhandledSubnetConnector,
   566  		subnets.New(ctx.NodeID, subnets.Config{}),
   567  		commontracker.NewPeers(),
   568  		p2pTracker,
   569  		prometheus.NewRegistry(),
   570  	)
   571  	require.NoError(err)
   572  
   573  	bootstrapper := &common.BootstrapperTest{
   574  		EngineTest: common.EngineTest{
   575  			T: t,
   576  		},
   577  	}
   578  	bootstrapper.Default(true)
   579  	bootstrapper.CantGossip = false
   580  	bootstrapper.ContextF = func() *snow.ConsensusContext {
   581  		return ctx2
   582  	}
   583  	bootstrapper.ConnectedF = func(context.Context, ids.NodeID, *version.Application) error {
   584  		return nil
   585  	}
   586  	queriesToSend := 2
   587  	awaiting := make([]chan struct{}, queriesToSend)
   588  	for i := 0; i < queriesToSend; i++ {
   589  		awaiting[i] = make(chan struct{}, 1)
   590  	}
   591  	bootstrapper.QueryFailedF = func(_ context.Context, _ ids.NodeID, reqID uint32) error {
   592  		close(awaiting[int(reqID)])
   593  		return nil
   594  	}
   595  	h.SetEngineManager(&handler.EngineManager{
   596  		Avalanche: &handler.Engine{
   597  			StateSyncer:  nil,
   598  			Bootstrapper: bootstrapper,
   599  			Consensus:    nil,
   600  		},
   601  		Snowman: &handler.Engine{
   602  			StateSyncer:  nil,
   603  			Bootstrapper: bootstrapper,
   604  			Consensus:    nil,
   605  		},
   606  	})
   607  	ctx2.State.Set(snow.EngineState{
   608  		Type:  p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   609  		State: snow.Bootstrapping, // assumed bootstrap is ongoing
   610  	})
   611  
   612  	chainRouter.AddChain(context.Background(), h)
   613  
   614  	bootstrapper.StartF = func(context.Context, uint32) error {
   615  		return nil
   616  	}
   617  	h.Start(context.Background(), false)
   618  
   619  	go func() {
   620  		for i := 0; i < queriesToSend; i++ {
   621  			// Send a pull query to some random peer that won't respond
   622  			// because they don't exist. This will almost immediately trigger
   623  			// a query failed message
   624  			vdrIDs := set.Of(ids.GenerateTestNodeID())
   625  			sender.SendPullQuery(context.Background(), vdrIDs, uint32(i), ids.Empty, 0)
   626  		}
   627  	}()
   628  
   629  	for _, await := range awaiting {
   630  		<-await
   631  	}
   632  }
   633  
   634  func TestSender_Bootstrap_Requests(t *testing.T) {
   635  	var (
   636  		successNodeID = ids.GenerateTestNodeID()
   637  		failedNodeID  = ids.GenerateTestNodeID()
   638  		deadline      = time.Second
   639  		requestID     = uint32(1337)
   640  		heights       = []uint64{1, 2, 3}
   641  		containerIDs  = []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()}
   642  	)
   643  	snowCtx := snowtest.Context(t, snowtest.PChainID)
   644  	ctx := snowtest.ConsensusContext(snowCtx)
   645  
   646  	type test struct {
   647  		name                    string
   648  		failedMsgF              func(nodeID ids.NodeID) message.InboundMessage
   649  		assertMsgToMyself       func(require *require.Assertions, msg message.InboundMessage)
   650  		expectedResponseOp      message.Op
   651  		setMsgCreatorExpect     func(msgCreator *message.MockOutboundMsgBuilder)
   652  		setExternalSenderExpect func(externalSender *MockExternalSender)
   653  		sendF                   func(require *require.Assertions, sender common.Sender, nodeIDs set.Set[ids.NodeID])
   654  	}
   655  
   656  	tests := []test{
   657  		{
   658  			name: "GetStateSummaryFrontier",
   659  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
   660  				return message.InternalGetStateSummaryFrontierFailed(
   661  					nodeID,
   662  					ctx.ChainID,
   663  					requestID,
   664  				)
   665  			},
   666  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   667  				require.IsType(&p2ppb.GetStateSummaryFrontier{}, msg.Message())
   668  				innerMsg := msg.Message().(*p2ppb.GetStateSummaryFrontier)
   669  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   670  				require.Equal(requestID, innerMsg.RequestId)
   671  				require.Equal(uint64(deadline), innerMsg.Deadline)
   672  			},
   673  			expectedResponseOp: message.StateSummaryFrontierOp,
   674  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   675  				msgCreator.EXPECT().GetStateSummaryFrontier(
   676  					ctx.ChainID,
   677  					requestID,
   678  					deadline,
   679  				).Return(nil, nil)
   680  			},
   681  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   682  				externalSender.EXPECT().Send(
   683  					gomock.Any(), // Outbound message
   684  					common.SendConfig{
   685  						// Note [myNodeID] is not in this set
   686  						NodeIDs: set.Of(successNodeID, failedNodeID),
   687  					},
   688  					ctx.SubnetID, // Subnet ID
   689  					gomock.Any(),
   690  				).Return(set.Of(successNodeID))
   691  			},
   692  			sendF: func(_ *require.Assertions, sender common.Sender, nodeIDs set.Set[ids.NodeID]) {
   693  				sender.SendGetStateSummaryFrontier(
   694  					context.Background(),
   695  					nodeIDs,
   696  					requestID,
   697  				)
   698  			},
   699  		},
   700  		{
   701  			name: "GetAcceptedStateSummary",
   702  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
   703  				return message.InternalGetAcceptedStateSummaryFailed(
   704  					nodeID,
   705  					ctx.ChainID,
   706  					requestID,
   707  				)
   708  			},
   709  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   710  				require.IsType(&p2ppb.GetAcceptedStateSummary{}, msg.Message())
   711  				innerMsg := msg.Message().(*p2ppb.GetAcceptedStateSummary)
   712  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   713  				require.Equal(requestID, innerMsg.RequestId)
   714  				require.Equal(uint64(deadline), innerMsg.Deadline)
   715  				require.Equal(heights, innerMsg.Heights)
   716  			},
   717  			expectedResponseOp: message.AcceptedStateSummaryOp,
   718  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   719  				msgCreator.EXPECT().GetAcceptedStateSummary(
   720  					ctx.ChainID,
   721  					requestID,
   722  					deadline,
   723  					heights,
   724  				).Return(nil, nil)
   725  			},
   726  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   727  				externalSender.EXPECT().Send(
   728  					gomock.Any(), // Outbound message
   729  					common.SendConfig{
   730  						// Note [myNodeID] is not in this set
   731  						NodeIDs: set.Of(successNodeID, failedNodeID),
   732  					},
   733  					ctx.SubnetID, // Subnet ID
   734  					gomock.Any(),
   735  				).Return(set.Of(successNodeID))
   736  			},
   737  			sendF: func(_ *require.Assertions, sender common.Sender, nodeIDs set.Set[ids.NodeID]) {
   738  				sender.SendGetAcceptedStateSummary(context.Background(), nodeIDs, requestID, heights)
   739  			},
   740  		},
   741  		{
   742  			name: "GetAcceptedFrontier",
   743  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
   744  				return message.InternalGetAcceptedFrontierFailed(
   745  					nodeID,
   746  					ctx.ChainID,
   747  					requestID,
   748  				)
   749  			},
   750  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   751  				require.IsType(&p2ppb.GetAcceptedFrontier{}, msg.Message())
   752  				innerMsg := msg.Message().(*p2ppb.GetAcceptedFrontier)
   753  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   754  				require.Equal(requestID, innerMsg.RequestId)
   755  				require.Equal(uint64(deadline), innerMsg.Deadline)
   756  			},
   757  			expectedResponseOp: message.AcceptedFrontierOp,
   758  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   759  				msgCreator.EXPECT().GetAcceptedFrontier(
   760  					ctx.ChainID,
   761  					requestID,
   762  					deadline,
   763  				).Return(nil, nil)
   764  			},
   765  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   766  				externalSender.EXPECT().Send(
   767  					gomock.Any(), // Outbound message
   768  					common.SendConfig{
   769  						// Note [myNodeID] is not in this set
   770  						NodeIDs: set.Of(successNodeID, failedNodeID),
   771  					},
   772  					ctx.SubnetID, // Subnet ID
   773  					gomock.Any(),
   774  				).Return(set.Of(successNodeID))
   775  			},
   776  			sendF: func(_ *require.Assertions, sender common.Sender, nodeIDs set.Set[ids.NodeID]) {
   777  				sender.SendGetAcceptedFrontier(context.Background(), nodeIDs, requestID)
   778  			},
   779  		},
   780  		{
   781  			name: "GetAccepted",
   782  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
   783  				return message.InternalGetAcceptedFailed(
   784  					nodeID,
   785  					ctx.ChainID,
   786  					requestID,
   787  				)
   788  			},
   789  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   790  				require.IsType(&p2ppb.GetAccepted{}, msg.Message())
   791  				innerMsg := msg.Message().(*p2ppb.GetAccepted)
   792  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   793  				require.Equal(requestID, innerMsg.RequestId)
   794  				require.Equal(uint64(deadline), innerMsg.Deadline)
   795  			},
   796  			expectedResponseOp: message.AcceptedOp,
   797  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   798  				msgCreator.EXPECT().GetAccepted(
   799  					ctx.ChainID,
   800  					requestID,
   801  					deadline,
   802  					containerIDs,
   803  				).Return(nil, nil)
   804  			},
   805  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   806  				externalSender.EXPECT().Send(
   807  					gomock.Any(), // Outbound message
   808  					common.SendConfig{
   809  						// Note [myNodeID] is not in this set
   810  						NodeIDs: set.Of(successNodeID, failedNodeID),
   811  					},
   812  					ctx.SubnetID, // Subnet ID
   813  					gomock.Any(),
   814  				).Return(set.Of(successNodeID))
   815  			},
   816  			sendF: func(_ *require.Assertions, sender common.Sender, nodeIDs set.Set[ids.NodeID]) {
   817  				sender.SendGetAccepted(context.Background(), nodeIDs, requestID, containerIDs)
   818  			},
   819  		},
   820  	}
   821  
   822  	for _, tt := range tests {
   823  		t.Run(tt.name, func(t *testing.T) {
   824  			require := require.New(t)
   825  			ctrl := gomock.NewController(t)
   826  
   827  			var (
   828  				msgCreator     = message.NewMockOutboundMsgBuilder(ctrl)
   829  				externalSender = NewMockExternalSender(ctrl)
   830  				timeoutManager = timeout.NewMockManager(ctrl)
   831  				router         = router.NewMockRouter(ctrl)
   832  				nodeIDs        = set.Of(successNodeID, failedNodeID, ctx.NodeID)
   833  				nodeIDsCopy    set.Set[ids.NodeID]
   834  			)
   835  			nodeIDsCopy.Union(nodeIDs)
   836  
   837  			// Instantiate new registerers to avoid duplicate metrics
   838  			// registration
   839  			ctx.Registerer = prometheus.NewRegistry()
   840  
   841  			sender, err := New(
   842  				ctx,
   843  				msgCreator,
   844  				externalSender,
   845  				router,
   846  				timeoutManager,
   847  				p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
   848  				subnets.New(ctx.NodeID, subnets.Config{}),
   849  				prometheus.NewRegistry(),
   850  			)
   851  			require.NoError(err)
   852  
   853  			// Set the timeout (deadline)
   854  			timeoutManager.EXPECT().TimeoutDuration().Return(deadline).AnyTimes()
   855  
   856  			// Make sure we register requests with the router
   857  			for nodeID := range nodeIDs {
   858  				expectedFailedMsg := tt.failedMsgF(nodeID)
   859  				router.EXPECT().RegisterRequest(
   860  					gomock.Any(),          // Context
   861  					nodeID,                // Node ID
   862  					ctx.ChainID,           // Source Chain
   863  					ctx.ChainID,           // Destination Chain
   864  					requestID,             // Request ID
   865  					tt.expectedResponseOp, // Operation
   866  					expectedFailedMsg,     // Failure Message
   867  					p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED,
   868  				)
   869  			}
   870  
   871  			// Make sure we send a message to ourselves since [myNodeID]
   872  			// is in [nodeIDs].
   873  			// Note that HandleInbound is called in a separate goroutine
   874  			// so we need to use a channel to synchronize the test.
   875  			calledHandleInbound := make(chan struct{})
   876  			router.EXPECT().HandleInbound(gomock.Any(), gomock.Any()).Do(
   877  				func(_ context.Context, msg message.InboundMessage) {
   878  					// Make sure we're sending ourselves
   879  					// the expected message.
   880  					tt.assertMsgToMyself(require, msg)
   881  					close(calledHandleInbound)
   882  				},
   883  			)
   884  
   885  			// Make sure we're making the correct outbound message.
   886  			tt.setMsgCreatorExpect(msgCreator)
   887  
   888  			// Make sure we're sending the message
   889  			tt.setExternalSenderExpect(externalSender)
   890  
   891  			tt.sendF(require, sender, nodeIDsCopy)
   892  
   893  			<-calledHandleInbound
   894  		})
   895  	}
   896  }
   897  
   898  func TestSender_Bootstrap_Responses(t *testing.T) {
   899  	var (
   900  		destinationNodeID = ids.GenerateTestNodeID()
   901  		deadline          = time.Second
   902  		requestID         = uint32(1337)
   903  		summaryIDs        = []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()}
   904  		summary           = []byte{1, 2, 3}
   905  	)
   906  	snowCtx := snowtest.Context(t, snowtest.PChainID)
   907  	ctx := snowtest.ConsensusContext(snowCtx)
   908  
   909  	type test struct {
   910  		name                    string
   911  		assertMsgToMyself       func(require *require.Assertions, msg message.InboundMessage)
   912  		setMsgCreatorExpect     func(msgCreator *message.MockOutboundMsgBuilder)
   913  		setExternalSenderExpect func(externalSender *MockExternalSender)
   914  		sendF                   func(require *require.Assertions, sender common.Sender, nodeID ids.NodeID)
   915  	}
   916  
   917  	tests := []test{
   918  		{
   919  			name: "StateSummaryFrontier",
   920  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   921  				msgCreator.EXPECT().StateSummaryFrontier(
   922  					ctx.ChainID,
   923  					requestID,
   924  					summary,
   925  				).Return(nil, nil) // Don't care about the message
   926  			},
   927  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   928  				require.IsType(&p2ppb.StateSummaryFrontier{}, msg.Message())
   929  				innerMsg := msg.Message().(*p2ppb.StateSummaryFrontier)
   930  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   931  				require.Equal(requestID, innerMsg.RequestId)
   932  				require.Equal(summary, innerMsg.Summary)
   933  			},
   934  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   935  				externalSender.EXPECT().Send(
   936  					gomock.Any(), // Outbound message
   937  					common.SendConfig{
   938  						NodeIDs: set.Of(destinationNodeID),
   939  					},
   940  					ctx.SubnetID, // Subnet ID
   941  					gomock.Any(),
   942  				).Return(nil)
   943  			},
   944  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
   945  				sender.SendStateSummaryFrontier(context.Background(), nodeID, requestID, summary)
   946  			},
   947  		},
   948  		{
   949  			name: "AcceptedStateSummary",
   950  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   951  				msgCreator.EXPECT().AcceptedStateSummary(
   952  					ctx.ChainID,
   953  					requestID,
   954  					summaryIDs,
   955  				).Return(nil, nil) // Don't care about the message
   956  			},
   957  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   958  				require.IsType(&p2ppb.AcceptedStateSummary{}, msg.Message())
   959  				innerMsg := msg.Message().(*p2ppb.AcceptedStateSummary)
   960  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   961  				require.Equal(requestID, innerMsg.RequestId)
   962  				for i, summaryID := range summaryIDs {
   963  					require.Equal(summaryID[:], innerMsg.SummaryIds[i])
   964  				}
   965  			},
   966  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   967  				externalSender.EXPECT().Send(
   968  					gomock.Any(), // Outbound message
   969  					common.SendConfig{
   970  						NodeIDs: set.Of(destinationNodeID),
   971  					},
   972  					ctx.SubnetID, // Subnet ID
   973  					gomock.Any(),
   974  				).Return(nil)
   975  			},
   976  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
   977  				sender.SendAcceptedStateSummary(context.Background(), nodeID, requestID, summaryIDs)
   978  			},
   979  		},
   980  		{
   981  			name: "AcceptedFrontier",
   982  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
   983  				msgCreator.EXPECT().AcceptedFrontier(
   984  					ctx.ChainID,
   985  					requestID,
   986  					summaryIDs[0],
   987  				).Return(nil, nil) // Don't care about the message
   988  			},
   989  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
   990  				require.IsType(&p2ppb.AcceptedFrontier{}, msg.Message())
   991  				innerMsg := msg.Message().(*p2ppb.AcceptedFrontier)
   992  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
   993  				require.Equal(requestID, innerMsg.RequestId)
   994  				require.Equal(summaryIDs[0][:], innerMsg.ContainerId)
   995  			},
   996  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
   997  				externalSender.EXPECT().Send(
   998  					gomock.Any(), // Outbound message
   999  					common.SendConfig{
  1000  						NodeIDs: set.Of(destinationNodeID),
  1001  					},
  1002  					ctx.SubnetID, // Subnet ID
  1003  					gomock.Any(),
  1004  				).Return(nil)
  1005  			},
  1006  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
  1007  				sender.SendAcceptedFrontier(context.Background(), nodeID, requestID, summaryIDs[0])
  1008  			},
  1009  		},
  1010  		{
  1011  			name: "Accepted",
  1012  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
  1013  				msgCreator.EXPECT().Accepted(
  1014  					ctx.ChainID,
  1015  					requestID,
  1016  					summaryIDs,
  1017  				).Return(nil, nil) // Don't care about the message
  1018  			},
  1019  			assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) {
  1020  				require.IsType(&p2ppb.Accepted{}, msg.Message())
  1021  				innerMsg := msg.Message().(*p2ppb.Accepted)
  1022  				require.Equal(ctx.ChainID[:], innerMsg.ChainId)
  1023  				require.Equal(requestID, innerMsg.RequestId)
  1024  				for i, summaryID := range summaryIDs {
  1025  					require.Equal(summaryID[:], innerMsg.ContainerIds[i])
  1026  				}
  1027  			},
  1028  			setExternalSenderExpect: func(externalSender *MockExternalSender) {
  1029  				externalSender.EXPECT().Send(
  1030  					gomock.Any(), // Outbound message
  1031  					common.SendConfig{
  1032  						NodeIDs: set.Of(destinationNodeID),
  1033  					},
  1034  					ctx.SubnetID, // Subnet ID
  1035  					gomock.Any(),
  1036  				).Return(nil)
  1037  			},
  1038  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
  1039  				sender.SendAccepted(context.Background(), nodeID, requestID, summaryIDs)
  1040  			},
  1041  		},
  1042  	}
  1043  
  1044  	for _, tt := range tests {
  1045  		t.Run(tt.name, func(t *testing.T) {
  1046  			require := require.New(t)
  1047  			ctrl := gomock.NewController(t)
  1048  
  1049  			var (
  1050  				msgCreator     = message.NewMockOutboundMsgBuilder(ctrl)
  1051  				externalSender = NewMockExternalSender(ctrl)
  1052  				timeoutManager = timeout.NewMockManager(ctrl)
  1053  				router         = router.NewMockRouter(ctrl)
  1054  			)
  1055  
  1056  			sender, err := New(
  1057  				ctx,
  1058  				msgCreator,
  1059  				externalSender,
  1060  				router,
  1061  				timeoutManager,
  1062  				p2ppb.EngineType_ENGINE_TYPE_SNOWMAN,
  1063  				subnets.New(ctx.NodeID, subnets.Config{}),
  1064  				prometheus.NewRegistry(),
  1065  			)
  1066  			require.NoError(err)
  1067  
  1068  			// Set the timeout (deadline)
  1069  			timeoutManager.EXPECT().TimeoutDuration().Return(deadline).AnyTimes()
  1070  
  1071  			// Case: sending to ourselves
  1072  			{
  1073  				calledHandleInbound := make(chan struct{})
  1074  				router.EXPECT().HandleInbound(gomock.Any(), gomock.Any()).Do(
  1075  					func(_ context.Context, msg message.InboundMessage) {
  1076  						// Make sure we're sending ourselves
  1077  						// the expected message.
  1078  						tt.assertMsgToMyself(require, msg)
  1079  						close(calledHandleInbound)
  1080  					},
  1081  				)
  1082  				tt.sendF(require, sender, ctx.NodeID)
  1083  				<-calledHandleInbound
  1084  			}
  1085  
  1086  			// Case: not sending to ourselves
  1087  
  1088  			// Make sure we're making the correct outbound message.
  1089  			tt.setMsgCreatorExpect(msgCreator)
  1090  
  1091  			// Make sure we're sending the message
  1092  			tt.setExternalSenderExpect(externalSender)
  1093  
  1094  			tt.sendF(require, sender, destinationNodeID)
  1095  		})
  1096  	}
  1097  }
  1098  
  1099  func TestSender_Single_Request(t *testing.T) {
  1100  	var (
  1101  		destinationNodeID = ids.GenerateTestNodeID()
  1102  		deadline          = time.Second
  1103  		requestID         = uint32(1337)
  1104  		containerID       = ids.GenerateTestID()
  1105  		engineType        = p2ppb.EngineType_ENGINE_TYPE_SNOWMAN
  1106  	)
  1107  	snowCtx := snowtest.Context(t, snowtest.PChainID)
  1108  	ctx := snowtest.ConsensusContext(snowCtx)
  1109  
  1110  	type test struct {
  1111  		name                    string
  1112  		failedMsgF              func(nodeID ids.NodeID) message.InboundMessage
  1113  		shouldFailMessageToSelf bool
  1114  		assertMsg               func(require *require.Assertions, msg message.InboundMessage)
  1115  		expectedResponseOp      message.Op
  1116  		setMsgCreatorExpect     func(msgCreator *message.MockOutboundMsgBuilder)
  1117  		setExternalSenderExpect func(externalSender *MockExternalSender, sentTo set.Set[ids.NodeID])
  1118  		sendF                   func(require *require.Assertions, sender common.Sender, nodeID ids.NodeID)
  1119  		expectedEngineType      p2ppb.EngineType
  1120  	}
  1121  
  1122  	tests := []test{
  1123  		{
  1124  			name: "GetAncestors",
  1125  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
  1126  				return message.InternalGetAncestorsFailed(
  1127  					nodeID,
  1128  					ctx.ChainID,
  1129  					requestID,
  1130  					engineType,
  1131  				)
  1132  			},
  1133  			shouldFailMessageToSelf: false,
  1134  			assertMsg: func(require *require.Assertions, msg message.InboundMessage) {
  1135  				require.IsType(&message.GetAncestorsFailed{}, msg.Message())
  1136  				innerMsg := msg.Message().(*message.GetAncestorsFailed)
  1137  				require.Equal(ctx.ChainID, innerMsg.ChainID)
  1138  				require.Equal(requestID, innerMsg.RequestID)
  1139  				require.Equal(engineType, innerMsg.EngineType)
  1140  			},
  1141  			expectedResponseOp: message.AncestorsOp,
  1142  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
  1143  				msgCreator.EXPECT().GetAncestors(
  1144  					ctx.ChainID,
  1145  					requestID,
  1146  					deadline,
  1147  					containerID,
  1148  					engineType,
  1149  				).Return(nil, nil)
  1150  			},
  1151  			setExternalSenderExpect: func(externalSender *MockExternalSender, sentTo set.Set[ids.NodeID]) {
  1152  				externalSender.EXPECT().Send(
  1153  					gomock.Any(), // Outbound message
  1154  					common.SendConfig{
  1155  						NodeIDs: set.Of(destinationNodeID),
  1156  					},
  1157  					ctx.SubnetID,
  1158  					gomock.Any(),
  1159  				).Return(sentTo)
  1160  			},
  1161  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
  1162  				sender.SendGetAncestors(context.Background(), nodeID, requestID, containerID)
  1163  			},
  1164  			expectedEngineType: engineType,
  1165  		},
  1166  		{
  1167  			name: "Get",
  1168  			failedMsgF: func(nodeID ids.NodeID) message.InboundMessage {
  1169  				return message.InternalGetFailed(
  1170  					nodeID,
  1171  					ctx.ChainID,
  1172  					requestID,
  1173  				)
  1174  			},
  1175  			shouldFailMessageToSelf: true,
  1176  			assertMsg: func(require *require.Assertions, msg message.InboundMessage) {
  1177  				require.IsType(&message.GetFailed{}, msg.Message())
  1178  				innerMsg := msg.Message().(*message.GetFailed)
  1179  				require.Equal(ctx.ChainID, innerMsg.ChainID)
  1180  				require.Equal(requestID, innerMsg.RequestID)
  1181  			},
  1182  			expectedResponseOp: message.PutOp,
  1183  			setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) {
  1184  				msgCreator.EXPECT().Get(
  1185  					ctx.ChainID,
  1186  					requestID,
  1187  					deadline,
  1188  					containerID,
  1189  				).Return(nil, nil)
  1190  			},
  1191  			setExternalSenderExpect: func(externalSender *MockExternalSender, sentTo set.Set[ids.NodeID]) {
  1192  				externalSender.EXPECT().Send(
  1193  					gomock.Any(), // Outbound message
  1194  					common.SendConfig{
  1195  						NodeIDs: set.Of(destinationNodeID),
  1196  					},
  1197  					ctx.SubnetID,
  1198  					gomock.Any(),
  1199  				).Return(sentTo)
  1200  			},
  1201  			sendF: func(_ *require.Assertions, sender common.Sender, nodeID ids.NodeID) {
  1202  				sender.SendGet(context.Background(), nodeID, requestID, containerID)
  1203  			},
  1204  		},
  1205  	}
  1206  
  1207  	for _, tt := range tests {
  1208  		t.Run(tt.name, func(t *testing.T) {
  1209  			require := require.New(t)
  1210  			ctrl := gomock.NewController(t)
  1211  
  1212  			var (
  1213  				msgCreator     = message.NewMockOutboundMsgBuilder(ctrl)
  1214  				externalSender = NewMockExternalSender(ctrl)
  1215  				timeoutManager = timeout.NewMockManager(ctrl)
  1216  				router         = router.NewMockRouter(ctrl)
  1217  			)
  1218  
  1219  			// Instantiate new registerers to avoid duplicate metrics
  1220  			// registration
  1221  			ctx.Registerer = prometheus.NewRegistry()
  1222  
  1223  			sender, err := New(
  1224  				ctx,
  1225  				msgCreator,
  1226  				externalSender,
  1227  				router,
  1228  				timeoutManager,
  1229  				engineType,
  1230  				subnets.New(ctx.NodeID, subnets.Config{}),
  1231  				prometheus.NewRegistry(),
  1232  			)
  1233  			require.NoError(err)
  1234  
  1235  			// Set the timeout (deadline)
  1236  			timeoutManager.EXPECT().TimeoutDuration().Return(deadline).AnyTimes()
  1237  
  1238  			// Case: sending to myself
  1239  			{
  1240  				// Make sure we register requests with the router
  1241  				expectedFailedMsg := tt.failedMsgF(ctx.NodeID)
  1242  				router.EXPECT().RegisterRequest(
  1243  					gomock.Any(),          // Context
  1244  					ctx.NodeID,            // Node ID
  1245  					ctx.ChainID,           // Source Chain
  1246  					ctx.ChainID,           // Destination Chain
  1247  					requestID,             // Request ID
  1248  					tt.expectedResponseOp, // Operation
  1249  					expectedFailedMsg,     // Failure Message
  1250  					tt.expectedEngineType, // Engine Type
  1251  				)
  1252  
  1253  				// Note that HandleInbound is called in a separate goroutine
  1254  				// so we need to use a channel to synchronize the test.
  1255  				calledHandleInbound := make(chan struct{})
  1256  				if tt.shouldFailMessageToSelf {
  1257  					router.EXPECT().HandleInbound(gomock.Any(), gomock.Any()).Do(
  1258  						func(_ context.Context, msg message.InboundMessage) {
  1259  							// Make sure we're sending ourselves
  1260  							// the expected message.
  1261  							tt.assertMsg(require, msg)
  1262  							close(calledHandleInbound)
  1263  						},
  1264  					)
  1265  				} else {
  1266  					close(calledHandleInbound)
  1267  				}
  1268  
  1269  				tt.sendF(require, sender, ctx.NodeID)
  1270  
  1271  				<-calledHandleInbound
  1272  			}
  1273  
  1274  			// Case: Node is benched
  1275  			{
  1276  				timeoutManager.EXPECT().IsBenched(destinationNodeID, ctx.ChainID).Return(true)
  1277  
  1278  				timeoutManager.EXPECT().RegisterRequestToUnreachableValidator()
  1279  
  1280  				// Make sure we register requests with the router
  1281  				expectedFailedMsg := tt.failedMsgF(destinationNodeID)
  1282  				router.EXPECT().RegisterRequest(
  1283  					gomock.Any(),          // Context
  1284  					destinationNodeID,     // Node ID
  1285  					ctx.ChainID,           // Source Chain
  1286  					ctx.ChainID,           // Destination Chain
  1287  					requestID,             // Request ID
  1288  					tt.expectedResponseOp, // Operation
  1289  					expectedFailedMsg,     // Failure Message
  1290  					tt.expectedEngineType, // Engine Type
  1291  				)
  1292  
  1293  				// Note that HandleInbound is called in a separate goroutine
  1294  				// so we need to use a channel to synchronize the test.
  1295  				calledHandleInbound := make(chan struct{})
  1296  				router.EXPECT().HandleInbound(gomock.Any(), gomock.Any()).Do(
  1297  					func(_ context.Context, msg message.InboundMessage) {
  1298  						// Make sure we're sending ourselves
  1299  						// the expected message.
  1300  						tt.assertMsg(require, msg)
  1301  						close(calledHandleInbound)
  1302  					},
  1303  				)
  1304  
  1305  				tt.sendF(require, sender, destinationNodeID)
  1306  
  1307  				<-calledHandleInbound
  1308  			}
  1309  
  1310  			// Case: Node is not myself, not benched and send fails
  1311  			{
  1312  				timeoutManager.EXPECT().IsBenched(destinationNodeID, ctx.ChainID).Return(false)
  1313  
  1314  				timeoutManager.EXPECT().RegisterRequestToUnreachableValidator()
  1315  
  1316  				// Make sure we register requests with the router
  1317  				expectedFailedMsg := tt.failedMsgF(destinationNodeID)
  1318  				router.EXPECT().RegisterRequest(
  1319  					gomock.Any(),          // Context
  1320  					destinationNodeID,     // Node ID
  1321  					ctx.ChainID,           // Source Chain
  1322  					ctx.ChainID,           // Destination Chain
  1323  					requestID,             // Request ID
  1324  					tt.expectedResponseOp, // Operation
  1325  					expectedFailedMsg,     // Failure Message
  1326  					tt.expectedEngineType, // Engine Type
  1327  				)
  1328  
  1329  				// Note that HandleInbound is called in a separate goroutine
  1330  				// so we need to use a channel to synchronize the test.
  1331  				calledHandleInbound := make(chan struct{})
  1332  				router.EXPECT().HandleInbound(gomock.Any(), gomock.Any()).Do(
  1333  					func(_ context.Context, msg message.InboundMessage) {
  1334  						// Make sure we're sending ourselves
  1335  						// the expected message.
  1336  						tt.assertMsg(require, msg)
  1337  						close(calledHandleInbound)
  1338  					},
  1339  				)
  1340  
  1341  				// Make sure we're making the correct outbound message.
  1342  				tt.setMsgCreatorExpect(msgCreator)
  1343  
  1344  				// Make sure we're sending the message
  1345  				tt.setExternalSenderExpect(externalSender, set.Set[ids.NodeID]{})
  1346  
  1347  				tt.sendF(require, sender, destinationNodeID)
  1348  
  1349  				<-calledHandleInbound
  1350  			}
  1351  		})
  1352  	}
  1353  }