mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
changing minimal run to current-1 to current with a shorter total epoch run and moving full post merge run to post submit
This commit is contained in:
@@ -5,6 +5,7 @@ load("@prysm//tools/go:def.bzl", "go_test")
|
||||
# gazelle:exclude mainnet_scenario_e2e_test.go
|
||||
# gazelle:exclude minimal_scenario_e2e_test.go
|
||||
# gazelle:exclude minimal_builder_e2e_test.go
|
||||
# gazelle:exclude minimal_postmerge_e2e_test.go
|
||||
|
||||
# Presubmit tests represent the group of endtoend tests that are run on pull
|
||||
# requests and must be passing before a pull request can merge.
|
||||
@@ -27,6 +28,7 @@ test_suite(
|
||||
],
|
||||
tests = [
|
||||
":go_builder_test",
|
||||
":go_minimal_postmerge_test",
|
||||
":go_mainnet_test",
|
||||
],
|
||||
)
|
||||
@@ -153,6 +155,39 @@ go_test(
|
||||
deps = common_deps,
|
||||
)
|
||||
|
||||
# Full e2e test from post-merge (Bellatrix) through all forks (post-submit only, takes longer)
|
||||
go_test(
|
||||
name = "go_minimal_postmerge_test",
|
||||
size = "enormous",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"component_handler_test.go",
|
||||
"endtoend_setup_test.go",
|
||||
"endtoend_test.go",
|
||||
"minimal_postmerge_e2e_test.go",
|
||||
],
|
||||
args = ["-test.v"],
|
||||
data = [
|
||||
"//:prysm_sh",
|
||||
"//cmd/beacon-chain",
|
||||
"//cmd/validator",
|
||||
"//config/params:custom_configs",
|
||||
"//tools/bootnode",
|
||||
"@com_github_ethereum_go_ethereum//cmd/geth",
|
||||
"@web3signer",
|
||||
],
|
||||
eth_network = "minimal",
|
||||
flaky = True,
|
||||
shard_count = 2,
|
||||
tags = [
|
||||
"e2e",
|
||||
"manual",
|
||||
"minimal",
|
||||
"requires-network",
|
||||
],
|
||||
deps = common_deps,
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_mainnet_test",
|
||||
size = "large",
|
||||
|
||||
@@ -35,6 +35,10 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
|
||||
}
|
||||
tracingPort := e2eParams.TestParams.Ports.JaegerTracingPort
|
||||
tracingEndpoint := fmt.Sprintf("127.0.0.1:%d", tracingPort)
|
||||
// Default exit epoch used for voluntary exit tests.
|
||||
// Can be overridden via WithExitEpoch option for shorter test runs.
|
||||
exitEpoch := primitives.Epoch(7)
|
||||
|
||||
evals := []types.Evaluator{
|
||||
ev.PeersConnect,
|
||||
ev.HealthzCheck,
|
||||
@@ -44,10 +48,7 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
|
||||
ev.FinalizationOccurs(3),
|
||||
ev.VerifyBlockGraffiti,
|
||||
ev.PeersCheck,
|
||||
ev.ProposeVoluntaryExit,
|
||||
ev.ValidatorsHaveExited,
|
||||
ev.SubmitWithdrawal,
|
||||
ev.ValidatorsHaveWithdrawn,
|
||||
// Exit-related evaluators are added after processing options to allow custom exit epoch.
|
||||
ev.ProcessesDepositsInBlocks,
|
||||
ev.ActivatesDepositedValidators,
|
||||
ev.DepositedValidatorsAreActive,
|
||||
@@ -88,6 +89,18 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
|
||||
for _, o := range cfgo {
|
||||
o(testConfig)
|
||||
}
|
||||
|
||||
// Add exit-related evaluators using custom exit epoch if configured, otherwise use default.
|
||||
if testConfig.ExitEpoch > 0 {
|
||||
exitEpoch = testConfig.ExitEpoch
|
||||
}
|
||||
testConfig.Evaluators = append(testConfig.Evaluators,
|
||||
ev.ProposeVoluntaryExitAtEpoch(exitEpoch),
|
||||
ev.ValidatorsHaveExitedAtEpoch(exitEpoch+1),
|
||||
ev.SubmitWithdrawalAtEpoch(exitEpoch+1),
|
||||
ev.ValidatorsHaveWithdrawnAfterExitAtEpoch(exitEpoch),
|
||||
)
|
||||
|
||||
if testConfig.UseBuilder {
|
||||
testConfig.Evaluators = append(testConfig.Evaluators, ev.BuilderIsActive)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ var depositsInBlockStart = params.E2ETestConfig().EpochsPerEth1VotingPeriod * 2
|
||||
// deposits included + finalization + MaxSeedLookahead for activation.
|
||||
var depositActivationStartEpoch = depositsInBlockStart + 2 + params.E2ETestConfig().MaxSeedLookahead
|
||||
var depositEndEpoch = depositActivationStartEpoch + primitives.Epoch(math.Ceil(float64(depositValCount)/float64(params.E2ETestConfig().MinPerEpochChurnLimit)))
|
||||
var exitSubmissionEpoch = primitives.Epoch(7)
|
||||
|
||||
// Default exit submission epoch for standard tests.
|
||||
var defaultExitSubmissionEpoch = primitives.Epoch(7)
|
||||
|
||||
// ProcessesDepositsInBlocks ensures the expected amount of deposits are accepted into blocks.
|
||||
// Note: This evaluator only works for pre-Electra genesis since Electra uses EIP-6110 deposit requests.
|
||||
@@ -92,61 +94,87 @@ var DepositedValidatorsAreActive = e2etypes.Evaluator{
|
||||
}
|
||||
|
||||
// ProposeVoluntaryExit sends a voluntary exit from randomly selected validator in the genesis set.
|
||||
var ProposeVoluntaryExit = e2etypes.Evaluator{
|
||||
Name: "propose_voluntary_exit_epoch_%d",
|
||||
Policy: policies.OnEpoch(exitSubmissionEpoch),
|
||||
Evaluation: proposeVoluntaryExit,
|
||||
// Uses the default exit submission epoch (7).
|
||||
var ProposeVoluntaryExit = ProposeVoluntaryExitAtEpoch(defaultExitSubmissionEpoch)
|
||||
|
||||
// ProposeVoluntaryExitAtEpoch sends a voluntary exit at the specified epoch.
|
||||
var ProposeVoluntaryExitAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
|
||||
return e2etypes.Evaluator{
|
||||
Name: "propose_voluntary_exit_epoch_%d",
|
||||
Policy: policies.OnEpoch(epoch),
|
||||
Evaluation: proposeVoluntaryExit,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatorsHaveExited checks the beacon state for the exited validator and ensures its marked as exited.
|
||||
var ValidatorsHaveExited = e2etypes.Evaluator{
|
||||
Name: "voluntary_has_exited_%d",
|
||||
Policy: policies.OnEpoch(8),
|
||||
Evaluation: validatorsHaveExited,
|
||||
// Uses the default exit submission epoch + 1 (epoch 8).
|
||||
var ValidatorsHaveExited = ValidatorsHaveExitedAtEpoch(defaultExitSubmissionEpoch + 1)
|
||||
|
||||
// ValidatorsHaveExitedAtEpoch checks validators have exited at the specified epoch.
|
||||
var ValidatorsHaveExitedAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
|
||||
return e2etypes.Evaluator{
|
||||
Name: "voluntary_has_exited_%d",
|
||||
Policy: policies.OnEpoch(epoch),
|
||||
Evaluation: validatorsHaveExited,
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitWithdrawal sends a withdrawal from a previously exited validator.
|
||||
var SubmitWithdrawal = e2etypes.Evaluator{
|
||||
Name: "submit_withdrawal_epoch_%d",
|
||||
Policy: func(currentEpoch primitives.Epoch) bool {
|
||||
fEpoch := params.BeaconConfig().CapellaForkEpoch
|
||||
// If Capella is disabled (starting at Deneb+), run after exit submission
|
||||
if e2etypes.GenesisFork() >= version.Deneb {
|
||||
return policies.OnEpoch(exitSubmissionEpoch + 1)(currentEpoch)
|
||||
}
|
||||
return policies.BetweenEpochs(fEpoch-2, fEpoch+1)(currentEpoch)
|
||||
},
|
||||
Evaluation: submitWithdrawal,
|
||||
// Uses default timing based on exit submission epoch.
|
||||
var SubmitWithdrawal = SubmitWithdrawalAtEpoch(defaultExitSubmissionEpoch + 1)
|
||||
|
||||
// SubmitWithdrawalAtEpoch sends a withdrawal at the specified epoch.
|
||||
// For pre-Deneb genesis, it runs around the Capella fork epoch instead.
|
||||
var SubmitWithdrawalAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
|
||||
return e2etypes.Evaluator{
|
||||
Name: "submit_withdrawal_epoch_%d",
|
||||
Policy: func(currentEpoch primitives.Epoch) bool {
|
||||
fEpoch := params.BeaconConfig().CapellaForkEpoch
|
||||
// If Capella is disabled (starting at Deneb+), run at the specified epoch
|
||||
if e2etypes.GenesisFork() >= version.Deneb {
|
||||
return policies.OnEpoch(epoch)(currentEpoch)
|
||||
}
|
||||
return policies.BetweenEpochs(fEpoch-2, fEpoch+1)(currentEpoch)
|
||||
},
|
||||
Evaluation: submitWithdrawal,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatorsHaveWithdrawn checks the beacon state for the withdrawn validator and ensures it has been withdrawn.
|
||||
var ValidatorsHaveWithdrawn = e2etypes.Evaluator{
|
||||
Name: "validator_has_withdrawn_%d",
|
||||
Policy: func(currentEpoch primitives.Epoch) bool {
|
||||
// TODO: Fix this for mainnet configs.
|
||||
if params.BeaconConfig().ConfigName != params.EndToEndName {
|
||||
return false
|
||||
}
|
||||
// Only run this for minimal setups after capella
|
||||
fEpoch := params.BeaconConfig().CapellaForkEpoch
|
||||
// If Capella is disabled (starting at Deneb+), run after withdrawal submission
|
||||
var validWithdrawnEpoch primitives.Epoch
|
||||
if e2etypes.GenesisFork() >= version.Deneb {
|
||||
// Exit submitted at exitSubmissionEpoch (7)
|
||||
// Exit epoch = exitSubmissionEpoch + 1 + MAX_SEED_LOOKAHEAD
|
||||
// Withdrawable epoch = exit epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
||||
// Add 1 more epoch for the sweep to process
|
||||
exitEpoch := exitSubmissionEpoch + 1 + primitives.Epoch(params.BeaconConfig().MaxSeedLookahead)
|
||||
withdrawableEpoch := exitEpoch + primitives.Epoch(params.BeaconConfig().MinValidatorWithdrawabilityDelay)
|
||||
validWithdrawnEpoch = withdrawableEpoch + 1
|
||||
} else {
|
||||
validWithdrawnEpoch = fEpoch + 1
|
||||
}
|
||||
// Uses default timing based on exit submission epoch.
|
||||
var ValidatorsHaveWithdrawn = ValidatorsHaveWithdrawnAfterExitAtEpoch(defaultExitSubmissionEpoch)
|
||||
|
||||
requiredPolicy := policies.OnEpoch(validWithdrawnEpoch)
|
||||
return requiredPolicy(currentEpoch)
|
||||
},
|
||||
Evaluation: validatorsAreWithdrawn,
|
||||
// ValidatorsHaveWithdrawnAfterExitAtEpoch checks validators have withdrawn after exiting at the specified epoch.
|
||||
// For pre-Deneb genesis, it runs at CapellaForkEpoch + 1 instead.
|
||||
var ValidatorsHaveWithdrawnAfterExitAtEpoch = func(exitSubmitEpoch primitives.Epoch) e2etypes.Evaluator {
|
||||
return e2etypes.Evaluator{
|
||||
Name: "validator_has_withdrawn_%d",
|
||||
Policy: func(currentEpoch primitives.Epoch) bool {
|
||||
// TODO: Fix this for mainnet configs.
|
||||
if params.BeaconConfig().ConfigName != params.EndToEndName {
|
||||
return false
|
||||
}
|
||||
// Only run this for minimal setups after capella
|
||||
fEpoch := params.BeaconConfig().CapellaForkEpoch
|
||||
// If Capella is disabled (starting at Deneb+), run after withdrawal submission
|
||||
var validWithdrawnEpoch primitives.Epoch
|
||||
if e2etypes.GenesisFork() >= version.Deneb {
|
||||
// Exit submitted at exitSubmitEpoch
|
||||
// Exit epoch = exitSubmitEpoch + 1 + MAX_SEED_LOOKAHEAD
|
||||
// Withdrawable epoch = exit epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
||||
// Add 1 more epoch for the sweep to process
|
||||
exitEpoch := exitSubmitEpoch + 1 + primitives.Epoch(params.BeaconConfig().MaxSeedLookahead)
|
||||
withdrawableEpoch := exitEpoch + primitives.Epoch(params.BeaconConfig().MinValidatorWithdrawabilityDelay)
|
||||
validWithdrawnEpoch = withdrawableEpoch + 1
|
||||
} else {
|
||||
validWithdrawnEpoch = fEpoch + 1
|
||||
}
|
||||
|
||||
requiredPolicy := policies.OnEpoch(validWithdrawnEpoch)
|
||||
return requiredPolicy(currentEpoch)
|
||||
},
|
||||
Evaluation: validatorsAreWithdrawn,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm.
|
||||
@@ -357,8 +385,7 @@ func depositedValidatorsAreActive(ec *e2etypes.EvaluationContext, conns ...*grpc
|
||||
continue // we aren't checking for this validator
|
||||
}
|
||||
// ignore voluntary exits when checking balance and active status
|
||||
exited := ec.ExitedVals[key]
|
||||
if exited {
|
||||
if _, exited := ec.ExitedVals[key]; exited {
|
||||
nexits++
|
||||
delete(expected, key)
|
||||
continue
|
||||
@@ -463,7 +490,7 @@ func proposeVoluntaryExit(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientC
|
||||
return errors.Wrap(err, "could not propose exit")
|
||||
}
|
||||
pubk := bytesutil.ToBytes48(deposits[exitedIndex].Data.PublicKey)
|
||||
ec.ExitedVals[pubk] = true
|
||||
ec.ExitedVals[pubk] = chainHead.HeadEpoch // Store submission epoch
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -477,7 +504,7 @@ func proposeVoluntaryExit(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientC
|
||||
// Send an exit for a non-exited validator.
|
||||
for i := 0; i < numOfExits; {
|
||||
randIndex := primitives.ValidatorIndex(rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount)
|
||||
if ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())] {
|
||||
if _, alreadyExited := ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())]; alreadyExited {
|
||||
continue
|
||||
}
|
||||
if err := sendExit(randIndex); err != nil {
|
||||
|
||||
@@ -62,6 +62,7 @@ var ValidatorSyncParticipation = types.Evaluator{
|
||||
func validatorsAreActive(ec *types.EvaluationContext, conns ...*grpc.ClientConn) error {
|
||||
conn := conns[0]
|
||||
client := ethpb.NewBeaconChainClient(conn)
|
||||
|
||||
// Balances actually fluctuate but we just want to check initial balance.
|
||||
validatorRequest := ðpb.ListValidatorsRequest{
|
||||
PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount),
|
||||
@@ -72,17 +73,26 @@ func validatorsAreActive(ec *types.EvaluationContext, conns ...*grpc.ClientConn)
|
||||
return errors.Wrap(err, "failed to get validators")
|
||||
}
|
||||
|
||||
expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount
|
||||
// Count should be MinGenesisActiveValidatorCount minus any validators that have exited.
|
||||
// We determine actual exited count from the difference, as exits may be submitted but
|
||||
// not yet processed, or affected by churn limits.
|
||||
receivedCount := uint64(len(validators.ValidatorList))
|
||||
if expectedCount != receivedCount {
|
||||
return fmt.Errorf("expected validator count to be %d, received %d", expectedCount, receivedCount)
|
||||
maxExpected := params.BeaconConfig().MinGenesisActiveValidatorCount
|
||||
minExpected := maxExpected - uint64(len(ec.ExitedVals))
|
||||
|
||||
if receivedCount > maxExpected {
|
||||
return fmt.Errorf("validator count %d exceeds genesis count %d", receivedCount, maxExpected)
|
||||
}
|
||||
if receivedCount < minExpected {
|
||||
return fmt.Errorf("validator count %d is less than expected minimum %d (genesis %d - %d submitted exits)",
|
||||
receivedCount, minExpected, maxExpected, len(ec.ExitedVals))
|
||||
}
|
||||
|
||||
effBalanceLowCount := 0
|
||||
exitEpochWrongCount := 0
|
||||
withdrawEpochWrongCount := 0
|
||||
for _, item := range validators.ValidatorList {
|
||||
if ec.ExitedVals[bytesutil.ToBytes48(item.Validator.PublicKey)] {
|
||||
if _, exited := ec.ExitedVals[bytesutil.ToBytes48(item.Validator.PublicKey)]; exited {
|
||||
continue
|
||||
}
|
||||
if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// Run mainnet e2e config with the current release validator against latest beacon node.
|
||||
func TestEndToEnd_MainnetConfig_ValidatorAtCurrentRelease(t *testing.T) {
|
||||
r := e2eMainnet(t, true, false, types.InitForkCfg(version.Bellatrix, version.Electra, params.E2EMainnetTestConfig()))
|
||||
r := e2eMainnet(t, true, false, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2EMainnetTestConfig()))
|
||||
r.run()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,27 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
|
||||
)
|
||||
|
||||
// TestEndToEnd_MinimalConfig is the pre-submit e2e test from Electra to Fulu
|
||||
// with compressed epochs. It runs 10 epochs with exit at epoch 4 (the earliest
|
||||
// possible due to ShardCommitteePeriod=4), allowing all evaluators to complete:
|
||||
// - Participation at epoch 2
|
||||
// - Finalization at epoch 3
|
||||
// - Fulu fork transition at epoch 2
|
||||
// - Exit proposed at epoch 4
|
||||
// - Exit confirmed at epoch 5
|
||||
// - Withdrawal submitted at epoch 5
|
||||
// - Withdrawal verified at epoch 8 (exit epoch 4 + 1 + MaxSeedLookahead + MinValidatorWithdrawabilityDelay + 1)
|
||||
func TestEndToEnd_MinimalConfig(t *testing.T) {
|
||||
r := e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2ETestConfig()), types.WithCheckpointSync())
|
||||
cfg := params.E2ETestConfig()
|
||||
cfg = types.InitForkCfg(version.Electra, version.Fulu, cfg)
|
||||
// Set Fulu fork at epoch 2 for a quick fork transition test
|
||||
cfg.FuluForkEpoch = 2
|
||||
cfg.InitializeForkSchedule()
|
||||
|
||||
r := e2eMinimal(t, cfg,
|
||||
types.WithCheckpointSync(),
|
||||
types.WithEpochs(10),
|
||||
types.WithExitEpoch(4), // Minimum due to ShardCommitteePeriod=4
|
||||
)
|
||||
r.run()
|
||||
}
|
||||
17
testing/endtoend/minimal_postmerge_e2e_test.go
Normal file
17
testing/endtoend/minimal_postmerge_e2e_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package endtoend
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
|
||||
)
|
||||
|
||||
// TestEndToEnd_MinimalConfig_PostMerge is a post-submit test that runs the full
|
||||
// e2e test from Bellatrix (post-merge) through all fork transitions to the latest fork.
|
||||
// This test takes longer but provides comprehensive coverage of all forks.
|
||||
func TestEndToEnd_MinimalConfig_PostMerge(t *testing.T) {
|
||||
r := e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2ETestConfig()), types.WithCheckpointSync())
|
||||
r.run()
|
||||
}
|
||||
@@ -67,6 +67,14 @@ func WithSSZOnly() E2EConfigOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithExitEpoch sets a custom epoch for voluntary exit submission.
|
||||
// This affects ProposeVoluntaryExit, ValidatorsHaveExited, SubmitWithdrawal, and ValidatorsHaveWithdrawn evaluators.
|
||||
func WithExitEpoch(e primitives.Epoch) E2EConfigOpt {
|
||||
return func(cfg *E2EConfig) {
|
||||
cfg.ExitEpoch = e
|
||||
}
|
||||
}
|
||||
|
||||
// E2EConfig defines the struct for all configurations needed for E2E testing.
|
||||
type E2EConfig struct {
|
||||
TestCheckpointSync bool
|
||||
@@ -82,6 +90,7 @@ type E2EConfig struct {
|
||||
UseBeaconRestApi bool
|
||||
UseBuilder bool
|
||||
EpochsToRun uint64
|
||||
ExitEpoch primitives.Epoch // Custom epoch for voluntary exit submission (0 means use default)
|
||||
Seed int64
|
||||
TracingSinkEndpoint string
|
||||
Evaluators []Evaluator
|
||||
@@ -149,7 +158,9 @@ type DepositBalancer interface {
|
||||
// EvaluationContext allows for additional data to be provided to evaluators that need extra state.
|
||||
type EvaluationContext struct {
|
||||
DepositBalancer
|
||||
ExitedVals map[[48]byte]bool
|
||||
// ExitedVals maps validator pubkey to the epoch when their exit was submitted.
|
||||
// The actual exit takes effect at: submission_epoch + 1 + MaxSeedLookahead
|
||||
ExitedVals map[[48]byte]primitives.Epoch
|
||||
SeenVotes map[primitives.Slot][]byte
|
||||
ExpectedEth1DataVote []byte
|
||||
}
|
||||
@@ -158,7 +169,7 @@ type EvaluationContext struct {
|
||||
func NewEvaluationContext(d DepositBalancer) *EvaluationContext {
|
||||
return &EvaluationContext{
|
||||
DepositBalancer: d,
|
||||
ExitedVals: make(map[[48]byte]bool),
|
||||
ExitedVals: make(map[[48]byte]primitives.Epoch),
|
||||
SeenVotes: make(map[primitives.Slot][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user