Compare commits

...

11 Commits

Author SHA1 Message Date
Potuz
9039816ac9 Revert "Fork choice filter changes (#13464)"
This reverts commit 6a605e6b6d.
2024-03-06 02:31:35 -03:00
Radosław Kapka
ee9274a9bc REST VC metrics (#13588)
* REST VC metrics

* validator status is not used

* refactor

* remove test

* server-side

* server-side per endpoint

* cleanup

* add config endpoints

* add proper HTTP methods to endpoints

* initialize missing fields
2024-03-05 18:15:46 +00:00
Manu NALEPA
ef21d3adf8 Implement EIP-3076 minimal slashing protection, using a filesystem database (#13360)
* `EpochFromString`: Use already defined `Uint64FromString` function.

* `Test_uint64FromString` => `Test_FromString`

This test function tests more functions than `Uint64FromString`.

* Slashing protection history: Remove unreachable code.

The function `NewKVStore` creates, via `kv.UpdatePublicKeysBuckets`,
a new item in the `proposal-history-bucket-interchange`.

IMO there is no real reason to prefer `proposal` than `attestation`
as a prefix for this bucket, but this is the way it is done right now
and renaming the bucket will probably be backward incompatible.

An `attestedPublicKey` cannot exist without
the corresponding `proposedPublicKey`.

Thus, the `else` portion of code removed in this commit is not reachable.
We raise an error if we get there.

This is also probably the reason why the removed `else` portion was not
tested.

* `NewKVStore`: Switch items in `createBuckets`.

So the order corresponds to `schema.go`

* `slashableAttestationCheck`: Fix comments and logs.

* `ValidatorClient.db`: Use `iface.ValidatorDB`.

* BoltDB database: Implement `GraffitiFileHash`.

* Filesystem database: Creates `db.go`.

This file defines the following structs:
- `Store`
- `Graffiti`
- `Configuration`
- `ValidatorSlashingProtection`

This files implements the following public functions:
- `NewStore`
- `Close`
- `Backup`
- `DatabasePath`
- `ClearDB`
- `UpdatePublicKeysBuckets`

This files implements the following private functions:
- `slashingProtectionDirPath`
- `configurationFilePath`
- `configuration`
- `saveConfiguration`
- `validatorSlashingProtection`
- `saveValidatorSlashingProtection`
- `publicKeys`

* Filesystem database: Creates `genesis.go`.

This file defines the following public functions:
- `GenesisValidatorsRoot`
- `SaveGenesisValidatorsRoot`

* Filesystem database: Creates `graffiti.go`.

This file defines the following public functions:
- `SaveGraffitiOrderedIndex`
- `GraffitiOrderedIndex`

* Filesystem database: Creates `migration.go`.

This file defines the following public functions:
- `RunUpMigrations`
- `RunDownMigrations`

* Filesystem database: Creates proposer_settings.go.

This file defines the following public functions:
- `ProposerSettings`
- `ProposerSettingsExists`
- `SaveProposerSettings`

* Filesystem database: Creates `attester_protection.go`.

This file defines the following public functions:
- `EIPImportBlacklistedPublicKeys`
- `SaveEIPImportBlacklistedPublicKeys`
- `SigningRootAtTargetEpoch`
- `LowestSignedTargetEpoch`
- `LowestSignedSourceEpoch`
- `AttestedPublicKeys`
- `CheckSlashableAttestation`
- `SaveAttestationForPubKey`
- `SaveAttestationsForPubKey`
- `AttestationHistoryForPubKey`

* Filesystem database: Creates `proposer_protection.go`.

This file defines the following public functions:
- `HighestSignedProposal`
- `LowestSignedProposal`
- `ProposalHistoryForPubKey`
- `ProposalHistoryForSlot`
- `ProposedPublicKeys`

* Ensure that the filesystem store implements the `ValidatorDB` interface.

* `slashableAttestationCheck`: Check the database type.

* `slashableProposalCheck`: Check the database type.

* `slashableAttestationCheck`: Allow usage of minimal slashing protection.

* `slashableProposalCheck`: Allow usage of minimal slashing protection.

* `ImportStandardProtectionJSON`: Check the database type.

* `ImportStandardProtectionJSON`: Allow usage of min slashing protection.

* Implement `RecursiveDirFind`.

* Implement minimal<->complete DB conversion.

3 public functions are implemented:
- `IsCompleteDatabaseExisting`
- `IsMinimalDatabaseExisting`
- `ConvertDatabase`

* `setupDB`: Add `isSlashingProtectionMinimal` argument.

The feature addition is located in `validator/node/node_test.go`.
The rest of this commit consists in minimal slashing protection testing.

* `setupWithKey`: Add `isSlashingProtectionMinimal` argument.

The feature addition is located in `validator/client/propose_test.go`.

The rest of this commit consists in tests wrapping.

* `setup`: Add `isSlashingProtectionMinimal` argument.

The added feature is located in the `validator/client/propose_test.go`
file.

The rest of this commit consists in tests wrapping.

* `initializeFromCLI` and `initializeForWeb`: Factorize db init.

* Add `convert-complete-to-minimal` command.

* Creates `--enable-minimal-slashing-protection` flag.

* `importSlashingProtectionJSON`: Check database type.

* `exportSlashingProtectionJSON`: Check database type.

* `TestClearDB`: Test with minimal slashing protection.

* KeyManager: Test with minimal slashing protection.

* RPC: KeyManager: Test with minimal slashing protection.

* `convert-complete-to-minimal`: Change option names.

Options were:
- `--source` (for source data directory), and
- `--target` (for target data directory)

However, since this command deals with slashing protection, which has
source (epochs) and target (epochs), the initial option names may confuse
the user.

In this commit:
`--source` ==> `--source-data-dir`
`--target` ==> `--target-data-dir`

* Set `SlashableAttestationCheck` as an iface method.

And delete `CheckSlashableAttestation` from iface.

* Move helpers functions in a more general directory.

No functional change.

* Extract common structs out of `kv`.

==> `filesystem` does not depend anymore on `kv`.
==> `iface` does not depend anymore on `kv`.
==> `slashing-protection` does not depend anymore on `kv`.

* Move `ValidateMetadata` in `validator/helpers`.

* `ValidateMetadata`: Test with mock.

This way, we can:
- Avoid any circular import for tests.
- Implement once for all `iface.ValidatorDB` implementations
  the `ValidateMetadata`function.
- Have tests (and coverage) of `ValidateMetadata`in
  its own package.

The ideal solution would have been to implement `ValidateMetadata` as
a method with the `iface.ValidatorDB`receiver.
Unfortunately, golang does not allow that.

* `iface.ValidatorDB`: Implement ImportStandardProtectionJSON.

The whole purpose of this commit is to avoid the `switch validatorDB.(type)`
in `ImportStandardProtectionJSON`.

* `iface.ValidatorDB`: Implement `SlashableProposalCheck`.

* Remove now useless `slashableProposalCheck`.

* Delete useless `ImportStandardProtectionJSON`.

* `file.Exists`: Detect directories and return an error.

Before, `Exists` was only able to detect if a file exists.
Now, this function takes an extra `File` or `Directory` argument.
It detects either if a file or a directory exists.

Before, if an error was returned by `os.Stat`, the the file was
considered as non existing.
Now, it is treated as a real error.

* Replace `os.Stat` by `file.Exists`.

* Remove `Is{Complete,Minimal}DatabaseExisting`.

* `publicKeys`: Add log if unexpected file found.

* Move `{Source,Target}DataDirFlag`in `db.go`.

* `failedAttLocalProtectionErr`: `var`==> `const`

* `signingRoot`: `32`==> `fieldparams.RootLength`.

* `validatorClientData`==> `validator-client-data`.

To be consistent with `slashing-protection`.

* Add progress bars for `import` and `convert`.

* `parseBlocksForUniquePublicKeys`: Move in `db/kv`.

* helpers: Remove unused `initializeProgressBar` function.
2024-03-05 15:27:15 +00:00
Potuz
b6ce6c2eba Do not check parent weight on early FCU (#13683)
When a late block arrives and the beacon is proposing the next block, we
perform several checks to allow for the next block to reorg the incoming
late block.

Among those checks, we check that the parent block has been heavily
attested (currently 160% of the committee size).

We perform this check in these circumstances:
- When the late block arrives
- At 10 seconds into the slot
- At 0 seconds into the next slot (at proposing time)

The problem is that for blocks that arrive between 4 seconds and 10
seconds, the parent block will not have yet this expected weight since
attestations from the current committee were not imported yet, and thus
Prysm will send an FCU with payload attributes anyway at this time.

What happens is that Prysm keeps the EL building different blocks based
on different parents at the same time, when later in the next slot it
calls to propose, it will reorg the late block anyway and the EL would
have been computing a second payload uselessly.

This PR enables this check only when calling `ShouldOverrideFCU` after
10 seconds into the slot which we do only after having imported the
current attestations. We may want to actually remove this check entirely
from `ShouldOverrideFCU` and only keep it in `ProposerHead`.

Shout out to Anthithesis for reporting an issue that led to this
discoverly.
2024-03-05 15:07:39 +00:00
kasey
b3caaa9acc exit blob fetching for cp block if outside retention (#13686)
* exit blob fetching for cp block if outside retention

* regression test

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2024-03-05 02:57:06 +00:00
james-prysm
d6fb8c29c9 refactoring how proposer settings load into validator client (#13645)
* refactoring how proposer settings load

* fixing tests and moving test data

* fixing linting and adding comments

* accidently removed a function, adding it back in

* fixing usage of dependency

* gaz

* fixing package visibility

* gaz

* iface config gaz

* adding visibility for db

* fix ineffectual assignment to err

* adding in log for when the builder is set but ignored due to no fee recipient

* Update config/validator/service/proposer_settings.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/validator/service/proposer_settings.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/validator/service/proposer_settings.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/proposer/loader.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/proposer/loader.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/proposer/loader.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/proposer/loader.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/proposer/loader.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/validator/service/proposer_settings.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update config/util.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* some of the review feedback

* more review comments

* adding more test coverage

* Update config/proposer/loader.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* Update config/proposer/loader.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* Update config/proposer/loader.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* Update config/proposer/loader.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* updating based on feedback

* renaming variable

* fixing unhandled errors

* fixing tests

* gaz

* adding in gaslimit log

* fixing log

* some more review comments

* renaming and moving proposer settings file

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
2024-03-04 15:12:11 +00:00
Manu NALEPA
3df7a1f067 VC: Improve logging in case of fatal error (#13681)
* VC: Display `FATAL` log in case of uncaught error.

* `initializeFromCLI`: Remove `Println`.
2024-03-04 09:28:49 +00:00
kasey
4c3dbae3c0 tests for origin blob fetching (#13667)
* tests for origin blob fetching

* Update beacon-chain/sync/initial-sync/service.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2024-03-02 15:11:22 +00:00
Nishant Das
68b78dd520 fix race (#13680) 2024-03-01 09:29:51 +00:00
Potuz
2e2ef4a179 Fix failed reorg log (#13679) 2024-02-29 15:23:29 +00:00
Nishant Das
b61d17731e Downgrade Level DB to Stable Version (#13671)
* downgrade level db

* fix current issues

* update geth

* Fix zstd build. The patch is now longer needed now that https://github.com/bazelbuild/rules_go/issues/3411 is fixed.

* Revert "update geth"

This reverts commit 2a7c51a952.

* change to hex

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2024-02-29 04:49:02 +00:00
164 changed files with 13013 additions and 7622 deletions

View File

@@ -163,7 +163,7 @@ func Test_getBlkParentHashAndTD(t *testing.T) {
parentHash, totalDifficulty, err := service.getBlkParentHashAndTD(ctx, h[:])
require.NoError(t, err)
require.Equal(t, p, bytesutil.ToBytes32(parentHash))
require.Equal(t, td, totalDifficulty.String())
require.Equal(t, td, totalDifficulty.Hex())
_, _, err = service.getBlkParentHashAndTD(ctx, []byte{'c'})
require.ErrorContains(t, "could not get pow block: block not found", err)

View File

@@ -307,16 +307,16 @@ func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.Beacon
if err := helpers.UpdateProposerIndicesInCache(ctx, st, e); err != nil {
return errors.Wrap(err, "could not update proposer index cache")
}
go func() {
go func(ep primitives.Epoch) {
// Use a custom deadline here, since this method runs asynchronously.
// We ignore the parent method's context and instead create a new one
// with a custom deadline, therefore using the background context instead.
slotCtx, cancel := context.WithTimeout(context.Background(), slotDeadline)
defer cancel()
if err := helpers.UpdateCommitteeCache(slotCtx, st, e+1); err != nil {
if err := helpers.UpdateCommitteeCache(slotCtx, st, ep+1); err != nil {
log.WithError(err).Warn("Could not update committee cache")
}
}()
}(e)
// The latest block header is from the previous epoch
r, err := st.LatestBlockHeader().HashTreeRoot()
if err != nil {

View File

@@ -2047,9 +2047,7 @@ func TestFillMissingBlockPayloadId_PrepareAllPayloads(t *testing.T) {
// boost. It alters the genesisTime tracked by the store.
func driftGenesisTime(s *Service, slot, delay int64) {
offset := slot*int64(params.BeaconConfig().SecondsPerSlot) + delay
newTime := time.Unix(time.Now().Unix()-offset, 0)
s.SetGenesisTime(newTime)
s.cfg.ForkChoiceStore.SetGenesisTime(uint64(newTime.Unix()))
s.SetGenesisTime(time.Unix(time.Now().Unix()-offset, 0))
}
func TestMissingIndices(t *testing.T) {
@@ -2115,7 +2113,7 @@ func TestMissingIndices(t *testing.T) {
for _, c := range cases {
bm, bs := filesystem.NewEphemeralBlobStorageWithMocker(t)
t.Run(c.name, func(t *testing.T) {
require.NoError(t, bm.CreateFakeIndices(c.root, c.present))
require.NoError(t, bm.CreateFakeIndices(c.root, c.present...))
missing, err := missingIndices(bs, c.root, c.expected)
if c.err != nil {
require.ErrorIs(t, err, c.err)

View File

@@ -37,7 +37,7 @@ type BlobMocker struct {
// CreateFakeIndices creates empty blob sidecar files at the expected path for the given
// root and indices to influence the result of Indices().
func (bm *BlobMocker) CreateFakeIndices(root [32]byte, indices []uint64) error {
func (bm *BlobMocker) CreateFakeIndices(root [32]byte, indices ...uint64) error {
for i := range indices {
n := blobNamer{root: root, index: indices[i]}
if err := bm.fs.MkdirAll(n.dir(), directoryPermissions); err != nil {

View File

@@ -22,7 +22,14 @@ func Restore(cliCtx *cli.Context) error {
targetDir := cliCtx.String(cmd.RestoreTargetDirFlag.Name)
restoreDir := path.Join(targetDir, kv.BeaconNodeDbDirName)
if file.Exists(path.Join(restoreDir, kv.DatabaseFileName)) {
restoreFile := path.Join(restoreDir, kv.DatabaseFileName)
dbExists, err := file.Exists(restoreFile, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if database exists in %s", restoreFile)
}
if dbExists {
resp, err := prompt.ValidatePrompt(
os.Stdin, dbExistsYesNoPrompt, prompt.ValidateYesOrNo,
)

View File

@@ -77,6 +77,5 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//time/slots:go_default_library",
],
)

View File

@@ -93,13 +93,13 @@ func (n *Node) updateBestDescendant(ctx context.Context, justifiedEpoch, finaliz
// Any node with different finalized or justified epoch than
// the ones in fork choice store should not be viable to head.
func (n *Node) viableForHead(justifiedEpoch, currentEpoch primitives.Epoch) bool {
if justifiedEpoch == 0 {
return true
justified := justifiedEpoch == n.justifiedEpoch || justifiedEpoch == 0
if !justified && justifiedEpoch+1 == currentEpoch {
if n.unrealizedJustifiedEpoch+1 >= currentEpoch && n.justifiedEpoch+2 >= currentEpoch {
justified = true
}
}
// We use n.justifiedEpoch as the voting source because:
// 1. if this node is from current epoch, n.justifiedEpoch is the realized justification epoch.
// 2. if this node is from a previous epoch, n.justifiedEpoch has already been updated to the unrealized justification epoch.
return n.justifiedEpoch == justifiedEpoch || n.justifiedEpoch+2 >= currentEpoch
return justified
}
func (n *Node) leadsToViableHead(justifiedEpoch, currentEpoch primitives.Epoch) bool {

View File

@@ -146,9 +146,7 @@ func TestNode_ViableForHead(t *testing.T) {
{&Node{}, 1, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, true},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 2}, 3, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 2}, 4, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 3}, 4, true},
{&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, true},
}
for _, tc := range tests {
got := tc.n.viableForHead(tc.justifiedEpoch, 5)

View File

@@ -82,6 +82,15 @@ func (f *ForkChoice) ShouldOverrideFCU() (override bool) {
return
}
// Return early if we are checking before 10 seconds into the slot
secs, err := slots.SecondsSinceSlotStart(head.slot, f.store.genesisTime, uint64(time.Now().Unix()))
if err != nil {
log.WithError(err).Error("could not check current slot")
return true
}
if secs < ProcessAttestationsThreshold {
return true
}
// Only orphan a block if the parent LMD vote is strong
if parent.weight*100 < f.store.committeeWeight*params.BeaconConfig().ReorgParentWeightThreshold {
return

View File

@@ -85,11 +85,19 @@ func TestForkChoice_ShouldOverrideFCU(t *testing.T) {
require.Equal(t, false, f.ShouldOverrideFCU())
f.store.headNode.parent = saved
})
t.Run("parent is weak", func(t *testing.T) {
t.Run("parent is weak early call", func(t *testing.T) {
saved := f.store.headNode.parent.weight
f.store.headNode.parent.weight = 0
require.Equal(t, true, f.ShouldOverrideFCU())
f.store.headNode.parent.weight = saved
})
t.Run("parent is weak late call", func(t *testing.T) {
saved := f.store.headNode.parent.weight
driftGenesisTime(f, 2, 11)
f.store.headNode.parent.weight = 0
require.Equal(t, false, f.ShouldOverrideFCU())
f.store.headNode.parent.weight = saved
driftGenesisTime(f, 2, orphanLateBlockFirstThreshold+1)
})
t.Run("Head is strong", func(t *testing.T) {
f.store.headNode.weight = f.store.committeeWeight

View File

@@ -8,7 +8,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func TestStore_SetUnrealizedEpochs(t *testing.T) {
@@ -64,14 +63,14 @@ func TestStore_UpdateUnrealizedCheckpoints(t *testing.T) {
func TestStore_LongFork(t *testing.T) {
f := setup(1, 1)
ctx := context.Background()
state, blkRoot, err := prepareForkchoiceState(ctx, 75, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1)
state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
state, blkRoot, err = prepareForkchoiceState(ctx, 80, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1)
state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'b'}, 2))
state, blkRoot, err = prepareForkchoiceState(ctx, 95, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1)
state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'c'}, 2))
@@ -79,28 +78,27 @@ func TestStore_LongFork(t *testing.T) {
// Add an attestation to c, it is head
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'c'}, 1)
f.justifiedBalances = []uint64{100}
c := f.store.nodeByRoot[[32]byte{'c'}]
require.Equal(t, primitives.Epoch(2), slots.ToEpoch(c.slot))
driftGenesisTime(f, c.slot, 0)
headRoot, err := f.Head(ctx)
require.NoError(t, err)
require.Equal(t, [32]byte{'c'}, headRoot)
// c remains the head even if a block d with higher realized justification is seen
// D is head even though its weight is lower.
ha := [32]byte{'a'}
state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'b'}, [32]byte{'D'}, 2, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
require.NoError(t, f.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: 2, Root: ha}))
d := f.store.nodeByRoot[[32]byte{'d'}]
require.Equal(t, primitives.Epoch(3), slots.ToEpoch(d.slot))
driftGenesisTime(f, d.slot, 0)
require.Equal(t, true, d.viableForHead(f.store.justifiedCheckpoint.Epoch, slots.ToEpoch(d.slot)))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, [32]byte{'d'}, headRoot)
require.Equal(t, uint64(0), f.store.nodeByRoot[[32]byte{'d'}].weight)
require.Equal(t, uint64(100), f.store.nodeByRoot[[32]byte{'c'}].weight)
// Update unrealized justification, c becomes head
require.NoError(t, f.updateUnrealizedCheckpoints(ctx))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, [32]byte{'c'}, headRoot)
require.Equal(t, uint64(0), f.store.nodeByRoot[[32]byte{'d'}].weight)
require.Equal(t, uint64(100), f.store.nodeByRoot[[32]byte{'c'}].weight)
}
// Epoch 1 Epoch 2 Epoch 3

View File

@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"endpoints.go",
"log.go",
"service.go",
],
@@ -58,6 +59,9 @@ go_library(
"@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//plugin/ocgrpc:go_default_library",
"@org_golang_google_grpc//:go_default_library",
@@ -70,23 +74,14 @@ go_library(
go_test(
name = "go_default_test",
size = "medium",
srcs = ["service_test.go"],
srcs = [
"endpoints_test.go",
"service_test.go",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/execution/testing:go_default_library",
"//beacon-chain/rpc/eth/beacon:go_default_library",
"//beacon-chain/rpc/eth/blob:go_default_library",
"//beacon-chain/rpc/eth/builder:go_default_library",
"//beacon-chain/rpc/eth/debug:go_default_library",
"//beacon-chain/rpc/eth/events:go_default_library",
"//beacon-chain/rpc/eth/light-client:go_default_library",
"//beacon-chain/rpc/eth/node:go_default_library",
"//beacon-chain/rpc/eth/rewards:go_default_library",
"//beacon-chain/rpc/eth/validator:go_default_library",
"//beacon-chain/rpc/prysm/beacon:go_default_library",
"//beacon-chain/rpc/prysm/node:go_default_library",
"//beacon-chain/rpc/prysm/validator:go_default_library",
"//beacon-chain/startup:go_default_library",
"//beacon-chain/sync/initial-sync/testing:go_default_library",
"//testing/assert:go_default_library",

View File

@@ -0,0 +1,780 @@
package rpc
import (
"net/http"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/beacon"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/blob"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/builder"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/config"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/debug"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/events"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/light-client"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/node"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/rewards"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/validator"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
beaconprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/beacon"
nodeprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/node"
validatorv1alpha1 "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/v1alpha1/validator"
validatorprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/validator"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
)
type endpoint struct {
template string
name string
handler http.HandlerFunc
methods []string
}
func (s *Service) endpoints(
enableDebug bool,
blocker lookup.Blocker,
stater lookup.Stater,
rewardFetcher rewards.BlockRewardsFetcher,
validatorServer *validatorv1alpha1.Server,
coreService *core.Service,
ch *stategen.CanonicalHistory,
) []endpoint {
endpoints := make([]endpoint, 0)
endpoints = append(endpoints, s.rewardsEndpoints(blocker, stater, rewardFetcher)...)
endpoints = append(endpoints, s.builderEndpoints(stater)...)
endpoints = append(endpoints, s.blobEndpoints(blocker)...)
endpoints = append(endpoints, s.validatorEndpoints(validatorServer, stater, coreService, rewardFetcher)...)
endpoints = append(endpoints, s.nodeEndpoints()...)
endpoints = append(endpoints, s.beaconEndpoints(ch, stater, blocker, validatorServer, coreService)...)
endpoints = append(endpoints, s.configEndpoints()...)
endpoints = append(endpoints, s.lightClientEndpoints(blocker, stater)...)
endpoints = append(endpoints, s.eventsEndpoints()...)
endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater)...)
endpoints = append(endpoints, s.prysmNodeEndpoints()...)
endpoints = append(endpoints, s.prysmValidatorEndpoints(coreService, stater)...)
if enableDebug {
endpoints = append(endpoints, s.debugEndpoints(stater)...)
}
return endpoints
}
func (s *Service) rewardsEndpoints(blocker lookup.Blocker, stater lookup.Stater, rewardFetcher rewards.BlockRewardsFetcher) []endpoint {
server := &rewards.Server{
Blocker: blocker,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
Stater: stater,
HeadFetcher: s.cfg.HeadFetcher,
BlockRewardFetcher: rewardFetcher,
}
const namespace = "rewards"
return []endpoint{
{
template: "/eth/v1/beacon/rewards/blocks/{block_id}",
name: namespace + ".BlockRewards",
handler: server.BlockRewards,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/rewards/attestations/{epoch}",
name: namespace + ".AttestationRewards",
handler: server.AttestationRewards,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/rewards/sync_committee/{block_id}",
name: namespace + ".SyncCommitteeRewards",
handler: server.SyncCommitteeRewards,
methods: []string{http.MethodPost},
},
}
}
func (s *Service) builderEndpoints(stater lookup.Stater) []endpoint {
server := &builder.Server{
FinalizationFetcher: s.cfg.FinalizationFetcher,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
Stater: stater,
}
const namespace = "builder"
return []endpoint{
{
template: "/eth/v1/builder/states/{state_id}/expected_withdrawals",
name: namespace + ".ExpectedWithdrawals",
handler: server.ExpectedWithdrawals,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) blobEndpoints(blocker lookup.Blocker) []endpoint {
server := &blob.Server{
Blocker: blocker,
}
const namespace = "blob"
return []endpoint{
{
template: "/eth/v1/beacon/blob_sidecars/{block_id}",
name: namespace + ".Blobs",
handler: server.Blobs,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) validatorEndpoints(
validatorServer *validatorv1alpha1.Server,
stater lookup.Stater,
coreService *core.Service,
rewardFetcher rewards.BlockRewardsFetcher,
) []endpoint {
server := &validator.Server{
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
AttestationsPool: s.cfg.AttestationsPool,
PeerManager: s.cfg.PeerManager,
Broadcaster: s.cfg.Broadcaster,
V1Alpha1Server: validatorServer,
Stater: stater,
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
BeaconDB: s.cfg.BeaconDB,
BlockBuilder: s.cfg.BlockBuilder,
OperationNotifier: s.cfg.OperationNotifier,
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
PayloadIDCache: s.cfg.PayloadIDCache,
CoreService: coreService,
BlockRewardFetcher: rewardFetcher,
}
const namespace = "validator"
return []endpoint{
{
template: "/eth/v1/validator/aggregate_attestation",
name: namespace + ".GetAggregateAttestation",
handler: server.GetAggregateAttestation,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/contribution_and_proofs",
name: namespace + ".SubmitContributionAndProofs",
handler: server.SubmitContributionAndProofs,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/aggregate_and_proofs",
name: namespace + ".SubmitAggregateAndProofs",
handler: server.SubmitAggregateAndProofs,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/sync_committee_contribution",
name: namespace + ".ProduceSyncCommitteeContribution",
handler: server.ProduceSyncCommitteeContribution,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/sync_committee_subscriptions",
name: namespace + ".SubmitSyncCommitteeSubscription",
handler: server.SubmitSyncCommitteeSubscription,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/beacon_committee_subscriptions",
name: namespace + ".SubmitBeaconCommitteeSubscription",
handler: server.SubmitBeaconCommitteeSubscription,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/attestation_data",
name: namespace + ".GetAttestationData",
handler: server.GetAttestationData,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/register_validator",
name: namespace + ".RegisterValidator",
handler: server.RegisterValidator,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/duties/attester/{epoch}",
name: namespace + ".GetAttesterDuties",
handler: server.GetAttesterDuties,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/duties/proposer/{epoch}",
name: namespace + ".GetProposerDuties",
handler: server.GetProposerDuties,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/duties/sync/{epoch}",
name: namespace + ".GetSyncCommitteeDuties",
handler: server.GetSyncCommitteeDuties,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/prepare_beacon_proposer",
name: namespace + ".PrepareBeaconProposer",
handler: server.PrepareBeaconProposer,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/liveness/{epoch}",
name: namespace + ".GetLiveness",
handler: server.GetLiveness,
methods: []string{http.MethodPost},
},
{
template: "/eth/v2/validator/blocks/{slot}",
name: namespace + ".ProduceBlockV2",
handler: server.ProduceBlockV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/blinded_blocks/{slot}",
name: namespace + ".ProduceBlindedBlock",
handler: server.ProduceBlindedBlock,
methods: []string{http.MethodGet},
},
{
template: "/eth/v3/validator/blocks/{slot}",
name: namespace + ".ProduceBlockV3",
handler: server.ProduceBlockV3,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/beacon_committee_selections",
name: namespace + ".BeaconCommitteeSelections",
handler: server.BeaconCommitteeSelections,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/sync_committee_selections",
name: namespace + ".SyncCommittee Selections",
handler: server.SyncCommitteeSelections,
methods: []string{http.MethodPost},
},
}
}
func (s *Service) nodeEndpoints() []endpoint {
server := &node.Server{
BeaconDB: s.cfg.BeaconDB,
Server: s.grpcServer,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
PeersFetcher: s.cfg.PeersFetcher,
PeerManager: s.cfg.PeerManager,
MetadataProvider: s.cfg.MetadataProvider,
HeadFetcher: s.cfg.HeadFetcher,
ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
}
const namespace = "node"
return []endpoint{
{
template: "/eth/v1/node/syncing",
name: namespace + ".GetSyncStatus",
handler: server.GetSyncStatus,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/identity",
name: namespace + ".GetIdentity",
handler: server.GetIdentity,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/peers/{peer_id}",
name: namespace + ".GetPeer",
handler: server.GetPeer,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/peers",
name: namespace + ".GetPeers",
handler: server.GetPeers,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/peer_count",
name: namespace + ".GetPeerCount",
handler: server.GetPeerCount,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/version",
name: namespace + ".GetVersion",
handler: server.GetVersion,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/node/health",
name: namespace + ".GetHealth",
handler: server.GetHealth,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) beaconEndpoints(
ch *stategen.CanonicalHistory,
stater lookup.Stater,
blocker lookup.Blocker,
validatorServer *validatorv1alpha1.Server,
coreService *core.Service,
) []endpoint {
server := &beacon.Server{
CanonicalHistory: ch,
BeaconDB: s.cfg.BeaconDB,
AttestationsPool: s.cfg.AttestationsPool,
SlashingsPool: s.cfg.SlashingsPool,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
BlockNotifier: s.cfg.BlockNotifier,
OperationNotifier: s.cfg.OperationNotifier,
Broadcaster: s.cfg.Broadcaster,
BlockReceiver: s.cfg.BlockReceiver,
StateGenService: s.cfg.StateGen,
Stater: stater,
Blocker: blocker,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
VoluntaryExitsPool: s.cfg.ExitPool,
V1Alpha1ValidatorServer: validatorServer,
SyncChecker: s.cfg.SyncService,
ExecutionPayloadReconstructor: s.cfg.ExecutionPayloadReconstructor,
BLSChangesPool: s.cfg.BLSChangesPool,
FinalizationFetcher: s.cfg.FinalizationFetcher,
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
CoreService: coreService,
}
const namespace = "beacon"
return []endpoint{
{
template: "/eth/v1/beacon/states/{state_id}/committees",
name: namespace + ".GetCommittees",
handler: server.GetCommittees,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/fork",
name: namespace + ".GetStateFork",
handler: server.GetStateFork,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/root",
name: namespace + ".GetStateRoot",
handler: server.GetStateRoot,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/sync_committees",
name: namespace + ".GetSyncCommittees",
handler: server.GetSyncCommittees,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/randao",
name: namespace + ".GetRandao",
handler: server.GetRandao,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/blocks",
name: namespace + ".PublishBlock",
handler: server.PublishBlock,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/blinded_blocks",
name: namespace + ".PublishBlindedBlock",
handler: server.PublishBlindedBlock,
methods: []string{http.MethodPost},
},
{
template: "/eth/v2/beacon/blocks",
name: namespace + ".PublishBlockV2",
handler: server.PublishBlockV2,
methods: []string{http.MethodPost},
},
{
template: "/eth/v2/beacon/blinded_blocks",
name: namespace + ".PublishBlindedBlockV2",
handler: server.PublishBlindedBlockV2,
methods: []string{http.MethodPost},
},
{
template: "/eth/v2/beacon/blocks/{block_id}",
name: namespace + ".GetBlockV2",
handler: server.GetBlockV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/blocks/{block_id}/attestations",
name: namespace + ".GetBlockAttestations",
handler: server.GetBlockAttestations,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/blinded_blocks/{block_id}",
name: namespace + ".GetBlindedBlock",
handler: server.GetBlindedBlock,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/blocks/{block_id}/root",
name: namespace + ".GetBlockRoot",
handler: server.GetBlockRoot,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/attestations",
name: namespace + ".ListAttestations",
handler: server.ListAttestations,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/attestations",
name: namespace + ".SubmitAttestations",
handler: server.SubmitAttestations,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/pool/voluntary_exits",
name: namespace + ".ListVoluntaryExits",
handler: server.ListVoluntaryExits,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/voluntary_exits",
name: namespace + ".SubmitVoluntaryExit",
handler: server.SubmitVoluntaryExit,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/pool/sync_committees",
name: namespace + ".SubmitSyncCommitteeSignatures",
handler: server.SubmitSyncCommitteeSignatures,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/pool/bls_to_execution_changes",
name: namespace + ".ListBLSToExecutionChanges",
handler: server.ListBLSToExecutionChanges,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/bls_to_execution_changes",
name: namespace + ".SubmitBLSToExecutionChanges",
handler: server.SubmitBLSToExecutionChanges,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/pool/attester_slashings",
name: namespace + ".GetAttesterSlashings",
handler: server.GetAttesterSlashings,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/attester_slashings",
name: namespace + ".SubmitAttesterSlashing",
handler: server.SubmitAttesterSlashing,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/pool/proposer_slashings",
name: namespace + ".GetProposerSlashings",
handler: server.GetProposerSlashings,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/proposer_slashings",
name: namespace + ".SubmitProposerSlashing",
handler: server.SubmitProposerSlashing,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/headers",
name: namespace + ".GetBlockHeaders",
handler: server.GetBlockHeaders,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/headers/{block_id}",
name: namespace + ".GetBlockHeader",
handler: server.GetBlockHeader,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/genesis",
name: namespace + ".GetGenesis",
handler: server.GetGenesis,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/finality_checkpoints",
name: namespace + ".GetFinalityCheckpoints",
handler: server.GetFinalityCheckpoints,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/validators",
name: namespace + ".GetValidators",
handler: server.GetValidators,
methods: []string{http.MethodGet, http.MethodPost},
},
{
template: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}",
name: namespace + ".GetValidator",
handler: server.GetValidator,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/validator_balances",
name: namespace + ".GetValidatorBalances",
handler: server.GetValidatorBalances,
methods: []string{http.MethodGet, http.MethodPost},
},
}
}
func (s *Service) configEndpoints() []endpoint {
const namespace = "config"
return []endpoint{
{
template: "/eth/v1/config/deposit_contract",
name: namespace + ".GetDepositContract",
handler: config.GetDepositContract,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/config/fork_schedule",
name: namespace + ".GetForkSchedule",
handler: config.GetForkSchedule,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/config/spec",
name: namespace + ".GetSpec",
handler: config.GetSpec,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) lightClientEndpoints(blocker lookup.Blocker, stater lookup.Stater) []endpoint {
server := &lightclient.Server{
Blocker: blocker,
Stater: stater,
HeadFetcher: s.cfg.HeadFetcher,
}
const namespace = "lightclient"
return []endpoint{
{
template: "/eth/v1/beacon/light_client/bootstrap/{block_root}",
name: namespace + ".GetLightClientBootstrap",
handler: server.GetLightClientBootstrap,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/light_client/updates",
name: namespace + ".GetLightClientUpdatesByRange",
handler: server.GetLightClientUpdatesByRange,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/light_client/finality_update",
name: namespace + ".GetLightClientFinalityUpdate",
handler: server.GetLightClientFinalityUpdate,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/light_client/optimistic_update",
name: namespace + ".GetLightClientOptimisticUpdate",
handler: server.GetLightClientOptimisticUpdate,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint {
server := &debug.Server{
BeaconDB: s.cfg.BeaconDB,
HeadFetcher: s.cfg.HeadFetcher,
Stater: stater,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
ForkFetcher: s.cfg.ForkFetcher,
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
}
const namespace = "debug"
return []endpoint{
{
template: "/eth/v2/debug/beacon/states/{state_id}",
name: namespace + ".GetBeaconStateV2",
handler: server.GetBeaconStateV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v2/debug/beacon/heads",
name: namespace + ".GetForkChoiceHeadsV2",
handler: server.GetForkChoiceHeadsV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/debug/fork_choice",
name: namespace + ".GetForkChoice",
handler: server.GetForkChoice,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) eventsEndpoints() []endpoint {
server := &events.Server{
StateNotifier: s.cfg.StateNotifier,
OperationNotifier: s.cfg.OperationNotifier,
HeadFetcher: s.cfg.HeadFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
}
const namespace = "events"
return []endpoint{
{
template: "/eth/v1/events",
name: namespace + ".StreamEvents",
handler: server.StreamEvents,
methods: []string{http.MethodGet},
},
}
}
// Prysm custom endpoints
func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater lookup.Stater) []endpoint {
server := &beaconprysm.Server{
SyncChecker: s.cfg.SyncService,
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
CanonicalHistory: ch,
BeaconDB: s.cfg.BeaconDB,
Stater: stater,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
}
const namespace = "prysm.beacon"
return []endpoint{
{
template: "/prysm/v1/beacon/weak_subjectivity",
name: namespace + ".GetWeakSubjectivity",
handler: server.GetWeakSubjectivity,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/states/{state_id}/validator_count",
name: namespace + ".GetValidatorCount",
handler: server.GetValidatorCount,
methods: []string{http.MethodGet},
},
{
template: "/prysm/v1/beacon/states/{state_id}/validator_count",
name: namespace + ".GetValidatorCount",
handler: server.GetValidatorCount,
methods: []string{http.MethodGet},
},
}
}
func (s *Service) prysmNodeEndpoints() []endpoint {
server := &nodeprysm.Server{
BeaconDB: s.cfg.BeaconDB,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
PeersFetcher: s.cfg.PeersFetcher,
PeerManager: s.cfg.PeerManager,
MetadataProvider: s.cfg.MetadataProvider,
HeadFetcher: s.cfg.HeadFetcher,
ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
}
const namespace = "prysm.node"
return []endpoint{
{
template: "/prysm/node/trusted_peers",
name: namespace + ".ListTrustedPeer",
handler: server.ListTrustedPeer,
methods: []string{http.MethodGet},
},
{
template: "/prysm/v1/node/trusted_peers",
name: namespace + ".ListTrustedPeer",
handler: server.ListTrustedPeer,
methods: []string{http.MethodGet},
},
{
template: "/prysm/node/trusted_peers",
name: namespace + ".AddTrustedPeer",
handler: server.AddTrustedPeer,
methods: []string{http.MethodPost},
},
{
template: "/prysm/v1/node/trusted_peers",
name: namespace + ".AddTrustedPeer",
handler: server.AddTrustedPeer,
methods: []string{http.MethodPost},
},
{
template: "/prysm/node/trusted_peers/{peer_id}",
name: namespace + ".RemoveTrustedPeer",
handler: server.RemoveTrustedPeer,
methods: []string{http.MethodDelete},
},
{
template: "/prysm/v1/node/trusted_peers/{peer_id}",
name: namespace + ".RemoveTrustedPeer",
handler: server.RemoveTrustedPeer,
methods: []string{http.MethodDelete},
},
}
}
func (s *Service) prysmValidatorEndpoints(coreService *core.Service, stater lookup.Stater) []endpoint {
server := &validatorprysm.Server{
CoreService: coreService,
}
const namespace = "prysm.validator"
return []endpoint{
{
template: "/prysm/validators/performance",
name: namespace + ".GetValidatorPerformance",
handler: server.GetValidatorPerformance,
methods: []string{http.MethodPost},
},
{
template: "/prysm/v1/validators/performance",
name: namespace + ".GetValidatorPerformance",
handler: server.GetValidatorPerformance,
methods: []string{http.MethodPost},
},
}
}

View File

@@ -0,0 +1,150 @@
package rpc
import (
"net/http"
"testing"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
)
func Test_endpoints(t *testing.T) {
rewardsRoutes := map[string][]string{
"/eth/v1/beacon/rewards/blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/rewards/attestations/{epoch}": {http.MethodPost},
"/eth/v1/beacon/rewards/sync_committee/{block_id}": {http.MethodPost},
}
beaconRoutes := map[string][]string{
"/eth/v1/beacon/genesis": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/root": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/fork": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/finality_checkpoints": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validators": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validator_balances": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/sync_committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/randao": {http.MethodGet},
"/eth/v1/beacon/headers": {http.MethodGet},
"/eth/v1/beacon/headers/{block_id}": {http.MethodGet},
"/eth/v1/beacon/blinded_blocks": {http.MethodPost},
"/eth/v2/beacon/blinded_blocks": {http.MethodPost},
"/eth/v1/beacon/blocks": {http.MethodPost},
"/eth/v2/beacon/blocks": {http.MethodPost},
"/eth/v1/beacon/blocks/{block_id}": {http.MethodGet},
"/eth/v2/beacon/blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet},
"/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet},
"/eth/v1/beacon/deposit_snapshot": {http.MethodGet},
"/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/sync_committees": {http.MethodPost},
"/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/bls_to_execution_changes": {http.MethodGet, http.MethodPost},
}
lightClientRoutes := map[string][]string{
"/eth/v1/beacon/light_client/bootstrap/{block_root}": {http.MethodGet},
"/eth/v1/beacon/light_client/updates": {http.MethodGet},
"/eth/v1/beacon/light_client/finality_update": {http.MethodGet},
"/eth/v1/beacon/light_client/optimistic_update": {http.MethodGet},
}
builderRoutes := map[string][]string{
"/eth/v1/builder/states/{state_id}/expected_withdrawals": {http.MethodGet},
}
blobRoutes := map[string][]string{
"/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet},
}
configRoutes := map[string][]string{
"/eth/v1/config/fork_schedule": {http.MethodGet},
"/eth/v1/config/spec": {http.MethodGet},
"/eth/v1/config/deposit_contract": {http.MethodGet},
}
debugRoutes := map[string][]string{
"/eth/v1/debug/beacon/states/{state_id}": {http.MethodGet},
"/eth/v2/debug/beacon/states/{state_id}": {http.MethodGet},
"/eth/v2/debug/beacon/heads": {http.MethodGet},
"/eth/v1/debug/fork_choice": {http.MethodGet},
}
eventsRoutes := map[string][]string{
"/eth/v1/events": {http.MethodGet},
}
nodeRoutes := map[string][]string{
"/eth/v1/node/identity": {http.MethodGet},
"/eth/v1/node/peers": {http.MethodGet},
"/eth/v1/node/peers/{peer_id}": {http.MethodGet},
"/eth/v1/node/peer_count": {http.MethodGet},
"/eth/v1/node/version": {http.MethodGet},
"/eth/v1/node/syncing": {http.MethodGet},
"/eth/v1/node/health": {http.MethodGet},
}
validatorRoutes := map[string][]string{
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
"/eth/v2/validator/blocks/{slot}": {http.MethodGet},
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
"/eth/v1/validator/blinded_blocks/{slot}": {http.MethodGet},
"/eth/v1/validator/attestation_data": {http.MethodGet},
"/eth/v1/validator/aggregate_attestation": {http.MethodGet},
"/eth/v1/validator/aggregate_and_proofs": {http.MethodPost},
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
"/eth/v1/validator/register_validator": {http.MethodPost},
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
}
prysmBeaconRoutes := map[string][]string{
"/prysm/v1/beacon/weak_subjectivity": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
"/prysm/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
}
prysmNodeRoutes := map[string][]string{
"/prysm/node/trusted_peers": {http.MethodGet, http.MethodPost},
"/prysm/v1/node/trusted_peers": {http.MethodGet, http.MethodPost},
"/prysm/node/trusted_peers/{peer_id}": {http.MethodDelete},
"/prysm/v1/node/trusted_peers/{peer_id}": {http.MethodDelete},
}
prysmValidatorRoutes := map[string][]string{
"/prysm/validators/performance": {http.MethodPost},
"/prysm/v1/validators/performance": {http.MethodPost},
}
s := &Service{cfg: &Config{}}
routesMap := combineMaps(beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes, nodeRoutes, validatorRoutes, rewardsRoutes, lightClientRoutes, blobRoutes, prysmValidatorRoutes, prysmNodeRoutes, prysmBeaconRoutes)
actual := s.endpoints(true, nil, nil, nil, nil, nil, nil)
for _, e := range actual {
methods, ok := routesMap[e.template]
assert.Equal(t, true, ok, "endpoint "+e.template+" not found")
if ok {
for _, em := range e.methods {
methodFound := false
for _, m := range methods {
if m == em {
methodFound = true
break
}
}
assert.Equal(t, true, methodFound, "method "+em+" for endpoint "+e.template+" not found")
}
}
}
}

View File

@@ -115,13 +115,7 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
return vs.constructGenericBeaconBlock(sBlk, bundleCache.get(req.Slot))
}
func (vs *Server) handleFailedReorgAttempt(ctx context.Context, slot primitives.Slot, parentRoot, headRoot [32]byte) (state.BeaconState, error) {
blockchain.LateBlockAttemptedReorgCount.Inc()
log.WithFields(logrus.Fields{
"slot": slot,
"parentRoot": fmt.Sprintf("%#x", parentRoot),
"headRoot": fmt.Sprintf("%#x", headRoot),
}).Warn("late block attempted reorg failed")
func (vs *Server) handleSuccesfulReorgAttempt(ctx context.Context, slot primitives.Slot, parentRoot, headRoot [32]byte) (state.BeaconState, error) {
// Try to get the state from the NSC
head := transition.NextSlotState(parentRoot[:], slot)
if head != nil {
@@ -135,7 +129,16 @@ func (vs *Server) handleFailedReorgAttempt(ctx context.Context, slot primitives.
return head, nil
}
func (vs *Server) getHeadNoFailedReorg(ctx context.Context, slot primitives.Slot, parentRoot [32]byte) (state.BeaconState, error) {
func logFailedReorgAttempt(slot primitives.Slot, oldHeadRoot, headRoot [32]byte) {
blockchain.LateBlockAttemptedReorgCount.Inc()
log.WithFields(logrus.Fields{
"slot": slot,
"oldHeadRoot": fmt.Sprintf("%#x", oldHeadRoot),
"headRoot": fmt.Sprintf("%#x", headRoot),
}).Warn("late block attempted reorg failed")
}
func (vs *Server) getHeadNoReorg(ctx context.Context, slot primitives.Slot, parentRoot [32]byte) (state.BeaconState, error) {
// Try to get the state from the NSC
head := transition.NextSlotState(parentRoot[:], slot)
if head != nil {
@@ -148,11 +151,14 @@ func (vs *Server) getHeadNoFailedReorg(ctx context.Context, slot primitives.Slot
return head, nil
}
func (vs *Server) getParentStateFromReorgData(ctx context.Context, slot primitives.Slot, parentRoot, headRoot [32]byte) (head state.BeaconState, err error) {
func (vs *Server) getParentStateFromReorgData(ctx context.Context, slot primitives.Slot, oldHeadRoot, parentRoot, headRoot [32]byte) (head state.BeaconState, err error) {
if parentRoot != headRoot {
head, err = vs.handleFailedReorgAttempt(ctx, slot, parentRoot, headRoot)
head, err = vs.handleSuccesfulReorgAttempt(ctx, slot, parentRoot, headRoot)
} else {
head, err = vs.getHeadNoFailedReorg(ctx, slot, parentRoot)
if oldHeadRoot != headRoot {
logFailedReorgAttempt(slot, oldHeadRoot, headRoot)
}
head, err = vs.getHeadNoReorg(ctx, slot, parentRoot)
}
if err != nil {
return nil, err
@@ -169,10 +175,11 @@ func (vs *Server) getParentStateFromReorgData(ctx context.Context, slot primitiv
func (vs *Server) getParentState(ctx context.Context, slot primitives.Slot) (state.BeaconState, [32]byte, error) {
// process attestations and update head in forkchoice
oldHeadRoot := vs.ForkchoiceFetcher.CachedHeadRoot()
vs.ForkchoiceFetcher.UpdateHead(ctx, vs.TimeFetcher.CurrentSlot())
headRoot := vs.ForkchoiceFetcher.CachedHeadRoot()
parentRoot := vs.ForkchoiceFetcher.GetProposerHead()
head, err := vs.getParentStateFromReorgData(ctx, slot, parentRoot, headRoot)
head, err := vs.getParentStateFromReorgData(ctx, slot, oldHeadRoot, parentRoot, headRoot)
return head, parentRoot, err
}

View File

@@ -2874,8 +2874,8 @@ func TestProposer_GetParentHeadState(t *testing.T) {
Eth1BlockFetcher: &mockExecution.Chain{},
StateGen: stategen.New(db, doublylinkedtree.New()),
}
t.Run("failed reorg", func(tt *testing.T) {
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, parentRoot, headRoot)
t.Run("successful reorg", func(tt *testing.T) {
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, parentRoot, parentRoot, headRoot)
require.NoError(t, err)
st := parentState.Copy()
st, err = transition.ProcessSlots(ctx, st, st.Slot()+1)
@@ -2892,7 +2892,7 @@ func TestProposer_GetParentHeadState(t *testing.T) {
t.Run("no reorg", func(tt *testing.T) {
require.NoError(t, transition.UpdateNextSlotCache(ctx, headRoot[:], headState))
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, headRoot, headRoot)
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, headRoot, headRoot, headRoot)
require.NoError(t, err)
st := headState.Copy()
st, err = transition.ProcessSlots(ctx, st, st.Slot()+1)
@@ -2906,4 +2906,23 @@ func TestProposer_GetParentHeadState(t *testing.T) {
require.Equal(t, [32]byte(str), [32]byte(headStr))
require.NotEqual(t, [32]byte(str), [32]byte(genesisStr))
})
t.Run("failed reorg", func(tt *testing.T) {
hook := logTest.NewGlobal()
require.NoError(t, transition.UpdateNextSlotCache(ctx, headRoot[:], headState))
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, parentRoot, headRoot, headRoot)
require.NoError(t, err)
st := headState.Copy()
st, err = transition.ProcessSlots(ctx, st, st.Slot()+1)
require.NoError(t, err)
str, err := st.StateRootAtIndex(0)
require.NoError(t, err)
headStr, err := head.StateRootAtIndex(0)
require.NoError(t, err)
genesisStr, err := parentState.StateRootAtIndex(0)
require.NoError(t, err)
require.Equal(t, [32]byte(str), [32]byte(headStr))
require.NotEqual(t, [32]byte(str), [32]byte(genesisStr))
require.LogsContain(t, hook, "late block attempted reorg failed")
})
}

View File

@@ -6,7 +6,6 @@ import (
"context"
"fmt"
"net"
"net/http"
"sync"
"github.com/gorilla/mux"
@@ -15,6 +14,9 @@ import (
grpcopentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/builder"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
@@ -32,24 +34,12 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/voluntaryexits"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/beacon"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/blob"
rpcBuilder "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/builder"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/config"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/debug"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/events"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/light-client"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/node"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/rewards"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/validator"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
beaconprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/beacon"
nodeprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/node"
beaconv1alpha1 "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/v1alpha1/beacon"
debugv1alpha1 "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/v1alpha1/debug"
nodev1alpha1 "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/v1alpha1/node"
validatorv1alpha1 "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/v1alpha1/validator"
validatorprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/validator"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
chainSync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
@@ -68,6 +58,24 @@ import (
const attestationBufferSize = 100
var (
httpRequestLatency = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_latency_seconds",
Help: "Latency of HTTP requests in seconds",
Buckets: []float64{0.001, 0.01, 0.025, 0.1, 0.25, 1, 2.5, 10},
},
[]string{"endpoint", "code", "method"},
)
httpRequestCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_request_count",
Help: "Number of HTTP requests",
},
[]string{"endpoint", "code", "method"},
)
)
// Service defining an RPC server for a beacon node.
type Service struct {
cfg *Config
@@ -208,25 +216,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
BlobStorage: s.cfg.BlobStorage,
}
rewardFetcher := &rewards.BlockRewardService{Replayer: ch}
s.initializeRewardServerRoutes(&rewards.Server{
Blocker: blocker,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
Stater: stater,
HeadFetcher: s.cfg.HeadFetcher,
BlockRewardFetcher: rewardFetcher,
})
s.initializeBuilderServerRoutes(&rpcBuilder.Server{
FinalizationFetcher: s.cfg.FinalizationFetcher,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
Stater: stater,
})
s.initializeBlobServerRoutes(&blob.Server{
Blocker: blocker,
})
coreService := &core.Service{
HeadFetcher: s.cfg.HeadFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
@@ -240,7 +229,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
FinalizedFetcher: s.cfg.FinalizationFetcher,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
}
validatorServer := &validatorv1alpha1.Server{
Ctx: s.ctx,
AttPool: s.cfg.AttestationsPool,
@@ -280,26 +268,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
PayloadIDCache: s.cfg.PayloadIDCache,
}
s.validatorServer = validatorServer
s.initializeValidatorServerRoutes(&validator.Server{
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
AttestationsPool: s.cfg.AttestationsPool,
PeerManager: s.cfg.PeerManager,
Broadcaster: s.cfg.Broadcaster,
V1Alpha1Server: validatorServer,
Stater: stater,
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
BeaconDB: s.cfg.BeaconDB,
BlockBuilder: s.cfg.BlockBuilder,
OperationNotifier: s.cfg.OperationNotifier,
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
PayloadIDCache: s.cfg.PayloadIDCache,
CoreService: coreService,
BlockRewardFetcher: rewardFetcher,
})
nodeServer := &nodev1alpha1.Server{
LogsStreamer: logs.NewStreamServer(),
StreamLogsBufferSize: 1000, // Enough to handle bursts of beacon node logs for gRPC streaming.
@@ -314,19 +282,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
BeaconMonitoringHost: s.cfg.BeaconMonitoringHost,
BeaconMonitoringPort: s.cfg.BeaconMonitoringPort,
}
s.initializeNodeServerRoutes(&node.Server{
BeaconDB: s.cfg.BeaconDB,
Server: s.grpcServer,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
PeersFetcher: s.cfg.PeersFetcher,
PeerManager: s.cfg.PeerManager,
MetadataProvider: s.cfg.MetadataProvider,
HeadFetcher: s.cfg.HeadFetcher,
ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
})
beaconChainServer := &beaconv1alpha1.Server{
Ctx: s.ctx,
BeaconDB: s.cfg.BeaconDB,
@@ -352,52 +307,23 @@ func NewService(ctx context.Context, cfg *Config) *Service {
CoreService: coreService,
}
s.initializeBeaconServerRoutes(&beacon.Server{
CanonicalHistory: ch,
BeaconDB: s.cfg.BeaconDB,
AttestationsPool: s.cfg.AttestationsPool,
SlashingsPool: s.cfg.SlashingsPool,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
BlockNotifier: s.cfg.BlockNotifier,
OperationNotifier: s.cfg.OperationNotifier,
Broadcaster: s.cfg.Broadcaster,
BlockReceiver: s.cfg.BlockReceiver,
StateGenService: s.cfg.StateGen,
Stater: stater,
Blocker: blocker,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
VoluntaryExitsPool: s.cfg.ExitPool,
V1Alpha1ValidatorServer: validatorServer,
SyncChecker: s.cfg.SyncService,
ExecutionPayloadReconstructor: s.cfg.ExecutionPayloadReconstructor,
BLSChangesPool: s.cfg.BLSChangesPool,
FinalizationFetcher: s.cfg.FinalizationFetcher,
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
CoreService: coreService,
})
s.initializeConfigRoutes()
s.initializeEventsServerRoutes(&events.Server{
StateNotifier: s.cfg.StateNotifier,
OperationNotifier: s.cfg.OperationNotifier,
HeadFetcher: s.cfg.HeadFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
})
s.initializeLightClientServerRoutes(&lightclient.Server{
Blocker: blocker,
Stater: stater,
HeadFetcher: s.cfg.HeadFetcher,
})
endpoints := s.endpoints(s.cfg.EnableDebugRPCEndpoints, blocker, stater, rewardFetcher, validatorServer, coreService, ch)
for _, e := range endpoints {
s.cfg.Router.HandleFunc(
e.template,
promhttp.InstrumentHandlerDuration(
httpRequestLatency.MustCurryWith(prometheus.Labels{"endpoint": e.name}),
promhttp.InstrumentHandlerCounter(
httpRequestCount.MustCurryWith(prometheus.Labels{"endpoint": e.name}),
e.handler,
),
),
).Methods(e.methods...)
}
ethpbv1alpha1.RegisterNodeServer(s.grpcServer, nodeServer)
ethpbv1alpha1.RegisterHealthServer(s.grpcServer, nodeServer)
ethpbv1alpha1.RegisterBeaconChainServer(s.grpcServer, beaconChainServer)
if s.cfg.EnableDebugRPCEndpoints {
log.Info("Enabled debug gRPC endpoints")
debugServer := &debugv1alpha1.Server{
@@ -409,47 +335,12 @@ func NewService(ctx context.Context, cfg *Config) *Service {
PeersFetcher: s.cfg.PeersFetcher,
ReplayerBuilder: ch,
}
s.initializeDebugServerRoutes(&debug.Server{
BeaconDB: s.cfg.BeaconDB,
HeadFetcher: s.cfg.HeadFetcher,
Stater: stater,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
ForkFetcher: s.cfg.ForkFetcher,
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
})
ethpbv1alpha1.RegisterDebugServer(s.grpcServer, debugServer)
}
ethpbv1alpha1.RegisterBeaconNodeValidatorServer(s.grpcServer, validatorServer)
// Register reflection service on gRPC server.
reflection.Register(s.grpcServer)
s.initializePrysmBeaconServerRoutes(&beaconprysm.Server{
SyncChecker: s.cfg.SyncService,
HeadFetcher: s.cfg.HeadFetcher,
TimeFetcher: s.cfg.GenesisTimeFetcher,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
CanonicalHistory: ch,
BeaconDB: s.cfg.BeaconDB,
Stater: stater,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
})
s.initializePrysmNodeServerRoutes(&nodeprysm.Server{
BeaconDB: s.cfg.BeaconDB,
SyncChecker: s.cfg.SyncService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
PeersFetcher: s.cfg.PeersFetcher,
PeerManager: s.cfg.PeerManager,
MetadataProvider: s.cfg.MetadataProvider,
HeadFetcher: s.cfg.HeadFetcher,
ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
})
s.initializePrysmValidatorServerRoutes(&validatorprysm.Server{CoreService: coreService})
return s
}
@@ -457,130 +348,6 @@ func NewService(ctx context.Context, cfg *Config) *Service {
var _ stategen.CanonicalChecker = blockchain.ChainInfoFetcher(nil)
var _ stategen.CurrentSlotter = blockchain.ChainInfoFetcher(nil)
func (s *Service) initializeRewardServerRoutes(rewardsServer *rewards.Server) {
s.cfg.Router.HandleFunc("/eth/v1/beacon/rewards/blocks/{block_id}", rewardsServer.BlockRewards).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/rewards/attestations/{epoch}", rewardsServer.AttestationRewards).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/rewards/sync_committee/{block_id}", rewardsServer.SyncCommitteeRewards).Methods(http.MethodPost)
}
func (s *Service) initializeBuilderServerRoutes(builderServer *rpcBuilder.Server) {
s.cfg.Router.HandleFunc("/eth/v1/builder/states/{state_id}/expected_withdrawals", builderServer.ExpectedWithdrawals).Methods(http.MethodGet)
}
func (s *Service) initializeBlobServerRoutes(blobServer *blob.Server) {
s.cfg.Router.HandleFunc("/eth/v1/beacon/blob_sidecars/{block_id}", blobServer.Blobs).Methods(http.MethodGet)
}
func (s *Service) initializeValidatorServerRoutes(validatorServer *validator.Server) {
s.cfg.Router.HandleFunc("/eth/v1/validator/aggregate_attestation", validatorServer.GetAggregateAttestation).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/contribution_and_proofs", validatorServer.SubmitContributionAndProofs).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/aggregate_and_proofs", validatorServer.SubmitAggregateAndProofs).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/sync_committee_contribution", validatorServer.ProduceSyncCommitteeContribution).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/sync_committee_subscriptions", validatorServer.SubmitSyncCommitteeSubscription).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/beacon_committee_subscriptions", validatorServer.SubmitBeaconCommitteeSubscription).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/attestation_data", validatorServer.GetAttestationData).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/register_validator", validatorServer.RegisterValidator).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/duties/attester/{epoch}", validatorServer.GetAttesterDuties).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/duties/proposer/{epoch}", validatorServer.GetProposerDuties).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/duties/sync/{epoch}", validatorServer.GetSyncCommitteeDuties).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/prepare_beacon_proposer", validatorServer.PrepareBeaconProposer).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/liveness/{epoch}", validatorServer.GetLiveness).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v2/validator/blocks/{slot}", validatorServer.ProduceBlockV2).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/blinded_blocks/{slot}", validatorServer.ProduceBlindedBlock).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v3/validator/blocks/{slot}", validatorServer.ProduceBlockV3).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/validator/beacon_committee_selections", validatorServer.BeaconCommitteeSelections).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/validator/sync_committee_selections", validatorServer.SyncCommitteeSelections).Methods(http.MethodPost)
}
func (s *Service) initializeNodeServerRoutes(nodeServer *node.Server) {
s.cfg.Router.HandleFunc("/eth/v1/node/syncing", nodeServer.GetSyncStatus).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/identity", nodeServer.GetIdentity).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/peers/{peer_id}", nodeServer.GetPeer).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/peers", nodeServer.GetPeers).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/peer_count", nodeServer.GetPeerCount).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/version", nodeServer.GetVersion).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/node/health", nodeServer.GetHealth).Methods(http.MethodGet)
}
func (s *Service) initializeBeaconServerRoutes(beaconServer *beacon.Server) {
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/committees", beaconServer.GetCommittees).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/fork", beaconServer.GetStateFork).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/root", beaconServer.GetStateRoot).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/sync_committees", beaconServer.GetSyncCommittees).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/randao", beaconServer.GetRandao).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks", beaconServer.PublishBlock).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blinded_blocks", beaconServer.PublishBlindedBlock).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks", beaconServer.PublishBlockV2).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blinded_blocks", beaconServer.PublishBlindedBlockV2).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks/{block_id}", beaconServer.GetBlockV2).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}/attestations", beaconServer.GetBlockAttestations).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blinded_blocks/{block_id}", beaconServer.GetBlindedBlock).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}/root", beaconServer.GetBlockRoot).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/deposit_snapshot", beaconServer.GetDepositSnapshot).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/attestations", beaconServer.ListAttestations).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/attestations", beaconServer.SubmitAttestations).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/voluntary_exits", beaconServer.ListVoluntaryExits).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/voluntary_exits", beaconServer.SubmitVoluntaryExit).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/sync_committees", beaconServer.SubmitSyncCommitteeSignatures).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/bls_to_execution_changes", beaconServer.ListBLSToExecutionChanges).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/bls_to_execution_changes", beaconServer.SubmitBLSToExecutionChanges).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/attester_slashings", beaconServer.GetAttesterSlashings).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/attester_slashings", beaconServer.SubmitAttesterSlashing).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/proposer_slashings", beaconServer.GetProposerSlashings).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/proposer_slashings", beaconServer.SubmitProposerSlashing).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/headers", beaconServer.GetBlockHeaders).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/headers/{block_id}", beaconServer.GetBlockHeader).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/genesis", beaconServer.GetGenesis).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/finality_checkpoints", beaconServer.GetFinalityCheckpoints).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validators", beaconServer.GetValidators).Methods(http.MethodGet, http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validators/{validator_id}", beaconServer.GetValidator).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_balances", beaconServer.GetValidatorBalances).Methods(http.MethodGet, http.MethodPost)
}
func (s *Service) initializeConfigRoutes() {
s.cfg.Router.HandleFunc("/eth/v1/config/deposit_contract", config.GetDepositContract).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/config/fork_schedule", config.GetForkSchedule).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/config/spec", config.GetSpec).Methods(http.MethodGet)
}
func (s *Service) initializeEventsServerRoutes(eventsServer *events.Server) {
s.cfg.Router.HandleFunc("/eth/v1/events", eventsServer.StreamEvents).Methods(http.MethodGet)
}
func (s *Service) initializeLightClientServerRoutes(lightClientServer *lightclient.Server) {
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/bootstrap/{block_root}", lightClientServer.GetLightClientBootstrap).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/updates", lightClientServer.GetLightClientUpdatesByRange).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/finality_update", lightClientServer.GetLightClientFinalityUpdate).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/light_client/optimistic_update", lightClientServer.GetLightClientOptimisticUpdate).Methods(http.MethodGet)
}
func (s *Service) initializeDebugServerRoutes(debugServer *debug.Server) {
s.cfg.Router.HandleFunc("/eth/v2/debug/beacon/states/{state_id}", debugServer.GetBeaconStateV2).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v2/debug/beacon/heads", debugServer.GetForkChoiceHeadsV2).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/debug/fork_choice", debugServer.GetForkChoice).Methods(http.MethodGet)
}
// prysm internal routes
func (s *Service) initializePrysmBeaconServerRoutes(beaconServerPrysm *beaconprysm.Server) {
s.cfg.Router.HandleFunc("/prysm/v1/beacon/weak_subjectivity", beaconServerPrysm.GetWeakSubjectivity).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", beaconServerPrysm.GetValidatorCount).Methods(http.MethodGet) // TODO: deprecate in Swagger, remove in v6
s.cfg.Router.HandleFunc("/prysm/v1/beacon/states/{state_id}/validator_count", beaconServerPrysm.GetValidatorCount).Methods(http.MethodGet)
}
func (s *Service) initializePrysmNodeServerRoutes(nodeServerPrysm *nodeprysm.Server) {
s.cfg.Router.HandleFunc("/prysm/node/trusted_peers", nodeServerPrysm.ListTrustedPeer).Methods(http.MethodGet) // TODO: deprecate in Swagger, remove in v6
s.cfg.Router.HandleFunc("/prysm/v1/node/trusted_peers", nodeServerPrysm.ListTrustedPeer).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/prysm/node/trusted_peers", nodeServerPrysm.AddTrustedPeer).Methods(http.MethodPost) // TODO: deprecate in Swagger, remove in v6
s.cfg.Router.HandleFunc("/prysm/v1/node/trusted_peers", nodeServerPrysm.AddTrustedPeer).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/prysm/node/trusted_peers/{peer_id}", nodeServerPrysm.RemoveTrustedPeer).Methods(http.MethodDelete) // TODO: deprecate in Swagger, remove in v6
s.cfg.Router.HandleFunc("/prysm/v1/node/trusted_peers/{peer_id}", nodeServerPrysm.RemoveTrustedPeer).Methods(http.MethodDelete)
}
func (s *Service) initializePrysmValidatorServerRoutes(validatorServerPrysm *validatorprysm.Server) {
s.cfg.Router.HandleFunc("/prysm/validator/performance", validatorServerPrysm.GetValidatorPerformance).Methods(http.MethodPost) // TODO: deprecate in Swagger, remove in v6
s.cfg.Router.HandleFunc("/prysm/v1/validator/performance", validatorServerPrysm.GetValidatorPerformance).Methods(http.MethodPost)
}
// Start the gRPC server.
func (s *Service) Start() {
grpcprometheus.EnableHandlingTimeHistogram()

View File

@@ -4,25 +4,12 @@ import (
"context"
"errors"
"io"
"net/http"
"testing"
"time"
"github.com/gorilla/mux"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
mockExecution "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/beacon"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/blob"
rpcBuilder "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/builder"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/debug"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/events"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/light-client"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/node"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/rewards"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/validator"
beaconprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/beacon"
nodeprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/node"
validatorprysm "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/validator"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
mockSync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/initial-sync/testing"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
@@ -48,148 +35,6 @@ func combineMaps(maps ...map[string][]string) map[string][]string {
return combinedMap
}
func TestServer_InitializeRoutes(t *testing.T) {
s := Service{
cfg: &Config{
Router: mux.NewRouter(),
},
}
s.initializeRewardServerRoutes(&rewards.Server{})
s.initializeBuilderServerRoutes(&rpcBuilder.Server{})
s.initializeBlobServerRoutes(&blob.Server{})
s.initializeValidatorServerRoutes(&validator.Server{})
s.initializeNodeServerRoutes(&node.Server{})
s.initializeBeaconServerRoutes(&beacon.Server{})
s.initializeConfigRoutes()
s.initializeEventsServerRoutes(&events.Server{})
s.initializeLightClientServerRoutes(&lightclient.Server{})
s.initializeDebugServerRoutes(&debug.Server{})
//prysm internal
s.initializePrysmBeaconServerRoutes(&beaconprysm.Server{})
s.initializePrysmNodeServerRoutes(&nodeprysm.Server{})
s.initializePrysmValidatorServerRoutes(&validatorprysm.Server{})
beaconRoutes := map[string][]string{
"/eth/v1/beacon/genesis": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/root": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/fork": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/finality_checkpoints": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validators": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validator_balances": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/sync_committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/randao": {http.MethodGet},
"/eth/v1/beacon/headers": {http.MethodGet},
"/eth/v1/beacon/headers/{block_id}": {http.MethodGet},
"/eth/v1/beacon/blinded_blocks": {http.MethodPost},
"/eth/v2/beacon/blinded_blocks": {http.MethodPost},
"/eth/v1/beacon/blocks": {http.MethodPost},
"/eth/v2/beacon/blocks": {http.MethodPost},
"/eth/v2/beacon/blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet},
"/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet},
"/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet},
"/eth/v1/beacon/rewards/sync_committee/{block_id}": {http.MethodPost},
"/eth/v1/beacon/deposit_snapshot": {http.MethodGet},
"/eth/v1/beacon/rewards/blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/rewards/attestations/{epoch}": {http.MethodPost},
"/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/light_client/bootstrap/{block_root}": {http.MethodGet},
"/eth/v1/beacon/light_client/updates": {http.MethodGet},
"/eth/v1/beacon/light_client/finality_update": {http.MethodGet},
"/eth/v1/beacon/light_client/optimistic_update": {http.MethodGet},
"/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/sync_committees": {http.MethodPost},
"/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/bls_to_execution_changes": {http.MethodGet, http.MethodPost},
}
builderRoutes := map[string][]string{
"/eth/v1/builder/states/{state_id}/expected_withdrawals": {http.MethodGet},
}
configRoutes := map[string][]string{
"/eth/v1/config/fork_schedule": {http.MethodGet},
"/eth/v1/config/spec": {http.MethodGet},
"/eth/v1/config/deposit_contract": {http.MethodGet},
}
debugRoutes := map[string][]string{
"/eth/v2/debug/beacon/states/{state_id}": {http.MethodGet},
"/eth/v2/debug/beacon/heads": {http.MethodGet},
"/eth/v1/debug/fork_choice": {http.MethodGet},
}
eventsRoutes := map[string][]string{
"/eth/v1/events": {http.MethodGet},
}
nodeRoutes := map[string][]string{
"/eth/v1/node/identity": {http.MethodGet},
"/eth/v1/node/peers": {http.MethodGet},
"/eth/v1/node/peers/{peer_id}": {http.MethodGet},
"/eth/v1/node/peer_count": {http.MethodGet},
"/eth/v1/node/version": {http.MethodGet},
"/eth/v1/node/syncing": {http.MethodGet},
"/eth/v1/node/health": {http.MethodGet},
}
validatorRoutes := map[string][]string{
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
"/eth/v2/validator/blocks/{slot}": {http.MethodGet}, //deprecated
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
"/eth/v1/validator/blinded_blocks/{slot}": {http.MethodGet}, //deprecated
"/eth/v1/validator/attestation_data": {http.MethodGet},
"/eth/v1/validator/aggregate_attestation": {http.MethodGet},
"/eth/v1/validator/aggregate_and_proofs": {http.MethodPost},
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
"/eth/v1/validator/register_validator": {http.MethodPost},
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
}
prysmCustomRoutes := map[string][]string{
"/prysm/v1/beacon/weak_subjectivity": {http.MethodGet},
"/prysm/node/trusted_peers": {http.MethodGet, http.MethodPost},
"/prysm/v1/node/trusted_peers": {http.MethodGet, http.MethodPost},
"/prysm/node/trusted_peers/{peer_id}": {http.MethodDelete},
"/prysm/v1/node/trusted_peers/{peer_id}": {http.MethodDelete},
"/prysm/validator/performance": {http.MethodPost},
"/prysm/v1/validator/performance": {http.MethodPost},
"/eth/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
"/prysm/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
}
wantRouteList := combineMaps(beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes, nodeRoutes, validatorRoutes, prysmCustomRoutes)
gotRouteList := make(map[string][]string)
err := s.cfg.Router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
tpl, err1 := route.GetPathTemplate()
require.NoError(t, err1)
met, err2 := route.GetMethods()
require.NoError(t, err2)
methods, ok := gotRouteList[tpl]
if !ok {
gotRouteList[tpl] = met
} else {
gotRouteList[tpl] = append(methods, met...)
}
return nil
})
require.NoError(t, err)
require.DeepEqual(t, wantRouteList, gotRouteList)
}
func TestLifecycle_OK(t *testing.T) {
hook := logTest.NewGlobal()
chainService := &mock.ChainService{

View File

@@ -77,6 +77,7 @@ go_test(
"//beacon-chain/das:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/filesystem:go_default_library",
"//beacon-chain/db/kv:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/p2p/peers:go_default_library",

View File

@@ -279,34 +279,22 @@ func (s *Service) markSynced() {
close(s.cfg.InitialSyncComplete)
}
func (s *Service) fetchOriginBlobs(pids []peer.ID) error {
r, err := s.cfg.DB.OriginCheckpointBlockRoot(s.ctx)
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
return nil
}
blk, err := s.cfg.DB.Block(s.ctx, r)
if err != nil {
log.WithField("root", r).Error("Block for checkpoint sync origin root not found in db")
return err
}
func missingBlobRequest(blk blocks.ROBlock, store *filesystem.BlobStorage) (p2ptypes.BlobSidecarsByRootReq, error) {
r := blk.Root()
if blk.Version() < version.Deneb {
return nil
return nil, nil
}
cmts, err := blk.Block().Body().BlobKzgCommitments()
if err != nil {
log.WithField("root", r).Error("Error reading commitments from checkpoint sync origin block")
return err
return nil, err
}
if len(cmts) == 0 {
return nil
return nil, nil
}
rob, err := blocks.NewROBlockWithRoot(blk, r)
onDisk, err := store.Indices(r)
if err != nil {
return err
}
onDisk, err := s.cfg.BlobStorage.Indices(r)
if err != nil {
return errors.Wrapf(err, "error checking existing blobs for checkpoint sync bloc root %#x", r)
return nil, errors.Wrapf(err, "error checking existing blobs for checkpoint sync block root %#x", r)
}
req := make(p2ptypes.BlobSidecarsByRootReq, 0, len(cmts))
for i := range cmts {
@@ -315,8 +303,32 @@ func (s *Service) fetchOriginBlobs(pids []peer.ID) error {
}
req = append(req, &eth.BlobIdentifier{BlockRoot: r[:], Index: uint64(i)})
}
return req, nil
}
func (s *Service) fetchOriginBlobs(pids []peer.ID) error {
r, err := s.cfg.DB.OriginCheckpointBlockRoot(s.ctx)
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
return nil
}
blk, err := s.cfg.DB.Block(s.ctx, r)
if err != nil {
log.WithField("root", fmt.Sprintf("%#x", r)).Error("Block for checkpoint sync origin root not found in db")
return err
}
if !params.WithinDAPeriod(slots.ToEpoch(blk.Block().Slot()), slots.ToEpoch(s.clock.CurrentSlot())) {
return nil
}
rob, err := blocks.NewROBlockWithRoot(blk, r)
if err != nil {
return err
}
req, err := missingBlobRequest(rob, s.cfg.BlobStorage)
if err != nil {
return err
}
if len(req) == 0 {
log.WithField("nBlobs", len(cmts)).WithField("root", fmt.Sprintf("%#x", r)).Debug("All checkpoint block blobs are present")
log.WithField("root", fmt.Sprintf("%#x", r)).Debug("All blobs for checkpoint block are present")
return nil
}
shufflePeers(pids)

View File

@@ -6,16 +6,19 @@ import (
"testing"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/paulbellamy/ratecounter"
"github.com/prysmaticlabs/prysm/v5/async/abool"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/kv"
dbtest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
p2pt "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
@@ -420,3 +423,91 @@ func TestService_Synced(t *testing.T) {
s.synced.Set()
assert.Equal(t, true, s.Synced())
}
func TestMissingBlobRequest(t *testing.T) {
cases := []struct {
name string
setup func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage)
nReq int
err error
}{
{
name: "pre-deneb",
setup: func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage) {
cb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
require.NoError(t, err)
rob, err := blocks.NewROBlockWithRoot(cb, [32]byte{})
require.NoError(t, err)
return rob, nil
},
nReq: 0,
},
{
name: "deneb zero commitments",
setup: func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage) {
bk, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, 0)
return bk, nil
},
nReq: 0,
},
{
name: "2 commitments, all missing",
setup: func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage) {
bk, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, 2)
fs := filesystem.NewEphemeralBlobStorage(t)
return bk, fs
},
nReq: 2,
},
{
name: "2 commitments, 1 missing",
setup: func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage) {
bk, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, 2)
bm, fs := filesystem.NewEphemeralBlobStorageWithMocker(t)
require.NoError(t, bm.CreateFakeIndices(bk.Root(), 1))
return bk, fs
},
nReq: 1,
},
{
name: "2 commitments, 0 missing",
setup: func(t *testing.T) (blocks.ROBlock, *filesystem.BlobStorage) {
bk, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, 2)
bm, fs := filesystem.NewEphemeralBlobStorageWithMocker(t)
require.NoError(t, bm.CreateFakeIndices(bk.Root(), 0, 1))
return bk, fs
},
nReq: 0,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
blk, store := c.setup(t)
req, err := missingBlobRequest(blk, store)
require.NoError(t, err)
require.Equal(t, c.nReq, len(req))
})
}
}
func TestOriginOutsideRetention(t *testing.T) {
ctx := context.Background()
bdb := dbtest.SetupDB(t)
genesis := time.Unix(0, 0)
secsPerEpoch := params.BeaconConfig().SecondsPerSlot * uint64(params.BeaconConfig().SlotsPerEpoch)
retentionSeconds := time.Second * time.Duration(uint64(params.BeaconConfig().MinEpochsForBlobsSidecarsRequest+1)*secsPerEpoch)
outsideRetention := genesis.Add(retentionSeconds)
now := func() time.Time {
return outsideRetention
}
clock := startup.NewClock(genesis, [32]byte{}, startup.WithNower(now))
s := &Service{ctx: ctx, cfg: &Config{DB: bdb}, clock: clock}
blk, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, 0, 1)
require.NoError(t, bdb.SaveBlock(ctx, blk))
concreteDB, ok := bdb.(*kv.Store)
require.Equal(t, true, ok)
require.NoError(t, concreteDB.SaveOriginCheckpointBlockRoot(ctx, blk.Root()))
// This would break due to missing service dependencies, but will return nil fast due to being outside retention.
require.Equal(t, false, params.WithinDAPeriod(slots.ToEpoch(blk.Block().Slot()), slots.ToEpoch(clock.CurrentSlot())))
require.NoError(t, s.fetchOriginBlobs([]peer.ID{}))
}

View File

@@ -98,7 +98,9 @@ func TestBackupAccounts_Noninteractive_Derived(t *testing.T) {
// We check a backup.zip file was created at the output path.
zipFilePath := filepath.Join(backupDir, accounts.ArchiveFilename)
assert.DeepEqual(t, true, file.Exists(zipFilePath))
fileExists, err := file.Exists(zipFilePath, file.Regular)
require.NoError(t, err, "could not check if backup file exists")
assert.Equal(t, true, fileExists, "backup file does not exist")
// We attempt to unzip the file and verify the keystores do match our accounts.
f, err := os.Open(zipFilePath)
@@ -189,7 +191,9 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) {
// We check a backup.zip file was created at the output path.
zipFilePath := filepath.Join(backupDir, accounts.ArchiveFilename)
assert.DeepEqual(t, true, file.Exists(zipFilePath))
exists, err := file.Exists(zipFilePath, file.Regular)
require.NoError(t, err, "could not check if backup file exists")
assert.Equal(t, true, exists, "backup file does not exist")
// We attempt to unzip the file and verify the keystores do match our accounts.
f, err := os.Open(zipFilePath)

View File

@@ -395,5 +395,7 @@ func TestExitAccountsCli_WriteJSON_NoBroadcast(t *testing.T) {
require.Equal(t, 1, len(formattedExitedKeys))
assert.Equal(t, "0x"+keystore.Pubkey[:12], formattedExitedKeys[0])
require.Equal(t, true, file.Exists(path.Join(out, "validator-exit-1.json")), "Expected file to exist")
exists, err := file.Exists(path.Join(out, "validator-exit-1.json"), file.Regular)
require.NoError(t, err, "could not check if exit file exists")
require.Equal(t, true, exists, "Expected file to exist")
}

View File

@@ -10,6 +10,22 @@ import (
var log = logrus.WithField("prefix", "db")
var (
// SourceDataDirFlag defines a path on disk where source Prysm databases are stored. Used for conversion.
SourceDataDirFlag = &cli.StringFlag{
Name: "source-data-dir",
Usage: "Source data directory",
Required: true,
}
// SourceDataDirFlag defines a path on disk where source Prysm databases are stored. Used for conversion.
TargetDataDirFlag = &cli.StringFlag{
Name: "target-data-dir",
Usage: "Target data directory",
Required: true,
}
)
// Commands for interacting with the Prysm validator database.
var Commands = &cli.Command{
Name: "db",
@@ -66,5 +82,29 @@ var Commands = &cli.Command{
},
},
},
{
Name: "convert-complete-to-minimal",
Category: "db",
Usage: "Convert a complete EIP-3076 slashing protection to a minimal one",
Flags: []cli.Flag{
SourceDataDirFlag,
TargetDataDirFlag,
},
Before: func(cliCtx *cli.Context) error {
return cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags)
},
Action: func(cliCtx *cli.Context) error {
sourcedDatabasePath := cliCtx.String(SourceDataDirFlag.Name)
targetDatabasePath := cliCtx.String(TargetDataDirFlag.Name)
// Convert the database
err := validatordb.ConvertDatabase(cliCtx.Context, sourcedDatabasePath, targetDatabasePath, false)
if err != nil {
log.WithError(err).Fatal("Could not convert database")
}
return nil
},
},
},
}

