mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 21:57:16 +00:00
3ff5b95945
* Remove database field from v1alpha1 validator server * remove field * gzl * remove not needed variable Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
1266 lines
43 KiB
Go
1266 lines
43 KiB
Go
package validator
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
mockPOW "github.com/prysmaticlabs/prysm/beacon-chain/powchain/testing"
|
|
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
|
|
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
|
mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing"
|
|
pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/shared/bls"
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"github.com/prysmaticlabs/prysm/shared/testutil"
|
|
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
|
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
|
"github.com/prysmaticlabs/prysm/shared/trieutil"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
func TestValidatorStatus_DepositedEth1(t *testing.T) {
|
|
ctx := context.Background()
|
|
deposits, _, err := testutil.DeterministicDepositsAndKeys(1)
|
|
require.NoError(t, err, "Could not generate deposits and keys")
|
|
deposit := deposits[0]
|
|
pubKey1 := deposit.Data.PublicKey
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{})
|
|
require.NoError(t, err)
|
|
vs := &Server{
|
|
DepositFetcher: depositCache,
|
|
BlockFetcher: p,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: stateObj,
|
|
},
|
|
Eth1InfoFetcher: p,
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey1,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_DEPOSITED, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_Deposited(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey1 := pubKey(1)
|
|
depData := ðpb.Deposit_Data{
|
|
Amount: params.BeaconConfig().MaxEffectiveBalance,
|
|
PublicKey: pubKey1,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{
|
|
Validators: []*ethpb.Validator{
|
|
{
|
|
PublicKey: pubKey1,
|
|
ActivationEligibilityEpoch: 1,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
vs := &Server{
|
|
DepositFetcher: depositCache,
|
|
BlockFetcher: p,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: stateObj,
|
|
},
|
|
Eth1InfoFetcher: p,
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey1,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_DEPOSITED, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_PartiallyDeposited(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey1 := pubKey(1)
|
|
depData := ðpb.Deposit_Data{
|
|
Amount: params.BeaconConfig().MinDepositAmount,
|
|
PublicKey: pubKey1,
|
|
Signature: []byte("hi"),
|
|
WithdrawalCredentials: []byte("hey"),
|
|
}
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{
|
|
Validators: []*ethpb.Validator{
|
|
{
|
|
PublicKey: pubKey1,
|
|
ActivationEligibilityEpoch: 1,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
vs := &Server{
|
|
DepositFetcher: depositCache,
|
|
BlockFetcher: p,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: stateObj,
|
|
},
|
|
Eth1InfoFetcher: p,
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey1,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_PARTIALLY_DEPOSITED, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_Pending(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey := pubKey(1)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
// Pending active because activation epoch is still defaulted at far future slot.
|
|
state, err := testutil.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, state.SetSlot(5000))
|
|
err = state.SetValidators([]*ethpb.Validator{
|
|
{
|
|
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKey,
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
BlockFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: state, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_PENDING, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_Active(t *testing.T) {
|
|
// This test breaks if it doesnt use mainnet config
|
|
params.SetupTestConfigCleanup(t)
|
|
params.OverrideBeaconConfig(params.MainnetConfig())
|
|
ctx := context.Background()
|
|
|
|
pubKey := pubKey(1)
|
|
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
|
|
// Active because activation epoch <= current epoch < exit epoch.
|
|
activeEpoch := helpers.ActivationExitEpoch(0)
|
|
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
|
|
state := &pbp2p.BeaconState{
|
|
GenesisTime: uint64(time.Unix(0, 0).Unix()),
|
|
Slot: 10000,
|
|
Validators: []*ethpb.Validator{{
|
|
ActivationEpoch: activeEpoch,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKey},
|
|
}}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(state)
|
|
require.NoError(t, err)
|
|
|
|
timestamp := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
int(params.BeaconConfig().Eth1FollowDistance): uint64(timestamp),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
BlockFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
|
|
expected := ðpb.ValidatorStatusResponse{
|
|
Status: ethpb.ValidatorStatus_ACTIVE,
|
|
ActivationEpoch: 5,
|
|
}
|
|
if !proto.Equal(resp, expected) {
|
|
t.Errorf("Wanted %v, got %v", expected, resp)
|
|
}
|
|
}
|
|
|
|
func TestValidatorStatus_Exiting(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey := pubKey(1)
|
|
|
|
// Initiated exit because validator exit epoch and withdrawable epoch are not FAR_FUTURE_EPOCH
|
|
slot := types.Slot(10000)
|
|
epoch := helpers.SlotToEpoch(slot)
|
|
exitEpoch := helpers.ActivationExitEpoch(epoch)
|
|
withdrawableEpoch := exitEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
|
|
state := &pbp2p.BeaconState{
|
|
Slot: slot,
|
|
Validators: []*ethpb.Validator{{
|
|
PublicKey: pubKey,
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: exitEpoch,
|
|
WithdrawableEpoch: withdrawableEpoch},
|
|
}}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(state)
|
|
require.NoError(t, err)
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
BlockFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_EXITING, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_Slashing(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey := pubKey(1)
|
|
|
|
// Exit slashed because slashed is true, exit epoch is =< current epoch and withdrawable epoch > epoch .
|
|
slot := types.Slot(10000)
|
|
epoch := helpers.SlotToEpoch(slot)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
|
|
state := &pbp2p.BeaconState{
|
|
Slot: slot,
|
|
Validators: []*ethpb.Validator{{
|
|
Slashed: true,
|
|
PublicKey: pubKey,
|
|
WithdrawableEpoch: epoch + 1},
|
|
}}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(state)
|
|
require.NoError(t, err)
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
BlockFetcher: p,
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_EXITED, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_Exited(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pubKey := pubKey(1)
|
|
|
|
// Exit because only exit epoch is =< current epoch.
|
|
slot := types.Slot(10000)
|
|
epoch := helpers.SlotToEpoch(slot)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
params.SetupTestConfigCleanup(t)
|
|
params.OverrideBeaconConfig(params.MainnetConfig())
|
|
state, err := testutil.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, state.SetSlot(slot))
|
|
err = state.SetValidators([]*ethpb.Validator{{
|
|
PublicKey: pubKey,
|
|
WithdrawableEpoch: epoch + 1,
|
|
WithdrawalCredentials: make([]byte, 32)},
|
|
})
|
|
require.NoError(t, err)
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey,
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
BlockFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: state, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_EXITED, resp.Status)
|
|
}
|
|
|
|
func TestValidatorStatus_UnknownStatus(t *testing.T) {
|
|
pubKey := pubKey(1)
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{
|
|
Slot: 0,
|
|
})
|
|
require.NoError(t, err)
|
|
vs := &Server{
|
|
DepositFetcher: depositCache,
|
|
Eth1InfoFetcher: &mockPOW.POWChain{},
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: stateObj,
|
|
},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_UNKNOWN_STATUS, resp.Status)
|
|
}
|
|
|
|
func TestActivationStatus_OK(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
deposits, _, err := testutil.DeterministicDepositsAndKeys(4)
|
|
require.NoError(t, err)
|
|
pubKeys := [][]byte{deposits[0].Data.PublicKey, deposits[1].Data.PublicKey, deposits[2].Data.PublicKey, deposits[3].Data.PublicKey}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{
|
|
Slot: 4000,
|
|
Validators: []*ethpb.Validator{
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[0],
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[1],
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[3],
|
|
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
dep := deposits[0]
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, depositTrie.Root()))
|
|
|
|
dep = deposits[2]
|
|
depositTrie.Insert(dep.Data.Signature, 15)
|
|
assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, depositTrie.Root()))
|
|
|
|
vs := &Server{
|
|
Ctx: context.Background(),
|
|
CanonicalStateChan: make(chan *pbp2p.BeaconState, 1),
|
|
ChainStartFetcher: &mockPOW.POWChain{},
|
|
BlockFetcher: &mockPOW.POWChain{},
|
|
Eth1InfoFetcher: &mockPOW.POWChain{},
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
}
|
|
activeExists, response, err := vs.activationStatus(context.Background(), pubKeys)
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, activeExists, "No activated validator exists when there was supposed to be 2")
|
|
if response[0].Status.Status != ethpb.ValidatorStatus_ACTIVE {
|
|
t.Errorf("Validator with pubkey %#x is not activated and instead has this status: %s",
|
|
response[0].PublicKey, response[0].Status.Status.String())
|
|
}
|
|
if response[0].Index != 0 {
|
|
t.Errorf("Validator with pubkey %#x is expected to have index %d, received %d", response[0].PublicKey, 0, response[0].Index)
|
|
}
|
|
|
|
if response[1].Status.Status != ethpb.ValidatorStatus_ACTIVE {
|
|
t.Errorf("Validator with pubkey %#x was activated when not supposed to",
|
|
response[1].PublicKey)
|
|
}
|
|
if response[1].Index != 1 {
|
|
t.Errorf("Validator with pubkey %#x is expected to have index %d, received %d", response[1].PublicKey, 1, response[1].Index)
|
|
}
|
|
|
|
if response[2].Status.Status != ethpb.ValidatorStatus_DEPOSITED {
|
|
t.Errorf("Validator with pubkey %#x is not unknown and instead has this status: %s",
|
|
response[2].PublicKey, response[2].Status.Status.String())
|
|
}
|
|
if uint64(response[2].Index) != uint64(params.BeaconConfig().FarFutureEpoch) {
|
|
t.Errorf("Validator with pubkey %#x is expected to have index %d, received %d", response[2].PublicKey, params.BeaconConfig().FarFutureEpoch, response[2].Index)
|
|
}
|
|
|
|
if response[3].Status.Status != ethpb.ValidatorStatus_DEPOSITED {
|
|
t.Errorf("Validator with pubkey %#x was not deposited and has this status: %s",
|
|
response[3].PublicKey, response[3].Status.Status.String())
|
|
}
|
|
if response[3].Index != 2 {
|
|
t.Errorf("Validator with pubkey %#x is expected to have index %d, received %d", response[3].PublicKey, 2, response[3].Index)
|
|
}
|
|
}
|
|
|
|
func TestValidatorStatus_CorrectActivationQueue(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pbKey := pubKey(5)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
currentSlot := types.Slot(5000)
|
|
// Pending active because activation epoch is still defaulted at far future slot.
|
|
validators := []*ethpb.Validator{
|
|
{
|
|
ActivationEpoch: 0,
|
|
PublicKey: pubKey(0),
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
PublicKey: pubKey(1),
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
PublicKey: pubKey(2),
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
PublicKey: pubKey(3),
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
{
|
|
ActivationEpoch: types.Epoch(currentSlot/params.BeaconConfig().SlotsPerEpoch + 1),
|
|
PublicKey: pbKey,
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
{
|
|
ActivationEpoch: types.Epoch(currentSlot/params.BeaconConfig().SlotsPerEpoch + 4),
|
|
PublicKey: pubKey(5),
|
|
WithdrawalCredentials: make([]byte, 32),
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
}
|
|
state, err := testutil.NewBeaconState()
|
|
require.NoError(t, err)
|
|
require.NoError(t, state.SetValidators(validators))
|
|
require.NoError(t, state.SetSlot(currentSlot))
|
|
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < 6; i++ {
|
|
depData := ðpb.Deposit_Data{
|
|
PublicKey: pubKey(uint64(i)),
|
|
Signature: bytesutil.PadTo([]byte("hi"), 96),
|
|
WithdrawalCredentials: bytesutil.PadTo([]byte("hey"), 32),
|
|
}
|
|
|
|
deposit := ðpb.Deposit{
|
|
Data: depData,
|
|
}
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, int64(i), depositTrie.Root()))
|
|
|
|
}
|
|
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
vs := &Server{
|
|
ChainStartFetcher: p,
|
|
BlockFetcher: p,
|
|
Eth1InfoFetcher: p,
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: state, Root: genesisRoot[:]},
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pbKey,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_PENDING, resp.Status)
|
|
assert.Equal(t, uint64(2), resp.PositionInActivationQueue, "Unexpected position in activation queue")
|
|
}
|
|
|
|
func TestMultipleValidatorStatus_Pubkeys(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
deposits, _, err := testutil.DeterministicDepositsAndKeys(6)
|
|
require.NoError(t, err)
|
|
pubKeys := [][]byte{
|
|
deposits[0].Data.PublicKey,
|
|
deposits[1].Data.PublicKey,
|
|
deposits[2].Data.PublicKey,
|
|
deposits[3].Data.PublicKey,
|
|
deposits[4].Data.PublicKey,
|
|
deposits[5].Data.PublicKey,
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{
|
|
Slot: 4000,
|
|
Validators: []*ethpb.Validator{
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[0],
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[1],
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[3],
|
|
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[4],
|
|
EffectiveBalance: params.BeaconConfig().MinDepositAmount,
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[5],
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
dep := deposits[0]
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, depositTrie.Root()))
|
|
dep = deposits[2]
|
|
depositTrie.Insert(dep.Data.Signature, 15)
|
|
assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, depositTrie.Root()))
|
|
|
|
vs := &Server{
|
|
Ctx: context.Background(),
|
|
CanonicalStateChan: make(chan *pbp2p.BeaconState, 1),
|
|
ChainStartFetcher: &mockPOW.POWChain{},
|
|
BlockFetcher: &mockPOW.POWChain{},
|
|
Eth1InfoFetcher: &mockPOW.POWChain{},
|
|
DepositFetcher: depositCache,
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
want := []*ethpb.ValidatorStatusResponse{
|
|
{
|
|
Status: ethpb.ValidatorStatus_ACTIVE,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_ACTIVE,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_DEPOSITED,
|
|
ActivationEpoch: 18446744073709551615,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_DEPOSITED,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_PARTIALLY_DEPOSITED,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_PENDING,
|
|
},
|
|
}
|
|
|
|
req := ðpb.MultipleValidatorStatusRequest{PublicKeys: pubKeys}
|
|
response, err := vs.MultipleValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, len(response.PublicKeys), len(pubKeys))
|
|
for i, resp := range response.PublicKeys {
|
|
require.DeepEqual(t, pubKeys[i], resp)
|
|
}
|
|
assert.Equal(t, len(pubKeys), len(response.Statuses))
|
|
for i, resp := range response.Statuses {
|
|
if !proto.Equal(want[i], resp) {
|
|
t.Fatalf("Wanted %v\n Recieved: %v\n", want[i], resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMultipleValidatorStatus_Indices(t *testing.T) {
|
|
slot := types.Slot(10000)
|
|
epoch := helpers.SlotToEpoch(slot)
|
|
pubKeys := [][]byte{pubKey(1), pubKey(2), pubKey(3), pubKey(4), pubKey(5), pubKey(6), pubKey(7)}
|
|
beaconState := &pbp2p.BeaconState{
|
|
Slot: 4000,
|
|
Validators: []*ethpb.Validator{
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[0],
|
|
},
|
|
{
|
|
ActivationEpoch: 0,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[1],
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[2],
|
|
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
|
},
|
|
{
|
|
Slashed: true,
|
|
ExitEpoch: epoch + 1,
|
|
PublicKey: pubKeys[3],
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[4],
|
|
EffectiveBalance: params.BeaconConfig().MinDepositAmount,
|
|
},
|
|
{
|
|
ActivationEligibilityEpoch: 700,
|
|
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
PublicKey: pubKeys[5],
|
|
},
|
|
},
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(beaconState)
|
|
require.NoError(t, err)
|
|
block := testutil.NewBeaconBlock()
|
|
genesisRoot, err := block.Block.HashTreeRoot()
|
|
require.NoError(t, err, "Could not get signing root")
|
|
|
|
vs := &Server{
|
|
Ctx: context.Background(),
|
|
CanonicalStateChan: make(chan *pbp2p.BeaconState, 1),
|
|
ChainStartFetcher: &mockPOW.POWChain{},
|
|
BlockFetcher: &mockPOW.POWChain{},
|
|
Eth1InfoFetcher: &mockPOW.POWChain{},
|
|
HeadFetcher: &mockChain.ChainService{State: stateObj, Root: genesisRoot[:]},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
|
|
want := []*ethpb.ValidatorStatusResponse{
|
|
{
|
|
Status: ethpb.ValidatorStatus_ACTIVE,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_ACTIVE,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_DEPOSITED,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_SLASHING,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_PARTIALLY_DEPOSITED,
|
|
},
|
|
{
|
|
Status: ethpb.ValidatorStatus_PENDING,
|
|
},
|
|
}
|
|
|
|
// Note: Index 6 should be skipped.
|
|
req := ðpb.MultipleValidatorStatusRequest{Indices: []int64{0, 1, 2, 3, 4, 5, 6}}
|
|
response, err := vs.MultipleValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, len(beaconState.Validators), len(response.PublicKeys))
|
|
for i, resp := range response.PublicKeys {
|
|
expected := beaconState.Validators[i].PublicKey
|
|
require.DeepEqual(t, expected, resp)
|
|
}
|
|
assert.Equal(t, len(beaconState.Validators), len(response.Statuses))
|
|
for i, resp := range response.Statuses {
|
|
if !proto.Equal(want[i], resp) {
|
|
t.Fatalf("Wanted %v\n Recieved: %v\n", want[i], resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidatorStatus_Invalid(t *testing.T) {
|
|
ctx := context.Background()
|
|
deposits, _, err := testutil.DeterministicDepositsAndKeys(1)
|
|
require.NoError(t, err, "Could not generate deposits and keys")
|
|
deposit := deposits[0]
|
|
pubKey1 := deposit.Data.PublicKey
|
|
deposit.Data.Signature = deposit.Data.Signature[1:]
|
|
depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
|
require.NoError(t, err, "Could not setup deposit trie")
|
|
depositCache, err := depositcache.New()
|
|
require.NoError(t, err)
|
|
|
|
assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.Root()))
|
|
height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix()
|
|
p := &mockPOW.POWChain{
|
|
TimesByHeight: map[int]uint64{
|
|
0: uint64(height),
|
|
},
|
|
}
|
|
stateObj, err := v1.InitializeFromProtoUnsafe(&pbp2p.BeaconState{})
|
|
require.NoError(t, err)
|
|
vs := &Server{
|
|
DepositFetcher: depositCache,
|
|
BlockFetcher: p,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: stateObj,
|
|
},
|
|
Eth1InfoFetcher: p,
|
|
}
|
|
req := ðpb.ValidatorStatusRequest{
|
|
PublicKey: pubKey1,
|
|
}
|
|
resp, err := vs.ValidatorStatus(context.Background(), req)
|
|
require.NoError(t, err, "Could not get validator status")
|
|
assert.Equal(t, ethpb.ValidatorStatus_INVALID, resp.Status)
|
|
}
|
|
|
|
func Test_DepositStatus(t *testing.T) {
|
|
assert.Equal(t, depositStatus(0), ethpb.ValidatorStatus_PENDING)
|
|
assert.Equal(t, depositStatus(params.BeaconConfig().MinDepositAmount), ethpb.ValidatorStatus_PARTIALLY_DEPOSITED)
|
|
assert.Equal(t, depositStatus(params.BeaconConfig().MaxEffectiveBalance), ethpb.ValidatorStatus_DEPOSITED)
|
|
}
|
|
|
|
func TestServer_CheckDoppelGanger(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
wantErr bool
|
|
svSetup func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse)
|
|
}{
|
|
{
|
|
name: "normal doppelganger request",
|
|
wantErr: false,
|
|
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
|
mockGen := stategen.NewMockService()
|
|
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
|
// Previous Epoch State
|
|
for i := 0; i < 3; i++ {
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 100 gwei, to mock an inactivity leak
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
|
|
}
|
|
// Older Epoch State
|
|
for i := 0; i < 3; i++ {
|
|
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 200 gwei, to mock an inactivity leak
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
|
|
}
|
|
vs := &Server{
|
|
StateGen: mockGen,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: hs,
|
|
},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
request := ðpb.DoppelGangerRequest{
|
|
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
|
}
|
|
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
|
|
for i := 0; i < 3; i++ {
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: false,
|
|
})
|
|
}
|
|
return vs, request, response
|
|
},
|
|
},
|
|
{
|
|
name: "doppelganger exists current epoch",
|
|
wantErr: false,
|
|
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
|
mockGen := stategen.NewMockService()
|
|
|
|
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
|
// Previous Epoch State
|
|
for i := 0; i < 2; i++ {
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 100 gwei, to mock an inactivity leak
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
|
|
}
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(2))
|
|
assert.NoError(t, err)
|
|
// Sub 100 gwei, to mock an active validator.
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
|
|
|
|
// Older Epoch State
|
|
for i := 0; i < 2; i++ {
|
|
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 200 gwei, to mock an inactivity leak
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
|
|
}
|
|
bal, err = os.BalanceAtIndex(types.ValidatorIndex(2))
|
|
assert.NoError(t, err)
|
|
// Sub 100 gwei, to mock an active validator.
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
|
|
|
|
vs := &Server{
|
|
StateGen: mockGen,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: hs,
|
|
},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
request := ðpb.DoppelGangerRequest{
|
|
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
|
}
|
|
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
|
|
for i := 0; i < 2; i++ {
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: false,
|
|
})
|
|
}
|
|
|
|
// Add in for duplicate validator
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[2].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[2].PublicKey().Marshal(),
|
|
DuplicateExists: true,
|
|
})
|
|
return vs, request, response
|
|
},
|
|
},
|
|
{
|
|
name: "doppelganger exists previous epoch",
|
|
wantErr: false,
|
|
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
|
mockGen := stategen.NewMockService()
|
|
|
|
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
|
// Previous Epoch State
|
|
for i := 0; i < 2; i++ {
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 100 gwei, to mock an inactivity leak
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
|
|
}
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(2))
|
|
assert.NoError(t, err)
|
|
// Sub 100 gwei, to mock an active validator.
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
|
|
|
|
// Older Epoch State
|
|
for i := 0; i < 2; i++ {
|
|
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 200 gwei, to mock an inactivity leak
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
|
|
}
|
|
bal, err = os.BalanceAtIndex(types.ValidatorIndex(2))
|
|
assert.NoError(t, err)
|
|
// Sub 200 gwei, to mock an active validator.
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-200))
|
|
|
|
vs := &Server{
|
|
StateGen: mockGen,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: hs,
|
|
},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
request := ðpb.DoppelGangerRequest{
|
|
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
|
}
|
|
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
|
|
for i := 0; i < 2; i++ {
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: false,
|
|
})
|
|
}
|
|
|
|
// Add in for duplicate validator
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[2].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[2].PublicKey().Marshal(),
|
|
DuplicateExists: true,
|
|
})
|
|
return vs, request, response
|
|
},
|
|
},
|
|
{
|
|
name: "multiple doppelganger exists",
|
|
wantErr: false,
|
|
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
|
mockGen := stategen.NewMockService()
|
|
|
|
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
|
// Previous Epoch State
|
|
for i := 10; i < 15; i++ {
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 100 gwei, to mock an inactivity leak
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-100))
|
|
}
|
|
|
|
// Older Epoch State
|
|
for i := 10; i < 15; i++ {
|
|
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 200 gwei, to mock an inactivity leak
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-200))
|
|
}
|
|
|
|
vs := &Server{
|
|
StateGen: mockGen,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: hs,
|
|
},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
request := ðpb.DoppelGangerRequest{
|
|
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
|
}
|
|
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
|
|
for i := 0; i < 10; i++ {
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: false,
|
|
})
|
|
}
|
|
for i := 10; i < 15; i++ {
|
|
// Add in for duplicate validator
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 1,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: true,
|
|
})
|
|
}
|
|
|
|
return vs, request, response
|
|
},
|
|
},
|
|
{
|
|
name: "attesters are too recent",
|
|
wantErr: false,
|
|
svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) {
|
|
mockGen := stategen.NewMockService()
|
|
|
|
hs, ps, os, keys := createStateSetup(t, 4, mockGen)
|
|
// Previous Epoch State
|
|
for i := 10; i < 15; i++ {
|
|
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 100 gwei, to mock an active validator
|
|
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-100))
|
|
}
|
|
|
|
// Older Epoch State
|
|
for i := 10; i < 15; i++ {
|
|
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
|
|
assert.NoError(t, err)
|
|
// Add 200 gwei, to mock an active validator
|
|
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-200))
|
|
}
|
|
|
|
vs := &Server{
|
|
StateGen: mockGen,
|
|
HeadFetcher: &mockChain.ChainService{
|
|
State: hs,
|
|
},
|
|
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
|
}
|
|
request := ðpb.DoppelGangerRequest{
|
|
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
|
|
}
|
|
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
|
|
for i := 0; i < 15; i++ {
|
|
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
Epoch: 2,
|
|
SignedRoot: []byte{'A'},
|
|
})
|
|
response.Responses = append(response.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{
|
|
PublicKey: keys[i].PublicKey().Marshal(),
|
|
DuplicateExists: false,
|
|
})
|
|
}
|
|
|
|
return vs, request, response
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
vs, req, resp := tt.svSetup(t)
|
|
got, err := vs.CheckDoppelGanger(context.Background(), req)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("CheckDoppelGanger() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, resp) {
|
|
t.Errorf("CheckDoppelGanger() got = %v, want %v", got.String(), resp.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func createStateSetup(t *testing.T, head types.Epoch, mockgen *stategen.MockStateManager) (iface.BeaconState,
|
|
iface.BeaconState, iface.BeaconState, []bls.SecretKey) {
|
|
gs, keys := testutil.DeterministicGenesisState(t, 64)
|
|
hs := gs.Copy()
|
|
// Head State
|
|
headEpoch := head
|
|
headSlot := types.Slot(headEpoch) * params.BeaconConfig().SlotsPerEpoch
|
|
assert.NoError(t, hs.SetSlot(headSlot))
|
|
mockgen.StatesBySlot[headSlot] = hs
|
|
|
|
// Previous Epoch State
|
|
prevEpoch := headEpoch - 1
|
|
ps := gs.Copy()
|
|
prevSlot := types.Slot(prevEpoch) * params.BeaconConfig().SlotsPerEpoch
|
|
assert.NoError(t, ps.SetSlot(prevSlot))
|
|
mockgen.StatesBySlot[prevSlot] = ps
|
|
|
|
// Older Epoch State
|
|
olderEpoch := prevEpoch - 1
|
|
os := gs.Copy()
|
|
olderSlot := types.Slot(olderEpoch) * params.BeaconConfig().SlotsPerEpoch
|
|
assert.NoError(t, os.SetSlot(olderSlot))
|
|
mockgen.StatesBySlot[olderSlot] = os
|
|
return hs, ps, os, keys
|
|
}
|