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 }