View File

@@ -10,6 +10,7 @@ go_library(
visibility = [
"//cmd/prysmctl:__subpackages__",
"//cmd/validator:__subpackages__",
"//config:__subpackages__",
"//testing/endtoend:__subpackages__",
"//validator:__subpackages__",
],

View File

@@ -127,7 +127,8 @@ func main() {
Version: version.Version(),
Action: func(ctx *cli.Context) error {
if err := startNode(ctx); err != nil {
return cli.Exit(err.Error(), 1)
log.Fatal(err.Error())
return err
}
return nil
},

View File

@@ -17,8 +17,11 @@ go_library(
"//io/file:go_default_library",
"//runtime/tos:go_default_library",
"//validator/accounts/userprompt:go_default_library",
"//validator/db/filesystem:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/slashing-protection-history:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
@@ -35,7 +38,7 @@ go_test(
"//io/file:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/db/common:go_default_library",
"//validator/db/testing:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"//validator/testing:go_default_library",

View File

@@ -8,10 +8,14 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/userprompt"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
slashingprotection "github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history"
"github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history/format"
"github.com/urfave/cli/v2"
)
@@ -30,11 +34,21 @@ const (
// the validator's db into an EIP standard slashing protection format
// 4. Format and save the JSON file to a user's specified output directory.
func exportSlashingProtectionJSON(cliCtx *cli.Context) error {
var (
validatorDB iface.ValidatorDB
found bool
err error
)
log.Info(
"This command exports your validator's attestation and proposal history into " +
"a file that can then be imported into any other Prysm setup across computers",
)
var err error
// Check if a minimal database is requested
isDatabaseMinimal := cliCtx.Bool(features.EnableMinimalSlashingProtection.Name)
// Read the data directory from the CLI context.
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
if !cliCtx.IsSet(cmd.DataDirFlag.Name) {
dataDir, err = userprompt.InputDirectory(cliCtx, userprompt.DataDirDirPromptText, cmd.DataDirFlag)
@@ -42,27 +56,45 @@ func exportSlashingProtectionJSON(cliCtx *cli.Context) error {
return errors.Wrapf(err, "could not read directory value from input")
}
}
// ensure that the validator.db is found under the specified dir or its subdirectories
found, _, err := file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
// Ensure that the database is found under the specified dir or its subdirectories
if isDatabaseMinimal {
found, _, err = file.RecursiveDirFind(filesystem.DatabaseDirName, dataDir)
} else {
found, _, err = file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
}
if err != nil {
return errors.Wrapf(err, "error finding validator database at path %s", dataDir)
}
if !found {
return fmt.Errorf(
"validator.db file (validator database) was not found at path %s, so nothing to export",
dataDir,
)
databaseFileDir := kv.ProtectionDbFileName
if isDatabaseMinimal {
databaseFileDir = filesystem.DatabaseDirName
}
return fmt.Errorf("%s (validator database) was not found at path %s, so nothing to export", databaseFileDir, dataDir)
}
// Open the validator database.
if isDatabaseMinimal {
validatorDB, err = filesystem.NewStore(dataDir, nil)
} else {
validatorDB, err = kv.NewKVStore(cliCtx.Context, dataDir, nil)
}
validatorDB, err := kv.NewKVStore(cliCtx.Context, dataDir, &kv.Config{})
if err != nil {
return errors.Wrapf(err, "could not access validator database at path %s", dataDir)
}
// Close the database when we're done.
defer func() {
if err := validatorDB.Close(); err != nil {
log.WithError(err).Errorf("Could not close validator DB")
}
}()
// Export the slashing protection history from the validator's database.
eipJSON, err := slashingprotection.ExportStandardProtectionJSON(cliCtx.Context, validatorDB)
if err != nil {
return errors.Wrap(err, "could not export slashing protection history")
@@ -79,39 +111,60 @@ func exportSlashingProtectionJSON(cliCtx *cli.Context) error {
)
}
// Write the result to the output file
if err := writeToOutput(cliCtx, eipJSON); err != nil {
return errors.Wrap(err, "could not write slashing protection history to output file")
}
return nil
}
func writeToOutput(cliCtx *cli.Context, eipJSON *format.EIPSlashingProtectionFormat) error {
// Get the output directory where the slashing protection history file will be stored
outputDir, err := userprompt.InputDirectory(
cliCtx,
"Enter your desired output directory for your slashing protection history file",
flags.SlashingProtectionExportDirFlag,
)
if err != nil {
return errors.Wrap(err, "could not get slashing protection json file")
}
if outputDir == "" {
return errors.New("output directory not specified")
}
// Check is the output directory already exists, if not, create it
exists, err := file.HasDir(outputDir)
if err != nil {
return errors.Wrapf(err, "could not check if output directory %s already exists", outputDir)
}
if !exists {
if err := file.MkdirAll(outputDir); err != nil {
return errors.Wrapf(err, "could not create output directory %s", outputDir)
}
}
// Write into the output file
outputFilePath := filepath.Join(outputDir, jsonExportFileName)
log.Infof("Writing slashing protection export JSON file to %s", outputFilePath)
encoded, err := json.MarshalIndent(eipJSON, "", "\t")
if err != nil {
return errors.Wrap(err, "could not JSON marshal slashing protection history")
}
if err := file.WriteFile(outputFilePath, encoded); err != nil {
return errors.Wrapf(err, "could not write file to path %s", outputFilePath)
}
log.Infof(
"Successfully wrote %s. You can import this file using Prysm's "+
"validator slashing-protection-history import command in another machine",
outputFilePath,
)
return nil
}

View File

@@ -7,10 +7,12 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/userprompt"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
slashingprotection "github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history"
"github.com/urfave/cli/v2"
)
@@ -24,7 +26,16 @@ import (
// 4. Call the function which actually imports the data from
// the standard slashing protection JSON file into our database.
func importSlashingProtectionJSON(cliCtx *cli.Context) error {
var err error
var (
valDB iface.ValidatorDB
found bool
err error
)
// Check if a minimal database is requested
isDatabaseMimimal := cliCtx.Bool(features.EnableMinimalSlashingProtection.Name)
// Get the data directory from the CLI context.
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
if !cliCtx.IsSet(cmd.DataDirFlag.Name) {
dataDir, err = userprompt.InputDirectory(cliCtx, userprompt.DataDirDirPromptText, cmd.DataDirFlag)
@@ -32,28 +43,44 @@ func importSlashingProtectionJSON(cliCtx *cli.Context) error {
return errors.Wrapf(err, "could not read directory value from input")
}
}
// ensure that the validator.db is found under the specified dir or its subdirectories
found, _, err := file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
// Ensure that the database is found under the specified directory or its subdirectories
if isDatabaseMimimal {
found, _, err = file.RecursiveDirFind(filesystem.DatabaseDirName, dataDir)
} else {
found, _, err = file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
}
if err != nil {
return errors.Wrapf(err, "error finding validator database at path %s", dataDir)
}
message := "Found existing database inside of %s"
if !found {
log.Infof(
"Did not find existing validator.db inside of %s, creating a new one",
dataDir,
)
} else {
log.Infof("Found existing validator.db inside of %s", dataDir)
message = "Did not find existing database inside of %s, creating a new one"
}
valDB, err := kv.NewKVStore(cliCtx.Context, dataDir, &kv.Config{})
log.Infof(message, dataDir)
// Open the validator database.
if isDatabaseMimimal {
valDB, err = filesystem.NewStore(dataDir, nil)
} else {
valDB, err = kv.NewKVStore(cliCtx.Context, dataDir, nil)
}
if err != nil {
return errors.Wrapf(err, "could not access validator database at path: %s", dataDir)
}
// Close the database when we're done.
defer func() {
if err := valDB.Close(); err != nil {
log.WithError(err).Errorf("Could not close validator DB")
}
}()
// Get the path to the slashing protection JSON file from the CLI context.
protectionFilePath, err := userprompt.InputDirectory(cliCtx, userprompt.SlashingProtectionJSONPromptText, flags.SlashingProtectionJSONFileFlag)
if err != nil {
return errors.Wrap(err, "could not get slashing protection json file")
@@ -65,17 +92,22 @@ func importSlashingProtectionJSON(cliCtx *cli.Context) error {
flags.SlashingProtectionJSONFileFlag.Name,
)
}
// Read the JSON file from user input.
enc, err := file.ReadFileAsBytes(protectionFilePath)
if err != nil {
return err
}
// Import the data from the standard slashing protection JSON file into our database.
log.Infof("Starting import of slashing protection file %s", protectionFilePath)
buf := bytes.NewBuffer(enc)
if err := slashingprotection.ImportStandardProtectionJSON(
cliCtx.Context, valDB, buf,
); err != nil {
return err
if err := valDB.ImportStandardProtectionJSON(cliCtx.Context, buf); err != nil {
return errors.Wrapf(err, "could not import slashing protection JSON file %s", protectionFilePath)
}
log.Infof("Slashing protection JSON successfully imported into %s", dataDir)
return nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
"github.com/prysmaticlabs/prysm/v5/validator/db/common"
dbTest "github.com/prysmaticlabs/prysm/v5/validator/db/testing"
"github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history/format"
mocks "github.com/prysmaticlabs/prysm/v5/validator/testing"
@@ -35,6 +35,11 @@ func setupCliCtx(
return cli.NewContext(&app, set, nil)
}
// TestImportExportSlashingProtectionCli_RoundTrip imports a EIP-3076 interchange format JSON file,
// and exports it back to disk. It then compare the exported file to the original file.
// This test is only suitable for complete slashing protection history database, since minimal
// slashing protection history database will keep only the latest signed block slot / attestations,
// and thus will not be able to export the same data as the original file.
func TestImportExportSlashingProtectionCli_RoundTrip(t *testing.T) {
numValidators := 10
outputPath := filepath.Join(t.TempDir(), "slashing-exports")
@@ -59,7 +64,8 @@ func TestImportExportSlashingProtectionCli_RoundTrip(t *testing.T) {
require.NoError(t, err)
// We create a CLI context with the required values, such as the database datadir and output directory.
validatorDB := dbTest.SetupDB(t, pubKeys)
isSlashingProtectionMinimal := false
validatorDB := dbTest.SetupDB(t, pubKeys, isSlashingProtectionMinimal)
dbPath := validatorDB.DatabasePath()
require.NoError(t, validatorDB.Close())
cliCtx := setupCliCtx(t, dbPath, protectionFilePath, outputPath)
@@ -108,6 +114,11 @@ func TestImportExportSlashingProtectionCli_RoundTrip(t *testing.T) {
}
}
// TestImportExportSlashingProtectionCli_EmptyData imports a EIP-3076 interchange format JSON file,
// and exports it back to disk. It then compare the exported file to the original file.
// This test is only suitable for complete slashing protection history database, since minimal
// slashing protection history database will keep only the latest signed block slot / attestations,
// and thus will not be able to export the same data as the original file.
func TestImportExportSlashingProtectionCli_EmptyData(t *testing.T) {
numValidators := 10
outputPath := filepath.Join(t.TempDir(), "slashing-exports")
@@ -118,10 +129,10 @@ func TestImportExportSlashingProtectionCli_EmptyData(t *testing.T) {
// Create some mock slashing protection history. and JSON file
pubKeys, err := mocks.CreateRandomPubKeys(numValidators)
require.NoError(t, err)
attestingHistory := make([][]*kv.AttestationRecord, 0)
proposalHistory := make([]kv.ProposalHistoryForPubkey, len(pubKeys))
attestingHistory := make([][]*common.AttestationRecord, 0)
proposalHistory := make([]common.ProposalHistoryForPubkey, len(pubKeys))
for i := 0; i < len(pubKeys); i++ {
proposalHistory[i].Proposals = make([]kv.Proposal, 0)
proposalHistory[i].Proposals = make([]common.Proposal, 0)
}
mockJSON, err := mocks.MockSlashingProtectionJSON(pubKeys, attestingHistory, proposalHistory)
require.NoError(t, err)
@@ -135,7 +146,8 @@ func TestImportExportSlashingProtectionCli_EmptyData(t *testing.T) {
require.NoError(t, err)
// We create a CLI context with the required values, such as the database datadir and output directory.
validatorDB := dbTest.SetupDB(t, pubKeys)
isSlashingProtectionMinimal := false
validatorDB := dbTest.SetupDB(t, pubKeys, isSlashingProtectionMinimal)
dbPath := validatorDB.DatabasePath()
require.NoError(t, validatorDB.Close())
cliCtx := setupCliCtx(t, dbPath, protectionFilePath, outputPath)

View File

@@ -25,6 +25,7 @@ var Commands = &cli.Command{
features.PraterTestnet,
features.SepoliaTestnet,
features.HoleskyTestnet,
features.EnableMinimalSlashingProtection,
cmd.AcceptTosFlag,
}),
Before: func(cliCtx *cli.Context) error {
@@ -53,6 +54,7 @@ var Commands = &cli.Command{
features.PraterTestnet,
features.SepoliaTestnet,
features.HoleskyTestnet,
features.EnableMinimalSlashingProtection,
cmd.AcceptTosFlag,
}),
Before: func(cliCtx *cli.Context) error {

View File

@@ -1,3 +1,5 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
config_setting(
name = "mainnet",
flag_values = {
@@ -11,3 +13,28 @@ config_setting(
"//proto:network": "minimal",
},
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/config",
visibility = ["//visibility:public"],
deps = [
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_apimachinery//pkg/util/yaml:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
embed = [":go_default_library"],
deps = [
"//config/params:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],
)

View File

@@ -57,7 +57,8 @@ type Flags struct {
AttestTimely bool // AttestTimely fixes #8185. It is gated behind a flag to ensure beacon node's fix can safely roll out first. We'll invert this in v1.1.0.
EnableSlasher bool // Enable slasher in the beacon node runtime.
EnableSlashingProtectionPruning bool // EnableSlashingProtectionPruning for the validator client.
EnableSlashingProtectionPruning bool // Enable slashing protection pruning for the validator client.
EnableMinimalSlashingProtection bool // Enable minimal slashing protection database for the validator client.
SaveFullExecutionPayloads bool // Save full beacon blocks with execution payloads in the database.
EnableStartOptimistic bool // EnableStartOptimistic treats every block as optimistic at startup.
@@ -276,6 +277,10 @@ func ConfigureValidator(ctx *cli.Context) error {
logEnabled(enableSlashingProtectionPruning)
cfg.EnableSlashingProtectionPruning = true
}
if ctx.Bool(EnableMinimalSlashingProtection.Name) {
logEnabled(EnableMinimalSlashingProtection)
cfg.EnableMinimalSlashingProtection = true
}
if ctx.Bool(enableDoppelGangerProtection.Name) {
logEnabled(enableDoppelGangerProtection)
cfg.EnableDoppelGanger = true

View File

@@ -95,6 +95,10 @@ var (
Name: "enable-slashing-protection-history-pruning",
Usage: "Enables the pruning of the validator client's slashing protection database.",
}
EnableMinimalSlashingProtection = &cli.BoolFlag{
Name: "enable-minimal-slashing-protection",
Usage: "Enables the minimal slashing protection. See EIP-3076 for more details.",
}
enableDoppelGangerProtection = &cli.BoolFlag{
Name: "enable-doppelganger",
Usage: `Enables the validator to perform a doppelganger check.
@@ -177,6 +181,7 @@ var ValidatorFlags = append(deprecatedFlags, []cli.Flag{
dynamicKeyReloadDebounceInterval,
attestTimely,
enableSlashingProtectionPruning,
EnableMinimalSlashingProtection,
enableDoppelGangerProtection,
EnableBeaconRESTApi,
}...)

View File

@@ -27,8 +27,6 @@ var placeholderFields = []string{
"EIP6110_FORK_VERSION",
"EIP7002_FORK_EPOCH",
"EIP7002_FORK_VERSION",
"EIP7594_FORK_EPOCH",
"EIP7594_FORK_VERSION",
"MAX_BLOBS_PER_BLOCK",
"REORG_HEAD_WEIGHT_THRESHOLD",
"UPDATE_TIMEOUT",

View File

@@ -2,10 +2,11 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["proposer_settings.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/config/validator/service",
srcs = ["settings.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/config/proposer",
visibility = ["//visibility:public"],
deps = [
"//config:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/validator:go_default_library",
"//encoding/bytesutil:go_default_library",
@@ -18,7 +19,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["proposer_settings_test.go"],
srcs = ["settings_test.go"],
embed = [":go_default_library"],
deps = [
"//config/fieldparams:go_default_library",

View File

@@ -0,0 +1,45 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_test(
name = "go_default_test",
size = "small",
srcs = ["loader_test.go"],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//cmd/validator/flags:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/validator:go_default_library",
"//encoding/bytesutil:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/db/testing:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["loader.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/config/proposer/loader",
visibility = ["//visibility:public"],
deps = [
"//cmd/validator/flags:go_default_library",
"//config:go_default_library",
"//config/params:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/validator:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/db/iface:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
],
)

View File

@@ -0,0 +1,283 @@
package loader
import (
"fmt"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v5/config"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/validator"
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
type settingsType int
const (
none settingsType = iota
defaultFlag
fileFlag
urlFlag
onlyDB
)
type settingsLoader struct {
loadMethods []settingsType
existsInDB bool
db iface.ValidatorDB
options *flagOptions
}
type flagOptions struct {
builderConfig *proposer.BuilderConfig
gasLimit *validator.Uint64
}
// SettingsLoaderOption sets additional options that affect the proposer settings
type SettingsLoaderOption func(cliCtx *cli.Context, psl *settingsLoader) error
// WithBuilderConfig applies the --enable-builder flag to proposer settings
func WithBuilderConfig() SettingsLoaderOption {
return func(cliCtx *cli.Context, psl *settingsLoader) error {
if cliCtx.Bool(flags.EnableBuilderFlag.Name) {
psl.options.builderConfig = &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
}
}
return nil
}
}
// WithGasLimit applies the --suggested-gas-limit flag to proposer settings
func WithGasLimit() SettingsLoaderOption {
return func(cliCtx *cli.Context, psl *settingsLoader) error {
sgl := cliCtx.String(flags.BuilderGasLimitFlag.Name)
if sgl != "" {
gl, err := strconv.ParseUint(sgl, 10, 64)
if err != nil {
return errors.Errorf("Value set by --%s is not a uint64", flags.BuilderGasLimitFlag.Name)
}
if gl == 0 {
log.Warnf("Gas limit was intentionally set to 0, this will be replaced with the default gas limit of %d", params.BeaconConfig().DefaultBuilderGasLimit)
}
rgl := reviewGasLimit(validator.Uint64(gl))
psl.options.gasLimit = &rgl
}
return nil
}
}
// NewProposerSettingsLoader returns a new proposer settings loader that can process the proposer settings based on flag options
func NewProposerSettingsLoader(cliCtx *cli.Context, db iface.ValidatorDB, opts ...SettingsLoaderOption) (*settingsLoader, error) {
if cliCtx.IsSet(flags.ProposerSettingsFlag.Name) && cliCtx.IsSet(flags.ProposerSettingsURLFlag.Name) {
return nil, fmt.Errorf("cannot specify both --%s and --%s flags; choose one method for specifying proposer settings", flags.ProposerSettingsFlag.Name, flags.ProposerSettingsURLFlag.Name)
}
psExists, err := db.ProposerSettingsExists(cliCtx.Context)
if err != nil {
return nil, err
}
psl := &settingsLoader{db: db, existsInDB: psExists, options: &flagOptions{}}
if cliCtx.IsSet(flags.SuggestedFeeRecipientFlag.Name) {
psl.loadMethods = append(psl.loadMethods, defaultFlag)
}
if cliCtx.IsSet(flags.ProposerSettingsFlag.Name) {
psl.loadMethods = append(psl.loadMethods, fileFlag)
}
if cliCtx.IsSet(flags.ProposerSettingsURLFlag.Name) {
psl.loadMethods = append(psl.loadMethods, urlFlag)
}
if len(psl.loadMethods) == 0 {
method := none
if psExists {
// override with db
method = onlyDB
}
psl.loadMethods = append(psl.loadMethods, method)
}
for _, o := range opts {
if err := o(cliCtx, psl); err != nil {
return nil, err
}
}
return psl, nil
}
// Load saves the proposer settings to the database
func (psl *settingsLoader) Load(cliCtx *cli.Context) (*proposer.Settings, error) {
loadConfig := &validatorpb.ProposerSettingsPayload{}
// override settings based on other options
if psl.options.builderConfig != nil && psl.options.gasLimit != nil {
psl.options.builderConfig.GasLimit = *psl.options.gasLimit
}
// check if database has settings already
if psl.existsInDB {
dbps, err := psl.db.ProposerSettings(cliCtx.Context)
if err != nil {
return nil, err
}
loadConfig = dbps.ToConsensus()
}
// start to process based on load method
for _, method := range psl.loadMethods {
switch method {
case defaultFlag:
suggestedFeeRecipient := cliCtx.String(flags.SuggestedFeeRecipientFlag.Name)
if !common.IsHexAddress(suggestedFeeRecipient) {
return nil, errors.Errorf("--%s is not a valid Ethereum address", flags.SuggestedFeeRecipientFlag.Name)
}
if err := config.WarnNonChecksummedAddress(suggestedFeeRecipient); err != nil {
return nil, err
}
defaultConfig := &validatorpb.ProposerOptionPayload{
FeeRecipient: suggestedFeeRecipient,
}
if psl.options.builderConfig != nil {
defaultConfig.Builder = psl.options.builderConfig.ToConsensus()
}
loadConfig.DefaultConfig = defaultConfig
case fileFlag:
var settingFromFile *validatorpb.ProposerSettingsPayload
if err := config.UnmarshalFromFile(cliCtx.String(flags.ProposerSettingsFlag.Name), &settingFromFile); err != nil {
return nil, err
}
if settingFromFile == nil {
return nil, errors.Errorf("proposer settings is empty after unmarshalling from file specified by %s flag", flags.ProposerSettingsFlag.Name)
}
loadConfig = psl.processProposerSettings(settingFromFile, loadConfig)
case urlFlag:
var settingFromURL *validatorpb.ProposerSettingsPayload
if err := config.UnmarshalFromURL(cliCtx.Context, cliCtx.String(flags.ProposerSettingsURLFlag.Name), &settingFromURL); err != nil {
return nil, err
}
if settingFromURL == nil {
return nil, errors.New("proposer settings is empty after unmarshalling from url")
}
loadConfig = psl.processProposerSettings(settingFromURL, loadConfig)
case onlyDB:
loadConfig = psl.processProposerSettings(nil, loadConfig)
case none:
if psl.options.builderConfig != nil {
// if there are no proposer settings provided, create a default where fee recipient is not populated, this will be skipped for validator registration on validators that don't have a fee recipient set.
// skip saving to DB if only builder settings are provided until a trigger like keymanager API updates with fee recipient values
option := &proposer.Option{
BuilderConfig: psl.options.builderConfig.Clone(),
}
loadConfig.DefaultConfig = option.ToConsensus()
}
default:
return nil, errors.New("load method for proposer settings does not exist")
}
}
// exit early if nothing is provided
if loadConfig == nil || (loadConfig.ProposerConfig == nil && loadConfig.DefaultConfig == nil) {
log.Warn("No proposer settings were provided")
return nil, nil
}
ps, err := proposer.SettingFromConsensus(loadConfig)
if err != nil {
return nil, err
}
if err := psl.db.SaveProposerSettings(cliCtx.Context, ps); err != nil {
return nil, err
}
return ps, nil
}
func (psl *settingsLoader) processProposerSettings(loadedSettings, dbSettings *validatorpb.ProposerSettingsPayload) *validatorpb.ProposerSettingsPayload {
if loadedSettings == nil && dbSettings == nil {
return nil
}
// loaded settings have higher priority than db settings
newSettings := &validatorpb.ProposerSettingsPayload{}
var builderConfig *validatorpb.BuilderConfig
var gasLimitOnly *validator.Uint64
if psl.options != nil {
if psl.options.builderConfig != nil {
builderConfig = psl.options.builderConfig.ToConsensus()
}
if psl.options.gasLimit != nil {
gasLimitOnly = psl.options.gasLimit
}
}
if dbSettings != nil && dbSettings.DefaultConfig != nil {
if builderConfig == nil {
dbSettings.DefaultConfig.Builder = nil
}
newSettings.DefaultConfig = dbSettings.DefaultConfig
}
if loadedSettings != nil && loadedSettings.DefaultConfig != nil {
newSettings.DefaultConfig = loadedSettings.DefaultConfig
}
// process any builder overrides on defaults
if newSettings.DefaultConfig != nil {
newSettings.DefaultConfig.Builder = processBuilderConfig(newSettings.DefaultConfig.Builder, builderConfig, gasLimitOnly)
}
if dbSettings != nil && len(dbSettings.ProposerConfig) != 0 {
for _, option := range dbSettings.ProposerConfig {
if builderConfig == nil {
option.Builder = nil
}
}
newSettings.ProposerConfig = dbSettings.ProposerConfig
}
if loadedSettings != nil && len(loadedSettings.ProposerConfig) != 0 {
newSettings.ProposerConfig = loadedSettings.ProposerConfig
}
// process any overrides for proposer config
for _, option := range newSettings.ProposerConfig {
if option != nil {
option.Builder = processBuilderConfig(option.Builder, builderConfig, gasLimitOnly)
}
}
// if default and proposer configs are both missing even after db setting
if newSettings.DefaultConfig == nil && newSettings.ProposerConfig == nil {
return nil
}
return newSettings
}
func processBuilderConfig(current *validatorpb.BuilderConfig, override *validatorpb.BuilderConfig, gasLimitOnly *validator.Uint64) *validatorpb.BuilderConfig {
if current != nil {
current.GasLimit = reviewGasLimit(current.GasLimit)
if override != nil {
current.Enabled = override.Enabled
}
if gasLimitOnly != nil {
current.GasLimit = *gasLimitOnly
}
return current
}
return override
}
func reviewGasLimit(gasLimit validator.Uint64) validator.Uint64 {
// sets gas limit to default if not defined or set to 0
if gasLimit == 0 {
return validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit)
}
// TODO(10810): add in warning for ranges
return gasLimit
}

View File

@@ -0,0 +1,927 @@
package loader
import (
"context"
"flag"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
dbTest "github.com/prysmaticlabs/prysm/v5/validator/db/testing"
logtest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2"
)
func TestProposerSettingsLoader(t *testing.T) {
hook := logtest.NewGlobal()
type proposerSettingsFlag struct {
dir string
url string
defaultfee string
defaultgas string
}
type args struct {
proposerSettingsFlagValues *proposerSettingsFlag
}
tests := []struct {
name string
args args
want func() *proposer.Settings
urlResponse string
wantInitErr string
wantErr string
wantLog string
withdb func(db iface.ValidatorDB) error
validatorRegistrationEnabled bool
skipDBSavedCheck bool
}{
{
name: "db settings override file settings if file default config is missing",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/proposer-config-only.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
},
}
},
withdb: func(db iface.ValidatorDB) error {
settings := &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
return db.SaveProposerSettings(context.Background(), settings)
},
},
{
name: "db settings override file settings if file proposer config is missing and enable builder is true",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/default-only-proposer-config.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
withdb: func(db iface.ValidatorDB) error {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
settings := &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
},
},
},
}
return db.SaveProposerSettings(context.Background(), settings)
},
validatorRegistrationEnabled: true,
},
{
name: "Empty json file loaded throws a warning",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/empty.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return nil
},
wantLog: "No proposer settings were provided",
skipDBSavedCheck: true,
},
{
name: "Happy Path default only proposer settings file with builder settings,",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/default-only-proposer-config.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
},
{
name: "Happy Path Config file File, bad checksum",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config-badchecksum.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xae967917c465db8578ca9024c205720b1a3651A9"),
},
},
}
},
wantErr: "",
wantLog: "is not a checksum Ethereum address",
},
{
name: "Happy Path Config file File multiple fee recipients",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config-multiple.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
key2, err := hexutil.Decode("0xb057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7b")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
bytesutil.ToBytes48(key2): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x60155530FCE8a85ec7055A5F8b2bE214B3DaeFd4"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(35000000),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
},
},
}
},
wantErr: "",
},
{
name: "Happy Path Config URL File",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "./testdata/good-prepare-beacon-proposer-config.json",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
},
wantErr: "",
},
{
name: "Happy Path Config YAML file with custom Gas Limit",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.yaml",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: 40000000,
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: false,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
wantErr: "",
},
{
name: "Happy Path Suggested Fee ",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89A",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{
ProposeConfig: nil,
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
},
wantErr: "",
},
{
name: "Happy Path Suggested Fee , validator registration enabled",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89A",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{
ProposeConfig: nil,
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
wantErr: "",
validatorRegistrationEnabled: true,
},
{
name: "Happy Path Suggested Fee , validator registration enabled and default gas",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89A",
defaultgas: "50000000",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{
ProposeConfig: nil,
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: 50000000,
},
},
}
},
wantErr: "",
validatorRegistrationEnabled: true,
},
{
name: "File with default gas that overrides",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.yaml",
url: "",
defaultfee: "",
defaultgas: "50000000",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: 50000000,
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: false,
GasLimit: validator.Uint64(50000000),
},
},
}
},
wantErr: "",
},
{
name: "Suggested Fee does not Override Config",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.json",
url: "",
defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89B",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
},
wantErr: "",
},
{
name: "Suggested Fee with validator registration does not Override Config",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.json",
url: "",
defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89B",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
wantErr: "",
validatorRegistrationEnabled: true,
},
{
name: "Enable Builder flag overrides empty config",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
validatorRegistrationEnabled: true,
},
{
name: "Enable Builder flag does override completed builder config",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.yaml",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
validatorRegistrationEnabled: true,
},
{
name: "Only Enable Builder flag",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{
DefaultConfig: &proposer.Option{
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
validatorRegistrationEnabled: true,
skipDBSavedCheck: true,
},
{
name: "No Flags but saved to DB with builder and override removed builder data",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
},
withdb: func(db iface.ValidatorDB) error {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
settings := &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
return db.SaveProposerSettings(context.Background(), settings)
},
},
{
name: "Enable builder flag but saved to DB without builder data now includes builder data",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
},
},
}
},
withdb: func(db iface.ValidatorDB) error {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
settings := &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
return db.SaveProposerSettings(context.Background(), settings)
},
validatorRegistrationEnabled: true,
},
{
name: "No flags, but saved to database",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
return &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
},
withdb: func(db iface.ValidatorDB) error {
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
require.NoError(t, err)
settings := &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
},
}
return db.SaveProposerSettings(context.Background(), settings)
},
},
{
name: "No flags set means empty config",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return nil
},
wantErr: "",
skipDBSavedCheck: true,
},
{
name: "Bad File Path",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/bad-prepare-beacon-proposer-config.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return nil
},
wantErr: "failed to unmarshal yaml file",
},
{
name: "Both URL and Dir flags used resulting in error",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/good-prepare-beacon-proposer-config.json",
url: "./testdata/good-prepare-beacon-proposer-config.json",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return &proposer.Settings{}
},
wantInitErr: "cannot specify both",
},
{
name: "Bad Gas value in JSON",
args: args{
proposerSettingsFlagValues: &proposerSettingsFlag{
dir: "./testdata/bad-gas-value-proposer-settings.json",
url: "",
defaultfee: "",
},
},
want: func() *proposer.Settings {
return nil
},
wantErr: "failed to unmarshal yaml file",
},
}
for _, tt := range tests {
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("%v-minimal:%v", tt.name, isSlashingProtectionMinimal), func(t *testing.T) {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
if tt.args.proposerSettingsFlagValues.dir != "" {
set.String(flags.ProposerSettingsFlag.Name, tt.args.proposerSettingsFlagValues.dir, "")
require.NoError(t, set.Set(flags.ProposerSettingsFlag.Name, tt.args.proposerSettingsFlagValues.dir))
}
if tt.args.proposerSettingsFlagValues.url != "" {
content, err := os.ReadFile(tt.args.proposerSettingsFlagValues.url)
require.NoError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json")
_, err := fmt.Fprintf(w, "%s", content)
require.NoError(t, err)
}))
defer srv.Close()
set.String(flags.ProposerSettingsURLFlag.Name, tt.args.proposerSettingsFlagValues.url, "")
require.NoError(t, set.Set(flags.ProposerSettingsURLFlag.Name, srv.URL))
}
if tt.args.proposerSettingsFlagValues.defaultfee != "" {
set.String(flags.SuggestedFeeRecipientFlag.Name, tt.args.proposerSettingsFlagValues.defaultfee, "")
require.NoError(t, set.Set(flags.SuggestedFeeRecipientFlag.Name, tt.args.proposerSettingsFlagValues.defaultfee))
}
if tt.args.proposerSettingsFlagValues.defaultgas != "" {
set.String(flags.BuilderGasLimitFlag.Name, tt.args.proposerSettingsFlagValues.defaultgas, "")
require.NoError(t, set.Set(flags.BuilderGasLimitFlag.Name, tt.args.proposerSettingsFlagValues.defaultgas))
}
if tt.validatorRegistrationEnabled {
set.Bool(flags.EnableBuilderFlag.Name, true, "")
}
cliCtx := cli.NewContext(&app, set, nil)
validatorDB := dbTest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{}, isSlashingProtectionMinimal)
if tt.withdb != nil {
err := tt.withdb(validatorDB)
require.NoError(t, err)
}
loader, err := NewProposerSettingsLoader(
cliCtx,
validatorDB,
WithBuilderConfig(),
WithGasLimit(),
)
if tt.wantInitErr != "" {
require.ErrorContains(t, tt.wantInitErr, err)
return
} else {
require.NoError(t, err)
}
got, err := loader.Load(cliCtx)
if tt.wantErr != "" {
require.ErrorContains(t, tt.wantErr, err)
return
}
if tt.wantLog != "" {
assert.LogsContain(t, hook,
tt.wantLog,
)
}
w := tt.want()
require.DeepEqual(t, w, got)
if !tt.skipDBSavedCheck {
dbSettings, err := validatorDB.ProposerSettings(cliCtx.Context)
require.NoError(t, err)
require.DeepEqual(t, w, dbSettings)
}
})
}
}
}
func Test_ProposerSettingsLoaderWithOnlyBuilder_DoesNotSaveInDB(t *testing.T) {
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("minimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.Bool(flags.EnableBuilderFlag.Name, true, "")
cliCtx := cli.NewContext(&app, set, nil)
validatorDB := dbTest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{}, isSlashingProtectionMinimal)
loader, err := NewProposerSettingsLoader(
cliCtx,
validatorDB,
WithBuilderConfig(),
WithGasLimit(),
)
require.NoError(t, err)
got, err := loader.Load(cliCtx)
require.NoError(t, err)
_, err = validatorDB.ProposerSettings(cliCtx.Context)
require.ErrorContains(t, "no proposer settings found in bucket", err)
want := &proposer.Settings{
DefaultConfig: &proposer.Option{
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit),
Relays: nil,
},
},
}
require.DeepEqual(t, want, got)
})
}
}

View File

@@ -1,5 +1,4 @@
{
"proposer_config": {},
"default_config": {
"fee_recipient": "0xAe967917c465db8578ca9024c205720b1a3651A9",
"builder": {"enabled": true}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,7 @@
{
"proposer_config": {
"0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": {
"fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"
}
}
}

View File

@@ -1,4 +1,4 @@
package validator_service_config
package proposer
import (
"fmt"
@@ -6,51 +6,77 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/config"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
)
// ToSettings converts struct to ProposerSettings
func ToSettings(ps *validatorpb.ProposerSettingsPayload) (*ProposerSettings, error) {
settings := &ProposerSettings{}
if ps.ProposerConfig != nil {
settings.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption)
// SettingFromConsensus converts struct to Settings while verifying the fields
func SettingFromConsensus(ps *validatorpb.ProposerSettingsPayload) (*Settings, error) {
settings := &Settings{}
if ps.ProposerConfig != nil && len(ps.ProposerConfig) != 0 {
settings.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*Option)
for key, optionPayload := range ps.ProposerConfig {
if optionPayload.FeeRecipient == "" {
continue
}
b, err := hexutil.Decode(key)
decodedKey, err := hexutil.Decode(key)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("cannot decode public key %s", key))
}
p := &ProposerOption{
if len(decodedKey) != fieldparams.BLSPubkeyLength {
return nil, fmt.Errorf("%v is not a bls public key", key)
}
if err := verifyOption(key, optionPayload); err != nil {
return nil, err
}
p := &Option{
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress(optionPayload.FeeRecipient),
},
}
if optionPayload.Builder != nil {
p.BuilderConfig = ToBuilderConfig(optionPayload.Builder)
p.BuilderConfig = BuilderConfigFromConsensus(optionPayload.Builder)
}
settings.ProposeConfig[bytesutil.ToBytes48(b)] = p
settings.ProposeConfig[bytesutil.ToBytes48(decodedKey)] = p
}
}
if ps.DefaultConfig != nil {
d := &ProposerOption{}
d := &Option{}
if ps.DefaultConfig.FeeRecipient != "" {
if !common.IsHexAddress(ps.DefaultConfig.FeeRecipient) {
return nil, errors.New("default fee recipient is not a valid Ethereum address")
}
if err := config.WarnNonChecksummedAddress(ps.DefaultConfig.FeeRecipient); err != nil {
return nil, err
}
d.FeeRecipientConfig = &FeeRecipientConfig{
FeeRecipient: common.HexToAddress(ps.DefaultConfig.FeeRecipient),
}
}
if ps.DefaultConfig.Builder != nil {
d.BuilderConfig = ToBuilderConfig(ps.DefaultConfig.Builder)
d.BuilderConfig = BuilderConfigFromConsensus(ps.DefaultConfig.Builder)
}
settings.DefaultConfig = d
}
return settings, nil
}
func verifyOption(key string, option *validatorpb.ProposerOptionPayload) error {
if option == nil {
return fmt.Errorf("fee recipient is required for proposer %s", key)
}
if !common.IsHexAddress(option.FeeRecipient) {
return errors.New("fee recipient is not a valid Ethereum address")
}
if err := config.WarnNonChecksummedAddress(option.FeeRecipient); err != nil {
return err
}
return nil
}
// BuilderConfig is the struct representation of the JSON config file set in the validator through the CLI.
// GasLimit is a number set to help the network decide on the maximum gas in each block.
type BuilderConfig struct {
@@ -59,29 +85,28 @@ type BuilderConfig struct {
Relays []string `json:"relays,omitempty" yaml:"relays,omitempty"`
}
// ToBuilderConfig converts protobuf to a builder config used in inmemory storage
func ToBuilderConfig(from *validatorpb.BuilderConfig) *BuilderConfig {
// BuilderConfigFromConsensus converts protobuf to a builder config used in in-memory storage
func BuilderConfigFromConsensus(from *validatorpb.BuilderConfig) *BuilderConfig {
if from == nil {
return nil
}
config := &BuilderConfig{
c := &BuilderConfig{
Enabled: from.Enabled,
GasLimit: from.GasLimit,
}
if from.Relays != nil {
relays := make([]string, len(from.Relays))
copy(relays, from.Relays)
config.Relays = relays
c.Relays = relays
}
return config
return c
}
// ProposerSettings is a Prysm internal representation of the fee recipient config on the validator client.
// validatorpb.ProposerSettingsPayload maps to ProposerSettings on import through the CLI.
type ProposerSettings struct {
ProposeConfig map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption
DefaultConfig *ProposerOption
// Settings is a Prysm internal representation of the fee recipient config on the validator client.
// validatorpb.ProposerSettingsPayload maps to Settings on import through the CLI.
type Settings struct {
ProposeConfig map[[fieldparams.BLSPubkeyLength]byte]*Option
DefaultConfig *Option
}
// ShouldBeSaved goes through checks to see if the value should be saveable
@@ -89,12 +114,12 @@ type ProposerSettings struct {
// 1. settings are not nil
// 2. proposeconfig is not nil (this defines specific settings for each validator key), default config can be nil in this case and fall back to beacon node settings
// 3. defaultconfig is not nil, meaning it has at least fee recipient settings (this defines general settings for all validator keys but keys will use settings from propose config if available), propose config can be nil in this case
func (settings *ProposerSettings) ShouldBeSaved() bool {
return settings != nil && (settings.ProposeConfig != nil || settings.DefaultConfig != nil && settings.DefaultConfig.FeeRecipientConfig != nil)
func (ps *Settings) ShouldBeSaved() bool {
return ps != nil && (ps.ProposeConfig != nil || ps.DefaultConfig != nil && ps.DefaultConfig.FeeRecipientConfig != nil)
}
// ToPayload converts struct to ProposerSettingsPayload
func (ps *ProposerSettings) ToPayload() *validatorpb.ProposerSettingsPayload {
// ToConsensus converts struct to ProposerSettingsPayload
func (ps *Settings) ToConsensus() *validatorpb.ProposerSettingsPayload {
if ps == nil {
return nil
}
@@ -102,25 +127,11 @@ func (ps *ProposerSettings) ToPayload() *validatorpb.ProposerSettingsPayload {
if ps.ProposeConfig != nil {
payload.ProposerConfig = make(map[string]*validatorpb.ProposerOptionPayload)
for key, option := range ps.ProposeConfig {
p := &validatorpb.ProposerOptionPayload{}
if option.FeeRecipientConfig != nil {
p.FeeRecipient = option.FeeRecipientConfig.FeeRecipient.Hex()
}
if option.BuilderConfig != nil {
p.Builder = option.BuilderConfig.ToPayload()
}
payload.ProposerConfig[hexutil.Encode(key[:])] = p
payload.ProposerConfig[hexutil.Encode(key[:])] = option.ToConsensus()
}
}
if ps.DefaultConfig != nil {
p := &validatorpb.ProposerOptionPayload{}
if ps.DefaultConfig.FeeRecipientConfig != nil {
p.FeeRecipient = ps.DefaultConfig.FeeRecipientConfig.FeeRecipient.Hex()
}
if ps.DefaultConfig.BuilderConfig != nil {
p.Builder = ps.DefaultConfig.BuilderConfig.ToPayload()
}
payload.DefaultConfig = p
payload.DefaultConfig = ps.DefaultConfig.ToConsensus()
}
return payload
}
@@ -130,23 +141,52 @@ type FeeRecipientConfig struct {
FeeRecipient common.Address
}
// ProposerOption is a Prysm internal representation of the ProposerOptionPayload on the validator client in bytes format instead of hex.
type ProposerOption struct {
// Option is a Prysm internal representation of the ProposerOptionPayload on the validator client in bytes format instead of hex.
type Option struct {
FeeRecipientConfig *FeeRecipientConfig
BuilderConfig *BuilderConfig
}
// Clone creates a deep copy of proposer option
func (po *Option) Clone() *Option {
if po == nil {
return nil
}
p := &Option{}
if po.FeeRecipientConfig != nil {
p.FeeRecipientConfig = po.FeeRecipientConfig.Clone()
}
if po.BuilderConfig != nil {
p.BuilderConfig = po.BuilderConfig.Clone()
}
return p
}
func (po *Option) ToConsensus() *validatorpb.ProposerOptionPayload {
if po == nil {
return nil
}
p := &validatorpb.ProposerOptionPayload{}
if po.FeeRecipientConfig != nil {
p.FeeRecipient = po.FeeRecipientConfig.FeeRecipient.Hex()
}
if po.BuilderConfig != nil {
p.Builder = po.BuilderConfig.ToConsensus()
}
return p
}
// Clone creates a deep copy of the proposer settings
func (ps *ProposerSettings) Clone() *ProposerSettings {
func (ps *Settings) Clone() *Settings {
if ps == nil {
return nil
}
clone := &ProposerSettings{}
clone := &Settings{}
if ps.DefaultConfig != nil {
clone.DefaultConfig = ps.DefaultConfig.Clone()
}
if ps.ProposeConfig != nil {
clone.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption)
clone.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*Option)
for k, v := range ps.ProposeConfig {
keyCopy := k
valCopy := v.Clone()
@@ -170,46 +210,31 @@ func (bc *BuilderConfig) Clone() *BuilderConfig {
if bc == nil {
return nil
}
config := &BuilderConfig{}
config.Enabled = bc.Enabled
config.GasLimit = bc.GasLimit
c := &BuilderConfig{}
c.Enabled = bc.Enabled
c.GasLimit = bc.GasLimit
var relays []string
if bc.Relays != nil {
relays = make([]string, len(bc.Relays))
copy(relays, bc.Relays)
config.Relays = relays
c.Relays = relays
}
return config
return c
}
// ToPayload converts Builder Config to the protobuf object
func (bc *BuilderConfig) ToPayload() *validatorpb.BuilderConfig {
// ToConsensus converts Builder Config to the protobuf object
func (bc *BuilderConfig) ToConsensus() *validatorpb.BuilderConfig {
if bc == nil {
return nil
}
config := &validatorpb.BuilderConfig{}
config.Enabled = bc.Enabled
c := &validatorpb.BuilderConfig{}
c.Enabled = bc.Enabled
var relays []string
if bc.Relays != nil {
relays = make([]string, len(bc.Relays))
copy(relays, bc.Relays)
config.Relays = relays
c.Relays = relays
}
config.GasLimit = bc.GasLimit
return config
}
// Clone creates a deep copy of proposer option
func (po *ProposerOption) Clone() *ProposerOption {
if po == nil {
return nil
}
p := &ProposerOption{}
if po.FeeRecipientConfig != nil {
p.FeeRecipientConfig = po.FeeRecipientConfig.Clone()
}
if po.BuilderConfig != nil {
p.BuilderConfig = po.BuilderConfig.Clone()
}
return p
c.GasLimit = bc.GasLimit
return c
}

View File

@@ -1,4 +1,4 @@
package validator_service_config
package proposer
import (
"testing"
@@ -16,8 +16,8 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
key1hex := "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a"
key1, err := hexutil.Decode(key1hex)
require.NoError(t, err)
settings := &ProposerSettings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption{
settings := &Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
@@ -29,7 +29,7 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
},
},
},
DefaultConfig: &ProposerOption{
DefaultConfig: &Option{
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
},
@@ -59,15 +59,15 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
require.NotEqual(t, settings.DefaultConfig.BuilderConfig.GasLimit, clone.GasLimit)
})
t.Run("Happy Path ToBuilderConfig", func(t *testing.T) {
t.Run("Happy Path BuilderConfigFromConsensus", func(t *testing.T) {
clone := settings.DefaultConfig.BuilderConfig.Clone()
config := ToBuilderConfig(clone.ToPayload())
config := BuilderConfigFromConsensus(clone.ToConsensus())
require.DeepEqual(t, config.Relays, clone.Relays)
require.Equal(t, config.Enabled, clone.Enabled)
require.Equal(t, config.GasLimit, clone.GasLimit)
})
t.Run("To Payload and ToSettings", func(t *testing.T) {
payload := settings.ToPayload()
t.Run("To Payload and SettingFromConsensus", func(t *testing.T) {
payload := settings.ToConsensus()
option, ok := settings.ProposeConfig[bytesutil.ToBytes48(key1)]
require.Equal(t, true, ok)
fee := option.FeeRecipientConfig.FeeRecipient.Hex()
@@ -77,7 +77,7 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
require.Equal(t, settings.DefaultConfig.FeeRecipientConfig.FeeRecipient.Hex(), payload.DefaultConfig.FeeRecipient)
require.Equal(t, settings.DefaultConfig.BuilderConfig.Enabled, payload.DefaultConfig.Builder.Enabled)
potion.FeeRecipient = ""
newSettings, err := ToSettings(payload)
newSettings, err := SettingFromConsensus(payload)
require.NoError(t, err)
// when converting to settings if a fee recipient is empty string then it will be skipped
@@ -88,7 +88,7 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
// if fee recipient is set it will not skip
potion.FeeRecipient = fee
newSettings, err = ToSettings(payload)
newSettings, err = SettingFromConsensus(payload)
require.NoError(t, err)
noption, ok = newSettings.ProposeConfig[bytesutil.ToBytes48(key1)]
require.Equal(t, true, ok)
@@ -104,8 +104,8 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
key1, err := hexutil.Decode(key1hex)
require.NoError(t, err)
type fields struct {
ProposeConfig map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption
DefaultConfig *ProposerOption
ProposeConfig map[[fieldparams.BLSPubkeyLength]byte]*Option
DefaultConfig *Option
}
tests := []struct {
name string
@@ -115,7 +115,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
{
name: "Should be saved, proposeconfig populated and no default config",
fields: fields{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
@@ -135,7 +135,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
name: "Should be saved, default populated and no proposeconfig ",
fields: fields{
ProposeConfig: nil,
DefaultConfig: &ProposerOption{
DefaultConfig: &Option{
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
@@ -151,7 +151,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
{
name: "Should be saved, all populated",
fields: fields{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*ProposerOption{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*Option{
bytesutil.ToBytes48(key1): {
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
@@ -163,7 +163,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
},
},
},
DefaultConfig: &ProposerOption{
DefaultConfig: &Option{
FeeRecipientConfig: &FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
},
@@ -189,7 +189,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
name: "Should not be saved, builder data only",
fields: fields{
ProposeConfig: nil,
DefaultConfig: &ProposerOption{
DefaultConfig: &Option{
BuilderConfig: &BuilderConfig{
Enabled: true,
GasLimit: validator.Uint64(40000000),
@@ -202,7 +202,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
settings := &ProposerSettings{
settings := &Settings{
ProposeConfig: tt.fields.ProposeConfig,
DefaultConfig: tt.fields.DefaultConfig,
}

76
config/util.go Normal file
View File

@@ -0,0 +1,76 @@
package config
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/yaml"
)
func UnmarshalFromURL(ctx context.Context, from string, to interface{}) error {
u, err := url.ParseRequestURI(from)
if err != nil {
return err
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("invalid URL: %s", from)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, from, nil)
if err != nil {
return errors.Wrap(err, "failed to create http request")
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "failed to send http request")
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
log.WithError(err).Error("Failed to close response body")
}
}(resp.Body)
if resp.StatusCode != http.StatusOK {
return errors.Errorf("http request to %v failed with status code %d", from, resp.StatusCode)
}
if err := json.NewDecoder(resp.Body).Decode(&to); err != nil {
return errors.Wrap(err, "failed to decode http response")
}
return nil
}
func UnmarshalFromFile(from string, to interface{}) error {
cleanpath := filepath.Clean(from)
b, err := os.ReadFile(cleanpath)
if err != nil {
return errors.Wrap(err, "failed to open file")
}
if err := yaml.Unmarshal(b, to); err != nil {
return errors.Wrap(err, "failed to unmarshal yaml file")
}
return nil
}
func WarnNonChecksummedAddress(feeRecipient string) error {
mixedcaseAddress, err := common.NewMixedcaseAddressFromString(feeRecipient)
if err != nil {
return errors.Wrapf(err, "could not decode fee recipient %s", feeRecipient)
}
if !mixedcaseAddress.ValidChecksum() {
log.Warnf("Fee recipient %s is not a checksum Ethereum address. "+
"The checksummed address is %s and will be used as the fee recipient. "+
"We recommend using a mixed-case address (checksum) "+
"to prevent spelling mistakes in your fee recipient Ethereum address", feeRecipient, mixedcaseAddress.Address().Hex())
}
return nil
}

61
config/util_test.go Normal file
View File

@@ -0,0 +1,61 @@
package config
import (
"context"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/sirupsen/logrus/hooks/test"
)
func TestUnmarshalFromURL_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`{"key":"value"}`))
require.NoError(t, err)
}))
defer server.Close()
var result map[string]string
err := UnmarshalFromURL(context.Background(), server.URL, &result)
if err != nil {
t.Errorf("UnmarshalFromURL failed: %v", err)
}
if result["key"] != "value" {
t.Errorf("Expected value to be 'value', got '%s'", result["key"])
}
}
func TestUnmarshalFromFile_Success(t *testing.T) {
// Temporarily create a YAML file
tmpFile, err := os.CreateTemp(t.TempDir(), "example.*.yaml")
require.NoError(t, err)
defer require.NoError(t, os.Remove(tmpFile.Name())) // Clean up
content := []byte("key: value")
require.NoError(t, os.WriteFile(tmpFile.Name(), content, params.BeaconIoConfig().ReadWritePermissions))
require.NoError(t, tmpFile.Close())
var result map[string]string
require.NoError(t, UnmarshalFromFile(tmpFile.Name(), &result))
require.Equal(t, result["key"], "value")
}
func TestWarnNonChecksummedAddress(t *testing.T) {
logHook := test.NewGlobal()
address := "0x967646dCD8d34F4E02204faeDcbAe0cC96fB9245"
err := WarnNonChecksummedAddress(address)
require.NoError(t, err)
assert.LogsDoNotContain(t, logHook, "is not a checksum Ethereum address")
address = strings.ToLower("0x967646dCD8d34F4E02204faeDcbAe0cC96fB9244")
err = WarnNonChecksummedAddress(address)
require.NoError(t, err)
assert.LogsContain(t, logHook, "is not a checksum Ethereum address")
}

775
deps.bzl

File diff suppressed because it is too large Load Diff

59
go.mod
View File

@@ -6,8 +6,8 @@ toolchain go1.21.5
require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1
github.com/MariusVanDerWijden/FuzzyVM v0.0.0-20221202121132-bd37e8fb1d0d
github.com/MariusVanDerWijden/tx-fuzz v1.0.2
github.com/MariusVanDerWijden/FuzzyVM v0.0.0-20240209103030-ec53fa766bf8
github.com/MariusVanDerWijden/tx-fuzz v1.3.3-0.20240227085032-f70dd7c85c97
github.com/aristanetworks/goarista v0.0.0-20200805130819-fd197cf57d96
github.com/bazelbuild/rules_go v0.23.2
github.com/btcsuite/btcd/btcec/v2 v2.3.2
@@ -17,7 +17,7 @@ require (
github.com/dgraph-io/ristretto v0.0.4-0.20210318174700-74754f61e018
github.com/dustin/go-humanize v1.0.0
github.com/emicklei/dot v0.11.0
github.com/ethereum/go-ethereum v1.13.5-0.20231027145059-2d7dba024d76
github.com/ethereum/go-ethereum v1.13.5
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/fsnotify/fsnotify v1.6.0
github.com/ghodss/yaml v1.0.0
@@ -36,7 +36,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e
github.com/holiman/uint256 v1.2.3
github.com/holiman/uint256 v1.2.4
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279
github.com/ipfs/go-log/v2 v2.5.1
github.com/joonix/log v0.0.0-20200409080653-9c1d2ceb5f1d
@@ -60,8 +60,8 @@ require (
github.com/paulbellamy/ratecounter v0.2.0
github.com/pborman/uuid v1.2.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/client_model v0.4.0
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.5.0
github.com/prometheus/prom2json v1.3.0
github.com/prysmaticlabs/fastssz v0.0.0-20221107182844-78142813af44
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
@@ -77,7 +77,7 @@ require (
github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e
github.com/trailofbits/go-mutexasserts v0.0.0-20230328101604-8cdbc5f3d279
github.com/tyler-smith/go-bip39 v1.1.0
github.com/urfave/cli/v2 v2.25.7
github.com/urfave/cli/v2 v2.26.0
github.com/uudashr/gocognit v1.0.5
github.com/wealdtech/go-bytesutil v1.1.1
github.com/wealdtech/go-eth2-util v1.6.3
@@ -93,7 +93,7 @@ require (
golang.org/x/tools v0.16.0
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
google.golang.org/protobuf v1.31.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -104,28 +104,28 @@ require (
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/DataDog/zstd v1.5.2 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.7.0 // indirect
github.com/bits-and-blooms/bitset v1.11.0 // indirect
github.com/cespare/cp v1.1.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/deckarep/golang-set/v2 v2.5.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
@@ -136,7 +136,7 @@ require (
github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9 // indirect
github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/getsentry/sentry-go v0.25.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@@ -148,13 +148,13 @@ require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/graph-gophers/graphql-go v1.3.0 // indirect
github.com/hashicorp/go-bexpr v0.1.10 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/holiman/goevmlab v0.0.0-20230917164918-f3777d0b880b // indirect
github.com/holiman/goevmlab v0.0.0-20231201084119-c73b3c97929c // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/influxdata/influxdb-client-go/v2 v2.4.0 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c // indirect
@@ -164,7 +164,7 @@ require (
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/text v0.2.0 // indirect
@@ -181,8 +181,9 @@ require (
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/miekg/dns v1.1.56 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
@@ -209,33 +210,33 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/quic-go/quic-go v0.39.4 // indirect
github.com/quic-go/webtransport-go v0.6.0 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect
github.com/wealdtech/go-eth2-types/v2 v2.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/dig v1.17.1 // indirect
go.uber.org/fx v1.20.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
@@ -253,7 +254,7 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/validator/v10 v10.13.0
github.com/peterh/liner v1.2.0 // indirect
github.com/prysmaticlabs/gohashtree v0.0.4-beta

424
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ go_library(
deps = [
"//config/params:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)

View File

@@ -14,7 +14,13 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/config/params"
log "github.com/sirupsen/logrus"
)
type ObjType int
const (
Regular ObjType = iota
Directory
)
// ExpandPath given a string which may be a relative path.
@@ -85,7 +91,13 @@ func WriteFile(file string, data []byte) error {
if err != nil {
return err
}
if Exists(expanded) {
exists, err := Exists(expanded, Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists at path %s", expanded)
}
if exists {
info, err := os.Stat(expanded)
if err != nil {
return err
@@ -136,19 +148,28 @@ func HasReadWritePermissions(itemPath string) (bool, error) {
// Exists returns true if a file is not a directory and exists
// at the specified path.
func Exists(filename string) bool {
func Exists(filename string, objType ObjType) (bool, error) {
filePath, err := ExpandPath(filename)
if err != nil {
return false
return false, errors.Wrapf(err, "could not expend path of file %s", filename)
}
info, err := os.Stat(filePath)
if err != nil {
if !os.IsNotExist(err) {
log.WithError(err).Info("Checking for file existence returned an error")
if os.IsNotExist(err) {
return false, nil
}
return false
return false, errors.Wrapf(err, "could not get file info for file %s", filename)
}
return info != nil && !info.IsDir()
if info == nil {
return false, errors.New("file info is nil")
}
isDir := info.IsDir()
return objType == Directory && isDir || objType == Regular && !isDir, nil
}
// RecursiveFileFind returns true, and the path, if a file is not a directory and exists
@@ -183,6 +204,40 @@ func RecursiveFileFind(filename, dir string) (bool, string, error) {
return found, fpath, nil
}
// RecursiveDirFind searches for directory in a directory and its subdirectories.
func RecursiveDirFind(dirname, dir string) (bool, string, error) {
var (
found bool
fpath string
)
dir = filepath.Clean(dir)
found = false
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return errors.Wrapf(err, "error walking directory %s", dir)
}
// Checks if its a file and has the exact name as the dirname
// need to break the walk function by using a non-fatal error
if info.IsDir() && dirname == info.Name() {
found = true
fpath = path
return errStopWalk
}
// No errors or file found
return nil
})
if err != nil && err != errStopWalk {
return false, "", errors.Wrapf(err, "error walking directory %s", dir)
}
return found, fpath, nil
}
// ReadFileAsBytes expands a file name's absolute path and reads it as bytes from disk.
func ReadFileAsBytes(filename string) ([]byte, error) {
filePath, err := ExpandPath(filename)
@@ -194,7 +249,12 @@ func ReadFileAsBytes(filename string) ([]byte, error) {
// CopyFile copy a file from source to destination path.
func CopyFile(src, dst string) error {
if !Exists(src) {
exists, err := Exists(src, Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists at path %s", src)
}
if !exists {
return errors.New("source file does not exist at provided path")
}
f, err := os.Open(src) // #nosec G304

View File

@@ -125,8 +125,9 @@ func TestWriteFile_OK(t *testing.T) {
require.NoError(t, err)
someFileName := filepath.Join(dirName, "somefile.txt")
require.NoError(t, file.WriteFile(someFileName, []byte("hi")))
exists := file.Exists(someFileName)
assert.Equal(t, true, exists)
exists, err := file.Exists(someFileName, file.Regular)
require.NoError(t, err, "could not check if file exists")
assert.Equal(t, true, exists, "file does not exist")
}
func TestCopyFile(t *testing.T) {
@@ -176,8 +177,14 @@ func TestCopyDir(t *testing.T) {
require.NoError(t, os.MkdirAll(filepath.Join(tmpDir1, "subfolder2"), 0777))
for _, fd := range fds {
require.NoError(t, file.WriteFile(filepath.Join(tmpDir1, fd.path), fd.content))
assert.Equal(t, true, file.Exists(filepath.Join(tmpDir1, fd.path)))
assert.Equal(t, false, file.Exists(filepath.Join(tmpDir2, fd.path)))
exists, err := file.Exists(filepath.Join(tmpDir1, fd.path), file.Regular)
require.NoError(t, err, "could not check if file exists")
assert.Equal(t, true, exists, "file does not exist")
exists, err = file.Exists(filepath.Join(tmpDir2, fd.path), file.Regular)
require.NoError(t, err, "could not check if file exists")
assert.Equal(t, false, exists, "file does exist")
}
// Make sure that files are copied into non-existent directory only. If directory exists function exits.
@@ -186,7 +193,9 @@ func TestCopyDir(t *testing.T) {
// Now, all files should have been copied.
for _, fd := range fds {
assert.Equal(t, true, file.Exists(filepath.Join(tmpDir2, fd.path)))
exists, err := file.Exists(filepath.Join(tmpDir2, fd.path), file.Regular)
require.NoError(t, err, "could not check if file exists")
assert.Equal(t, true, exists)
assert.Equal(t, true, deepCompare(t, filepath.Join(tmpDir1, fd.path), filepath.Join(tmpDir2, fd.path)))
}
assert.Equal(t, true, file.DirsEqual(tmpDir1, tmpDir2))
@@ -238,6 +247,66 @@ func TestHashDir(t *testing.T) {
})
}
func TestExists(t *testing.T) {
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "testfile")
nonExistentTmpFile := filepath.Join(tmpDir, "nonexistent")
_, err := os.Create(tmpFile)
require.NoError(t, err, "could not create test file")
tests := []struct {
name string
itemPath string
itemType file.ObjType
want bool
}{
{
name: "file exists",
itemPath: tmpFile,
itemType: file.Regular,
want: true,
},
{
name: "dir exists",
itemPath: tmpDir,
itemType: file.Directory,
want: true,
},
{
name: "non-existent file",
itemPath: nonExistentTmpFile,
itemType: file.Regular,
want: false,
},
{
name: "non-existent dir",
itemPath: nonExistentTmpFile,
itemType: file.Directory,
want: false,
},
{
name: "file is dir",
itemPath: tmpDir,
itemType: file.Regular,
want: false,
},
{
name: "dir is file",
itemPath: tmpFile,
itemType: file.Directory,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exists, err := file.Exists(tt.itemPath, tt.itemType)
require.NoError(t, err, "could not check if file exists")
assert.Equal(t, tt.want, exists)
})
}
}
func TestHashFile(t *testing.T) {
originalData := []byte("test data")
originalChecksum := sha256.Sum256(originalData)
@@ -290,40 +359,43 @@ func TestDirFiles(t *testing.T) {
func TestRecursiveFileFind(t *testing.T) {
tmpDir, _ := tmpDirWithContentsForRecursiveFind(t)
/*
tmpDir
├── file3
├── subfolder1
│ └── subfolder11
│ └── file1
└── subfolder2
└── file2
*/
tests := []struct {
name string
root string
path string
found bool
}{
{
name: "file1",
root: tmpDir,
path: "subfolder1/subfolder11/file1",
found: true,
},
{
name: "file2",
root: tmpDir,
path: "subfolder2/file2",
found: true,
},
{
name: "file1",
root: tmpDir + "/subfolder1",
path: "subfolder11/file1",
found: true,
},
{
name: "file3",
root: tmpDir,
path: "file3",
found: true,
},
{
name: "file4",
root: tmpDir,
path: "",
found: false,
},
}
@@ -338,6 +410,61 @@ func TestRecursiveFileFind(t *testing.T) {
}
}
func TestRecursiveDirFind(t *testing.T) {
tmpDir, _ := tmpDirWithContentsForRecursiveFind(t)
/*
tmpDir
├── file3
├── subfolder1
│ └── subfolder11
│ └── file1
└── subfolder2
└── file2
*/
tests := []struct {
name string
root string
found bool
}{
{
name: "subfolder11",
root: tmpDir,
found: true,
},
{
name: "subfolder2",
root: tmpDir,
found: true,
},
{
name: "subfolder11",
root: tmpDir + "/subfolder1",
found: true,
},
{
name: "file3",
root: tmpDir,
found: false,
},
{
name: "file4",
root: tmpDir,
found: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, _, err := file.RecursiveDirFind(tt.name, tt.root)
require.NoError(t, err)
assert.DeepEqual(t, tt.found, found)
})
}
}
func deepCompare(t *testing.T, file1, file2 string) bool {
sf, err := os.Open(file1)
assert.NoError(t, err)

View File

@@ -10,6 +10,7 @@ go_library(
"//io/file:go_default_library",
"//io/prompt:go_default_library",
"@com_github_logrusorgru_aurora//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
],

View File

@@ -1,10 +1,11 @@
package tos
import (
"errors"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/logrusorgru/aurora"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/io/file"
@@ -37,8 +38,13 @@ var (
// VerifyTosAcceptedOrPrompt checks if Tos was accepted before or asks to accept.
func VerifyTosAcceptedOrPrompt(ctx *cli.Context) error {
tosFilePath := filepath.Join(ctx.String(cmd.DataDirFlag.Name), acceptTosFilename)
if file.Exists(tosFilePath) {
acceptTosFilePath := filepath.Join(ctx.String(cmd.DataDirFlag.Name), acceptTosFilename)
exists, err := file.Exists(acceptTosFilePath, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists: %s", acceptTosFilePath)
}
if exists {
return nil
}

View File

@@ -1,147 +0,0 @@
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..f15d38e
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,131 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+cc_library(
+ name = "zstd_cc",
+ srcs = [
+ "bitstream.h",
+ "clevels.h",
+ "compiler.h",
+ "cover.c",
+ "cover.h",
+ "cpu.h",
+ "debug.c",
+ "debug.h",
+ "divsufsort.c",
+ "divsufsort.h",
+ "entropy_common.c",
+ "error_private.c",
+ "error_private.h",
+ "fastcover.c",
+ "fse.h",
+ "fse_compress.c",
+ "fse_decompress.c",
+ "hist.c",
+ "hist.h",
+ "huf.h",
+ "huf_compress.c",
+ "huf_decompress.c",
+ "huf_decompress_amd64.S",
+ "mem.h",
+ "pool.c",
+ "pool.h",
+ "portability_macros.h",
+ "threading.c",
+ "threading.h",
+ "xxhash.c",
+ "xxhash.h",
+ "zbuff.h",
+ "zbuff_common.c",
+ "zbuff_compress.c",
+ "zbuff_decompress.c",
+ "zdict.c",
+ "zdict.h",
+ "zstd.h",
+ "zstd_common.c",
+ "zstd_compress.c",
+ "zstd_compress_internal.h",
+ "zstd_compress_literals.c",
+ "zstd_compress_literals.h",
+ "zstd_compress_sequences.c",
+ "zstd_compress_sequences.h",
+ "zstd_compress_superblock.c",
+ "zstd_compress_superblock.h",
+ "zstd_cwksp.h",
+ "zstd_ddict.c",
+ "zstd_ddict.h",
+ "zstd_decompress.c",
+ "zstd_decompress_block.c",
+ "zstd_decompress_block.h",
+ "zstd_decompress_internal.h",
+ "zstd_deps.h",
+ "zstd_double_fast.c",
+ "zstd_double_fast.h",
+ "zstd_errors.h",
+ "zstd_fast.c",
+ "zstd_fast.h",
+ "zstd_internal.h",
+ "zstd_lazy.c",
+ "zstd_lazy.h",
+ "zstd_ldm.c",
+ "zstd_ldm.h",
+ "zstd_ldm_geartab.h",
+ "zstd_legacy.h",
+ "zstd_opt.c",
+ "zstd_opt.h",
+ "zstd_trace.h",
+ "zstd_v01.c",
+ "zstd_v01.h",
+ "zstd_v02.c",
+ "zstd_v02.h",
+ "zstd_v03.c",
+ "zstd_v03.h",
+ "zstd_v04.c",
+ "zstd_v04.h",
+ "zstd_v05.c",
+ "zstd_v05.h",
+ "zstd_v06.c",
+ "zstd_v06.h",
+ "zstd_v07.c",
+ "zstd_v07.h",
+ "zstdmt_compress.c",
+ "zstdmt_compress.h",
+ ],
+ copts = ["-DZSTD_LEGACY_SUPPORT=4"],
+
+)
+go_library(
+ name = "zstd",
+ srcs = [
+ "errors.go",
+ "zstd.go",
+ "zstd_bulk.go",
+ "zstd_ctx.go",
+ "zstd_stream.go",
+ "zstd.h",
+ ],
+ cgo = True,
+ cdeps = [":zstd_cc"],
+ copts = ["-DZSTD_LEGACY_SUPPORT=4"],
+ importpath = "github.com/DataDog/zstd",
+ visibility = ["//visibility:public"],
+)
+
+alias(
+ name = "go_default_library",
+ actual = ":zstd",
+ visibility = ["//visibility:public"],
+)
+
+go_test(
+ name = "zstd_test",
+ srcs = [
+ "errors_test.go",
+ "helpers_test.go",
+ "zstd_bullk_test.go",
+ "zstd_ctx_test.go",
+ "zstd_stream_test.go",
+ "zstd_test.go",
+ ],
+ embed = [":zstd"],
+)
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..dea5b27
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,2 @@
+# DO NOT EDIT: automatically generated WORKSPACE file for go_repository rule
+workspace(name = "com_github_datadog_zstd")
--
2.34.1

View File

@@ -156,7 +156,13 @@ func encrypt(cliCtx *cli.Context) error {
if err != nil {
return errors.Wrapf(err, "could not expand path: %s", outputPath)
}
if file.Exists(fullPath) {
exists, err := file.Exists(fullPath, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists: %s", fullPath)
}
if exists {
response, err := prompt.ValidatePrompt(
os.Stdin,
fmt.Sprintf("file at path %s already exists, are you sure you want to overwrite it? [y/n]", fullPath),

View File

@@ -47,7 +47,12 @@ func zipKeystoresToOutputDir(keystoresToBackup []*keymanager.Keystore, outputDir
// Marshal and zip all keystore files together and write the zip file
// to the specified output directory.
archivePath := filepath.Join(outputDir, ArchiveFilename)
if file.Exists(archivePath) {
exists, err := file.Exists(archivePath, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists: %s", archivePath)
}
if exists {
return errors.Errorf("Zip file already exists in directory: %s", archivePath)
}
// We create a new file to store our backup.zip.

View File

@@ -226,7 +226,13 @@ func importPrivateKeyAsAccount(ctx context.Context, wallet *wallet.Wallet, impor
if err != nil {
return errors.Wrapf(err, "could not expand file path for %s", privKeyFile)
}
if !file.Exists(fullPath) {
exists, err := file.Exists(fullPath, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if file exists: %s", fullPath)
}
if !exists {
return fmt.Errorf("file %s does not exist", fullPath)
}
privKeyHex, err := os.ReadFile(fullPath) // #nosec G304

View File

@@ -10,7 +10,7 @@ go_library(
"//validator:__subpackages__",
],
deps = [
"//config/validator/service:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//validator/accounts/iface:go_default_library",

View File

@@ -7,7 +7,7 @@ import (
"sync"
"time"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/iface"
@@ -89,7 +89,7 @@ func (_ *Wallet) InitializeKeymanager(_ context.Context, _ iface.InitKeymanagerC
type Validator struct {
Km keymanager.IKeymanager
proposerSettings *validatorserviceconfig.ProposerSettings
proposerSettings *proposer.Settings
}
func (_ *Validator) LogSubmittedSyncCommitteeMessages() {}
@@ -203,12 +203,12 @@ func (_ *Validator) SignValidatorRegistrationRequest(_ context.Context, _ iface2
}
// ProposerSettings for mocking
func (m *Validator) ProposerSettings() *validatorserviceconfig.ProposerSettings {
func (m *Validator) ProposerSettings() *proposer.Settings {
return m.proposerSettings
}
// SetProposerSettings for mocking
func (m *Validator) SetProposerSettings(_ context.Context, settings *validatorserviceconfig.ProposerSettings) error {
func (m *Validator) SetProposerSettings(_ context.Context, settings *proposer.Settings) error {
m.proposerSettings = settings
return nil
}

View File

@@ -378,7 +378,10 @@ func (w *Wallet) WriteFileAtPath(_ context.Context, filePath, fileName string, d
}
}
fullPath := filepath.Join(accountPath, fileName)
existedPreviously := file.Exists(fullPath)
existedPreviously, err := file.Exists(fullPath, file.Regular)
if err != nil {
return false, errors.Wrapf(err, "could not check if file exists: %s", fullPath)
}
if err := file.WriteFile(fullPath, data); err != nil {
return false, errors.Wrapf(err, "could not write %s", filePath)
}
@@ -439,7 +442,12 @@ func (w *Wallet) FileNameAtPath(_ context.Context, filePath, fileName string) (s
// for reading if it exists at the wallet path.
func (w *Wallet) ReadKeymanagerConfigFromDisk(_ context.Context) (io.ReadCloser, error) {
configFilePath := filepath.Join(w.accountsPath, KeymanagerConfigFileName)
if !file.Exists(configFilePath) {
exists, err := file.Exists(configFilePath, file.Regular)
if err != nil {
return nil, errors.Wrapf(err, "could not check if file exists: %s", configFilePath)
}
if !exists {
return nil, fmt.Errorf("no keymanager config file found at path: %s", w.accountsPath)
}
w.configFilePath = configFilePath

View File

@@ -5,13 +5,11 @@ go_library(
srcs = [
"aggregate.go",
"attest.go",
"attest_protect.go",
"key_reload.go",
"log.go",
"metrics.go",
"multiple_endpoints_grpc_resolver.go",
"propose.go",
"propose_protect.go",
"registration.go",
"runner.go",
"service.go",
@@ -36,7 +34,7 @@ go_library(
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/validator/service:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
@@ -49,7 +47,6 @@ go_library(
"//monitoring/tracing:go_default_library",
"//network/httputil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/slashings:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/version:go_default_library",
"//time:go_default_library",
@@ -62,7 +59,7 @@ go_library(
"//validator/client/node-client-factory:go_default_library",
"//validator/client/validator-client-factory:go_default_library",
"//validator/db:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/db/common:go_default_library",
"//validator/graffiti:go_default_library",
"//validator/helpers:go_default_library",
"//validator/keymanager:go_default_library",
@@ -98,14 +95,12 @@ go_library(
go_test(
name = "go_default_test",
size = "small",
size = "medium",
srcs = [
"aggregate_test.go",
"attest_protect_test.go",
"attest_test.go",
"key_reload_test.go",
"metrics_test.go",
"propose_protect_test.go",
"propose_test.go",
"registration_test.go",
"runner_test.go",
@@ -126,7 +121,7 @@ go_test(
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/validator/service:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/blocks/testing:go_default_library",
"//consensus-types/interfaces:go_default_library",
@@ -153,11 +148,11 @@ go_test(
"//validator/client/testutil:go_default_library",
"//validator/db/testing:go_default_library",
"//validator/graffiti:go_default_library",
"//validator/helpers:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
"//validator/slashing-protection-history:go_default_library",
"//validator/testing:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",

View File

@@ -3,6 +3,7 @@ package client
import (
"context"
"errors"
"fmt"
"testing"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
@@ -23,208 +24,235 @@ import (
)
func TestSubmitAggregateAndProof_GetDutiesRequestFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, _, validatorKey, finish := setup(t)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, _, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
require.LogsContain(t, hook, "Could not fetch validator assignment")
require.LogsContain(t, hook, "Could not fetch validator assignment")
})
}
}
func TestSubmitAggregateAndProof_SignFails(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
},
},
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
},
},
}
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: nil}, errors.New("bad domain root"))
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
})
}
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: nil}, errors.New("bad domain root"))
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
}
func TestSubmitAggregateAndProof_Ok(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
},
},
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
},
},
}
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedAggregateSubmitRequest{}),
).Return(&ethpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil)
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
})
}
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedAggregateSubmitRequest{}),
).Return(&ethpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil)
validator.SubmitAggregateAndProof(context.Background(), 0, pubKey)
}
func TestSubmitAggregateAndProof_Distributed(t *testing.T) {
validatorIdx := primitives.ValidatorIndex(123)
slot := primitives.Slot(456)
ctx := context.Background()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
validator, m, validatorKey, finish := setup(t)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
ValidatorIndex: validatorIdx,
AttesterSlot: slot,
},
},
}
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.DutiesResponse{
CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
validator.distributed = true
validator.attSelections = make(map[attSelectionKey]iface.BeaconCommitteeSelection)
validator.attSelections[attSelectionKey{
slot: slot,
index: 123,
}] = iface.BeaconCommitteeSelection{
SelectionProof: make([]byte, 96),
Slot: slot,
ValidatorIndex: validatorIdx,
AttesterSlot: slot,
},
},
}
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedAggregateSubmitRequest{}),
).Return(&ethpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil)
validator.SubmitAggregateAndProof(ctx, slot, pubKey)
})
}
validator.distributed = true
validator.attSelections = make(map[attSelectionKey]iface.BeaconCommitteeSelection)
validator.attSelections[attSelectionKey{
slot: slot,
index: 123,
}] = iface.BeaconCommitteeSelection{
SelectionProof: make([]byte, 96),
Slot: slot,
ValidatorIndex: validatorIdx,
}
m.validatorClient.EXPECT().SubmitAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AggregateSelectionRequest{}),
).Return(&ethpb.AggregateSelectionResponse{
AggregateAndProof: &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: make([]byte, 1),
}),
SelectionProof: make([]byte, 96),
},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
m.validatorClient.EXPECT().SubmitSignedAggregateSelectionProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedAggregateSubmitRequest{}),
).Return(&ethpb.SignedAggregateSubmitResponse{AttestationDataRoot: make([]byte, 32)}, nil)
validator.SubmitAggregateAndProof(ctx, slot, pubKey)
}
func TestWaitForSlotTwoThird_WaitCorrectly(t *testing.T) {
validator, _, _, finish := setup(t)
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = uint64(currentTime.Unix()) - uint64(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot))
oneThird := slots.DivideSlotBy(3 /* one third of slot duration */)
timeToSleep := oneThird + oneThird
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, _, _, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = uint64(currentTime.Unix()) - uint64(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot))
oneThird := slots.DivideSlotBy(3 /* one third of slot duration */)
timeToSleep := oneThird + oneThird
twoThirdTime := currentTime.Add(timeToSleep)
validator.waitToSlotTwoThirds(context.Background(), numOfSlots)
currentTime = time.Now()
assert.Equal(t, twoThirdTime.Unix(), currentTime.Unix())
twoThirdTime := currentTime.Add(timeToSleep)
validator.waitToSlotTwoThirds(context.Background(), numOfSlots)
currentTime = time.Now()
assert.Equal(t, twoThirdTime.Unix(), currentTime.Unix())
})
}
}
func TestWaitForSlotTwoThird_DoneContext_ReturnsImmediately(t *testing.T) {
validator, _, _, finish := setup(t)
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = uint64(currentTime.Unix()) - uint64(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot))
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, _, _, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = uint64(currentTime.Unix()) - uint64(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot))
expectedTime := time.Now()
ctx, cancel := context.WithCancel(context.Background())
cancel()
validator.waitToSlotTwoThirds(ctx, numOfSlots)
currentTime = time.Now()
assert.Equal(t, expectedTime.Unix(), currentTime.Unix())
expectedTime := time.Now()
ctx, cancel := context.WithCancel(context.Background())
cancel()
validator.waitToSlotTwoThirds(ctx, numOfSlots)
currentTime = time.Now()
assert.Equal(t, expectedTime.Unix(), currentTime.Unix())
})
}
}
func TestAggregateAndProofSignature_CanSignValidSignature(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
&ethpb.DomainRequest{Epoch: 0, Domain: params.BeaconConfig().DomainAggregateAndProof[:]},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
&ethpb.DomainRequest{Epoch: 0, Domain: params.BeaconConfig().DomainAggregateAndProof[:]},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
agg := &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: bitfield.NewBitlist(1),
}),
SelectionProof: make([]byte, 96),
agg := &ethpb.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&ethpb.Attestation{
AggregationBits: bitfield.NewBitlist(1),
}),
SelectionProof: make([]byte, 96),
}
sig, err := validator.aggregateAndProofSig(context.Background(), pubKey, agg, 0 /* slot */)
require.NoError(t, err)
_, err = bls.SignatureFromBytes(sig)
require.NoError(t, err)
})
}
sig, err := validator.aggregateAndProofSig(context.Background(), pubKey, agg, 0 /* slot */)
require.NoError(t, err)
_, err = bls.SignatureFromBytes(sig)
require.NoError(t, err)
}

View File

@@ -26,6 +26,8 @@ import (
"go.opencensus.io/trace"
)
var failedAttLocalProtectionErr = "attempted to make slashable attestation, rejected by local slashing protection"
// SubmitAttestation completes the validator client's attester responsibility at a given slot.
// It fetches the latest beacon block head along with the latest canonical beacon state
// information in order to sign the block and include information about the validator's
@@ -135,7 +137,7 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
// Set the signature of the attestation and send it out to the beacon node.
indexedAtt.Signature = sig
if err := v.slashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot); err != nil {
if err := v.db.SlashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil {
log.WithError(err).Error("Failed attestation slashing protection check")
log.WithFields(
attestationLogFields(pubKey, indexedAtt),

View File

@@ -1,86 +0,0 @@
package client
import (
"context"
"encoding/hex"
"fmt"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/slashings"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
"go.opencensus.io/trace"
)
var failedAttLocalProtectionErr = "attempted to make slashable attestation, rejected by local slashing protection"
// Checks if an attestation is slashable by comparing it with the attesting
// history for the given public key in our DB. If it is not, we then update the history
// with new values and save it to the database.
func (v *validator) slashableAttestationCheck(
ctx context.Context,
indexedAtt *ethpb.IndexedAttestation,
pubKey [fieldparams.BLSPubkeyLength]byte,
signingRoot32 [32]byte,
) error {
ctx, span := trace.StartSpan(ctx, "validator.postAttSignUpdate")
defer span.End()
signingRoot := signingRoot32[:]
// Based on EIP3076, validator should refuse to sign any attestation with source epoch less
// than the minimum source epoch present in that signers attestations.
lowestSourceEpoch, exists, err := v.db.LowestSignedSourceEpoch(ctx, pubKey)
if err != nil {
return err
}
if exists && indexedAtt.Data.Source.Epoch < lowestSourceEpoch {
return fmt.Errorf(
"could not sign attestation lower than lowest source epoch in db, %d < %d",
indexedAtt.Data.Source.Epoch,
lowestSourceEpoch,
)
}
existingSigningRoot, err := v.db.SigningRootAtTargetEpoch(ctx, pubKey, indexedAtt.Data.Target.Epoch)
if err != nil {
return err
}
signingRootsDiffer := slashings.SigningRootsDiffer(existingSigningRoot, signingRoot)
// Based on EIP3076, validator should refuse to sign any attestation with target epoch less
// than or equal to the minimum target epoch present in that signers attestations.
lowestTargetEpoch, exists, err := v.db.LowestSignedTargetEpoch(ctx, pubKey)
if err != nil {
return err
}
if signingRootsDiffer && exists && indexedAtt.Data.Target.Epoch <= lowestTargetEpoch {
return fmt.Errorf(
"could not sign attestation lower than or equal to lowest target epoch in db, %d <= %d",
indexedAtt.Data.Target.Epoch,
lowestTargetEpoch,
)
}
fmtKey := "0x" + hex.EncodeToString(pubKey[:])
slashingKind, err := v.db.CheckSlashableAttestation(ctx, pubKey, signingRoot, indexedAtt)
if err != nil {
if v.emitAccountMetrics {
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
switch slashingKind {
case kv.DoubleVote:
log.Warn("Attestation is slashable as it is a double vote")
case kv.SurroundingVote:
log.Warn("Attestation is slashable as it is surrounding a previous attestation")
case kv.SurroundedVote:
log.Warn("Attestation is slashable as it is surrounded by a previous attestation")
}
return errors.Wrap(err, failedAttLocalProtectionErr)
}
if err := v.db.SaveAttestationForPubKey(ctx, pubKey, signingRoot32, indexedAtt); err != nil {
return errors.Wrap(err, "could not save attestation history for validator public key")
}
return nil
}

View File

@@ -1,147 +0,0 @@
package client
import (
"context"
"testing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"go.uber.org/mock/gomock"
)
func Test_slashableAttestationCheck(t *testing.T) {
validator, _, validatorKey, finish := setup(t)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: bytesutil.PadTo([]byte("great block"), 32),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: bytesutil.PadTo([]byte("good source"), 32),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: bytesutil.PadTo([]byte("good target"), 32),
},
},
}
err := validator.slashableAttestationCheck(context.Background(), att, pubKey, [32]byte{1})
require.NoError(t, err, "Expected allowed attestation not to throw error")
}
func Test_slashableAttestationCheck_UpdatesLowestSignedEpochs(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
ctx := context.Background()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: bytesutil.PadTo([]byte("great block"), 32),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: bytesutil.PadTo([]byte("good source"), 32),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: bytesutil.PadTo([]byte("good target"), 32),
},
},
}
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
&ethpb.DomainRequest{Epoch: 10, Domain: []byte{1, 0, 0, 0}},
).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
_, sr, err := validator.getDomainAndSigningRoot(ctx, att.Data)
require.NoError(t, err)
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, sr)
require.NoError(t, err)
differentSigningRoot := [32]byte{2}
err = validator.slashableAttestationCheck(context.Background(), att, pubKey, differentSigningRoot)
require.ErrorContains(t, "could not sign attestation", err)
e, exists, err := validator.db.LowestSignedSourceEpoch(context.Background(), pubKey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, primitives.Epoch(4), e)
e, exists, err = validator.db.LowestSignedTargetEpoch(context.Background(), pubKey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, primitives.Epoch(10), e)
}
func Test_slashableAttestationCheck_OK(t *testing.T) {
ctx := context.Background()
validator, _, _, finish := setup(t)
defer finish()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: []byte("great block"),
Source: &ethpb.Checkpoint{
Epoch: 4,
Root: []byte("good source"),
},
Target: &ethpb.Checkpoint{
Epoch: 10,
Root: []byte("good target"),
},
},
}
sr := [32]byte{1}
fakePubkey := bytesutil.ToBytes48([]byte("test"))
err := validator.slashableAttestationCheck(ctx, att, fakePubkey, sr)
require.NoError(t, err, "Expected allowed attestation not to throw error")
}
func Test_slashableAttestationCheck_GenesisEpoch(t *testing.T) {
ctx := context.Background()
validator, _, _, finish := setup(t)
defer finish()
att := &ethpb.IndexedAttestation{
AttestingIndices: []uint64{1, 2},
Data: &ethpb.AttestationData{
Slot: 5,
CommitteeIndex: 2,
BeaconBlockRoot: bytesutil.PadTo([]byte("great block root"), 32),
Source: &ethpb.Checkpoint{
Epoch: 0,
Root: bytesutil.PadTo([]byte("great root"), 32),
},
Target: &ethpb.Checkpoint{
Epoch: 0,
Root: bytesutil.PadTo([]byte("great root"), 32),
},
},
}
fakePubkey := bytesutil.ToBytes48([]byte("test"))
err := validator.slashableAttestationCheck(ctx, att, fakePubkey, [32]byte{})
require.NoError(t, err, "Expected allowed attestation not to throw error")
e, exists, err := validator.db.LowestSignedSourceEpoch(context.Background(), fakePubkey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, primitives.Epoch(0), e)
e, exists, err = validator.db.LowestSignedTargetEpoch(context.Background(), fakePubkey)
require.NoError(t, err)
require.Equal(t, true, exists)
require.Equal(t, primitives.Epoch(0), e)
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ go_library(
"index.go",
"json_rest_handler.go",
"log.go",
"metrics.go",
"prepare_beacon_proposer.go",
"propose_attestation.go",
"propose_beacon_block.go",
@@ -60,6 +61,8 @@ go_library(
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",

View File

@@ -50,32 +50,38 @@ func NewBeaconApiValidatorClient(jsonRestHandler JsonRestHandler, opts ...Valida
}
func (c *beaconApiValidatorClient) GetDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
return c.getDuties(ctx, in)
return wrapInMetrics[*ethpb.DutiesResponse]("GetDuties", func() (*ethpb.DutiesResponse, error) {
return c.getDuties(ctx, in)
})
}
func (c *beaconApiValidatorClient) CheckDoppelGanger(ctx context.Context, in *ethpb.DoppelGangerRequest) (*ethpb.DoppelGangerResponse, error) {
return c.checkDoppelGanger(ctx, in)
return wrapInMetrics[*ethpb.DoppelGangerResponse]("CheckDoppelGanger", func() (*ethpb.DoppelGangerResponse, error) {
return c.checkDoppelGanger(ctx, in)
})
}
func (c *beaconApiValidatorClient) DomainData(ctx context.Context, in *ethpb.DomainRequest) (*ethpb.DomainResponse, error) {
if len(in.Domain) != 4 {
return nil, errors.Errorf("invalid domain type: %s", hexutil.Encode(in.Domain))
}
domainType := bytesutil.ToBytes4(in.Domain)
return c.getDomainData(ctx, in.Epoch, domainType)
return wrapInMetrics[*ethpb.DomainResponse]("DomainData", func() (*ethpb.DomainResponse, error) {
return c.getDomainData(ctx, in.Epoch, domainType)
})
}
func (c *beaconApiValidatorClient) GetAttestationData(ctx context.Context, in *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error) {
if in == nil {
return nil, errors.New("GetAttestationData received nil argument `in`")
}
return c.getAttestationData(ctx, in.Slot, in.CommitteeIndex)
return wrapInMetrics[*ethpb.AttestationData]("GetAttestationData", func() (*ethpb.AttestationData, error) {
return c.getAttestationData(ctx, in.Slot, in.CommitteeIndex)
})
}
func (c *beaconApiValidatorClient) GetBeaconBlock(ctx context.Context, in *ethpb.BlockRequest) (*ethpb.GenericBeaconBlock, error) {
return c.getBeaconBlock(ctx, in.Slot, in.RandaoReveal, in.Graffiti)
return wrapInMetrics[*ethpb.GenericBeaconBlock]("GetBeaconBlock", func() (*ethpb.GenericBeaconBlock, error) {
return c.getBeaconBlock(ctx, in.Slot, in.RandaoReveal, in.Graffiti)
})
}
func (c *beaconApiValidatorClient) GetFeeRecipientByPubKey(_ context.Context, _ *ethpb.FeeRecipientByPubKeyRequest) (*ethpb.FeeRecipientByPubKeyResponse, error) {
@@ -83,35 +89,51 @@ func (c *beaconApiValidatorClient) GetFeeRecipientByPubKey(_ context.Context, _
}
func (c *beaconApiValidatorClient) GetSyncCommitteeContribution(ctx context.Context, in *ethpb.SyncCommitteeContributionRequest) (*ethpb.SyncCommitteeContribution, error) {
return c.getSyncCommitteeContribution(ctx, in)
return wrapInMetrics[*ethpb.SyncCommitteeContribution]("GetSyncCommitteeContribution", func() (*ethpb.SyncCommitteeContribution, error) {
return c.getSyncCommitteeContribution(ctx, in)
})
}
func (c *beaconApiValidatorClient) GetSyncMessageBlockRoot(ctx context.Context, _ *empty.Empty) (*ethpb.SyncMessageBlockRootResponse, error) {
return c.getSyncMessageBlockRoot(ctx)
return wrapInMetrics[*ethpb.SyncMessageBlockRootResponse]("GetSyncMessageBlockRoot", func() (*ethpb.SyncMessageBlockRootResponse, error) {
return c.getSyncMessageBlockRoot(ctx)
})
}
func (c *beaconApiValidatorClient) GetSyncSubcommitteeIndex(ctx context.Context, in *ethpb.SyncSubcommitteeIndexRequest) (*ethpb.SyncSubcommitteeIndexResponse, error) {
return c.getSyncSubcommitteeIndex(ctx, in)
return wrapInMetrics[*ethpb.SyncSubcommitteeIndexResponse]("GetSyncSubcommitteeIndex", func() (*ethpb.SyncSubcommitteeIndexResponse, error) {
return c.getSyncSubcommitteeIndex(ctx, in)
})
}
func (c *beaconApiValidatorClient) MultipleValidatorStatus(ctx context.Context, in *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) {
return c.multipleValidatorStatus(ctx, in)
return wrapInMetrics[*ethpb.MultipleValidatorStatusResponse]("MultipleValidatorStatus", func() (*ethpb.MultipleValidatorStatusResponse, error) {
return c.multipleValidatorStatus(ctx, in)
})
}
func (c *beaconApiValidatorClient) PrepareBeaconProposer(ctx context.Context, in *ethpb.PrepareBeaconProposerRequest) (*empty.Empty, error) {
return new(empty.Empty), c.prepareBeaconProposer(ctx, in.Recipients)
return wrapInMetrics[*empty.Empty]("PrepareBeaconProposer", func() (*empty.Empty, error) {
return new(empty.Empty), c.prepareBeaconProposer(ctx, in.Recipients)
})
}
func (c *beaconApiValidatorClient) ProposeAttestation(ctx context.Context, in *ethpb.Attestation) (*ethpb.AttestResponse, error) {
return c.proposeAttestation(ctx, in)
return wrapInMetrics[*ethpb.AttestResponse]("ProposeAttestation", func() (*ethpb.AttestResponse, error) {
return c.proposeAttestation(ctx, in)
})
}
func (c *beaconApiValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
return c.proposeBeaconBlock(ctx, in)
return wrapInMetrics[*ethpb.ProposeResponse]("ProposeBeaconBlock", func() (*ethpb.ProposeResponse, error) {
return c.proposeBeaconBlock(ctx, in)
})
}
func (c *beaconApiValidatorClient) ProposeExit(ctx context.Context, in *ethpb.SignedVoluntaryExit) (*ethpb.ProposeExitResponse, error) {
return c.proposeExit(ctx, in)
return wrapInMetrics[*ethpb.ProposeExitResponse]("ProposeExit", func() (*ethpb.ProposeExitResponse, error) {
return c.proposeExit(ctx, in)
})
}
func (c *beaconApiValidatorClient) StreamSlots(ctx context.Context, in *ethpb.StreamSlotsRequest) (ethpb.BeaconNodeValidator_StreamSlotsClient, error) {
@@ -123,31 +145,45 @@ func (c *beaconApiValidatorClient) StreamBlocksAltair(ctx context.Context, in *e
}
func (c *beaconApiValidatorClient) SubmitAggregateSelectionProof(ctx context.Context, in *ethpb.AggregateSelectionRequest) (*ethpb.AggregateSelectionResponse, error) {
return c.submitAggregateSelectionProof(ctx, in)
return wrapInMetrics[*ethpb.AggregateSelectionResponse]("SubmitAggregateSelectionProof", func() (*ethpb.AggregateSelectionResponse, error) {
return c.submitAggregateSelectionProof(ctx, in)
})
}
func (c *beaconApiValidatorClient) SubmitSignedAggregateSelectionProof(ctx context.Context, in *ethpb.SignedAggregateSubmitRequest) (*ethpb.SignedAggregateSubmitResponse, error) {
return c.submitSignedAggregateSelectionProof(ctx, in)
return wrapInMetrics[*ethpb.SignedAggregateSubmitResponse]("SubmitSignedAggregateSelectionProof", func() (*ethpb.SignedAggregateSubmitResponse, error) {
return c.submitSignedAggregateSelectionProof(ctx, in)
})
}
func (c *beaconApiValidatorClient) SubmitSignedContributionAndProof(ctx context.Context, in *ethpb.SignedContributionAndProof) (*empty.Empty, error) {
return new(empty.Empty), c.submitSignedContributionAndProof(ctx, in)
return wrapInMetrics[*empty.Empty]("SubmitSignedContributionAndProof", func() (*empty.Empty, error) {
return new(empty.Empty), c.submitSignedContributionAndProof(ctx, in)
})
}
func (c *beaconApiValidatorClient) SubmitSyncMessage(ctx context.Context, in *ethpb.SyncCommitteeMessage) (*empty.Empty, error) {
return new(empty.Empty), c.submitSyncMessage(ctx, in)
return wrapInMetrics[*empty.Empty]("SubmitSyncMessage", func() (*empty.Empty, error) {
return new(empty.Empty), c.submitSyncMessage(ctx, in)
})
}
func (c *beaconApiValidatorClient) SubmitValidatorRegistrations(ctx context.Context, in *ethpb.SignedValidatorRegistrationsV1) (*empty.Empty, error) {
return new(empty.Empty), c.submitValidatorRegistrations(ctx, in.Messages)
return wrapInMetrics[*empty.Empty]("SubmitValidatorRegistrations", func() (*empty.Empty, error) {
return new(empty.Empty), c.submitValidatorRegistrations(ctx, in.Messages)
})
}
func (c *beaconApiValidatorClient) SubscribeCommitteeSubnets(ctx context.Context, in *ethpb.CommitteeSubnetsSubscribeRequest, validatorIndices []primitives.ValidatorIndex) (*empty.Empty, error) {
return new(empty.Empty), c.subscribeCommitteeSubnets(ctx, in, validatorIndices)
return wrapInMetrics[*empty.Empty]("SubscribeCommitteeSubnets", func() (*empty.Empty, error) {
return new(empty.Empty), c.subscribeCommitteeSubnets(ctx, in, validatorIndices)
})
}
func (c *beaconApiValidatorClient) ValidatorIndex(ctx context.Context, in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
return c.validatorIndex(ctx, in)
return wrapInMetrics[*ethpb.ValidatorIndexResponse]("ValidatorIndex", func() (*ethpb.ValidatorIndexResponse, error) {
return c.validatorIndex(ctx, in)
})
}
func (c *beaconApiValidatorClient) ValidatorStatus(ctx context.Context, in *ethpb.ValidatorStatusRequest) (*ethpb.ValidatorStatusResponse, error) {
@@ -183,3 +219,15 @@ func (c *beaconApiValidatorClient) GetAggregatedSelections(ctx context.Context,
func (c *beaconApiValidatorClient) GetAggregatedSyncSelections(ctx context.Context, selections []iface.SyncCommitteeSelection) ([]iface.SyncCommitteeSelection, error) {
return c.getAggregatedSyncSelections(ctx, selections)
}
func wrapInMetrics[Resp any](action string, f func() (Resp, error)) (Resp, error) {
now := time.Now()
resp, err := f()
httpActionCount.WithLabelValues(action).Inc()
if err == nil {
httpActionLatency.WithLabelValues(action).Observe(time.Since(now).Seconds())
} else {
failedHTTPActionCount.WithLabelValues(action).Inc()
}
return resp, err
}

View File

@@ -54,12 +54,6 @@ func TestBeaconApiValidatorClient_GetAttestationDataValid(t *testing.T) {
assert.DeepEqual(t, expectedResp, resp)
}
func TestBeaconApiValidatorClient_GetAttestationDataNilInput(t *testing.T) {
validatorClient := beaconApiValidatorClient{}
_, err := validatorClient.GetAttestationData(context.Background(), nil)
assert.ErrorContains(t, "GetAttestationData received nil argument `in`", err)
}
func TestBeaconApiValidatorClient_GetAttestationDataError(t *testing.T) {
const slot = primitives.Slot(1)
const committeeIndex = primitives.CommitteeIndex(2)

View File

@@ -0,0 +1,34 @@
package beacon_api
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
httpActionLatency = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "validator",
Name: "http_action_latency_seconds",
Help: "Latency of HTTP actions performed against the beacon node in seconds. This metric captures only actions that didn't result in an error.",
Buckets: []float64{0.001, 0.01, 0.025, 0.1, 0.25, 1, 2.5, 10},
},
[]string{"action"},
)
httpActionCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: "validator",
Name: "http_action_count",
Help: "Number of all HTTP actions performed against the beacon node",
},
[]string{"action"},
)
failedHTTPActionCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: "validator",
Name: "failed_http_action_count",
Help: "Number of failed HTTP actions performed against the beacon node",
},
[]string{"action"},
)
)

View File

@@ -13,7 +13,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//config/fieldparams:go_default_library",
"//config/validator/service:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",

View File

@@ -6,7 +6,7 @@ import (
"time"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -62,8 +62,8 @@ type Validator interface {
CheckDoppelGanger(ctx context.Context) error
PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, deadline time.Time) error
SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error)
ProposerSettings() *validatorserviceconfig.ProposerSettings
SetProposerSettings(context.Context, *validatorserviceconfig.ProposerSettings) error
ProposerSettings() *proposer.Settings
SetProposerSettings(context.Context, *proposer.Settings) error
StartEventStream(ctx context.Context) error
EventStreamIsRunning() bool
NodeIsHealthy(ctx context.Context) bool

View File

@@ -28,9 +28,12 @@ import (
"go.opencensus.io/trace"
)
const domainDataErr = "could not get domain data"
const signingRootErr = "could not get signing root"
const signExitErr = "could not sign voluntary exit proposal"
const (
domainDataErr = "could not get domain data"
signingRootErr = "could not get signing root"
signExitErr = "could not sign voluntary exit proposal"
failedBlockSignLocalErr = "block rejected by local protection"
)
// ProposeBlock proposes a new beacon block for a given slot. This method collects the
// previous beacon block, any pending deposits, and ETH1 data from the beacon
@@ -111,7 +114,7 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
return
}
if err := v.slashableProposalCheck(ctx, pubKey, blk, signingRoot); err != nil {
if err := v.db.SlashableProposalCheck(ctx, pubKey, blk, signingRoot, v.emitAccountMetrics, ValidatorProposeFailVec); err != nil {
log.WithFields(
blockLogFields(pubKey, wb, nil),
).WithError(err).Error("Failed block slashing protection check")
@@ -429,3 +432,15 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk
return []byte{}, nil
}
func blockLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, blk interfaces.ReadOnlyBeaconBlock, sig []byte) logrus.Fields {
fields := logrus.Fields{
"proposerPublicKey": fmt.Sprintf("%#x", pubKey),
"proposerIndex": blk.ProposerIndex(),
"blockSlot": blk.Slot(),
}
if sig != nil {
fields["signature"] = fmt.Sprintf("%#x", sig)
}
return fields
}

View File

@@ -1,100 +0,0 @@
package client
import (
"context"
"fmt"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/sirupsen/logrus"
)
var failedBlockSignLocalErr = "attempted to sign a double proposal, block rejected by local protection"
// slashableProposalCheck checks if a block proposal is slashable by comparing it with the
// block proposals history for the given public key in our DB. If it is not, we then update the history
// with new values and save it to the database.
func (v *validator) slashableProposalCheck(
ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, signedBlock interfaces.ReadOnlySignedBeaconBlock, signingRoot [32]byte,
) error {
fmtKey := fmt.Sprintf("%#x", pubKey[:])
blk := signedBlock.Block()
prevSigningRoot, proposalAtSlotExists, prevSigningRootExists, err := v.db.ProposalHistoryForSlot(ctx, pubKey, blk.Slot())
if err != nil {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.Wrap(err, "failed to get proposal history")
}
lowestSignedProposalSlot, lowestProposalExists, err := v.db.LowestSignedProposal(ctx, pubKey)
if err != nil {
return err
}
// Based on EIP-3076 - Condition 2
// -------------------------------
if lowestProposalExists {
// If the block slot is (strictly) less than the lowest signed proposal slot in the DB, we consider it slashable.
if blk.Slot() < lowestSignedProposalSlot {
return fmt.Errorf(
"could not sign block with slot < lowest signed slot in db, block slot: %d < lowest signed slot: %d",
blk.Slot(),
lowestSignedProposalSlot,
)
}
// If the block slot is equal to the lowest signed proposal slot and
// - condition1: there is no signed proposal in the DB for this slot, or
// - condition2: there is a signed proposal in the DB for this slot, but with no associated signing root, or
// - condition3: there is a signed proposal in the DB for this slot, but the signing root differs,
// ==> we consider it slashable.
condition1 := !proposalAtSlotExists
condition2 := proposalAtSlotExists && !prevSigningRootExists
condition3 := proposalAtSlotExists && prevSigningRootExists && prevSigningRoot != signingRoot
if blk.Slot() == lowestSignedProposalSlot && (condition1 || condition2 || condition3) {
return fmt.Errorf(
"could not sign block with slot == lowest signed slot in db if it is not a repeat signing, block slot: %d == slowest signed slot: %d",
blk.Slot(),
lowestSignedProposalSlot,
)
}
}
// Based on EIP-3076 - Condition 1
// -------------------------------
// If there is a signed proposal in the DB for this slot and
// - there is no associated signing root, or
// - the signing root differs,
// ==> we consider it slashable.
if proposalAtSlotExists && (!prevSigningRootExists || prevSigningRoot != signingRoot) {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.New(failedBlockSignLocalErr)
}
// Save the proposal for this slot.
if err := v.db.SaveProposalHistoryForSlot(ctx, pubKey, blk.Slot(), signingRoot[:]); err != nil {
if v.emitAccountMetrics {
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.Wrap(err, "failed to save updated proposal history")
}
return nil
}
func blockLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, blk interfaces.ReadOnlyBeaconBlock, sig []byte) logrus.Fields {
fields := logrus.Fields{
"pubkey": fmt.Sprintf("%#x", pubKey),
"proposerIndex": blk.ProposerIndex(),
"slot": blk.Slot(),
}
if sig != nil {
fields["signature"] = fmt.Sprintf("%#x", sig)
}
return fields
}

View File

@@ -1,155 +0,0 @@
package client
import (
"context"
"testing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
)
func Test_slashableProposalCheck_PreventsLowerThanMinProposal(t *testing.T) {
ctx := context.Background()
validator, _, validatorKey, finish := setup(t)
defer finish()
lowestSignedSlot := primitives.Slot(10)
var pubKeyBytes [fieldparams.BLSPubkeyLength]byte
copy(pubKeyBytes[:], validatorKey.PublicKey().Marshal())
// We save a proposal at the lowest signed slot in the DB.
err := validator.db.SaveProposalHistoryForSlot(ctx, pubKeyBytes, lowestSignedSlot, []byte{1})
require.NoError(t, err)
require.NoError(t, err)
// We expect the same block with a slot lower than the lowest
// signed slot to fail validation.
blk := &ethpb.SignedBeaconBlock{
Block: &ethpb.BeaconBlock{
Slot: lowestSignedSlot - 1,
ProposerIndex: 0,
Body: &ethpb.BeaconBlockBody{},
},
Signature: params.BeaconConfig().EmptySignature[:],
}
wsb, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKeyBytes, wsb, [32]byte{4})
require.ErrorContains(t, "could not sign block with slot < lowest signed", err)
// We expect the same block with a slot equal to the lowest
// signed slot to pass validation if signing roots are equal.
blk = &ethpb.SignedBeaconBlock{
Block: &ethpb.BeaconBlock{
Slot: lowestSignedSlot,
ProposerIndex: 0,
Body: &ethpb.BeaconBlockBody{},
},
Signature: params.BeaconConfig().EmptySignature[:],
}
wsb, err = blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKeyBytes, wsb, [32]byte{1})
require.NoError(t, err)
// We expect the same block with a slot equal to the lowest
// signed slot to fail validation if signing roots are different.
wsb, err = blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKeyBytes, wsb, [32]byte{4})
require.ErrorContains(t, "could not sign block with slot == lowest signed", err)
// We expect the same block with a slot > than the lowest
// signed slot to pass validation.
blk = &ethpb.SignedBeaconBlock{
Block: &ethpb.BeaconBlock{
Slot: lowestSignedSlot + 1,
ProposerIndex: 0,
Body: &ethpb.BeaconBlockBody{},
},
Signature: params.BeaconConfig().EmptySignature[:],
}
wsb, err = blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKeyBytes, wsb, [32]byte{3})
require.NoError(t, err)
}
func Test_slashableProposalCheck(t *testing.T) {
ctx := context.Background()
validator, _, validatorKey, finish := setup(t)
defer finish()
blk := util.HydrateSignedBeaconBlock(&ethpb.SignedBeaconBlock{
Block: &ethpb.BeaconBlock{
Slot: 10,
ProposerIndex: 0,
Body: &ethpb.BeaconBlockBody{},
},
Signature: params.BeaconConfig().EmptySignature[:],
})
var pubKeyBytes [fieldparams.BLSPubkeyLength]byte
copy(pubKeyBytes[:], validatorKey.PublicKey().Marshal())
// We save a proposal at slot 1 as our lowest proposal.
err := validator.db.SaveProposalHistoryForSlot(ctx, pubKeyBytes, 1, []byte{1})
require.NoError(t, err)
// We save a proposal at slot 10 with a dummy signing root.
dummySigningRoot := [32]byte{1}
err = validator.db.SaveProposalHistoryForSlot(ctx, pubKeyBytes, 10, dummySigningRoot[:])
require.NoError(t, err)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
sBlock, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
// We expect the same block sent out with the same root should not be slasahble.
err = validator.slashableProposalCheck(context.Background(), pubKey, sBlock, dummySigningRoot)
require.NoError(t, err)
// We expect the same block sent out with a different signing root should be slasahble.
err = validator.slashableProposalCheck(context.Background(), pubKey, sBlock, [32]byte{2})
require.ErrorContains(t, failedBlockSignLocalErr, err)
// We save a proposal at slot 11 with a nil signing root.
blk.Block.Slot = 11
sBlock, err = blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.db.SaveProposalHistoryForSlot(ctx, pubKeyBytes, blk.Block.Slot, nil)
require.NoError(t, err)
// We expect the same block sent out should return slashable error even
// if we had a nil signing root stored in the database.
err = validator.slashableProposalCheck(context.Background(), pubKey, sBlock, [32]byte{2})
require.ErrorContains(t, failedBlockSignLocalErr, err)
// A block with a different slot for which we do not have a proposing history
// should not be failing validation.
blk.Block.Slot = 9
sBlock, err = blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKey, sBlock, [32]byte{3})
require.NoError(t, err, "Expected allowed block not to throw error")
}
func Test_slashableProposalCheck_RemoteProtection(t *testing.T) {
validator, _, validatorKey, finish := setup(t)
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
blk := util.NewBeaconBlock()
blk.Block.Slot = 10
sBlock, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pubKey, sBlock, [32]byte{2})
require.NoError(t, err, "Expected allowed block not to throw error")
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ package client
import (
"context"
"fmt"
"testing"
"time"
@@ -17,38 +18,67 @@ import (
)
func TestSubmitValidatorRegistrations(t *testing.T) {
_, m, validatorKey, finish := setup(t)
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
_, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
ctx := context.Background()
validatorRegsBatchSize := 2
require.NoError(t, nil, SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{}, validatorRegsBatchSize))
ctx := context.Background()
validatorRegsBatchSize := 2
require.NoError(t, nil, SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{}, validatorRegsBatchSize))
regs := [...]*ethpb.ValidatorRegistrationV1{
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 789,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
}
regs := [...]*ethpb.ValidatorRegistrationV1{
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 789,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
},
}
gomock.InOrder(
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
gomock.InOrder(
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
{
Message: regs[0],
Signature: params.BeaconConfig().ZeroHash[:],
},
{
Message: regs[1],
Signature: params.BeaconConfig().ZeroHash[:],
},
},
}).
Return(nil, nil),
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
{
Message: regs[2],
Signature: params.BeaconConfig().ZeroHash[:],
},
},
}).
Return(nil, nil),
)
require.NoError(t, nil, SubmitValidatorRegistrations(
ctx, m.validatorClient,
[]*ethpb.SignedValidatorRegistrationV1{
{
Message: regs[0],
Signature: params.BeaconConfig().ZeroHash[:],
@@ -57,222 +87,206 @@ func TestSubmitValidatorRegistrations(t *testing.T) {
Message: regs[1],
Signature: params.BeaconConfig().ZeroHash[:],
},
},
}).
Return(nil, nil),
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
{
Message: regs[2],
Signature: params.BeaconConfig().ZeroHash[:],
},
},
}).
Return(nil, nil),
)
require.NoError(t, nil, SubmitValidatorRegistrations(
ctx, m.validatorClient,
[]*ethpb.SignedValidatorRegistrationV1{
{
Message: regs[0],
Signature: params.BeaconConfig().ZeroHash[:],
},
{
Message: regs[1],
Signature: params.BeaconConfig().ZeroHash[:],
},
{
Message: regs[2],
Signature: params.BeaconConfig().ZeroHash[:],
},
},
validatorRegsBatchSize,
))
validatorRegsBatchSize,
))
})
}
}
func TestSubmitValidatorRegistration_CantSign(t *testing.T) {
_, m, validatorKey, finish := setup(t)
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
_, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
ctx := context.Background()
validatorRegsBatchSize := 500
reg := &ethpb.ValidatorRegistrationV1{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
}
ctx := context.Background()
validatorRegsBatchSize := 500
reg := &ethpb.ValidatorRegistrationV1{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
}
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
m.validatorClient.EXPECT().
SubmitValidatorRegistrations(gomock.Any(), &ethpb.SignedValidatorRegistrationsV1{
Messages: []*ethpb.SignedValidatorRegistrationV1{
{Message: reg,
Signature: params.BeaconConfig().ZeroHash[:]},
},
}).
Return(nil, errors.New("could not sign"))
require.ErrorContains(t, "could not sign", SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{
{Message: reg,
Signature: params.BeaconConfig().ZeroHash[:]},
},
}).
Return(nil, errors.New("could not sign"))
require.ErrorContains(t, "could not sign", SubmitValidatorRegistrations(ctx, m.validatorClient, []*ethpb.SignedValidatorRegistrationV1{
{Message: reg,
Signature: params.BeaconConfig().ZeroHash[:]},
}, validatorRegsBatchSize))
}, validatorRegsBatchSize))
})
}
}
func Test_signValidatorRegistration(t *testing.T) {
_, m, validatorKey, finish := setup(t)
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
_, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
ctx := context.Background()
reg := &ethpb.ValidatorRegistrationV1{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
ctx := context.Background()
reg := &ethpb.ValidatorRegistrationV1{
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
GasLimit: 123456,
Timestamp: uint64(time.Now().Unix()),
Pubkey: validatorKey.PublicKey().Marshal(),
}
_, err := signValidatorRegistration(ctx, m.signfunc, reg)
require.NoError(t, err)
})
}
_, err := signValidatorRegistration(ctx, m.signfunc, reg)
require.NoError(t, err)
}
func TestValidator_SignValidatorRegistrationRequest(t *testing.T) {
_, m, validatorKey, finish := setup(t)
defer finish()
ctx := context.Background()
byteval, err := hexutil.Decode("0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766")
require.NoError(t, err)
tests := []struct {
name string
arg *ethpb.ValidatorRegistrationV1
validatorSetter func(t *testing.T) *validator
isCached bool
err string
}{
{
name: " Happy Path cached",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
_, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
ctx := context.Background()
byteval, err := hexutil.Decode("0x878705ba3f8bc32fcf7f4caa1a35e72af65cf766")
require.NoError(t, err)
tests := []struct {
name string
arg *ethpb.ValidatorRegistrationV1
validatorSetter func(t *testing.T) *validator
isCached bool
err string
}{
{
name: " Happy Path cached",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 30000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix()),
},
Signature: make([]byte, 0),
}
return &v
},
isCached: true,
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 30000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix()),
},
Signature: make([]byte, 0),
}
return &v
{
name: " Happy Path not cached gas updated",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 35000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix() - 1),
},
Signature: make([]byte, 0),
}
return &v
},
isCached: false,
},
isCached: true,
},
{
name: " Happy Path not cached gas updated",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
{
name: " Happy Path not cached feerecipient updated",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: byteval,
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 30000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix() - 1),
},
Signature: make([]byte, 0),
}
return &v
},
isCached: false,
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 35000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix() - 1),
},
Signature: make([]byte, 0),
}
return &v
{
name: " Happy Path not cached first Entry",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: byteval,
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
return &v
},
isCached: false,
},
isCached: false,
},
{
name: " Happy Path not cached feerecipient updated",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: byteval,
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
v.signedValidatorRegistrations[bytesutil.ToBytes48(validatorKey.PublicKey().Marshal())] = &ethpb.SignedValidatorRegistrationV1{
Message: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
GasLimit: 30000000,
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix() - 1),
},
Signature: make([]byte, 0),
}
return &v
},
isCached: false,
},
{
name: " Happy Path not cached first Entry",
arg: &ethpb.ValidatorRegistrationV1{
Pubkey: validatorKey.PublicKey().Marshal(),
FeeRecipient: byteval,
GasLimit: 30000000,
Timestamp: uint64(time.Now().Unix()),
},
validatorSetter: func(t *testing.T) *validator {
v := validator{
pubkeyToValidatorIndex: make(map[[fieldparams.BLSPubkeyLength]byte]primitives.ValidatorIndex),
signedValidatorRegistrations: make(map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1),
useWeb: false,
genesisTime: 0,
}
return &v
},
isCached: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := tt.validatorSetter(t)
}
for _, tt := range tests {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
v := tt.validatorSetter(t)
startingReq, ok := v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)]
startingReq, ok := v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)]
got, err := v.SignValidatorRegistrationRequest(ctx, m.signfunc, tt.arg)
require.NoError(t, err)
if tt.isCached {
require.DeepEqual(t, got, v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)])
} else {
if ok {
require.NotEqual(t, got.Message.Timestamp, startingReq.Message.Timestamp)
got, err := v.SignValidatorRegistrationRequest(ctx, m.signfunc, tt.arg)
require.NoError(t, err)
if tt.isCached {
require.DeepEqual(t, got, v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)])
} else {
if ok {
require.NotEqual(t, got.Message.Timestamp, startingReq.Message.Timestamp)
}
require.Equal(t, got.Message.Timestamp, tt.arg.Timestamp)
require.Equal(t, got.Message.GasLimit, tt.arg.GasLimit)
require.Equal(t, hexutil.Encode(got.Message.FeeRecipient), hexutil.Encode(tt.arg.FeeRecipient))
require.DeepEqual(t, got, v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)])
}
require.Equal(t, got.Message.Timestamp, tt.arg.Timestamp)
require.Equal(t, got.Message.GasLimit, tt.arg.GasLimit)
require.Equal(t, hexutil.Encode(got.Message.FeeRecipient), hexutil.Encode(tt.arg.FeeRecipient))
require.DeepEqual(t, got, v.signedValidatorRegistrations[bytesutil.ToBytes48(tt.arg.Pubkey)])
}
})
})
}
}
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
@@ -225,9 +225,9 @@ func notActive(t *testing.T) [fieldparams.BLSPubkeyLength]byte {
func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
err := v.SetProposerSettings(context.Background(), &validatorserviceconfig.ProposerSettings{
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
@@ -250,9 +250,9 @@ func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
func TestUpdateProposerSettingsAt_EpochEndOk(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, ProposerSettingWait: time.Duration(params.BeaconConfig().SecondsPerSlot-1) * time.Second}
err := v.SetProposerSettings(context.Background(), &validatorserviceconfig.ProposerSettings{
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
@@ -279,9 +279,9 @@ func TestUpdateProposerSettings_ContinuesAfterValidatorRegistrationFails(t *test
ProposerSettingsErr: errors.Wrap(ErrBuilderValidatorRegistration, errSomeotherError.Error()),
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
}
err := v.SetProposerSettings(context.Background(), &validatorserviceconfig.ProposerSettings{
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},

View File

@@ -17,7 +17,7 @@ import (
lruwrpr "github.com/prysmaticlabs/prysm/v5/cache/lru"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/wallet"
@@ -75,7 +75,7 @@ type ValidatorService struct {
grpcHeaders []string
graffiti []byte
Web3SignerConfig *remoteweb3signer.SetupConfig
proposerSettings *validatorserviceconfig.ProposerSettings
proposerSettings *proposer.Settings
validatorsRegBatchSize int
}
@@ -100,7 +100,7 @@ type Config struct {
GraffitiFlag string
Endpoint string
Web3SignerConfig *remoteweb3signer.SetupConfig
ProposerSettings *validatorserviceconfig.ProposerSettings
ProposerSettings *proposer.Settings
BeaconApiEndpoint string
BeaconApiTimeout time.Duration
ValidatorsRegBatchSize int
@@ -271,7 +271,7 @@ func (v *ValidatorService) Keymanager() (keymanager.IKeymanager, error) {
}
// ProposerSettings returns a deep copy of the underlying proposer settings in the validator
func (v *ValidatorService) ProposerSettings() *validatorserviceconfig.ProposerSettings {
func (v *ValidatorService) ProposerSettings() *proposer.Settings {
settings := v.validator.ProposerSettings()
if settings != nil {
return settings.Clone()
@@ -280,7 +280,7 @@ func (v *ValidatorService) ProposerSettings() *validatorserviceconfig.ProposerSe
}
// SetProposerSettings sets the proposer settings on the validator service as well as the underlying validator
func (v *ValidatorService) SetProposerSettings(ctx context.Context, settings *validatorserviceconfig.ProposerSettings) error {
func (v *ValidatorService) SetProposerSettings(ctx context.Context, settings *proposer.Settings) error {
// validator service proposer settings is only used for pass through from node -> validator service -> validator.
// in memory use of proposer settings happens on validator.
v.proposerSettings = settings

View File

@@ -5,6 +5,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"testing"
@@ -15,7 +16,7 @@ import (
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
history "github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history"
"github.com/prysmaticlabs/prysm/v5/validator/helpers"
)
type eip3076TestCase struct {
@@ -46,6 +47,7 @@ type eip3076TestCase struct {
Pubkey string `json:"pubkey"`
Slot string `json:"slot"`
SigningRoot string `json:"signing_root"`
ShouldSucceedMinimal bool `json:"should_succeed"`
ShouldSucceedComplete bool `json:"should_succeed_complete"`
} `json:"blocks"`
Attestations []struct {
@@ -53,6 +55,7 @@ type eip3076TestCase struct {
SourceEpoch string `json:"source_epoch"`
TargetEpoch string `json:"target_epoch"`
SigningRoot string `json:"signing_root"`
ShouldSucceedMinimal bool `json:"should_succeed"`
ShouldSucceedComplete bool `json:"should_succeed_complete"`
} `json:"attestations"`
} `json:"steps"`
@@ -76,99 +79,115 @@ func setupEIP3076SpecTests(t *testing.T) []*eip3076TestCase {
}
func TestEIP3076SpecTests(t *testing.T) {
testCases := setupEIP3076SpecTests(t)
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
if tt.Name == "" {
t.Skip("Skipping eip3076TestCase with empty name")
}
for _, isMinimal := range []bool{false, true} {
slashingProtectionType := "complete"
if isMinimal {
slashingProtectionType = "minimal"
}
// Set up validator client, one new validator client per eip3076TestCase.
// This ensures we initialize a new (empty) slashing protection database.
validator, _, _, _ := setup(t)
for _, step := range tt.Steps {
if tt.GenesisValidatorsRoot != "" {
r, err := history.RootFromHex(tt.GenesisValidatorsRoot)
require.NoError(t, validator.db.SaveGenesisValidatorsRoot(context.Background(), r[:]))
require.NoError(t, err)
for _, tt := range setupEIP3076SpecTests(t) {
t.Run(fmt.Sprintf("%s-%s", slashingProtectionType, tt.Name), func(t *testing.T) {
if tt.Name == "" {
t.Skip("Skipping eip3076TestCase with empty name")
}
// The eip3076TestCase config contains the interchange config in json.
// This loads the interchange data via ImportStandardProtectionJSON.
interchangeBytes, err := json.Marshal(step.Interchange)
if err != nil {
t.Fatal(err)
}
b := bytes.NewBuffer(interchangeBytes)
if err := history.ImportStandardProtectionJSON(context.Background(), validator.db, b); err != nil {
if step.ShouldSucceed {
// Set up validator client, one new validator client per eip3076TestCase.
// This ensures we initialize a new (empty) slashing protection database.
validator, _, _, _ := setup(t, isMinimal)
for _, step := range tt.Steps {
if tt.GenesisValidatorsRoot != "" {
r, err := helpers.RootFromHex(tt.GenesisValidatorsRoot)
require.NoError(t, validator.db.SaveGenesisValidatorsRoot(context.Background(), r[:]))
require.NoError(t, err)
}
// The eip3076TestCase config contains the interchange config in json.
// This loads the interchange data via ImportStandardProtectionJSON.
interchangeBytes, err := json.Marshal(step.Interchange)
if err != nil {
t.Fatal(err)
}
} else if !step.ShouldSucceed {
require.NotNil(t, err, "import standard protection json should have failed")
}
// This loops through a list of block signings to attempt after importing the interchange data above.
for _, sb := range step.Blocks {
bSlot, err := history.SlotFromString(sb.Slot)
require.NoError(t, err)
pk, err := history.PubKeyFromHex(sb.Pubkey)
require.NoError(t, err)
b := util.NewBeaconBlock()
b.Block.Slot = bSlot
var signingRoot [32]byte
if sb.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sb.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
b := bytes.NewBuffer(interchangeBytes)
if err := validator.db.ImportStandardProtectionJSON(context.Background(), b); err != nil {
if step.ShouldSucceed {
t.Fatal(err)
}
} else if !step.ShouldSucceed {
require.NotNil(t, err, "import standard protection json should have failed")
}
wsb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
err = validator.slashableProposalCheck(context.Background(), pk, wsb, signingRoot)
if sb.ShouldSucceedComplete {
// This loops through a list of block signings to attempt after importing the interchange data above.
for _, sb := range step.Blocks {
shouldSucceed := sb.ShouldSucceedComplete
if isMinimal {
shouldSucceed = sb.ShouldSucceedMinimal
}
bSlot, err := helpers.SlotFromString(sb.Slot)
require.NoError(t, err)
} else {
require.NotEqual(t, nil, err, "pre validation should have failed for block")
pk, err := helpers.PubKeyFromHex(sb.Pubkey)
require.NoError(t, err)
b := util.NewBeaconBlock()
b.Block.Slot = bSlot
var signingRoot [32]byte
if sb.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sb.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
}
wsb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
err = validator.db.SlashableProposalCheck(context.Background(), pk, wsb, signingRoot, validator.emitAccountMetrics, ValidatorProposeFailVec)
if shouldSucceed {
require.NoError(t, err)
} else {
require.NotEqual(t, nil, err, "pre validation should have failed for block")
}
}
// This loops through a list of attestation signings to attempt after importing the interchange data above.
for _, sa := range step.Attestations {
shouldSucceed := sa.ShouldSucceedComplete
if isMinimal {
shouldSucceed = sa.ShouldSucceedMinimal
}
target, err := helpers.EpochFromString(sa.TargetEpoch)
require.NoError(t, err)
source, err := helpers.EpochFromString(sa.SourceEpoch)
require.NoError(t, err)
pk, err := helpers.PubKeyFromHex(sa.Pubkey)
require.NoError(t, err)
ia := &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
BeaconBlockRoot: make([]byte, 32),
Target: &ethpb.Checkpoint{Epoch: target, Root: make([]byte, 32)},
Source: &ethpb.Checkpoint{Epoch: source, Root: make([]byte, 32)},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
}
var signingRoot [32]byte
if sa.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sa.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
}
err = validator.db.SlashableAttestationCheck(context.Background(), ia, pk, signingRoot, false, nil)
if shouldSucceed {
require.NoError(t, err)
} else {
require.NotNil(t, err, "pre validation should have failed for attestation")
}
}
}
// This loops through a list of attestation signings to attempt after importing the interchange data above.
for _, sa := range step.Attestations {
target, err := history.EpochFromString(sa.TargetEpoch)
require.NoError(t, err)
source, err := history.EpochFromString(sa.SourceEpoch)
require.NoError(t, err)
pk, err := history.PubKeyFromHex(sa.Pubkey)
require.NoError(t, err)
ia := &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
BeaconBlockRoot: make([]byte, 32),
Target: &ethpb.Checkpoint{Epoch: target, Root: make([]byte, 32)},
Source: &ethpb.Checkpoint{Epoch: source, Root: make([]byte, 32)},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
}
var signingRoot [32]byte
if sa.SigningRoot != "" {
signingRootBytes, err := hex.DecodeString(strings.TrimPrefix(sa.SigningRoot, "0x"))
require.NoError(t, err)
copy(signingRoot[:], signingRootBytes)
}
err = validator.slashableAttestationCheck(context.Background(), ia, pk, signingRoot)
if sa.ShouldSucceedComplete {
require.NoError(t, err)
} else {
require.NotNil(t, err, "pre validation should have failed for attestation")
}
}
}
require.NoError(t, validator.db.Close(), "failed to close slashing protection database")
})
require.NoError(t, validator.db.Close(), "failed to close slashing protection database")
})
}
}
}

View File

@@ -3,6 +3,7 @@ package client
import (
"context"
"encoding/hex"
"fmt"
"testing"
"github.com/pkg/errors"
@@ -20,246 +21,278 @@ import (
)
func TestSubmitSyncCommitteeMessage_ValidatorDutiesRequestFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo([]byte{}, 32),
}, nil)
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo([]byte{}, 32),
}, nil)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not fetch validator assignment")
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not fetch validator assignment")
})
}
}
func TestSubmitSyncCommitteeMessage_BadDomainData(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), gomock.Any()).
Return(nil, errors.New("uh oh"))
m.validatorClient.EXPECT().
DomainData(gomock.Any(), gomock.Any()).
Return(nil, errors.New("uh oh"))
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync committee domain data")
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync committee domain data")
})
}
}
func TestSubmitSyncCommitteeMessage_CouldNotSubmit(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().SubmitSyncMessage(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SyncCommitteeMessage{}),
).Return(&emptypb.Empty{}, errors.New("uh oh") /* error */)
m.validatorClient.EXPECT().SubmitSyncMessage(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SyncCommitteeMessage{}),
).Return(&emptypb.Empty{}, errors.New("uh oh") /* error */)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not submit sync committee message")
require.LogsContain(t, hook, "Could not submit sync committee message")
})
}
}
func TestSubmitSyncCommitteeMessage_OK(t *testing.T) {
validator, m, validatorKey, finish := setup(t)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
hook := logTest.NewGlobal()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
r := []byte{'a'}
m.validatorClient.EXPECT().GetSyncMessageBlockRoot(
gomock.Any(), // ctx
&emptypb.Empty{},
).Return(&ethpb.SyncMessageBlockRootResponse{
Root: bytesutil.PadTo(r, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
var generatedMsg *ethpb.SyncCommitteeMessage
m.validatorClient.EXPECT().SubmitSyncMessage(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SyncCommitteeMessage{}),
).Do(func(_ context.Context, msg *ethpb.SyncCommitteeMessage) {
generatedMsg = msg
}).Return(&emptypb.Empty{}, nil /* error */)
var generatedMsg *ethpb.SyncCommitteeMessage
m.validatorClient.EXPECT().SubmitSyncMessage(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SyncCommitteeMessage{}),
).Do(func(_ context.Context, msg *ethpb.SyncCommitteeMessage) {
generatedMsg = msg
}).Return(&emptypb.Empty{}, nil /* error */)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSyncCommitteeMessage(context.Background(), 1, pubKey)
require.LogsDoNotContain(t, hook, "Could not")
require.Equal(t, primitives.Slot(1), generatedMsg.Slot)
require.Equal(t, validatorIndex, generatedMsg.ValidatorIndex)
require.DeepEqual(t, bytesutil.PadTo(r, 32), generatedMsg.BlockRoot)
require.LogsDoNotContain(t, hook, "Could not")
require.Equal(t, primitives.Slot(1), generatedMsg.Slot)
require.Equal(t, validatorIndex, generatedMsg.ValidatorIndex)
require.DeepEqual(t, bytesutil.PadTo(r, 32), generatedMsg.BlockRoot)
})
}
}
func TestSubmitSignedContributionAndProof_ValidatorDutiesRequestFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, _, validatorKey, finish := setup(t)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, _, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not fetch validator assignment")
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not fetch validator assignment")
})
}
}
func TestSubmitSignedContributionAndProof_GetSyncSubcommitteeIndexFailure(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{}, errors.New("Bad index"))
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{}, errors.New("Bad index"))
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync subcommittee index")
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync subcommittee index")
})
}
}
func TestSubmitSignedContributionAndProof_NothingToDo(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{}}, nil)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{}}, nil)
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Empty subcommittee index list, do nothing")
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Empty subcommittee index list, do nothing")
})
}
}
func TestSubmitSignedContributionAndProof_BadDomain(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
hook := logTest.NewGlobal()
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, errors.New("bad domain response"))
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, errors.New("bad domain response"))
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get selection proofs")
require.LogsContain(t, hook, "bad domain response")
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get selection proofs")
require.LogsContain(t, hook, "bad domain response")
})
}
}
func TestSubmitSignedContributionAndProof_CouldNotGetContribution(t *testing.T) {
@@ -270,46 +303,50 @@ func TestSubmitSignedContributionAndProof_CouldNotGetContribution(t *testing.T)
validatorKey, err := bls.SecretKeyFromBytes(rawKey)
assert.NoError(t, err)
validator, m, validatorKey, finish := setupWithKey(t, validatorKey)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setupWithKey(t, validatorKey, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(nil, errors.New("Bad contribution"))
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(nil, errors.New("Bad contribution"))
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync committee contribution")
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not get sync committee contribution")
})
}
}
func TestSubmitSignedContributionAndProof_CouldNotSubmitContribution(t *testing.T) {
@@ -320,75 +357,79 @@ func TestSubmitSignedContributionAndProof_CouldNotSubmitContribution(t *testing.
validatorKey, err := bls.SecretKeyFromBytes(rawKey)
assert.NoError(t, err)
validator, m, validatorKey, finish := setupWithKey(t, validatorKey)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
aggBits := bitfield.NewBitvector128()
aggBits.SetBitAt(0, true)
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(&ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: aggBits,
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().SubmitSignedContributionAndProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedContributionAndProof{
Message: &ethpb.ContributionAndProof{
AggregatorIndex: 7,
Contribution: &ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: bitfield.NewBitvector128(),
Slot: 1,
SubcommitteeIndex: 1,
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setupWithKey(t, validatorKey, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
},
}),
).Return(&emptypb.Empty{}, errors.New("Could not submit contribution"))
}}
defer finish()
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not submit signed contribution and proof")
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
aggBits := bitfield.NewBitvector128()
aggBits.SetBitAt(0, true)
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(&ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: aggBits,
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().SubmitSignedContributionAndProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedContributionAndProof{
Message: &ethpb.ContributionAndProof{
AggregatorIndex: 7,
Contribution: &ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: bitfield.NewBitvector128(),
Slot: 1,
SubcommitteeIndex: 1,
},
},
}),
).Return(&emptypb.Empty{}, errors.New("Could not submit contribution"))
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
require.LogsContain(t, hook, "Could not submit signed contribution and proof")
})
}
}
func TestSubmitSignedContributionAndProof_Ok(t *testing.T) {
@@ -398,72 +439,76 @@ func TestSubmitSignedContributionAndProof_Ok(t *testing.T) {
validatorKey, err := bls.SecretKeyFromBytes(rawKey)
assert.NoError(t, err)
validator, m, validatorKey, finish := setupWithKey(t, validatorKey)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
}}
defer finish()
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
aggBits := bitfield.NewBitvector128()
aggBits.SetBitAt(0, true)
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(&ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: aggBits,
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().SubmitSignedContributionAndProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedContributionAndProof{
Message: &ethpb.ContributionAndProof{
AggregatorIndex: 7,
Contribution: &ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, 32),
Signature: make([]byte, 96),
AggregationBits: bitfield.NewBitvector128(),
Slot: 1,
SubcommitteeIndex: 1,
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) {
validator, m, validatorKey, finish := setupWithKey(t, validatorKey, isSlashingProtectionMinimal)
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
validator.duties = &ethpb.DutiesResponse{CurrentEpochDuties: []*ethpb.DutiesResponse_Duty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
Committee: committee,
ValidatorIndex: validatorIndex,
},
},
}),
).Return(&emptypb.Empty{}, nil)
}}
defer finish()
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
m.validatorClient.EXPECT().GetSyncSubcommitteeIndex(
gomock.Any(), // ctx
&ethpb.SyncSubcommitteeIndexRequest{
Slot: 1,
PublicKey: pubKey[:],
},
).Return(&ethpb.SyncSubcommitteeIndexResponse{Indices: []primitives.CommitteeIndex{1}}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
aggBits := bitfield.NewBitvector128()
aggBits.SetBitAt(0, true)
m.validatorClient.EXPECT().GetSyncCommitteeContribution(
gomock.Any(), // ctx
&ethpb.SyncCommitteeContributionRequest{
Slot: 1,
PublicKey: pubKey[:],
SubnetId: 0,
},
).Return(&ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, fieldparams.RootLength),
Signature: make([]byte, 96),
AggregationBits: aggBits,
}, nil)
m.validatorClient.EXPECT().
DomainData(gomock.Any(), // ctx
gomock.Any()). // epoch
Return(&ethpb.DomainResponse{
SignatureDomain: make([]byte, 32),
}, nil)
m.validatorClient.EXPECT().SubmitSignedContributionAndProof(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SignedContributionAndProof{
Message: &ethpb.ContributionAndProof{
AggregatorIndex: 7,
Contribution: &ethpb.SyncCommitteeContribution{
BlockRoot: make([]byte, 32),
Signature: make([]byte, 96),
AggregationBits: bitfield.NewBitvector128(),
Slot: 1,
SubcommitteeIndex: 1,
},
},
}),
).Return(&emptypb.Empty{}, nil)
validator.SubmitSignedContributionAndProof(context.Background(), 1, pubKey)
})
}
}

View File

@@ -11,7 +11,7 @@ go_library(
visibility = ["//validator:__subpackages__"],
deps = [
"//config/fieldparams:go_default_library",
"//config/validator/service:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",

View File

@@ -6,7 +6,7 @@ import (
"time"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
@@ -52,7 +52,7 @@ type FakeValidator struct {
IndexToPubkeyMap map[uint64][fieldparams.BLSPubkeyLength]byte
PubkeyToIndexMap map[[fieldparams.BLSPubkeyLength]byte]uint64
PubkeysToStatusesMap map[[fieldparams.BLSPubkeyLength]byte]ethpb.ValidatorStatus
proposerSettings *validatorserviceconfig.ProposerSettings
proposerSettings *proposer.Settings
ProposerSettingWait time.Duration
Km keymanager.IKeymanager
}
@@ -276,12 +276,12 @@ func (*FakeValidator) SignValidatorRegistrationRequest(_ context.Context, _ ifac
}
// ProposerSettings for mocking
func (fv *FakeValidator) ProposerSettings() *validatorserviceconfig.ProposerSettings {
func (fv *FakeValidator) ProposerSettings() *proposer.Settings {
return fv.proposerSettings
}
// SetProposerSettings for mocking
func (fv *FakeValidator) SetProposerSettings(_ context.Context, settings *validatorserviceconfig.ProposerSettings) error {
func (fv *FakeValidator) SetProposerSettings(_ context.Context, settings *proposer.Settings) error {
fv.proposerSettings = settings
return nil
}

View File

@@ -26,7 +26,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/features"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v5/config/validator/service"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
@@ -37,7 +37,7 @@ import (
beacon_api "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
vdb "github.com/prysmaticlabs/prysm/v5/validator/db"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
dbCommon "github.com/prysmaticlabs/prysm/v5/validator/db/common"
"github.com/prysmaticlabs/prysm/v5/validator/graffiti"
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/local"
@@ -106,7 +106,7 @@ type validator struct {
voteStats voteStats
syncCommitteeStats syncCommitteeStats
Web3SignerConfig *remoteweb3signer.SetupConfig
proposerSettings *validatorserviceconfig.ProposerSettings
proposerSettings *proposer.Settings
walletInitializedChannel chan *wallet.Wallet
validatorsRegBatchSize int
}
@@ -517,7 +517,7 @@ func buildDuplicateError(response []*ethpb.DoppelGangerResponse_ValidatorRespons
}
// Ensures that the latest attestation history is retrieved.
func retrieveLatestRecord(recs []*kv.AttestationRecord) *kv.AttestationRecord {
func retrieveLatestRecord(recs []*dbCommon.AttestationRecord) *dbCommon.AttestationRecord {
if len(recs) == 0 {
return nil
}
@@ -1028,12 +1028,12 @@ func (v *validator) logDuties(slot primitives.Slot, currentEpochDuties []*ethpb.
}
// ProposerSettings gets the current proposer settings saved in memory validator
func (v *validator) ProposerSettings() *validatorserviceconfig.ProposerSettings {
func (v *validator) ProposerSettings() *proposer.Settings {
return v.proposerSettings
}
// SetProposerSettings sets and saves the passed in proposer settings overriding the in memory one
func (v *validator) SetProposerSettings(ctx context.Context, settings *validatorserviceconfig.ProposerSettings) error {
func (v *validator) SetProposerSettings(ctx context.Context, settings *proposer.Settings) error {
if v.db == nil {
return errors.New("db is not set")
}
@@ -1196,6 +1196,10 @@ func (v *validator) buildSignedRegReqs(ctx context.Context, pubkeys [][fieldpara
gasLimit := params.BeaconConfig().DefaultBuilderGasLimit
enabled := false
if v.ProposerSettings().DefaultConfig != nil && v.ProposerSettings().DefaultConfig.FeeRecipientConfig == nil && v.ProposerSettings().DefaultConfig.BuilderConfig != nil {
log.Warn("Builder is `enabled` in default config but will be ignored because no fee recipient was provided!")
}
if v.ProposerSettings().DefaultConfig != nil && v.ProposerSettings().DefaultConfig.FeeRecipientConfig != nil {
defaultConfig := v.ProposerSettings().DefaultConfig
feeRecipient = defaultConfig.FeeRecipientConfig.FeeRecipient // Use cli defaultBuilderConfig for fee recipient.

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"alias.go",
"convert.go",
"log.go",
"migrate.go",
"restore.go",
@@ -15,8 +16,13 @@ go_library(
],
deps = [
"//cmd:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/primitives:go_default_library",
"//io/file:go_default_library",
"//io/prompt:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//validator/db/common:go_default_library",
"//validator/db/filesystem:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/db/kv:go_default_library",
"@com_github_pkg_errors//:go_default_library",
@@ -28,17 +34,27 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"convert_test.go",
"migrate_test.go",
"restore_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",
"//io/file:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/db/common:go_default_library",
"//validator/db/filesystem:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/db/testing:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
],

View File

@@ -0,0 +1,17 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"progress.go",
"structs.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/validator/db/common",
visibility = ["//visibility:public"],
deps = [
"//config/fieldparams:go_default_library",
"//consensus-types/primitives:go_default_library",
"@com_github_k0kubun_go_ansi//:go_default_library",
"@com_github_schollz_progressbar_v3//:go_default_library",
],
)

View File

@@ -0,0 +1,27 @@
package common
import (
"fmt"
"github.com/k0kubun/go-ansi"
"github.com/schollz/progressbar/v3"
)
func InitializeProgressBar(numItems int, msg string) *progressbar.ProgressBar {
return progressbar.NewOptions(
numItems,
progressbar.OptionFullWidth(),
progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}),
progressbar.OptionOnCompletion(func() { fmt.Println() }),
progressbar.OptionSetDescription(msg),
)
}

View File

@@ -0,0 +1,28 @@
package common
import (
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
const FailedBlockSignLocalErr = "block rejected by local protection"
// Proposal representation for a validator public key.
type Proposal struct {
Slot primitives.Slot `json:"slot"`
SigningRoot []byte `json:"signing_root"`
}
// ProposalHistoryForPubkey for a validator public key.
type ProposalHistoryForPubkey struct {
Proposals []Proposal
}
// AttestationRecord which can be represented by these simple values
// for manipulation by database methods.
type AttestationRecord struct {
PubKey [fieldparams.BLSPubkeyLength]byte
Source primitives.Epoch
Target primitives.Epoch
SigningRoot []byte
}

257
validator/db/convert.go Normal file
View File

@@ -0,0 +1,257 @@
package db
import (
"context"
"path/filepath"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/io/file"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/db/common"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
)
// ConvertDatabase converts a minimal database to a complete database or a complete database to a minimal database.
// Delete the source database after conversion.
func ConvertDatabase(ctx context.Context, sourceDataDir string, targetDataDir string, minimalToComplete bool) error {
// Check if the source database exists.
var (
sourceDatabaseExists bool
err error
)
if minimalToComplete {
sourceDataBasePath := filepath.Join(sourceDataDir, filesystem.DatabaseDirName)
sourceDatabaseExists, err = file.Exists(sourceDataBasePath, file.Directory)
} else {
sourceDataBasePath := filepath.Join(sourceDataDir, kv.ProtectionDbFileName)
sourceDatabaseExists, err = file.Exists(sourceDataBasePath, file.Regular)
}
if err != nil {
return errors.Wrap(err, "could not check if source database exists")
}
// If the source database does not exist, there is nothing to convert.
if !sourceDatabaseExists {
return errors.New("source database does not exist")
}
// Get the source database.
var sourceDatabase iface.ValidatorDB
if minimalToComplete {
sourceDatabase, err = filesystem.NewStore(sourceDataDir, nil)
} else {
sourceDatabase, err = kv.NewKVStore(ctx, sourceDataDir, nil)
}
if err != nil {
return errors.Wrap(err, "could not get source database")
}
// Close the source database.
defer func() {
if err := sourceDatabase.Close(); err != nil {
log.WithError(err).Error("Failed to close source database")
}
}()
// Create the target database.
var targetDatabase iface.ValidatorDB
if minimalToComplete {
targetDatabase, err = kv.NewKVStore(ctx, targetDataDir, nil)
} else {
targetDatabase, err = filesystem.NewStore(targetDataDir, nil)
}
if err != nil {
return errors.Wrap(err, "could not create target database")
}
// Close the target database.
defer func() {
if err := targetDatabase.Close(); err != nil {
log.WithError(err).Error("Failed to close target database")
}
}()
// Genesis
// -------
// Get the genesis validators root.
genesisValidatorRoot, err := sourceDatabase.GenesisValidatorsRoot(ctx)
if err != nil {
return errors.Wrap(err, "could not get genesis validators root from source database")
}
// Save the genesis validators root.
if err := targetDatabase.SaveGenesisValidatorsRoot(ctx, genesisValidatorRoot); err != nil {
return errors.Wrap(err, "could not save genesis validators root")
}
// Graffiti
// --------
// Get the graffiti file hash.
graffitiFileHash, exists, err := sourceDatabase.GraffitiFileHash()
if err != nil {
return errors.Wrap(err, "could not get graffiti file hash from source database")
}
if exists {
// Calling GraffitiOrderedIndex will save the graffiti file hash.
if _, err := targetDatabase.GraffitiOrderedIndex(ctx, graffitiFileHash); err != nil {
return errors.Wrap(err, "could get graffiti ordered index")
}
}
// Get the graffiti ordered index.
graffitiOrderedIndex, err := sourceDatabase.GraffitiOrderedIndex(ctx, graffitiFileHash)
if err != nil {
return errors.Wrap(err, "could not get graffiti ordered index from source database")
}
// Save the graffiti ordered index.
if err := targetDatabase.SaveGraffitiOrderedIndex(ctx, graffitiOrderedIndex); err != nil {
return errors.Wrap(err, "could not save graffiti ordered index")
}
// Proposer settings
// -----------------
// Get the proposer settings.
proposerSettings, err := sourceDatabase.ProposerSettings(ctx)
if err != nil {
return errors.Wrap(err, "could not get proposer settings from source database")
}
// Save the proposer settings.
if err := targetDatabase.SaveProposerSettings(ctx, proposerSettings); err != nil {
return errors.Wrap(err, "could not save proposer settings")
}
// Attestations
// ------------
// Get all public keys that have attested.
attestedPublicKeys, err := sourceDatabase.AttestedPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not get attested public keys from source database")
}
// Initialize the progress bar.
bar := common.InitializeProgressBar(
len(attestedPublicKeys),
"Processing attestations:",
)
for _, pubkey := range attestedPublicKeys {
// Update the progress bar.
if err := bar.Add(1); err != nil {
log.WithError(err).Debug("Could not increase progress bar")
}
// Get the attestation records.
attestationRecords, err := sourceDatabase.AttestationHistoryForPubKey(ctx, pubkey)
if err != nil {
return errors.Wrap(err, "could not get attestation history for public key")
}
// If there are no attestation records, skip this public key.
if len(attestationRecords) == 0 {
continue
}
highestSource, highestTarget := primitives.Epoch(0), primitives.Epoch(0)
for _, record := range attestationRecords {
// If the record is nil, skip it.
if record == nil {
continue
}
// Get the highest source and target epoch.
if record.Source > highestSource {
highestSource = record.Source
}
if record.Target > highestTarget {
highestTarget = record.Target
}
}
// Create the indexed attestation with the highest source and target epoch.
indexedAttestation := &ethpb.IndexedAttestation{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: highestSource,
},
Target: &ethpb.Checkpoint{
Epoch: highestTarget,
},
},
}
if err := targetDatabase.SaveAttestationForPubKey(ctx, pubkey, [fieldparams.RootLength]byte{}, indexedAttestation); err != nil {
return errors.Wrap(err, "could not save attestation for public key")
}
}
// Proposals
// ---------
// Get all pubkeys in database.
proposedPublicKeys, err := sourceDatabase.ProposedPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not get proposed public keys from source database")
}
// Initialize the progress bar.
bar = common.InitializeProgressBar(
len(attestedPublicKeys),
"Processing proposals:",
)
for _, pubkey := range proposedPublicKeys {
// Update the progress bar.
if err := bar.Add(1); err != nil {
log.WithError(err).Debug("Could not increase progress bar")
}
// Get the proposal history.
proposals, err := sourceDatabase.ProposalHistoryForPubKey(ctx, pubkey)
if err != nil {
return errors.Wrap(err, "could not get proposal history for public key")
}
// If there are no proposals, skip this public key.
if len(proposals) == 0 {
continue
}
highestSlot := primitives.Slot(0)
for _, proposal := range proposals {
// If proposal is nil, skip it.
if proposal == nil {
continue
}
// Get the highest slot.
if proposal.Slot > highestSlot {
highestSlot = proposal.Slot
}
}
// Save the proposal history for the highest slot.
if err := targetDatabase.SaveProposalHistoryForSlot(ctx, pubkey, highestSlot, nil); err != nil {
return errors.Wrap(err, "could not save proposal history for public key")
}
}
// Delete the source database.
if err := sourceDatabase.ClearDB(); err != nil {
return errors.Wrap(err, "could not delete source database")
}
return nil
}

View File

@@ -0,0 +1,265 @@
package db
import (
"context"
"fmt"
"path/filepath"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/io/file"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/validator/db/common"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
)
func getPubkeyFromString(t *testing.T, pubkeyString string) [fieldparams.BLSPubkeyLength]byte {
var pubkey [fieldparams.BLSPubkeyLength]byte
pubkeyBytes, err := hexutil.Decode(pubkeyString)
require.NoError(t, err, "hexutil.Decode should not return an error")
copy(pubkey[:], pubkeyBytes)
return pubkey
}
func getFeeRecipientFromString(t *testing.T, feeRecipientString string) [fieldparams.FeeRecipientLength]byte {
var feeRecipient [fieldparams.FeeRecipientLength]byte
feeRecipientBytes, err := hexutil.Decode(feeRecipientString)
require.NoError(t, err, "hexutil.Decode should not return an error")
copy(feeRecipient[:], feeRecipientBytes)
return feeRecipient
}
func TestDB_ConvertDatabase(t *testing.T) {
ctx := context.Background()
pubKeyString1 := "0x80000060606fa05c7339dd7bcd0d3e4d8b573fa30dea2fdb4997031a703e3300326e3c054be682f92d9c367cd647bbea"
pubKeyString2 := "0x81000060606fa05c7339dd7bcd0d3e4d8b573fa30dea2fdb4997031a703e3300326e3c054be682f92d9c367cd647bbea"
defaultFeeRecipientString := "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
customFeeRecipientString := "0xeD33259a056F4fb449FFB7B7E2eCB43a9B5685Bf"
pubkey1 := getPubkeyFromString(t, pubKeyString1)
pubkey2 := getPubkeyFromString(t, pubKeyString2)
defaultFeeRecipient := getFeeRecipientFromString(t, defaultFeeRecipientString)
customFeeRecipient := getFeeRecipientFromString(t, customFeeRecipientString)
for _, minimalToComplete := range []bool{false, true} {
t.Run(fmt.Sprintf("minimalToComplete=%v", minimalToComplete), func(t *testing.T) {
// Create signing root
signingRoot := [fieldparams.RootLength]byte{}
var signingRootBytes []byte
if minimalToComplete {
signingRootBytes = signingRoot[:]
}
// Create database directoriy path.
datadir := t.TempDir()
// Run source DB preparation.
// --------------------------
// Create the source database.
var (
sourceDatabase, targetDatabase iface.ValidatorDB
err error
)
if minimalToComplete {
sourceDatabase, err = filesystem.NewStore(datadir, &filesystem.Config{
PubKeys: [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2},
})
} else {
sourceDatabase, err = kv.NewKVStore(ctx, datadir, &kv.Config{
PubKeys: [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2},
})
}
require.NoError(t, err, "could not create source database")
// Save the genesis validator root.
expectedGenesisValidatorRoot := []byte("genesis-validator-root")
err = sourceDatabase.SaveGenesisValidatorsRoot(ctx, expectedGenesisValidatorRoot)
require.NoError(t, err, "could not save genesis validator root")
// Save the graffiti file hash.
// (Getting the graffiti ordered index will set the graffiti file hash)
expectedGraffitiFileHash := [32]byte{1}
_, err = sourceDatabase.GraffitiOrderedIndex(ctx, expectedGraffitiFileHash)
require.NoError(t, err, "could not get graffiti ordered index")
// Save the graffiti ordered index.
expectedGraffitiOrderedIndex := uint64(1)
err = sourceDatabase.SaveGraffitiOrderedIndex(ctx, expectedGraffitiOrderedIndex)
require.NoError(t, err, "could not save graffiti ordered index")
// Save the proposer settings.
var relays []string = nil
expectedProposerSettings := &proposer.Settings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
pubkey1: {
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: customFeeRecipient,
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: true,
GasLimit: 42,
Relays: relays,
},
},
},
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
FeeRecipient: defaultFeeRecipient,
},
BuilderConfig: &proposer.BuilderConfig{
Enabled: false,
GasLimit: 43,
Relays: relays,
},
},
}
err = sourceDatabase.SaveProposerSettings(ctx, expectedProposerSettings)
require.NoError(t, err, "could not save proposer settings")
// Save some attestations.
completeAttestations := []*ethpb.IndexedAttestation{
{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 1,
},
Target: &ethpb.Checkpoint{
Epoch: 2,
},
},
},
{
Data: &ethpb.AttestationData{
Source: &ethpb.Checkpoint{
Epoch: 2,
},
Target: &ethpb.Checkpoint{
Epoch: 3,
},
},
},
}
expectedAttestationRecords1 := []*common.AttestationRecord{
{
PubKey: pubkey1,
Source: primitives.Epoch(2),
Target: primitives.Epoch(3),
SigningRoot: signingRootBytes,
},
}
expectedAttestationRecords2 := []*common.AttestationRecord{
{
PubKey: pubkey2,
Source: primitives.Epoch(2),
Target: primitives.Epoch(3),
SigningRoot: signingRootBytes,
},
}
err = sourceDatabase.SaveAttestationsForPubKey(ctx, pubkey1, [][]byte{{1}, {2}}, completeAttestations)
require.NoError(t, err, "could not save attestations")
err = sourceDatabase.SaveAttestationsForPubKey(ctx, pubkey2, [][]byte{{1}, {2}}, completeAttestations)
require.NoError(t, err, "could not save attestations")
// Save some block proposals.
err = sourceDatabase.SaveProposalHistoryForSlot(ctx, pubkey1, 42, []byte{})
require.NoError(t, err, "could not save block proposal")
err = sourceDatabase.SaveProposalHistoryForSlot(ctx, pubkey1, 43, []byte{})
require.NoError(t, err, "could not save block proposal")
expectedProposals := []*common.Proposal{
{
Slot: 43,
SigningRoot: signingRootBytes,
},
}
// Close the source database.
err = sourceDatabase.Close()
require.NoError(t, err, "could not close source database")
// Source to target DB conversion.
// ----------------------------------------
err = ConvertDatabase(ctx, datadir, datadir, minimalToComplete)
require.NoError(t, err, "could not convert source to target database")
// Check the target database.
// --------------------------
if minimalToComplete {
targetDatabase, err = kv.NewKVStore(ctx, datadir, nil)
} else {
targetDatabase, err = filesystem.NewStore(datadir, nil)
}
require.NoError(t, err, "could not get minimal database")
// Check the genesis validator root.
actualGenesisValidatoRoot, err := targetDatabase.GenesisValidatorsRoot(ctx)
require.NoError(t, err, "could not get genesis validator root from target database")
require.DeepSSZEqual(t, expectedGenesisValidatorRoot, actualGenesisValidatoRoot, "genesis validator root should match")
// Check the graffiti file hash.
actualGraffitiFileHash, exists, err := targetDatabase.GraffitiFileHash()
require.NoError(t, err, "could not get graffiti file hash from target database")
require.Equal(t, true, exists, "graffiti file hash should exist")
require.Equal(t, expectedGraffitiFileHash, actualGraffitiFileHash, "graffiti file hash should match")
// Check the graffiti ordered index.
actualGraffitiOrderedIndex, err := targetDatabase.GraffitiOrderedIndex(ctx, expectedGraffitiFileHash)
require.NoError(t, err, "could not get graffiti ordered index from target database")
require.Equal(t, expectedGraffitiOrderedIndex, actualGraffitiOrderedIndex, "graffiti ordered index should match")
// Check the proposer settings.
actualProposerSettings, err := targetDatabase.ProposerSettings(ctx)
require.NoError(t, err, "could not get proposer settings from target database")
require.DeepEqual(t, expectedProposerSettings, actualProposerSettings, "proposer settings should match")
// Check the attestations.
actualAttestationRecords, err := targetDatabase.AttestationHistoryForPubKey(ctx, pubkey1)
require.NoError(t, err, "could not get attestations from target database")
require.DeepEqual(t, expectedAttestationRecords1, actualAttestationRecords, "attestations should match")
actualAttestationRecords, err = targetDatabase.AttestationHistoryForPubKey(ctx, pubkey2)
require.NoError(t, err, "could not get attestations from target database")
require.DeepEqual(t, expectedAttestationRecords2, actualAttestationRecords, "attestations should match")
// Check the block proposals.
actualProposals, err := targetDatabase.ProposalHistoryForPubKey(ctx, pubkey1)
require.NoError(t, err, "could not get block proposals from target database")
require.DeepEqual(t, expectedProposals, actualProposals, "block proposals should match")
// Close the target database.
err = targetDatabase.Close()
require.NoError(t, err, "could not close target database")
// Check the source database does not exist anymore.
var existing bool
if minimalToComplete {
databasePath := filepath.Join(datadir, filesystem.DatabaseDirName)
existing, err = file.Exists(databasePath, file.Directory)
} else {
databasePath := filepath.Join(datadir, kv.ProtectionDbFileName)
existing, err = file.Exists(databasePath, file.Regular)
}
require.NoError(t, err, "could not check if source database exists")
require.Equal(t, false, existing, "source database should not exist")
})
}
}

View File

@@ -0,0 +1,70 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"attester_protection.go",
"db.go",
"genesis.go",
"graffiti.go",
"import.go",
"migration.go",
"proposer_protection.go",
"proposer_settings.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/validator/db/filesystem",
visibility = ["//visibility:public"],
deps = [
"//config/fieldparams:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//io/file:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/db/common:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/helpers:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@in_gopkg_yaml_v3//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"attester_protection_test.go",
"db_test.go",
"genesis_test.go",
"graffiti_test.go",
"import_test.go",
"migration_test.go",
"proposer_protection_test.go",
"proposer_settings_test.go",
],
embed = [":go_default_library"],
deps = [
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library",
"//io/file:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//validator/db/common:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"//validator/testing:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
],
)

Some files were not shown because too many files have changed in this diff Show More