Skip to content

Commit

Permalink
add GetValidatorAndUptime to validator manager
Browse files Browse the repository at this point in the history
  • Loading branch information
ceyonur committed Nov 15, 2024
1 parent 8390b5f commit 42532d1
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 199 deletions.
11 changes: 9 additions & 2 deletions plugin/evm/validators/interfaces/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ package interfaces

import (
"context"
"time"

"github.com/ava-labs/avalanchego/ids"
avalancheuptime "github.com/ava-labs/avalanchego/snow/uptime"
stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces"
)

type ValidatorReader interface {
stateinterfaces.StateReader
avalancheuptime.Calculator
// GetValidatorUptime returns the uptime of the validator specified by validationID
GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error)
}

type Manager interface {
stateinterfaces.State
avalancheuptime.Manager
ValidatorReader

// Sync updates the validator set managed
// by the manager
Sync(ctx context.Context) error
// DispatchSync starts the sync process
DispatchSync(ctx context.Context)
}
27 changes: 27 additions & 0 deletions plugin/evm/validators/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type manager struct {
uptimeinterfaces.PausableManager
}

// NewManager returns a new validator manager
// that manages the validator state and the uptime manager.
func NewManager(
ctx *snow.Context,
db database.Database,
Expand All @@ -54,6 +56,30 @@ func NewManager(
}, nil
}

// GetValidatorUptime returns the calculated uptime of the validator specified by validationID
// and the last updated time.
// GetValidatorUptime holds the chain context lock while performing the operation and can be called concurrently.
func (m *manager) GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) {
// lock the state
m.chainCtx.Lock.RLock()
defer m.chainCtx.Lock.RUnlock()

// Get validator first
vdr, err := m.GetValidator(validationID)
if err != nil {
return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get validator: %w", err)
}

uptime, lastUpdated, err := m.CalculateUptime(vdr.NodeID)
if err != nil {
return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get uptime: %w", err)
}

return vdr, uptime, lastUpdated, nil
}

// DispatchSync starts the sync process
// DispatchSync holds the chain context lock while performing the sync.
func (m *manager) DispatchSync(ctx context.Context) {
ticker := time.NewTicker(SyncFrequency)
defer ticker.Stop()
Expand All @@ -74,6 +100,7 @@ func (m *manager) DispatchSync(ctx context.Context) {

// Sync synchronizes the validator state with the current validator set
// and writes the state to the database.
// Sync is not safe to call concurrently and should be called with the chain context locked.
func (m *manager) Sync(ctx context.Context) error {
now := time.Now()
log.Debug("performing validator sync")
Expand Down
1 change: 0 additions & 1 deletion plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,6 @@ func (vm *VM) Initialize(
vm.ctx.WarpSigner,
vm,
vm.validatorsManager,
vm.ctx.Lock.RLocker(),
vm.warpDB,
meteredCache,
offchainWarpMessages,
Expand Down
142 changes: 0 additions & 142 deletions plugin/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
commonEng "github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/snow/engine/enginetest"
"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
"github.com/ava-labs/avalanchego/upgrade"
"github.com/ava-labs/avalanchego/utils/formatting"
"github.com/ava-labs/avalanchego/utils/logging"
Expand All @@ -50,7 +49,6 @@ import (
"github.com/ava-labs/subnet-evm/eth"
"github.com/ava-labs/subnet-evm/metrics"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/plugin/evm/validators"
"github.com/ava-labs/subnet-evm/precompile/allowlist"
"github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist"
"github.com/ava-labs/subnet-evm/precompile/contracts/feemanager"
Expand All @@ -61,7 +59,6 @@ import (
"github.com/ava-labs/subnet-evm/utils"
"github.com/ava-labs/subnet-evm/vmerrs"

avagovalidators "github.com/ava-labs/avalanchego/snow/validators"
avagoconstants "github.com/ava-labs/avalanchego/utils/constants"
)

Expand Down Expand Up @@ -3176,142 +3173,3 @@ func TestStandaloneDB(t *testing.T) {
assert.False(t, isDBEmpty(vm.db))
assert.False(t, isDBEmpty(vm.acceptedBlockDB))
}

func TestValidatorState(t *testing.T) {
require := require.New(t)
genesis := &core.Genesis{}
require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONLatest)))
genesisJSON, err := genesis.MarshalJSON()
require.NoError(err)

vm := &VM{}
ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisJSON))
appSender := &enginetest.Sender{T: t}
appSender.CantSendAppGossip = true
testNodeIDs := []ids.NodeID{
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
ids.GenerateTestNodeID(),
}
testValidationIDs := []ids.ID{
ids.GenerateTestID(),
ids.GenerateTestID(),
ids.GenerateTestID(),
}
ctx.ValidatorState = &validatorstest.State{
GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagovalidators.GetCurrentValidatorOutput, uint64, error) {
return map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{
testValidationIDs[0]: {
NodeID: testNodeIDs[0],
PublicKey: nil,
Weight: 1,
},
testValidationIDs[1]: {
NodeID: testNodeIDs[1],
PublicKey: nil,
Weight: 1,
},
testValidationIDs[2]: {
NodeID: testNodeIDs[2],
PublicKey: nil,
Weight: 1,
},
}, 0, nil
},
}
appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil }
err = vm.Initialize(
context.Background(),
ctx,
dbManager,
genesisBytes,
[]byte(""),
[]byte(""),
issuer,
[]*commonEng.Fx{},
appSender,
)
require.NoError(err, "error initializing GenesisVM")

