github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/bootstrapper/majority_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 bootstrapper
     5  
     6  import (
     7  	"context"
     8  	"math"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/utils/logging"
    15  	"github.com/MetalBlockchain/metalgo/utils/set"
    16  
    17  	safemath "github.com/MetalBlockchain/metalgo/utils/math"
    18  )
    19  
    20  func TestNewMajority(t *testing.T) {
    21  	majority := NewMajority(
    22  		logging.NoLog{}, // log
    23  		map[ids.NodeID]uint64{
    24  			nodeID0: 1,
    25  			nodeID1: 1,
    26  		}, // nodeWeights
    27  		2, // maxOutstanding
    28  	)
    29  
    30  	expectedMajority := &Majority{
    31  		requests: requests{
    32  			maxOutstanding: 2,
    33  			pendingSend:    set.Of(nodeID0, nodeID1),
    34  		},
    35  		log: logging.NoLog{},
    36  		nodeWeights: map[ids.NodeID]uint64{
    37  			nodeID0: 1,
    38  			nodeID1: 1,
    39  		},
    40  		received: make(map[ids.ID]uint64),
    41  	}
    42  	require.Equal(t, expectedMajority, majority)
    43  }
    44  
    45  func TestMajorityGetPeers(t *testing.T) {
    46  	tests := []struct {
    47  		name          string
    48  		majority      Poll
    49  		expectedState Poll
    50  		expectedPeers set.Set[ids.NodeID]
    51  	}{
    52  		{
    53  			name: "max outstanding",
    54  			majority: &Majority{
    55  				requests: requests{
    56  					maxOutstanding: 1,
    57  					pendingSend:    set.Of(nodeID0),
    58  					outstanding:    set.Of(nodeID1),
    59  				},
    60  				log: logging.NoLog{},
    61  				nodeWeights: map[ids.NodeID]uint64{
    62  					nodeID0: 1,
    63  					nodeID1: 1,
    64  				},
    65  				received: make(map[ids.ID]uint64),
    66  			},
    67  			expectedState: &Majority{
    68  				requests: requests{
    69  					maxOutstanding: 1,
    70  					pendingSend:    set.Of(nodeID0),
    71  					outstanding:    set.Of(nodeID1),
    72  				},
    73  				log: logging.NoLog{},
    74  				nodeWeights: map[ids.NodeID]uint64{
    75  					nodeID0: 1,
    76  					nodeID1: 1,
    77  				},
    78  				received: make(map[ids.ID]uint64),
    79  			},
    80  			expectedPeers: nil,
    81  		},
    82  		{
    83  			name: "send until max outstanding",
    84  			majority: &Majority{
    85  				requests: requests{
    86  					maxOutstanding: 2,
    87  					pendingSend:    set.Of(nodeID0, nodeID1),
    88  				},
    89  				log: logging.NoLog{},
    90  				nodeWeights: map[ids.NodeID]uint64{
    91  					nodeID0: 1,
    92  					nodeID1: 1,
    93  				},
    94  				received: make(map[ids.ID]uint64),
    95  			},
    96  			expectedState: &Majority{
    97  				requests: requests{
    98  					maxOutstanding: 2,
    99  					pendingSend:    set.Set[ids.NodeID]{},
   100  					outstanding:    set.Of(nodeID0, nodeID1),
   101  				},
   102  				log: logging.NoLog{},
   103  				nodeWeights: map[ids.NodeID]uint64{
   104  					nodeID0: 1,
   105  					nodeID1: 1,
   106  				},
   107  				received: make(map[ids.ID]uint64),
   108  			},
   109  			expectedPeers: set.Of(nodeID0, nodeID1),
   110  		},
   111  		{
   112  			name: "send until no more to send",
   113  			majority: &Majority{
   114  				requests: requests{
   115  					maxOutstanding: 2,
   116  					pendingSend:    set.Of(nodeID0),
   117  				},
   118  				log: logging.NoLog{},
   119  				nodeWeights: map[ids.NodeID]uint64{
   120  					nodeID0: 1,
   121  				},
   122  				received: make(map[ids.ID]uint64),
   123  			},
   124  			expectedState: &Majority{
   125  				requests: requests{
   126  					maxOutstanding: 2,
   127  					pendingSend:    set.Set[ids.NodeID]{},
   128  					outstanding:    set.Of(nodeID0),
   129  				},
   130  				log: logging.NoLog{},
   131  				nodeWeights: map[ids.NodeID]uint64{
   132  					nodeID0: 1,
   133  				},
   134  				received: make(map[ids.ID]uint64),
   135  			},
   136  			expectedPeers: set.Of(nodeID0),
   137  		},
   138  	}
   139  	for _, test := range tests {
   140  		t.Run(test.name, func(t *testing.T) {
   141  			require := require.New(t)
   142  
   143  			peers := test.majority.GetPeers(context.Background())
   144  			require.Equal(test.expectedState, test.majority)
   145  			require.Equal(test.expectedPeers, peers)
   146  		})
   147  	}
   148  }
   149  
   150  func TestMajorityRecordOpinion(t *testing.T) {
   151  	tests := []struct {
   152  		name          string
   153  		majority      Poll
   154  		nodeID        ids.NodeID
   155  		blkIDs        set.Set[ids.ID]
   156  		expectedState Poll
   157  		expectedErr   error
   158  	}{
   159  		{
   160  			name: "unexpected response",
   161  			majority: &Majority{
   162  				requests: requests{
   163  					maxOutstanding: 1,
   164  					pendingSend:    set.Of(nodeID0),
   165  					outstanding:    set.Of(nodeID1),
   166  				},
   167  				log: logging.NoLog{},
   168  				nodeWeights: map[ids.NodeID]uint64{
   169  					nodeID0: 1,
   170  					nodeID1: 1,
   171  				},
   172  				received: make(map[ids.ID]uint64),
   173  			},
   174  			nodeID: nodeID0,
   175  			blkIDs: nil,
   176  			expectedState: &Majority{
   177  				requests: requests{
   178  					maxOutstanding: 1,
   179  					pendingSend:    set.Of(nodeID0),
   180  					outstanding:    set.Of(nodeID1),
   181  				},
   182  				log: logging.NoLog{},
   183  				nodeWeights: map[ids.NodeID]uint64{
   184  					nodeID0: 1,
   185  					nodeID1: 1,
   186  				},
   187  				received: make(map[ids.ID]uint64),
   188  			},
   189  			expectedErr: nil,
   190  		},
   191  		{
   192  			name: "unfinished after response",
   193  			majority: &Majority{
   194  				requests: requests{
   195  					maxOutstanding: 1,
   196  					pendingSend:    set.Of(nodeID0),
   197  					outstanding:    set.Of(nodeID1),
   198  				},
   199  				log: logging.NoLog{},
   200  				nodeWeights: map[ids.NodeID]uint64{
   201  					nodeID0: 2,
   202  					nodeID1: 3,
   203  				},
   204  				received: make(map[ids.ID]uint64),
   205  			},
   206  			nodeID: nodeID1,
   207  			blkIDs: set.Of(blkID0),
   208  			expectedState: &Majority{
   209  				requests: requests{
   210  					maxOutstanding: 1,
   211  					pendingSend:    set.Of(nodeID0),
   212  					outstanding:    set.Set[ids.NodeID]{},
   213  				},
   214  				log: logging.NoLog{},
   215  				nodeWeights: map[ids.NodeID]uint64{
   216  					nodeID0: 2,
   217  					nodeID1: 3,
   218  				},
   219  				received: map[ids.ID]uint64{
   220  					blkID0: 3,
   221  				},
   222  			},
   223  			expectedErr: nil,
   224  		},
   225  		{
   226  			name: "overflow during response",
   227  			majority: &Majority{
   228  				requests: requests{
   229  					maxOutstanding: 1,
   230  					outstanding:    set.Of(nodeID1),
   231  				},
   232  				log: logging.NoLog{},
   233  				nodeWeights: map[ids.NodeID]uint64{
   234  					nodeID0: 1,
   235  					nodeID1: math.MaxUint64,
   236  				},
   237  				received: map[ids.ID]uint64{
   238  					blkID0: 1,
   239  				},
   240  			},
   241  			nodeID: nodeID1,
   242  			blkIDs: set.Of(blkID0),
   243  			expectedState: &Majority{
   244  				requests: requests{
   245  					maxOutstanding: 1,
   246  					outstanding:    set.Set[ids.NodeID]{},
   247  				},
   248  				log: logging.NoLog{},
   249  				nodeWeights: map[ids.NodeID]uint64{
   250  					nodeID0: 1,
   251  					nodeID1: math.MaxUint64,
   252  				},
   253  				received: map[ids.ID]uint64{
   254  					blkID0: 1,
   255  				},
   256  			},
   257  			expectedErr: safemath.ErrOverflow,
   258  		},
   259  		{
   260  			name: "overflow during final response",
   261  			majority: &Majority{
   262  				requests: requests{
   263  					maxOutstanding: 1,
   264  					outstanding:    set.Of(nodeID1),
   265  				},
   266  				log: logging.NoLog{},
   267  				nodeWeights: map[ids.NodeID]uint64{
   268  					nodeID0: 1,
   269  					nodeID1: math.MaxUint64,
   270  				},
   271  				received: make(map[ids.ID]uint64),
   272  			},
   273  			nodeID: nodeID1,
   274  			blkIDs: set.Of(blkID0),
   275  			expectedState: &Majority{
   276  				requests: requests{
   277  					maxOutstanding: 1,
   278  					outstanding:    set.Set[ids.NodeID]{},
   279  				},
   280  				log: logging.NoLog{},
   281  				nodeWeights: map[ids.NodeID]uint64{
   282  					nodeID0: 1,
   283  					nodeID1: math.MaxUint64,
   284  				},
   285  				received: map[ids.ID]uint64{
   286  					blkID0: math.MaxUint64,
   287  				},
   288  			},
   289  			expectedErr: safemath.ErrOverflow,
   290  		},
   291  		{
   292  			name: "finished after response",
   293  			majority: &Majority{
   294  				requests: requests{
   295  					maxOutstanding: 1,
   296  					outstanding:    set.Of(nodeID2),
   297  				},
   298  				log: logging.NoLog{},
   299  				nodeWeights: map[ids.NodeID]uint64{
   300  					nodeID0: 1,
   301  					nodeID1: 1,
   302  					nodeID2: 1,
   303  				},
   304  				received: map[ids.ID]uint64{
   305  					blkID0: 1,
   306  					blkID1: 1,
   307  				},
   308  			},
   309  			nodeID: nodeID2,
   310  			blkIDs: set.Of(blkID1),
   311  			expectedState: &Majority{
   312  				requests: requests{
   313  					maxOutstanding: 1,
   314  					outstanding:    set.Set[ids.NodeID]{},
   315  				},
   316  				log: logging.NoLog{},
   317  				nodeWeights: map[ids.NodeID]uint64{
   318  					nodeID0: 1,
   319  					nodeID1: 1,
   320  					nodeID2: 1,
   321  				},
   322  				received: map[ids.ID]uint64{
   323  					blkID0: 1,
   324  					blkID1: 2,
   325  				},
   326  				accepted: []ids.ID{blkID1},
   327  			},
   328  			expectedErr: nil,
   329  		},
   330  	}
   331  	for _, test := range tests {
   332  		t.Run(test.name, func(t *testing.T) {
   333  			require := require.New(t)
   334  
   335  			err := test.majority.RecordOpinion(context.Background(), test.nodeID, test.blkIDs)
   336  			require.Equal(test.expectedState, test.majority)
   337  			require.ErrorIs(err, test.expectedErr)
   338  		})
   339  	}
   340  }
   341  
   342  func TestMajorityResult(t *testing.T) {
   343  	tests := []struct {
   344  		name              string
   345  		majority          Poll
   346  		expectedAccepted  []ids.ID
   347  		expectedFinalized bool
   348  	}{
   349  		{
   350  			name: "not finalized",
   351  			majority: &Majority{
   352  				requests: requests{
   353  					maxOutstanding: 1,
   354  					outstanding:    set.Of(nodeID1),
   355  				},
   356  				log: logging.NoLog{},
   357  				nodeWeights: map[ids.NodeID]uint64{
   358  					nodeID0: 1,
   359  					nodeID1: 1,
   360  				},
   361  				received: make(map[ids.ID]uint64),
   362  				accepted: nil,
   363  			},
   364  			expectedAccepted:  nil,
   365  			expectedFinalized: false,
   366  		},
   367  		{
   368  			name: "finalized",
   369  			majority: &Majority{
   370  				requests: requests{
   371  					maxOutstanding: 1,
   372  				},
   373  				log: logging.NoLog{},
   374  				nodeWeights: map[ids.NodeID]uint64{
   375  					nodeID0: 1,
   376  					nodeID1: 1,
   377  				},
   378  				received: map[ids.ID]uint64{
   379  					blkID0: 2,
   380  				},
   381  				accepted: []ids.ID{blkID0},
   382  			},
   383  			expectedAccepted:  []ids.ID{blkID0},
   384  			expectedFinalized: true,
   385  		},
   386  	}
   387  	for _, test := range tests {
   388  		t.Run(test.name, func(t *testing.T) {
   389  			require := require.New(t)
   390  
   391  			accepted, finalized := test.majority.Result(context.Background())
   392  			require.Equal(test.expectedAccepted, accepted)
   393  			require.Equal(test.expectedFinalized, finalized)
   394  		})
   395  	}
   396  }