github.com/MetalBlockchain/metalgo@v1.11.9/snow/uptime/manager_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 uptime
     5  
     6  import (
     7  	"errors"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/database"
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    16  )
    17  
    18  var errTest = errors.New("non-nil error")
    19  
    20  func TestStartTracking(t *testing.T) {
    21  	require := require.New(t)
    22  
    23  	nodeID0 := ids.GenerateTestNodeID()
    24  	subnetID := ids.GenerateTestID()
    25  	startTime := time.Now()
    26  
    27  	s := NewTestState()
    28  	s.AddNode(nodeID0, subnetID, startTime)
    29  
    30  	clk := mockable.Clock{}
    31  	up := NewManager(s, &clk)
    32  
    33  	currentTime := startTime.Add(time.Second)
    34  	clk.Set(currentTime)
    35  
    36  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
    37  
    38  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
    39  	require.NoError(err)
    40  	require.Equal(time.Second, duration)
    41  	require.Equal(clk.UnixTime(), lastUpdated)
    42  }
    43  
    44  func TestStartTrackingDBError(t *testing.T) {
    45  	require := require.New(t)
    46  
    47  	nodeID0 := ids.GenerateTestNodeID()
    48  	subnetID := ids.GenerateTestID()
    49  	startTime := time.Now()
    50  
    51  	s := NewTestState()
    52  	s.dbWriteError = errTest
    53  	s.AddNode(nodeID0, subnetID, startTime)
    54  
    55  	clk := mockable.Clock{}
    56  	up := NewManager(s, &clk)
    57  
    58  	currentTime := startTime.Add(time.Second)
    59  	clk.Set(currentTime)
    60  
    61  	err := up.StartTracking([]ids.NodeID{nodeID0}, subnetID)
    62  	require.ErrorIs(err, errTest)
    63  }
    64  
    65  func TestStartTrackingNonValidator(t *testing.T) {
    66  	require := require.New(t)
    67  
    68  	s := NewTestState()
    69  	clk := mockable.Clock{}
    70  	up := NewManager(s, &clk)
    71  
    72  	nodeID0 := ids.GenerateTestNodeID()
    73  	subnetID := ids.GenerateTestID()
    74  
    75  	err := up.StartTracking([]ids.NodeID{nodeID0}, subnetID)
    76  	require.ErrorIs(err, database.ErrNotFound)
    77  }
    78  
    79  func TestStartTrackingInThePast(t *testing.T) {
    80  	require := require.New(t)
    81  
    82  	nodeID0 := ids.GenerateTestNodeID()
    83  	subnetID := ids.GenerateTestID()
    84  	startTime := time.Now()
    85  
    86  	s := NewTestState()
    87  	s.AddNode(nodeID0, subnetID, startTime)
    88  
    89  	clk := mockable.Clock{}
    90  	up := NewManager(s, &clk)
    91  
    92  	currentTime := startTime.Add(-time.Second)
    93  	clk.Set(currentTime)
    94  
    95  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
    96  
    97  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
    98  	require.NoError(err)
    99  	require.Equal(time.Duration(0), duration)
   100  	require.Equal(startTime.Truncate(time.Second), lastUpdated)
   101  }
   102  
   103  func TestStopTrackingDecreasesUptime(t *testing.T) {
   104  	require := require.New(t)
   105  
   106  	nodeID0 := ids.GenerateTestNodeID()
   107  	subnetID := ids.GenerateTestID()
   108  	currentTime := time.Now()
   109  	startTime := currentTime
   110  
   111  	s := NewTestState()
   112  	s.AddNode(nodeID0, subnetID, startTime)
   113  
   114  	clk := mockable.Clock{}
   115  	up := NewManager(s, &clk)
   116  	clk.Set(currentTime)
   117  
   118  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   119  
   120  	currentTime = startTime.Add(time.Second)
   121  	clk.Set(currentTime)
   122  
   123  	require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID))
   124  
   125  	up = NewManager(s, &clk)
   126  
   127  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   128  
   129  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   130  	require.NoError(err)
   131  	require.Equal(time.Duration(0), duration)
   132  	require.Equal(clk.UnixTime(), lastUpdated)
   133  }
   134  
   135  func TestStopTrackingIncreasesUptime(t *testing.T) {
   136  	require := require.New(t)
   137  
   138  	nodeID0 := ids.GenerateTestNodeID()
   139  	subnetID := ids.GenerateTestID()
   140  	currentTime := time.Now()
   141  	startTime := currentTime
   142  
   143  	s := NewTestState()
   144  	s.AddNode(nodeID0, subnetID, startTime)
   145  
   146  	clk := mockable.Clock{}
   147  	up := NewManager(s, &clk)
   148  	clk.Set(currentTime)
   149  
   150  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   151  
   152  	require.NoError(up.Connect(nodeID0, subnetID))
   153  
   154  	currentTime = startTime.Add(time.Second)
   155  	clk.Set(currentTime)
   156  
   157  	require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID))
   158  
   159  	up = NewManager(s, &clk)
   160  
   161  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   162  
   163  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   164  	require.NoError(err)
   165  	require.Equal(time.Second, duration)
   166  	require.Equal(clk.UnixTime(), lastUpdated)
   167  }
   168  
   169  func TestStopTrackingDisconnectedNonValidator(t *testing.T) {
   170  	require := require.New(t)
   171  
   172  	nodeID0 := ids.GenerateTestNodeID()
   173  	subnetID := ids.GenerateTestID()
   174  
   175  	s := NewTestState()
   176  	clk := mockable.Clock{}
   177  	up := NewManager(s, &clk)
   178  
   179  	require.NoError(up.StartTracking(nil, subnetID))
   180  
   181  	err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID)
   182  	require.ErrorIs(err, database.ErrNotFound)
   183  }
   184  
   185  func TestStopTrackingConnectedDBError(t *testing.T) {
   186  	require := require.New(t)
   187  
   188  	nodeID0 := ids.GenerateTestNodeID()
   189  	subnetID := ids.GenerateTestID()
   190  	startTime := time.Now()
   191  
   192  	s := NewTestState()
   193  	s.AddNode(nodeID0, subnetID, startTime)
   194  	clk := mockable.Clock{}
   195  	up := NewManager(s, &clk)
   196  
   197  	require.NoError(up.StartTracking(nil, subnetID))
   198  
   199  	require.NoError(up.Connect(nodeID0, subnetID))
   200  
   201  	s.dbReadError = errTest
   202  	err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID)
   203  	require.ErrorIs(err, errTest)
   204  }
   205  
   206  func TestStopTrackingNonConnectedPast(t *testing.T) {
   207  	require := require.New(t)
   208  
   209  	nodeID0 := ids.GenerateTestNodeID()
   210  	subnetID := ids.GenerateTestID()
   211  	currentTime := time.Now()
   212  	startTime := currentTime
   213  
   214  	s := NewTestState()
   215  	s.AddNode(nodeID0, subnetID, startTime)
   216  	clk := mockable.Clock{}
   217  	up := NewManager(s, &clk)
   218  	clk.Set(currentTime)
   219  
   220  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   221  
   222  	currentTime = currentTime.Add(-time.Second)
   223  	clk.Set(currentTime)
   224  
   225  	require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID))
   226  
   227  	duration, lastUpdated, err := s.GetUptime(nodeID0, subnetID)
   228  	require.NoError(err)
   229  	require.Equal(time.Duration(0), duration)
   230  	require.Equal(startTime.Truncate(time.Second), lastUpdated)
   231  }
   232  
   233  func TestStopTrackingNonConnectedDBError(t *testing.T) {
   234  	require := require.New(t)
   235  
   236  	nodeID0 := ids.GenerateTestNodeID()
   237  	subnetID := ids.GenerateTestID()
   238  	currentTime := time.Now()
   239  	startTime := currentTime
   240  
   241  	s := NewTestState()
   242  	s.AddNode(nodeID0, subnetID, startTime)
   243  	clk := mockable.Clock{}
   244  	up := NewManager(s, &clk)
   245  	clk.Set(currentTime)
   246  
   247  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   248  
   249  	currentTime = currentTime.Add(time.Second)
   250  	clk.Set(currentTime)
   251  
   252  	s.dbWriteError = errTest
   253  	err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID)
   254  	require.ErrorIs(err, errTest)
   255  }
   256  
   257  func TestConnectAndDisconnect(t *testing.T) {
   258  	tests := []struct {
   259  		name      string
   260  		subnetIDs []ids.ID
   261  	}{
   262  		{
   263  			name:      "Single Subnet",
   264  			subnetIDs: []ids.ID{ids.GenerateTestID()},
   265  		},
   266  		{
   267  			name:      "Multiple Subnets",
   268  			subnetIDs: []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()},
   269  		},
   270  	}
   271  	for _, tt := range tests {
   272  		t.Run(tt.name, func(t *testing.T) {
   273  			require := require.New(t)
   274  
   275  			nodeID0 := ids.GenerateTestNodeID()
   276  			currentTime := time.Now()
   277  			startTime := currentTime
   278  
   279  			s := NewTestState()
   280  			clk := mockable.Clock{}
   281  			up := NewManager(s, &clk)
   282  			clk.Set(currentTime)
   283  
   284  			for _, subnetID := range tt.subnetIDs {
   285  				s.AddNode(nodeID0, subnetID, startTime)
   286  
   287  				connected := up.IsConnected(nodeID0, subnetID)
   288  				require.False(connected)
   289  
   290  				require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   291  
   292  				connected = up.IsConnected(nodeID0, subnetID)
   293  				require.False(connected)
   294  
   295  				duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   296  				require.NoError(err)
   297  				require.Equal(time.Duration(0), duration)
   298  				require.Equal(clk.UnixTime(), lastUpdated)
   299  
   300  				require.NoError(up.Connect(nodeID0, subnetID))
   301  
   302  				connected = up.IsConnected(nodeID0, subnetID)
   303  				require.True(connected)
   304  			}
   305  
   306  			currentTime = currentTime.Add(time.Second)
   307  			clk.Set(currentTime)
   308  
   309  			for _, subnetID := range tt.subnetIDs {
   310  				duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   311  				require.NoError(err)
   312  				require.Equal(time.Second, duration)
   313  				require.Equal(clk.UnixTime(), lastUpdated)
   314  			}
   315  
   316  			require.NoError(up.Disconnect(nodeID0))
   317  
   318  			for _, subnetID := range tt.subnetIDs {
   319  				connected := up.IsConnected(nodeID0, subnetID)
   320  				require.False(connected)
   321  			}
   322  
   323  			currentTime = currentTime.Add(time.Second)
   324  			clk.Set(currentTime)
   325  
   326  			for _, subnetID := range tt.subnetIDs {
   327  				duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   328  				require.NoError(err)
   329  				require.Equal(time.Second, duration)
   330  				require.Equal(clk.UnixTime(), lastUpdated)
   331  			}
   332  		})
   333  	}
   334  }
   335  
   336  func TestConnectAndDisconnectBeforeTracking(t *testing.T) {
   337  	require := require.New(t)
   338  
   339  	nodeID0 := ids.GenerateTestNodeID()
   340  	subnetID := ids.GenerateTestID()
   341  	currentTime := time.Now()
   342  	startTime := currentTime
   343  
   344  	s := NewTestState()
   345  	s.AddNode(nodeID0, subnetID, startTime)
   346  
   347  	clk := mockable.Clock{}
   348  	up := NewManager(s, &clk)
   349  	currentTime = currentTime.Add(time.Second)
   350  	clk.Set(currentTime)
   351  
   352  	require.NoError(up.Connect(nodeID0, subnetID))
   353  
   354  	currentTime = currentTime.Add(time.Second)
   355  	clk.Set(currentTime)
   356  
   357  	require.NoError(up.Disconnect(nodeID0))
   358  
   359  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   360  
   361  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   362  	require.NoError(err)
   363  	require.Equal(2*time.Second, duration)
   364  	require.Equal(clk.UnixTime(), lastUpdated)
   365  }
   366  
   367  func TestUnrelatedNodeDisconnect(t *testing.T) {
   368  	require := require.New(t)
   369  
   370  	nodeID0 := ids.GenerateTestNodeID()
   371  	subnetID := ids.GenerateTestID()
   372  	nodeID1 := ids.GenerateTestNodeID()
   373  	currentTime := time.Now()
   374  	startTime := currentTime
   375  
   376  	s := NewTestState()
   377  	s.AddNode(nodeID0, subnetID, startTime)
   378  
   379  	clk := mockable.Clock{}
   380  	up := NewManager(s, &clk)
   381  	clk.Set(currentTime)
   382  
   383  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   384  
   385  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   386  	require.NoError(err)
   387  	require.Equal(time.Duration(0), duration)
   388  	require.Equal(clk.UnixTime(), lastUpdated)
   389  
   390  	require.NoError(up.Connect(nodeID0, subnetID))
   391  
   392  	require.NoError(up.Connect(nodeID1, subnetID))
   393  
   394  	currentTime = currentTime.Add(time.Second)
   395  	clk.Set(currentTime)
   396  
   397  	duration, lastUpdated, err = up.CalculateUptime(nodeID0, subnetID)
   398  	require.NoError(err)
   399  	require.Equal(time.Second, duration)
   400  	require.Equal(clk.UnixTime(), lastUpdated)
   401  
   402  	require.NoError(up.Disconnect(nodeID1))
   403  
   404  	currentTime = currentTime.Add(time.Second)
   405  	clk.Set(currentTime)
   406  
   407  	duration, lastUpdated, err = up.CalculateUptime(nodeID0, subnetID)
   408  	require.NoError(err)
   409  	require.Equal(2*time.Second, duration)
   410  	require.Equal(clk.UnixTime(), lastUpdated)
   411  }
   412  
   413  func TestCalculateUptimeWhenNeverTracked(t *testing.T) {
   414  	require := require.New(t)
   415  
   416  	nodeID0 := ids.GenerateTestNodeID()
   417  	subnetID := ids.GenerateTestID()
   418  	startTime := time.Now()
   419  
   420  	s := NewTestState()
   421  	s.AddNode(nodeID0, subnetID, startTime)
   422  
   423  	clk := mockable.Clock{}
   424  	up := NewManager(s, &clk)
   425  
   426  	currentTime := startTime.Add(time.Second)
   427  	clk.Set(currentTime)
   428  
   429  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   430  	require.NoError(err)
   431  	require.Equal(time.Second, duration)
   432  	require.Equal(clk.UnixTime(), lastUpdated)
   433  
   434  	uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second))
   435  	require.NoError(err)
   436  	require.Equal(float64(1), uptime)
   437  }
   438  
   439  func TestCalculateUptimeWhenNeverConnected(t *testing.T) {
   440  	require := require.New(t)
   441  
   442  	nodeID0 := ids.GenerateTestNodeID()
   443  	subnetID := ids.GenerateTestID()
   444  	startTime := time.Now()
   445  
   446  	s := NewTestState()
   447  
   448  	clk := mockable.Clock{}
   449  	up := NewManager(s, &clk)
   450  
   451  	require.NoError(up.StartTracking([]ids.NodeID{}, subnetID))
   452  
   453  	s.AddNode(nodeID0, subnetID, startTime)
   454  
   455  	currentTime := startTime.Add(time.Second)
   456  	clk.Set(currentTime)
   457  
   458  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   459  	require.NoError(err)
   460  	require.Equal(time.Duration(0), duration)
   461  	require.Equal(clk.UnixTime(), lastUpdated)
   462  
   463  	uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime)
   464  	require.NoError(err)
   465  	require.Equal(float64(0), uptime)
   466  }
   467  
   468  func TestCalculateUptimeWhenConnectedBeforeTracking(t *testing.T) {
   469  	require := require.New(t)
   470  
   471  	nodeID0 := ids.GenerateTestNodeID()
   472  	subnetID := ids.GenerateTestID()
   473  	currentTime := time.Now()
   474  	startTime := currentTime
   475  
   476  	s := NewTestState()
   477  	s.AddNode(nodeID0, subnetID, startTime)
   478  
   479  	clk := mockable.Clock{}
   480  	up := NewManager(s, &clk)
   481  	clk.Set(currentTime)
   482  
   483  	require.NoError(up.Connect(nodeID0, subnetID))
   484  
   485  	currentTime = currentTime.Add(time.Second)
   486  	clk.Set(currentTime)
   487  
   488  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   489  
   490  	currentTime = currentTime.Add(time.Second)
   491  	clk.Set(currentTime)
   492  
   493  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   494  	require.NoError(err)
   495  	require.Equal(2*time.Second, duration)
   496  	require.Equal(clk.UnixTime(), lastUpdated)
   497  }
   498  
   499  func TestCalculateUptimeWhenConnectedInFuture(t *testing.T) {
   500  	require := require.New(t)
   501  
   502  	nodeID0 := ids.GenerateTestNodeID()
   503  	subnetID := ids.GenerateTestID()
   504  	currentTime := time.Now()
   505  	startTime := currentTime
   506  
   507  	s := NewTestState()
   508  	s.AddNode(nodeID0, subnetID, startTime)
   509  
   510  	clk := mockable.Clock{}
   511  	up := NewManager(s, &clk)
   512  	clk.Set(currentTime)
   513  
   514  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   515  
   516  	currentTime = currentTime.Add(2 * time.Second)
   517  	clk.Set(currentTime)
   518  
   519  	require.NoError(up.Connect(nodeID0, subnetID))
   520  
   521  	currentTime = currentTime.Add(-time.Second)
   522  	clk.Set(currentTime)
   523  
   524  	duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID)
   525  	require.NoError(err)
   526  	require.Equal(time.Duration(0), duration)
   527  	require.Equal(clk.UnixTime(), lastUpdated)
   528  }
   529  
   530  func TestCalculateUptimeNonValidator(t *testing.T) {
   531  	require := require.New(t)
   532  
   533  	nodeID0 := ids.GenerateTestNodeID()
   534  	subnetID := ids.GenerateTestID()
   535  	startTime := time.Now()
   536  
   537  	s := NewTestState()
   538  
   539  	clk := mockable.Clock{}
   540  	up := NewManager(s, &clk)
   541  
   542  	_, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime)
   543  	require.ErrorIs(err, database.ErrNotFound)
   544  }
   545  
   546  func TestCalculateUptimePercentageDivBy0(t *testing.T) {
   547  	require := require.New(t)
   548  
   549  	nodeID0 := ids.GenerateTestNodeID()
   550  	subnetID := ids.GenerateTestID()
   551  	currentTime := time.Now()
   552  	startTime := currentTime
   553  
   554  	s := NewTestState()
   555  	s.AddNode(nodeID0, subnetID, startTime)
   556  
   557  	clk := mockable.Clock{}
   558  	up := NewManager(s, &clk)
   559  	clk.Set(currentTime)
   560  
   561  	uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second))
   562  	require.NoError(err)
   563  	require.Equal(float64(1), uptime)
   564  }
   565  
   566  func TestCalculateUptimePercentage(t *testing.T) {
   567  	require := require.New(t)
   568  
   569  	nodeID0 := ids.GenerateTestNodeID()
   570  	currentTime := time.Now()
   571  	subnetID := ids.GenerateTestID()
   572  	startTime := currentTime
   573  
   574  	s := NewTestState()
   575  	s.AddNode(nodeID0, subnetID, startTime)
   576  
   577  	clk := mockable.Clock{}
   578  	up := NewManager(s, &clk)
   579  
   580  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   581  
   582  	currentTime = currentTime.Add(time.Second)
   583  	clk.Set(currentTime)
   584  
   585  	uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second))
   586  	require.NoError(err)
   587  	require.Equal(float64(0), uptime)
   588  }
   589  
   590  func TestStopTrackingUnixTimeRegression(t *testing.T) {
   591  	require := require.New(t)
   592  
   593  	nodeID0 := ids.GenerateTestNodeID()
   594  	currentTime := time.Now()
   595  	subnetID := ids.GenerateTestID()
   596  	startTime := currentTime
   597  
   598  	s := NewTestState()
   599  	s.AddNode(nodeID0, subnetID, startTime)
   600  
   601  	clk := mockable.Clock{}
   602  	up := NewManager(s, &clk)
   603  	clk.Set(currentTime)
   604  
   605  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   606  
   607  	require.NoError(up.Connect(nodeID0, subnetID))
   608  
   609  	currentTime = startTime.Add(time.Second)
   610  	clk.Set(currentTime)
   611  
   612  	require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID))
   613  
   614  	currentTime = startTime.Add(time.Second)
   615  	clk.Set(currentTime)
   616  
   617  	up = NewManager(s, &clk)
   618  
   619  	currentTime = startTime.Add(time.Second)
   620  	clk.Set(currentTime)
   621  
   622  	require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID))
   623  
   624  	require.NoError(up.Connect(nodeID0, subnetID))
   625  
   626  	currentTime = startTime.Add(time.Second)
   627  	clk.Set(currentTime)
   628  
   629  	perc, err := up.CalculateUptimePercent(nodeID0, subnetID)
   630  	require.NoError(err)
   631  	require.GreaterOrEqual(float64(1), perc)
   632  }