// Test case 1: state should not be populated until bootstrapped
require.NoError(vm.SetState(context.Background(), snow.Bootstrapping))
require.Equal(0, vm.validatorsManager.GetValidationIDs().Len())
_, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0])
require.ErrorIs(database.ErrNotFound, err)
require.False(vm.validatorsManager.StartedTracking())

// Test case 2: state should be populated after bootstrapped
require.NoError(vm.SetState(context.Background(), snow.NormalOp))
require.Len(vm.validatorsManager.GetValidationIDs(), 3)
_, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0])
require.NoError(err)
require.True(vm.validatorsManager.StartedTracking())

// Test case 3: restarting VM should not lose state
vm.Shutdown(context.Background())
// Shutdown should stop tracking
require.False(vm.validatorsManager.StartedTracking())

vm = &VM{}
err = vm.Initialize(
context.Background(),
utils.TestSnowContext(), // this context does not have validators state, making VM to source it from the database
dbManager,
genesisBytes,
[]byte(""),
[]byte(""),
issuer,
[]*commonEng.Fx{},
appSender,
)
require.NoError(err, "error initializing GenesisVM")
require.Len(vm.validatorsManager.GetValidationIDs(), 3)
_, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0])
require.NoError(err)
require.False(vm.validatorsManager.StartedTracking())

// Test case 4: new validators should be added to the state
newValidationID := ids.GenerateTestID()
newNodeID := ids.GenerateTestNodeID()
testState := &validatorstest.State{
GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagovalidators.GetCurrentValidatorOutput, uint64, error) {
return map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{
testValidationIDs[0]: {
NodeID: testNodeIDs[0],
PublicKey: nil,
Weight: 1,
},
testValidationIDs[1]: {
NodeID: testNodeIDs[1],
PublicKey: nil,
Weight: 1,
},
testValidationIDs[2]: {
NodeID: testNodeIDs[2],
PublicKey: nil,
Weight: 1,
},
newValidationID: {
NodeID: newNodeID,
PublicKey: nil,
Weight: 1,
},
}, 0, nil
},
}
// set VM as bootstrapped
require.NoError(vm.SetState(context.Background(), snow.Bootstrapping))
require.NoError(vm.SetState(context.Background(), snow.NormalOp))

vm.ctx.ValidatorState = testState

// new validator should be added to the state eventually after SyncFrequency
require.EventuallyWithT(func(c *assert.CollectT) {
vm.ctx.Lock.Lock()
defer vm.ctx.Lock.Unlock()
assert.Len(c, vm.validatorsManager.GetNodeIDs(), 4)
newValidator, err := vm.validatorsManager.GetValidator(newValidationID)
assert.NoError(c, err)
assert.Equal(c, newNodeID, newValidator.NodeID)
}, validators.SyncFrequency*2, 5*time.Second)
}
Loading

0 comments on commit 42532d1

Please sign in to comment.