mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-11 14:28:09 -05:00
Compare commits
8 Commits
evil-shape
...
debugLogs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
039b86bd89 | ||
|
|
9619170df1 | ||
|
|
4da0abace7 | ||
|
|
d919f800e3 | ||
|
|
38f095d556 | ||
|
|
04010d45c8 | ||
|
|
1a048a2f2a | ||
|
|
3df2dedbb2 |
@@ -28,13 +28,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
getSignedBlockPath = "/eth/v2/beacon/blocks"
|
||||
getBlockRootPath = "/eth/v1/beacon/blocks/{{.Id}}/root"
|
||||
getForkForStatePath = "/eth/v1/beacon/states/{{.Id}}/fork"
|
||||
getWeakSubjectivityPath = "/eth/v1/beacon/weak_subjectivity"
|
||||
getForkSchedulePath = "/eth/v1/config/fork_schedule"
|
||||
getStatePath = "/eth/v2/debug/beacon/states"
|
||||
getNodeVersionPath = "/eth/v1/node/version"
|
||||
getSignedBlockPath = "/eth/v2/beacon/blocks"
|
||||
getBlockRootPath = "/eth/v1/beacon/blocks/{{.Id}}/root"
|
||||
getForkForStatePath = "/eth/v1/beacon/states/{{.Id}}/fork"
|
||||
getWeakSubjectivityPath = "/eth/v1/beacon/weak_subjectivity"
|
||||
getForkSchedulePath = "/eth/v1/config/fork_schedule"
|
||||
getStatePath = "/eth/v2/debug/beacon/states"
|
||||
getNodeVersionPath = "/eth/v1/node/version"
|
||||
changeBLStoExecutionPath = "/eth/v1/beacon/pool/bls_to_execution_changes"
|
||||
)
|
||||
|
||||
// StateOrBlockId represents the block_id / state_id parameters that several of the Eth Beacon API methods accept.
|
||||
@@ -146,7 +147,6 @@ func withSSZEncoding() reqOption {
|
||||
// get is a generic, opinionated GET function to reduce boilerplate amongst the getters in this package.
|
||||
func (c *Client) get(ctx context.Context, path string, opts ...reqOption) ([]byte, error) {
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: path})
|
||||
log.Printf("requesting %s", u.String())
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -343,6 +343,60 @@ func (c *Client) GetWeakSubjectivity(ctx context.Context) (*WeakSubjectivityData
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SubmitChangeBLStoExecution calls a beacon API endpoint to set the withdrawal addresses based on the given signed messages.
|
||||
// If the API responds with something other than OK there will be failure messages associated to the corresponding request message.
|
||||
func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: changeBLStoExecutionPath})
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal JSON")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid format, failed to create new POST request object")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = resp.Body.Close()
|
||||
}()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
errorJson := &apimiddleware.IndexedVerificationFailureErrorJson{}
|
||||
if err := decoder.Decode(errorJson); err != nil {
|
||||
return errors.Wrapf(err, "failed to decode error JSON for %s", resp.Request.URL)
|
||||
}
|
||||
for _, failure := range errorJson.Failures {
|
||||
w := request[failure.Index].Message
|
||||
log.WithFields(log.Fields{
|
||||
"validator_index": w.ValidatorIndex,
|
||||
"withdrawal_address": w.ToExecutionAddress,
|
||||
}).Error(failure.Message)
|
||||
}
|
||||
return errors.Errorf("POST error %d: %s", errorJson.Code, errorJson.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBLStoExecutionChanges gets all the set withdrawal messages in the node's operation pool.
|
||||
// Returns a struct representation of json response.
|
||||
func (c *Client) GetBLStoExecutionChanges(ctx context.Context) (*apimiddleware.BLSToExecutionChangesPoolResponseJson, error) {
|
||||
body, err := c.get(ctx, changeBLStoExecutionPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
poolResponse := &apimiddleware.BLSToExecutionChangesPoolResponseJson{}
|
||||
err = json.Unmarshal(body, poolResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return poolResponse, nil
|
||||
}
|
||||
|
||||
func non200Err(response *http.Response) error {
|
||||
bodyBytes, err := io.ReadAll(response.Body)
|
||||
var body string
|
||||
|
||||
@@ -49,9 +49,13 @@ func (s *Service) validateMergeBlock(ctx context.Context, b interfaces.SignedBea
|
||||
if payload.IsNil() {
|
||||
return errors.New("nil execution payload")
|
||||
}
|
||||
if err := validateTerminalBlockHash(b.Block().Slot(), payload); err != nil {
|
||||
ok, err := canUseValidatedTerminalBlockHash(b.Block().Slot(), payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not validate terminal block hash")
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
mergeBlockParentHash, mergeBlockTD, err := s.getBlkParentHashAndTD(ctx, payload.ParentHash())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get merge block parent hash and total difficulty")
|
||||
@@ -105,7 +109,7 @@ func (s *Service) getBlkParentHashAndTD(ctx context.Context, blkHash []byte) ([]
|
||||
return blk.ParentHash[:], blkTDUint256, nil
|
||||
}
|
||||
|
||||
// validateTerminalBlockHash validates if the merge block is a valid terminal PoW block.
|
||||
// canUseValidatedTerminalBlockHash validates if the merge block is a valid terminal PoW block.
|
||||
// spec code:
|
||||
// if TERMINAL_BLOCK_HASH != Hash32():
|
||||
//
|
||||
@@ -113,17 +117,17 @@ func (s *Service) getBlkParentHashAndTD(ctx context.Context, blkHash []byte) ([]
|
||||
// assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
|
||||
// assert block.body.execution_payload.parent_hash == TERMINAL_BLOCK_HASH
|
||||
// return
|
||||
func validateTerminalBlockHash(blkSlot types.Slot, payload interfaces.ExecutionData) error {
|
||||
func canUseValidatedTerminalBlockHash(blkSlot types.Slot, payload interfaces.ExecutionData) (bool, error) {
|
||||
if bytesutil.ToBytes32(params.BeaconConfig().TerminalBlockHash.Bytes()) == [32]byte{} {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
if params.BeaconConfig().TerminalBlockHashActivationEpoch > slots.ToEpoch(blkSlot) {
|
||||
return errors.New("terminal block hash activation epoch not reached")
|
||||
return false, errors.New("terminal block hash activation epoch not reached")
|
||||
}
|
||||
if !bytes.Equal(payload.ParentHash(), params.BeaconConfig().TerminalBlockHash.Bytes()) {
|
||||
return errors.New("parent hash does not match terminal block hash")
|
||||
return false, errors.New("parent hash does not match terminal block hash")
|
||||
}
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// validateTerminalBlockDifficulties validates terminal pow block by comparing own total difficulty with parent's total difficulty.
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/util"
|
||||
)
|
||||
|
||||
func Test_validTerminalPowBlock(t *testing.T) {
|
||||
@@ -213,20 +214,42 @@ func Test_getBlkParentHashAndTD(t *testing.T) {
|
||||
func Test_validateTerminalBlockHash(t *testing.T) {
|
||||
wrapped, err := blocks.WrappedExecutionPayload(&enginev1.ExecutionPayload{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, validateTerminalBlockHash(1, wrapped))
|
||||
ok, err := canUseValidatedTerminalBlockHash(1, wrapped)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, ok)
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.TerminalBlockHash = [32]byte{0x01}
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
require.ErrorContains(t, "terminal block hash activation epoch not reached", validateTerminalBlockHash(1, wrapped))
|
||||
ok, err = canUseValidatedTerminalBlockHash(1, wrapped)
|
||||
require.ErrorContains(t, "terminal block hash activation epoch not reached", err)
|
||||
require.Equal(t, false, ok)
|
||||
|
||||
cfg.TerminalBlockHashActivationEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
require.ErrorContains(t, "parent hash does not match terminal block hash", validateTerminalBlockHash(1, wrapped))
|
||||
ok, err = canUseValidatedTerminalBlockHash(1, wrapped)
|
||||
require.ErrorContains(t, "parent hash does not match terminal block hash", err)
|
||||
require.Equal(t, false, ok)
|
||||
|
||||
wrapped, err = blocks.WrappedExecutionPayload(&enginev1.ExecutionPayload{
|
||||
ParentHash: cfg.TerminalBlockHash.Bytes(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, validateTerminalBlockHash(1, wrapped))
|
||||
ok, err = canUseValidatedTerminalBlockHash(1, wrapped)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
ctx := context.Background()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
opts := []Option{
|
||||
WithDatabase(beaconDB),
|
||||
WithStateGen(stategen.New(beaconDB, doublylinkedtree.New())),
|
||||
}
|
||||
service, err := NewService(ctx, opts...)
|
||||
require.NoError(t, err)
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlockBellatrix(ðpb.SignedBeaconBlockBellatrix{}))
|
||||
require.NoError(t, err)
|
||||
blk.Block().SetSlot(1)
|
||||
require.NoError(t, blk.Block().Body().SetExecution(wrapped))
|
||||
require.NoError(t, service.validateMergeBlock(ctx, blk))
|
||||
}
|
||||
|
||||
@@ -557,22 +557,6 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Be
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) handleBlockBLSToExecChanges(blk interfaces.BeaconBlock) error {
|
||||
if blk.Version() < version.Capella {
|
||||
return nil
|
||||
}
|
||||
changes, err := blk.Body().BLSToExecutionChanges()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get BLSToExecutionChanges")
|
||||
}
|
||||
for _, change := range changes {
|
||||
if err := s.cfg.BLSToExecPool.MarkIncluded(change); err != nil {
|
||||
return errors.Wrap(err, "could not mark BLSToExecutionChange as included")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSlashingsToForkChoiceStore inserts attester slashing indices to fork choice store.
|
||||
// To call this function, it's caller's responsibility to ensure the slashing object is valid.
|
||||
func (s *Service) InsertSlashingsToForkChoiceStore(ctx context.Context, slashings []*ethpb.AttesterSlashing) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/blstoexec"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
|
||||
@@ -2305,65 +2304,6 @@ func TestFillMissingBlockPayloadId_DiffSlotExitEarly(t *testing.T) {
|
||||
require.NoError(t, service.fillMissingBlockPayloadId(ctx, time.Unix(int64(params.BeaconConfig().SecondsPerSlot/2), 0)))
|
||||
}
|
||||
|
||||
func TestHandleBBlockBLSToExecutionChanges(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
fc := doublylinkedtree.New()
|
||||
pool := blstoexec.NewPool()
|
||||
opts := []Option{
|
||||
WithDatabase(beaconDB),
|
||||
WithStateGen(stategen.New(beaconDB, fc)),
|
||||
WithForkChoiceStore(fc),
|
||||
WithStateNotifier(&mock.MockStateNotifier{}),
|
||||
WithBLSToExecPool(pool),
|
||||
}
|
||||
service, err := NewService(ctx, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("pre Capella block", func(t *testing.T) {
|
||||
body := ðpb.BeaconBlockBodyBellatrix{}
|
||||
pbb := ðpb.BeaconBlockBellatrix{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := consensusblocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
})
|
||||
|
||||
t.Run("Post Capella no changes", func(t *testing.T) {
|
||||
body := ðpb.BeaconBlockBodyCapella{}
|
||||
pbb := ðpb.BeaconBlockCapella{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := consensusblocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
})
|
||||
|
||||
t.Run("Post Capella some changes", func(t *testing.T) {
|
||||
idx := types.ValidatorIndex(123)
|
||||
change := ðpb.BLSToExecutionChange{
|
||||
ValidatorIndex: idx,
|
||||
}
|
||||
signedChange := ðpb.SignedBLSToExecutionChange{
|
||||
Message: change,
|
||||
}
|
||||
body := ðpb.BeaconBlockBodyCapella{
|
||||
BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{signedChange},
|
||||
}
|
||||
pbb := ðpb.BeaconBlockCapella{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := consensusblocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool.InsertBLSToExecChange(signedChange)
|
||||
require.Equal(t, true, pool.ValidatorExists(idx))
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
require.Equal(t, false, pool.ValidatorExists(idx))
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to simulate the block being on time or delayed for proposer
|
||||
// boost. It alters the genesisTime tracked by the store.
|
||||
func driftGenesisTime(s *Service, slot int64, delay int64) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v3/monitoring/tracing"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v3/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v3/time"
|
||||
"github.com/prysmaticlabs/prysm/v3/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
@@ -150,6 +151,11 @@ func (s *Service) handlePostBlockOperations(b interfaces.BeaconBlock) error {
|
||||
s.cfg.ExitPool.MarkIncluded(e)
|
||||
}
|
||||
|
||||
// Mark block BLS changes as seen so we don't include same ones in future blocks.
|
||||
if err := s.handleBlockBLSToExecChanges(b); err != nil {
|
||||
return errors.Wrap(err, "could not process BLSToExecutionChanges")
|
||||
}
|
||||
|
||||
// Mark attester slashings as seen so we don't include same ones in future blocks.
|
||||
for _, as := range b.Body().AttesterSlashings() {
|
||||
s.cfg.SlashingPool.MarkIncludedAttesterSlashing(as)
|
||||
@@ -157,6 +163,20 @@ func (s *Service) handlePostBlockOperations(b interfaces.BeaconBlock) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) handleBlockBLSToExecChanges(blk interfaces.BeaconBlock) error {
|
||||
if blk.Version() < version.Capella {
|
||||
return nil
|
||||
}
|
||||
changes, err := blk.Body().BLSToExecutionChanges()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get BLSToExecutionChanges")
|
||||
}
|
||||
for _, change := range changes {
|
||||
s.cfg.BLSToExecPool.MarkIncluded(change)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This checks whether it's time to start saving hot state to DB.
|
||||
// It's time when there's `epochsSinceFinalitySaveHotStateDB` epochs of non-finality.
|
||||
func (s *Service) checkSaveHotStateDB(ctx context.Context) error {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/blstoexec"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/voluntaryexits"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||
@@ -332,3 +333,62 @@ func TestCheckSaveHotStateDB_Overflow(t *testing.T) {
|
||||
require.NoError(t, s.checkSaveHotStateDB(context.Background()))
|
||||
assert.LogsDoNotContain(t, hook, "Entering mode to save hot states in DB")
|
||||
}
|
||||
|
||||
func TestHandleBlockBLSToExecutionChanges(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
fc := doublylinkedtree.New()
|
||||
pool := blstoexec.NewPool()
|
||||
opts := []Option{
|
||||
WithDatabase(beaconDB),
|
||||
WithStateGen(stategen.New(beaconDB, fc)),
|
||||
WithForkChoiceStore(fc),
|
||||
WithStateNotifier(&blockchainTesting.MockStateNotifier{}),
|
||||
WithBLSToExecPool(pool),
|
||||
}
|
||||
service, err := NewService(ctx, opts...)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("pre Capella block", func(t *testing.T) {
|
||||
body := ðpb.BeaconBlockBodyBellatrix{}
|
||||
pbb := ðpb.BeaconBlockBellatrix{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := blocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
})
|
||||
|
||||
t.Run("Post Capella no changes", func(t *testing.T) {
|
||||
body := ðpb.BeaconBlockBodyCapella{}
|
||||
pbb := ðpb.BeaconBlockCapella{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := blocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
})
|
||||
|
||||
t.Run("Post Capella some changes", func(t *testing.T) {
|
||||
idx := types.ValidatorIndex(123)
|
||||
change := ðpb.BLSToExecutionChange{
|
||||
ValidatorIndex: idx,
|
||||
}
|
||||
signedChange := ðpb.SignedBLSToExecutionChange{
|
||||
Message: change,
|
||||
}
|
||||
body := ðpb.BeaconBlockBodyCapella{
|
||||
BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{signedChange},
|
||||
}
|
||||
pbb := ðpb.BeaconBlockCapella{
|
||||
Body: body,
|
||||
}
|
||||
blk, err := blocks.NewBeaconBlock(pbb)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool.InsertBLSToExecChange(signedChange)
|
||||
require.Equal(t, true, pool.ValidatorExists(idx))
|
||||
require.NoError(t, service.handleBlockBLSToExecChanges(blk))
|
||||
require.Equal(t, false, pool.ValidatorExists(idx))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,3 +131,118 @@ func TestProcessVoluntaryExits_AppliesCorrectStatus(t *testing.T) {
|
||||
helpers.ActivationExitEpoch(types.Epoch(state.Slot()/params.BeaconConfig().SlotsPerEpoch)), newRegistry[0].ExitEpoch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature(t *testing.T) {
|
||||
type args struct {
|
||||
currentSlot types.Slot
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
setup func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, *ethpb.Fork, []byte, error)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "Empty Exit",
|
||||
args: args{
|
||||
currentSlot: 0,
|
||||
},
|
||||
setup: func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, *ethpb.Fork, []byte, error) {
|
||||
fork := ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: 0,
|
||||
}
|
||||
genesisRoot := [32]byte{'a'}
|
||||
return ðpb.Validator{}, ðpb.SignedVoluntaryExit{}, fork, genesisRoot[:], nil
|
||||
},
|
||||
wantErr: "nil exit",
|
||||
},
|
||||
{
|
||||
name: "Happy Path",
|
||||
args: args{
|
||||
currentSlot: (params.BeaconConfig().SlotsPerEpoch * 2) + 1,
|
||||
},
|
||||
setup: func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, *ethpb.Fork, []byte, error) {
|
||||
fork := ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: 0,
|
||||
}
|
||||
signedExit := ðpb.SignedVoluntaryExit{
|
||||
Exit: ðpb.VoluntaryExit{
|
||||
Epoch: 2,
|
||||
ValidatorIndex: 0,
|
||||
},
|
||||
}
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
validator := bs.Validators()[0]
|
||||
validator.ActivationEpoch = 1
|
||||
err := bs.UpdateValidatorAtIndex(0, validator)
|
||||
require.NoError(t, err)
|
||||
sb, err := signing.ComputeDomainAndSign(bs, signedExit.Exit.Epoch, signedExit.Exit, params.BeaconConfig().DomainVoluntaryExit, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
signedExit.Signature = sig.Marshal()
|
||||
return validator, signedExit, fork, bs.GenesisValidatorsRoot(), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad signature",
|
||||
args: args{
|
||||
currentSlot: (params.BeaconConfig().SlotsPerEpoch * 2) + 1,
|
||||
},
|
||||
setup: func() (*ethpb.Validator, *ethpb.SignedVoluntaryExit, *ethpb.Fork, []byte, error) {
|
||||
fork := ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: 0,
|
||||
}
|
||||
signedExit := ðpb.SignedVoluntaryExit{
|
||||
Exit: ðpb.VoluntaryExit{
|
||||
Epoch: 2,
|
||||
ValidatorIndex: 0,
|
||||
},
|
||||
}
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
validator := bs.Validators()[0]
|
||||
validator.ActivationEpoch = 1
|
||||
|
||||
sb, err := signing.ComputeDomainAndSign(bs, signedExit.Exit.Epoch, signedExit.Exit, params.BeaconConfig().DomainVoluntaryExit, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
signedExit.Signature = sig.Marshal()
|
||||
genesisRoot := [32]byte{'a'}
|
||||
// use wrong genesis root and don't update validator
|
||||
return validator, signedExit, fork, genesisRoot[:], nil
|
||||
},
|
||||
wantErr: "signature did not verify",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := params.BeaconConfig().ShardCommitteePeriod
|
||||
params.BeaconConfig().ShardCommitteePeriod = 0
|
||||
validator, signedExit, fork, genesisRoot, err := tt.setup()
|
||||
require.NoError(t, err)
|
||||
rvalidator, err := state_native.NewValidator(validator)
|
||||
require.NoError(t, err)
|
||||
err = blocks.VerifyExitAndSignature(
|
||||
rvalidator,
|
||||
tt.args.currentSlot,
|
||||
fork,
|
||||
signedExit,
|
||||
genesisRoot,
|
||||
)
|
||||
if tt.wantErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
}
|
||||
params.BeaconConfig().ShardCommitteePeriod = c // prevent contamination
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ go_library(
|
||||
"//container/doubly-linked-list:go_default_library",
|
||||
"//crypto/bls/blst:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ func (m *PoolMock) PendingBLSToExecChanges() ([]*eth.SignedBLSToExecutionChange,
|
||||
}
|
||||
|
||||
// BLSToExecChangesForInclusion --
|
||||
func (m *PoolMock) BLSToExecChangesForInclusion(_ state.BeaconState) ([]*eth.SignedBLSToExecutionChange, error) {
|
||||
func (m *PoolMock) BLSToExecChangesForInclusion(_ state.ReadOnlyBeaconState) ([]*eth.SignedBLSToExecutionChange, error) {
|
||||
return m.Changes, nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (m *PoolMock) InsertBLSToExecChange(change *eth.SignedBLSToExecutionChange)
|
||||
}
|
||||
|
||||
// MarkIncluded --
|
||||
func (*PoolMock) MarkIncluded(_ *eth.SignedBLSToExecutionChange) error {
|
||||
func (*PoolMock) MarkIncluded(_ *eth.SignedBLSToExecutionChange) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||
@@ -19,9 +18,9 @@ import (
|
||||
// This pool is used by proposers to insert BLS-to-execution-change objects into new blocks.
|
||||
type PoolManager interface {
|
||||
PendingBLSToExecChanges() ([]*ethpb.SignedBLSToExecutionChange, error)
|
||||
BLSToExecChangesForInclusion(state.BeaconState) ([]*ethpb.SignedBLSToExecutionChange, error)
|
||||
BLSToExecChangesForInclusion(beaconState state.ReadOnlyBeaconState) ([]*ethpb.SignedBLSToExecutionChange, error)
|
||||
InsertBLSToExecChange(change *ethpb.SignedBLSToExecutionChange)
|
||||
MarkIncluded(change *ethpb.SignedBLSToExecutionChange) error
|
||||
MarkIncluded(change *ethpb.SignedBLSToExecutionChange)
|
||||
ValidatorExists(idx types.ValidatorIndex) bool
|
||||
}
|
||||
|
||||
@@ -61,9 +60,9 @@ func (p *Pool) PendingBLSToExecChanges() ([]*ethpb.SignedBLSToExecutionChange, e
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// BLSToExecChangesForInclusion returns objects that are ready for inclusion at the given slot.
|
||||
// BLSToExecChangesForInclusion returns objects that are ready for inclusion.
|
||||
// This method will not return more than the block enforced MaxBlsToExecutionChanges.
|
||||
func (p *Pool) BLSToExecChangesForInclusion(st state.BeaconState) ([]*ethpb.SignedBLSToExecutionChange, error) {
|
||||
func (p *Pool) BLSToExecChangesForInclusion(st state.ReadOnlyBeaconState) ([]*ethpb.SignedBLSToExecutionChange, error) {
|
||||
p.lock.RLock()
|
||||
length := int(math.Min(float64(params.BeaconConfig().MaxBlsToExecutionChanges), float64(p.pending.Len())))
|
||||
result := make([]*ethpb.SignedBLSToExecutionChange, 0, length)
|
||||
@@ -79,9 +78,7 @@ func (p *Pool) BLSToExecChangesForInclusion(st state.BeaconState) ([]*ethpb.Sign
|
||||
logrus.WithError(err).Warning("removing invalid BLSToExecutionChange from pool")
|
||||
// MarkIncluded removes the invalid change from the pool
|
||||
p.lock.RUnlock()
|
||||
if err := p.MarkIncluded(change); err != nil {
|
||||
return nil, errors.Wrap(err, "could not mark BLSToExecutionChange as included")
|
||||
}
|
||||
p.MarkIncluded(change)
|
||||
p.lock.RLock()
|
||||
} else {
|
||||
result = append(result, change)
|
||||
@@ -118,9 +115,7 @@ func (p *Pool) BLSToExecChangesForInclusion(st state.BeaconState) ([]*ethpb.Sign
|
||||
}
|
||||
if !signature.Verify(cSet.PublicKeys[i], cSet.Messages[i][:]) {
|
||||
logrus.Warning("removing BLSToExecutionChange with invalid signature from pool")
|
||||
if err := p.MarkIncluded(result[i]); err != nil {
|
||||
return nil, errors.Wrap(err, "could not mark BLSToExecutionChange as included")
|
||||
}
|
||||
p.MarkIncluded(result[i])
|
||||
} else {
|
||||
verified = append(verified, result[i])
|
||||
}
|
||||
@@ -143,19 +138,18 @@ func (p *Pool) InsertBLSToExecChange(change *ethpb.SignedBLSToExecutionChange) {
|
||||
}
|
||||
|
||||
// MarkIncluded is used when an object has been included in a beacon block. Every block seen by this
|
||||
// listNode should call this method to include the object. This will remove the object from the pool.
|
||||
func (p *Pool) MarkIncluded(change *ethpb.SignedBLSToExecutionChange) error {
|
||||
// node should call this method to include the object. This will remove the object from the pool.
|
||||
func (p *Pool) MarkIncluded(change *ethpb.SignedBLSToExecutionChange) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
node := p.m[change.Message.ValidatorIndex]
|
||||
if node == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
delete(p.m, change.Message.ValidatorIndex)
|
||||
p.pending.Remove(node)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatorExists checks if the bls to execution change object exists
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestMarkIncluded(t *testing.T) {
|
||||
ValidatorIndex: types.ValidatorIndex(0),
|
||||
}}
|
||||
pool.InsertBLSToExecChange(change)
|
||||
require.NoError(t, pool.MarkIncluded(change))
|
||||
pool.MarkIncluded(change)
|
||||
assert.Equal(t, 0, pool.pending.Len())
|
||||
_, ok := pool.m[0]
|
||||
assert.Equal(t, false, ok)
|
||||
@@ -259,7 +259,7 @@ func TestMarkIncluded(t *testing.T) {
|
||||
pool.InsertBLSToExecChange(first)
|
||||
pool.InsertBLSToExecChange(second)
|
||||
pool.InsertBLSToExecChange(third)
|
||||
require.NoError(t, pool.MarkIncluded(first))
|
||||
pool.MarkIncluded(first)
|
||||
require.Equal(t, 2, pool.pending.Len())
|
||||
_, ok := pool.m[0]
|
||||
assert.Equal(t, false, ok)
|
||||
@@ -281,7 +281,7 @@ func TestMarkIncluded(t *testing.T) {
|
||||
pool.InsertBLSToExecChange(first)
|
||||
pool.InsertBLSToExecChange(second)
|
||||
pool.InsertBLSToExecChange(third)
|
||||
require.NoError(t, pool.MarkIncluded(third))
|
||||
pool.MarkIncluded(third)
|
||||
require.Equal(t, 2, pool.pending.Len())
|
||||
_, ok := pool.m[2]
|
||||
assert.Equal(t, false, ok)
|
||||
@@ -303,7 +303,7 @@ func TestMarkIncluded(t *testing.T) {
|
||||
pool.InsertBLSToExecChange(first)
|
||||
pool.InsertBLSToExecChange(second)
|
||||
pool.InsertBLSToExecChange(third)
|
||||
require.NoError(t, pool.MarkIncluded(second))
|
||||
pool.MarkIncluded(second)
|
||||
require.Equal(t, 2, pool.pending.Len())
|
||||
_, ok := pool.m[1]
|
||||
assert.Equal(t, false, ok)
|
||||
@@ -324,7 +324,7 @@ func TestMarkIncluded(t *testing.T) {
|
||||
}}
|
||||
pool.InsertBLSToExecChange(first)
|
||||
pool.InsertBLSToExecChange(second)
|
||||
require.NoError(t, pool.MarkIncluded(change))
|
||||
pool.MarkIncluded(change)
|
||||
require.Equal(t, 2, pool.pending.Len())
|
||||
_, ok := pool.m[0]
|
||||
require.Equal(t, true, ok)
|
||||
@@ -378,7 +378,7 @@ func TestValidatorExists(t *testing.T) {
|
||||
ValidatorIndex: types.ValidatorIndex(0),
|
||||
}}
|
||||
pool.InsertBLSToExecChange(change)
|
||||
require.NoError(t, pool.MarkIncluded(change))
|
||||
pool.MarkIncluded(change)
|
||||
assert.Equal(t, false, pool.ValidatorExists(0))
|
||||
})
|
||||
t.Run("multiple validators added to pool and removed", func(t *testing.T) {
|
||||
@@ -399,8 +399,8 @@ func TestValidatorExists(t *testing.T) {
|
||||
}}
|
||||
pool.InsertBLSToExecChange(thirdChange)
|
||||
|
||||
assert.NoError(t, pool.MarkIncluded(firstChange))
|
||||
assert.NoError(t, pool.MarkIncluded(thirdChange))
|
||||
pool.MarkIncluded(firstChange)
|
||||
pool.MarkIncluded(thirdChange)
|
||||
|
||||
assert.Equal(t, false, pool.ValidatorExists(0))
|
||||
assert.Equal(t, true, pool.ValidatorExists(10))
|
||||
|
||||
@@ -166,6 +166,7 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
case "/eth/v1/beacon/pool/bls_to_execution_changes":
|
||||
endpoint.PostRequest = &SubmitBLSToExecutionChangesRequest{}
|
||||
endpoint.GetResponse = &BLSToExecutionChangesPoolResponseJson{}
|
||||
endpoint.Err = &IndexedVerificationFailureErrorJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: wrapBLSChangesArray,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package stategen
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -41,7 +42,8 @@ func (_ *State) replayBlocks(
|
||||
"endSlot": targetSlot,
|
||||
"diff": targetSlot - state.Slot(),
|
||||
})
|
||||
log.Debug("Replaying state")
|
||||
log.Debugf("Replaying state at %s", debug.Stack())
|
||||
|
||||
// The input block list is sorted in decreasing slots order.
|
||||
if len(signed) > 0 {
|
||||
for i := len(signed) - 1; i >= 0; i-- {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v3/monitoring/tracing"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v3/runtime/version"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -46,11 +45,6 @@ func (s *Service) validateBlsToExecutionChange(ctx context.Context, pid peer.ID,
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
// Ignore messages if our current head state doesn't support
|
||||
// capella.
|
||||
if st.Version() < version.Capella {
|
||||
return pubsub.ValidationIgnore, nil
|
||||
}
|
||||
// Validate that the execution change object is valid.
|
||||
_, err = blocks.ValidateBLSToExecutionChange(st, blsChange)
|
||||
if err != nil {
|
||||
|
||||
@@ -148,7 +148,7 @@ func TestService_ValidateBlsToExecutionChange(t *testing.T) {
|
||||
want: pubsub.ValidationIgnore,
|
||||
},
|
||||
{
|
||||
name: "Non-capella Head state",
|
||||
name: "Non-Capella HeadState Valid Execution Change Message",
|
||||
svc: NewService(context.Background(),
|
||||
WithP2P(mockp2p.NewTestP2P(t)),
|
||||
WithInitialSync(&mockSync.Sync{IsSyncing: false}),
|
||||
@@ -161,13 +161,23 @@ func TestService_ValidateBlsToExecutionChange(t *testing.T) {
|
||||
s.cfg.stateGen = stategen.New(beaconDB, doublylinkedtree.New())
|
||||
s.cfg.beaconDB = beaconDB
|
||||
s.initCaches()
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 128)
|
||||
st, keys := util.DeterministicGenesisStateBellatrix(t, 128)
|
||||
s.cfg.chain = &mockChain.ChainService{
|
||||
ValidatorsRoot: [32]byte{'A'},
|
||||
Genesis: time.Now().Add(-time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(10)),
|
||||
State: st,
|
||||
}
|
||||
|
||||
msg.Message.ValidatorIndex = 50
|
||||
// Provide invalid withdrawal key for validator
|
||||
msg.Message.FromBlsPubkey = keys[51].PublicKey().Marshal()
|
||||
msg.Message.ToExecutionAddress = wantedExecAddress
|
||||
epoch := slots.ToEpoch(st.Slot())
|
||||
domain, err := signing.Domain(st.Fork(), epoch, params.BeaconConfig().DomainBLSToExecutionChange, st.GenesisValidatorsRoot())
|
||||
assert.NoError(t, err)
|
||||
htr, err := signing.SigningData(msg.Message.HashTreeRoot, domain)
|
||||
assert.NoError(t, err)
|
||||
msg.Signature = keys[51].Sign(htr[:]).Marshal()
|
||||
return s, topic
|
||||
},
|
||||
args: args{
|
||||
@@ -182,7 +192,7 @@ func TestService_ValidateBlsToExecutionChange(t *testing.T) {
|
||||
},
|
||||
Signature: emptySig[:],
|
||||
}},
|
||||
want: pubsub.ValidationIgnore,
|
||||
want: pubsub.ValidationAccept,
|
||||
},
|
||||
{
|
||||
name: "Non-existent Validator Index",
|
||||
|
||||
@@ -15,8 +15,8 @@ go_library(
|
||||
"//cmd/prysmctl/db:go_default_library",
|
||||
"//cmd/prysmctl/deprecated:go_default_library",
|
||||
"//cmd/prysmctl/p2p:go_default_library",
|
||||
"//cmd/prysmctl/signing:go_default_library",
|
||||
"//cmd/prysmctl/testnet:go_default_library",
|
||||
"//cmd/prysmctl/validator:go_default_library",
|
||||
"//cmd/prysmctl/weaksubjectivity:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/db"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/deprecated"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/signing"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/testnet"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/validator"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/weaksubjectivity"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -36,5 +36,5 @@ func init() {
|
||||
prysmctlCommands = append(prysmctlCommands, p2p.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, testnet.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, weaksubjectivity.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, signing.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, validator.Commands...)
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["cmd.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/signing",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/validator/accounts:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//runtime/tos:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -1,64 +0,0 @@
|
||||
package signing
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v3/runtime/tos"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var Commands = []*cli.Command{
|
||||
{
|
||||
Name: "sign",
|
||||
Usage: "signs a message and broadcasts it to the network through the beacon node",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "voluntary-exit",
|
||||
Description: "Performs a voluntary exit on selected accounts",
|
||||
Flags: cmd.WrapFlags([]cli.Flag{
|
||||
flags.WalletDirFlag,
|
||||
flags.WalletPasswordFileFlag,
|
||||
flags.AccountPasswordFileFlag,
|
||||
flags.VoluntaryExitPublicKeysFlag,
|
||||
flags.BeaconRPCProviderFlag,
|
||||
flags.Web3SignerURLFlag,
|
||||
flags.Web3SignerPublicValidatorKeysFlag,
|
||||
flags.InteropNumValidators,
|
||||
flags.InteropStartIndex,
|
||||
cmd.GrpcMaxCallRecvMsgSizeFlag,
|
||||
flags.CertFlag,
|
||||
flags.GrpcHeadersFlag,
|
||||
flags.GrpcRetriesFlag,
|
||||
flags.GrpcRetryDelayFlag,
|
||||
flags.ExitAllFlag,
|
||||
flags.ForceExitFlag,
|
||||
features.Mainnet,
|
||||
features.PraterTestnet,
|
||||
features.RopstenTestnet,
|
||||
features.SepoliaTestnet,
|
||||
cmd.AcceptTosFlag,
|
||||
}),
|
||||
Before: func(cliCtx *cli.Context) error {
|
||||
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
return features.ConfigureValidator(cliCtx)
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := accounts.AccountsExit(cliCtx, os.Stdin); err != nil {
|
||||
log.WithError(err).Fatal("Could not perform voluntary exit")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
41
cmd/prysmctl/validator/BUILD.bazel
Normal file
41
cmd/prysmctl/validator/BUILD.bazel
Normal file
@@ -0,0 +1,41 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cmd.go",
|
||||
"withdraw.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v3/cmd/prysmctl/validator",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client/beacon:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/validator/accounts:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//runtime/tos:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common: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",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["withdraw_test.go"],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
137
cmd/prysmctl/validator/cmd.go
Normal file
137
cmd/prysmctl/validator/cmd.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v3/runtime/tos"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
BeaconHostFlag = &cli.StringFlag{
|
||||
Name: "beacon-node-host",
|
||||
Usage: "host:port for beacon node to query",
|
||||
Value: "127.0.0.1:3500",
|
||||
}
|
||||
PathFlag = &cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "path to the signed withdrawal messages JSON",
|
||||
}
|
||||
ConfirmFlag = &cli.BoolFlag{
|
||||
Name: "confirm",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "WARNING: User confirms and accepts responsibility of all input data provided and actions for setting their withdrawal address for their validator key. " +
|
||||
"This action is not reversible and withdrawal addresses can not be changed once set.",
|
||||
}
|
||||
VerifyOnlyFlag = &cli.BoolFlag{
|
||||
Name: "verify-only",
|
||||
Aliases: []string{"vo"},
|
||||
Usage: "overrides withdrawal command to only verify whether requests are in the pool and does not submit withdrawal requests",
|
||||
}
|
||||
)
|
||||
|
||||
var Commands = []*cli.Command{
|
||||
{
|
||||
Name: "validator",
|
||||
Aliases: []string{"v", "sign"}, // remove sign command should be depreciated but having as backwards compatability.
|
||||
Usage: "commands that affect the state of validators such as exiting or withdrawing",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "withdraw",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Assign Ethereum withdrawal addresses to validator keys. WARNING: once set values are included they can no longer be updated.",
|
||||
Flags: []cli.Flag{
|
||||
BeaconHostFlag,
|
||||
PathFlag,
|
||||
ConfirmFlag,
|
||||
VerifyOnlyFlag,
|
||||
cmd.ConfigFileFlag,
|
||||
cmd.AcceptTosFlag,
|
||||
},
|
||||
Before: func(cliCtx *cli.Context) error {
|
||||
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
|
||||
return err
|
||||
}
|
||||
au := aurora.NewAurora(true)
|
||||
if !cliCtx.Bool(cmd.AcceptTosFlag.Name) || !cliCtx.Bool(ConfirmFlag.Name) {
|
||||
fmt.Println(au.Red("===============IMPORTANT==============="))
|
||||
fmt.Println(au.Red("Please read the following carefully"))
|
||||
fmt.Print("This action will allow the partial withdrawal of amounts over the 32 staked ETH in your active validator balance. \n" +
|
||||
"You will also be entitled to the full withdrawal of the entire validator balance if your validator has exited. \n" +
|
||||
"Please navigate to our website (https://docs.prylabs.network/) and make sure you understand the full implications of setting your withdrawal address. \n")
|
||||
fmt.Println(au.Red("THIS ACTION WILL NOT BE REVERSIBLE ONCE INCLUDED. "))
|
||||
fmt.Println(au.Red("You will NOT be able to change the address again once changed. "))
|
||||
return fmt.Errorf("both the `--%s` and `--%s` flags are required to run this command. \n"+
|
||||
"By providing these flags the user has read and accepts the TERMS AND CONDITIONS: https://github.com/prysmaticlabs/prysm/blob/master/TERMS_OF_SERVICE.md "+
|
||||
"and confirms the action of setting withdrawals addresses", cmd.AcceptTosFlag.Name, ConfirmFlag.Name)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if cliCtx.Bool(VerifyOnlyFlag.Name) {
|
||||
if err := verifyWithdrawalsInPool(cliCtx); err != nil {
|
||||
log.WithError(err).Fatal("Could not verify withdrawal addresses")
|
||||
}
|
||||
} else {
|
||||
if err := setWithdrawalAddresses(cliCtx); err != nil {
|
||||
log.WithError(err).Fatal("Could not set withdrawal addresses")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "exit",
|
||||
Aliases: []string{"e", "voluntary-exit"},
|
||||
Usage: "Performs a voluntary exit on selected accounts",
|
||||
Flags: cmd.WrapFlags([]cli.Flag{
|
||||
flags.WalletDirFlag,
|
||||
flags.WalletPasswordFileFlag,
|
||||
flags.AccountPasswordFileFlag,
|
||||
flags.VoluntaryExitPublicKeysFlag,
|
||||
flags.BeaconRPCProviderFlag,
|
||||
flags.Web3SignerURLFlag,
|
||||
flags.Web3SignerPublicValidatorKeysFlag,
|
||||
flags.InteropNumValidators,
|
||||
flags.InteropStartIndex,
|
||||
cmd.GrpcMaxCallRecvMsgSizeFlag,
|
||||
flags.CertFlag,
|
||||
flags.GrpcHeadersFlag,
|
||||
flags.GrpcRetriesFlag,
|
||||
flags.GrpcRetryDelayFlag,
|
||||
flags.ExitAllFlag,
|
||||
flags.ForceExitFlag,
|
||||
features.Mainnet,
|
||||
features.PraterTestnet,
|
||||
features.RopstenTestnet,
|
||||
features.SepoliaTestnet,
|
||||
cmd.AcceptTosFlag,
|
||||
}),
|
||||
Before: func(cliCtx *cli.Context) error {
|
||||
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
return features.ConfigureValidator(cliCtx)
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
if err := accounts.AccountsExit(cliCtx, os.Stdin); err != nil {
|
||||
log.WithError(err).Fatal("Could not perform voluntary exit")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
1
cmd/prysmctl/validator/testdata/change-operations-multiple.json
vendored
Normal file
1
cmd/prysmctl/validator/testdata/change-operations-multiple.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"message":{"validator_index":"0","from_bls_pubkey":"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","to_execution_address":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"0xb6e640f0fc58e9f22585dbf434b6a0e8fc36b98e2f2a963e158716cfc84034141289f7898027de1ec56754937f1a837e01c7b066a6a56af7a379f8aec823d050788a5ecc799e9bc39f73d45b7c389c961cbaace61823e4c7bf2f93bd06c03127"},{"message":{"validator_index":"1","from_bls_pubkey":"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","to_execution_address":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"0xa97103e15d3dbdaa75fb15cea782e4a11329eea77d155864ec682d7907b3b70c7771960bef7be1b1c4e08fe735888b950c1a22053f6049b35736f48e6dd018392efa3896c9e427ea4e100e86e9131b5ea2673388a4bf188407a630ba405b7dc5"}]
|
||||
1
cmd/prysmctl/validator/testdata/change-operations-multiple_notfound.json
vendored
Normal file
1
cmd/prysmctl/validator/testdata/change-operations-multiple_notfound.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"message":{"validator_index":"3","from_bls_pubkey":"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","to_execution_address":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"0xb6e640f0fc58e9f22585dbf434b6a0e8fc36b98e2f2a963e158716cfc84034141289f7898027de1ec56754937f1a837e01c7b066a6a56af7a379f8aec823d050788a5ecc799e9bc39f73d45b7c389c961cbaace61823e4c7bf2f93bd06c03127"},{"message":{"validator_index":"5","from_bls_pubkey":"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","to_execution_address":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"0xa97103e15d3dbdaa75fb15cea782e4a11329eea77d155864ec682d7907b3b70c7771960bef7be1b1c4e08fe735888b950c1a22053f6049b35736f48e6dd018392efa3896c9e427ea4e100e86e9131b5ea2673388a4bf188407a630ba405b7dc5"}]
|
||||
1
cmd/prysmctl/validator/testdata/change-operations.json
vendored
Executable file
1
cmd/prysmctl/validator/testdata/change-operations.json
vendored
Executable file
@@ -0,0 +1 @@
|
||||
[{"message":{"validator_index":"1","from_bls_pubkey":"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","to_execution_address":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"0xa97103e15d3dbdaa75fb15cea782e4a11329eea77d155864ec682d7907b3b70c7771960bef7be1b1c4e08fe735888b950c1a22053f6049b35736f48e6dd018392efa3896c9e427ea4e100e86e9131b5ea2673388a4bf188407a630ba405b7dc5"}]
|
||||
1
cmd/prysmctl/validator/testdata/staking-cli-change-operations-multiple.json
vendored
Normal file
1
cmd/prysmctl/validator/testdata/staking-cli-change-operations-multiple.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"message":{"validator_index":"0","from_bls_pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","to_execution_address":"a94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"b6e640f0fc58e9f22585dbf434b6a0e8fc36b98e2f2a963e158716cfc84034141289f7898027de1ec56754937f1a837e01c7b066a6a56af7a379f8aec823d050788a5ecc799e9bc39f73d45b7c389c961cbaace61823e4c7bf2f93bd06c03127", "metadata":{ "network_name": "mainnet", "genesis_validators_root": "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95", "deposit_cli_version": "2.4.0"}},{"message":{"validator_index":"1","from_bls_pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","to_execution_address":"a94f5374fce5edbc8e2a8697c15331677e6ebf0b"},"signature":"a97103e15d3dbdaa75fb15cea782e4a11329eea77d155864ec682d7907b3b70c7771960bef7be1b1c4e08fe735888b950c1a22053f6049b35736f48e6dd018392efa3896c9e427ea4e100e86e9131b5ea2673388a4bf188407a630ba405b7dc5", "metadata":{ "network_name": "mainnet", "genesis_validators_root": "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95", "deposit_cli_version": "2.4.0"}}]
|
||||
171
cmd/prysmctl/validator/withdraw.go
Normal file
171
cmd/prysmctl/validator/withdraw.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v3/api/client/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
func setWithdrawalAddresses(c *cli.Context) error {
|
||||
ctx, span := trace.StartSpan(c.Context, "withdrawal.setWithdrawalAddresses")
|
||||
defer span.End()
|
||||
au := aurora.NewAurora(true)
|
||||
beaconNodeHost := c.String(BeaconHostFlag.Name)
|
||||
if !c.IsSet(PathFlag.Name) {
|
||||
return fmt.Errorf("no --%s flag value was provided", PathFlag.Name)
|
||||
}
|
||||
setWithdrawalAddressJsons, err := getWithdrawalMessagesFromPathFlag(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, request := range setWithdrawalAddressJsons {
|
||||
fmt.Println("SETTING VALIDATOR INDEX " + au.Red(request.Message.ValidatorIndex).String() + " TO WITHDRAWAL ADDRESS " + au.Red(request.Message.ToExecutionAddress).String())
|
||||
}
|
||||
return callWithdrawalEndpoints(ctx, beaconNodeHost, setWithdrawalAddressJsons)
|
||||
}
|
||||
|
||||
func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*apimiddleware.SignedBLSToExecutionChangeJson, error) {
|
||||
setWithdrawalAddressJsons := make([]*apimiddleware.SignedBLSToExecutionChangeJson, 0)
|
||||
foundFilePaths, err := findWithdrawalFiles(c.String(PathFlag.Name))
|
||||
if err != nil {
|
||||
return setWithdrawalAddressJsons, errors.Wrap(err, "failed to find withdrawal files")
|
||||
}
|
||||
for _, foundFilePath := range foundFilePaths {
|
||||
b, err := os.ReadFile(filepath.Clean(foundFilePath))
|
||||
if err != nil {
|
||||
return setWithdrawalAddressJsons, errors.Wrap(err, "failed to open file")
|
||||
}
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
if err := json.Unmarshal(b, &to); err != nil {
|
||||
log.Warnf("provided file: %s, is not a list of signed withdrawal messages", foundFilePath)
|
||||
continue
|
||||
}
|
||||
// verify 0x from file and add if needed
|
||||
for i, obj := range to {
|
||||
if len(obj.Message.FromBLSPubkey) == fieldparams.BLSPubkeyLength*2 {
|
||||
to[i].Message.FromBLSPubkey = fmt.Sprintf("0x%s", obj.Message.FromBLSPubkey)
|
||||
}
|
||||
if len(obj.Message.ToExecutionAddress) == common.AddressLength*2 {
|
||||
to[i].Message.ToExecutionAddress = fmt.Sprintf("0x%s", obj.Message.ToExecutionAddress)
|
||||
}
|
||||
if len(obj.Signature) == fieldparams.BLSSignatureLength*2 {
|
||||
to[i].Signature = fmt.Sprintf("0x%s", obj.Signature)
|
||||
}
|
||||
setWithdrawalAddressJsons = append(setWithdrawalAddressJsons, &apimiddleware.SignedBLSToExecutionChangeJson{
|
||||
Message: &apimiddleware.BLSToExecutionChangeJson{
|
||||
ValidatorIndex: to[i].Message.ValidatorIndex,
|
||||
FromBLSPubkey: to[i].Message.FromBLSPubkey,
|
||||
ToExecutionAddress: to[i].Message.ToExecutionAddress,
|
||||
},
|
||||
Signature: to[i].Signature,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
if len(setWithdrawalAddressJsons) == 0 {
|
||||
return setWithdrawalAddressJsons, errors.New("the list of signed requests is empty")
|
||||
}
|
||||
return setWithdrawalAddressJsons, nil
|
||||
}
|
||||
|
||||
func callWithdrawalEndpoints(ctx context.Context, host string, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
client, err := beacon.NewClient(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.SubmitChangeBLStoExecution(ctx, request); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Successfully published messages to update %d withdrawal addresses.", len(request))
|
||||
return checkIfWithdrawsAreInPool(ctx, client, request)
|
||||
}
|
||||
|
||||
func checkIfWithdrawsAreInPool(ctx context.Context, client *beacon.Client, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
log.Info("Verifying requested withdrawal messages known to node...")
|
||||
poolResponse, err := client.GetBLStoExecutionChanges(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestMap := make(map[string]string)
|
||||
for _, w := range request {
|
||||
requestMap[w.Message.ValidatorIndex] = w.Message.ToExecutionAddress
|
||||
}
|
||||
totalMessages := len(requestMap)
|
||||
for _, resp := range poolResponse.Data {
|
||||
value, found := requestMap[resp.Message.ValidatorIndex]
|
||||
if found && value == resp.Message.ToExecutionAddress {
|
||||
delete(requestMap, resp.Message.ValidatorIndex)
|
||||
}
|
||||
}
|
||||
if len(requestMap) != 0 {
|
||||
for key, address := range requestMap {
|
||||
log.WithFields(log.Fields{
|
||||
"validator_index": key,
|
||||
"execution_address:": address,
|
||||
}).Warn("Set withdrawal address message not found in the node's operations pool.")
|
||||
}
|
||||
log.Warn("Please check before resubmitting. Set withdrawal address messages that were not found in the pool may have been already included into a block.")
|
||||
} else {
|
||||
log.Infof("All (total:%d) signed withdrawal messages were found in the pool.", totalMessages)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findWithdrawalFiles(path string) ([]string, error) {
|
||||
var foundpaths []string
|
||||
maxdepth := 3
|
||||
cleanpath := filepath.Clean(path)
|
||||
if err := filepath.WalkDir(cleanpath, func(s string, d fs.DirEntry, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if d.IsDir() && strings.Count(cleanpath, string(os.PathSeparator)) > maxdepth {
|
||||
return fs.SkipDir
|
||||
}
|
||||
|
||||
if filepath.Ext(d.Name()) == ".json" {
|
||||
foundpaths = append(foundpaths, s)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to find compatible files")
|
||||
}
|
||||
if len(foundpaths) == 0 {
|
||||
return nil, errors.New("no compatible files were found")
|
||||
}
|
||||
log.Infof("found JSON files for setting withdrawals: %v", foundpaths)
|
||||
return foundpaths, nil
|
||||
}
|
||||
|
||||
func verifyWithdrawalsInPool(c *cli.Context) error {
|
||||
ctx, span := trace.StartSpan(c.Context, "withdrawal.verifyWithdrawalsInPool")
|
||||
defer span.End()
|
||||
beaconNodeHost := c.String(BeaconHostFlag.Name)
|
||||
if !c.IsSet(PathFlag.Name) {
|
||||
return fmt.Errorf("no --%s flag value was provided", PathFlag.Name)
|
||||
}
|
||||
client, err := beacon.NewClient(beaconNodeHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request, err := getWithdrawalMessagesFromPathFlag(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkIfWithdrawsAreInPool(ctx, client, request)
|
||||
}
|
||||
305
cmd/prysmctl/validator/withdraw_test.go
Normal file
305
cmd/prysmctl/validator/withdraw_test.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
logtest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestCallWithdrawalEndpoint(t *testing.T) {
|
||||
file := "./testdata/change-operations.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", file, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", file))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.LogsContain(t, hook, "Successfully published")
|
||||
}
|
||||
|
||||
func TestCallWithdrawalEndpoint_Mutiple(t *testing.T) {
|
||||
file := "./testdata/change-operations-multiple.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", file, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", file))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, hook, "Successfully published")
|
||||
assert.LogsContain(t, hook, "to update 2 withdrawal")
|
||||
assert.LogsContain(t, hook, "All (total:2) signed withdrawal messages were found in the pool.")
|
||||
assert.LogsDoNotContain(t, hook, "Set withdrawal address message not found in the node's operations pool.")
|
||||
}
|
||||
|
||||
func TestCallWithdrawalEndpoint_Mutiple_stakingcli(t *testing.T) {
|
||||
stakingcliFile := "./testdata/staking-cli-change-operations-multiple.json"
|
||||
file := "./testdata/change-operations-multiple.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", stakingcliFile, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", stakingcliFile))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, hook, "Successfully published")
|
||||
assert.LogsContain(t, hook, "to update 2 withdrawal")
|
||||
assert.LogsContain(t, hook, "All (total:2) signed withdrawal messages were found in the pool.")
|
||||
assert.LogsDoNotContain(t, hook, "Set withdrawal address message not found in the node's operations pool.")
|
||||
}
|
||||
|
||||
func TestCallWithdrawalEndpoint_Mutiple_notfound(t *testing.T) {
|
||||
respFile := "./testdata/change-operations-multiple_notfound.json"
|
||||
file := "./testdata/change-operations-multiple.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(respFile))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", file, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", file))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, hook, "Successfully published")
|
||||
assert.LogsContain(t, hook, "to update 2 withdrawal")
|
||||
assert.LogsContain(t, hook, "Set withdrawal address message not found in the node's operations pool.")
|
||||
assert.LogsContain(t, hook, "Please check before resubmitting. Set withdrawal address messages that were not found in the pool may have been already included into a block.")
|
||||
assert.LogsDoNotContain(t, hook, "Set withdrawal address message found in the node's operations pool.")
|
||||
}
|
||||
|
||||
func TestCallWithdrawalEndpoint_Empty(t *testing.T) {
|
||||
baseurl := "127.0.0.1:3500"
|
||||
content := []byte("[]")
|
||||
tmpfile, err := os.CreateTemp("./testdata", "*.json")
|
||||
require.NoError(t, err)
|
||||
_, err = tmpfile.Write(content)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := os.Remove(tmpfile.Name())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", tmpfile.Name(), "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", tmpfile.Name()))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
assert.ErrorContains(t, "the list of signed requests is empty", err)
|
||||
}
|
||||
|
||||
func TestCallWithdrawalEndpoint_Errors(t *testing.T) {
|
||||
file := "./testdata/change-operations.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(400)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.IndexedVerificationFailureErrorJson{
|
||||
Failures: []*apimiddleware.SingleIndexedVerificationFailureJson{
|
||||
{Index: 0, Message: "Could not validate SignedBLSToExecutionChange"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", file, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", file))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = setWithdrawalAddresses(cliCtx)
|
||||
assert.ErrorContains(t, "POST error", err)
|
||||
|
||||
assert.LogsContain(t, hook, "Could not validate SignedBLSToExecutionChange")
|
||||
}
|
||||
|
||||
func TestVerifyWithdrawal_Mutiple(t *testing.T) {
|
||||
file := "./testdata/change-operations-multiple.json"
|
||||
baseurl := "127.0.0.1:3500"
|
||||
l, err := net.Listen("tcp", baseurl)
|
||||
require.NoError(t, err)
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
err = srv.Listener.Close()
|
||||
require.NoError(t, err)
|
||||
srv.Listener = l
|
||||
srv.Start()
|
||||
defer srv.Close()
|
||||
hook := logtest.NewGlobal()
|
||||
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("beacon-node-host", baseurl, "")
|
||||
set.String("path", file, "")
|
||||
set.Bool("confirm", true, "")
|
||||
set.Bool("accept-terms-of-use", true, "")
|
||||
set.Bool("verify-only", true, "")
|
||||
assert.NoError(t, set.Set("beacon-node-host", baseurl))
|
||||
assert.NoError(t, set.Set("path", file))
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
|
||||
err = verifyWithdrawalsInPool(cliCtx)
|
||||
require.NoError(t, err)
|
||||
assert.LogsContain(t, hook, "All (total:2) signed withdrawal messages were found in the pool.")
|
||||
assert.LogsDoNotContain(t, hook, "set withdrawal address message not found in the node's operations pool.")
|
||||
}
|
||||
@@ -186,7 +186,7 @@ var Commands = &cli.Command{
|
||||
return features.ConfigureValidator(cliCtx)
|
||||
},
|
||||
Action: func(cliCtx *cli.Context) error {
|
||||
log.Info("This command will be deprecated in the future in favor of `prysmctl sign validator-exit`")
|
||||
log.Info("This command will be deprecated in the future in favor of `prysmctl validator exit`")
|
||||
if err := AccountsExit(cliCtx, os.Stdin); err != nil {
|
||||
log.WithError(err).Fatal("Could not perform voluntary exit")
|
||||
}
|
||||
|
||||
@@ -42,12 +42,7 @@ func NewBeaconApiValidatorClientWithFallback(host string, timeout time.Duration,
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) GetDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
|
||||
if c.fallbackClient != nil {
|
||||
return c.fallbackClient.GetDuties(ctx, in)
|
||||
}
|
||||
|
||||
// TODO: Implement me
|
||||
panic("beaconApiValidatorClient.GetDuties is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
|
||||
return c.getDuties(ctx, in)
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) CheckDoppelGanger(ctx context.Context, in *ethpb.DoppelGangerRequest) (*ethpb.DoppelGangerResponse, error) {
|
||||
|
||||
@@ -5,21 +5,224 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
type dutiesProvider interface {
|
||||
GetAttesterDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error)
|
||||
GetProposerDuties(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.ProposerDutyJson, error)
|
||||
GetSyncDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error)
|
||||
GetCommittees(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.CommitteeJson, error)
|
||||
}
|
||||
|
||||
type beaconApiDutiesProvider struct {
|
||||
jsonRestHandler jsonRestHandler
|
||||
}
|
||||
|
||||
type committeeIndexSlotPair struct {
|
||||
committeeIndex types.CommitteeIndex
|
||||
slot types.Slot
|
||||
}
|
||||
|
||||
func (c beaconApiValidatorClient) getDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
|
||||
multipleValidatorStatus, err := c.multipleValidatorStatus(ctx, ðpb.MultipleValidatorStatusRequest{PublicKeys: in.PublicKeys})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get validator status")
|
||||
}
|
||||
|
||||
// Sync committees are an Altair feature
|
||||
fetchSyncDuties := in.Epoch >= params.BeaconConfig().AltairForkEpoch
|
||||
|
||||
currentEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch, multipleValidatorStatus, fetchSyncDuties)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get duties for current epoch `%d`", in.Epoch)
|
||||
}
|
||||
|
||||
nextEpochDuties, err := c.getDutiesForEpoch(ctx, in.Epoch+1, multipleValidatorStatus, fetchSyncDuties)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get duties for next epoch `%d`", in.Epoch+1)
|
||||
}
|
||||
|
||||
return ðpb.DutiesResponse{
|
||||
Duties: currentEpochDuties,
|
||||
CurrentEpochDuties: currentEpochDuties,
|
||||
NextEpochDuties: nextEpochDuties,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c beaconApiValidatorClient) getDutiesForEpoch(
|
||||
ctx context.Context,
|
||||
epoch types.Epoch,
|
||||
multipleValidatorStatus *ethpb.MultipleValidatorStatusResponse,
|
||||
fetchSyncDuties bool,
|
||||
) ([]*ethpb.DutiesResponse_Duty, error) {
|
||||
attesterDuties, err := c.dutiesProvider.GetAttesterDuties(ctx, epoch, multipleValidatorStatus.Indices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get attester duties for epoch `%d`", epoch)
|
||||
}
|
||||
|
||||
var syncDuties []*apimiddleware.SyncCommitteeDuty
|
||||
if fetchSyncDuties {
|
||||
if syncDuties, err = c.dutiesProvider.GetSyncDuties(ctx, epoch, multipleValidatorStatus.Indices); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get sync duties for epoch `%d`", epoch)
|
||||
}
|
||||
}
|
||||
|
||||
var proposerDuties []*apimiddleware.ProposerDutyJson
|
||||
if proposerDuties, err = c.dutiesProvider.GetProposerDuties(ctx, epoch); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get proposer duties for epoch `%d`", epoch)
|
||||
}
|
||||
|
||||
committees, err := c.dutiesProvider.GetCommittees(ctx, epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get committees for epoch `%d`", epoch)
|
||||
}
|
||||
|
||||
// Mapping from a validator index to its attesting committee's index and slot
|
||||
attesterDutiesMapping := make(map[types.ValidatorIndex]committeeIndexSlotPair)
|
||||
for _, attesterDuty := range attesterDuties {
|
||||
validatorIndex, err := strconv.ParseUint(attesterDuty.ValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attester validator index `%s`", attesterDuty.ValidatorIndex)
|
||||
}
|
||||
|
||||
slot, err := strconv.ParseUint(attesterDuty.Slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attester slot `%s`", attesterDuty.Slot)
|
||||
}
|
||||
|
||||
committeeIndex, err := strconv.ParseUint(attesterDuty.CommitteeIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attester committee index `%s`", attesterDuty.CommitteeIndex)
|
||||
}
|
||||
|
||||
attesterDutiesMapping[types.ValidatorIndex(validatorIndex)] = committeeIndexSlotPair{
|
||||
slot: types.Slot(slot),
|
||||
committeeIndex: types.CommitteeIndex(committeeIndex),
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping from a validator index to its proposal slot
|
||||
proposerDutySlots := make(map[types.ValidatorIndex][]types.Slot)
|
||||
for _, proposerDuty := range proposerDuties {
|
||||
validatorIndex, err := strconv.ParseUint(proposerDuty.ValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse proposer validator index `%s`", proposerDuty.ValidatorIndex)
|
||||
}
|
||||
|
||||
slot, err := strconv.ParseUint(proposerDuty.Slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse proposer slot `%s`", proposerDuty.Slot)
|
||||
}
|
||||
|
||||
proposerDutySlots[types.ValidatorIndex(validatorIndex)] = append(proposerDutySlots[types.ValidatorIndex(validatorIndex)], types.Slot(slot))
|
||||
}
|
||||
|
||||
// Set containing all validator indices that are part of a sync committee for this epoch
|
||||
syncDutiesMapping := make(map[types.ValidatorIndex]bool)
|
||||
for _, syncDuty := range syncDuties {
|
||||
validatorIndex, err := strconv.ParseUint(syncDuty.ValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse sync validator index `%s`", syncDuty.ValidatorIndex)
|
||||
}
|
||||
|
||||
syncDutiesMapping[types.ValidatorIndex(validatorIndex)] = true
|
||||
}
|
||||
|
||||
// Mapping from the {committeeIndex, slot} to each of the committee's validator indices
|
||||
committeeMapping := make(map[committeeIndexSlotPair][]types.ValidatorIndex)
|
||||
for _, committee := range committees {
|
||||
committeeIndex, err := strconv.ParseUint(committee.Index, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse committee index `%s`", committee.Index)
|
||||
}
|
||||
|
||||
slot, err := strconv.ParseUint(committee.Slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse slot `%s`", committee.Slot)
|
||||
}
|
||||
|
||||
validatorIndices := make([]types.ValidatorIndex, len(committee.Validators))
|
||||
for index, validatorIndexString := range committee.Validators {
|
||||
validatorIndex, err := strconv.ParseUint(validatorIndexString, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse committee validator index `%s`", validatorIndexString)
|
||||
}
|
||||
validatorIndices[index] = types.ValidatorIndex(validatorIndex)
|
||||
}
|
||||
|
||||
key := committeeIndexSlotPair{
|
||||
committeeIndex: types.CommitteeIndex(committeeIndex),
|
||||
slot: types.Slot(slot),
|
||||
}
|
||||
committeeMapping[key] = validatorIndices
|
||||
}
|
||||
|
||||
duties := make([]*ethpb.DutiesResponse_Duty, len(multipleValidatorStatus.Statuses))
|
||||
for index, validatorStatus := range multipleValidatorStatus.Statuses {
|
||||
validatorIndex := multipleValidatorStatus.Indices[index]
|
||||
pubkey := multipleValidatorStatus.PublicKeys[index]
|
||||
|
||||
var attesterSlot types.Slot
|
||||
var committeeIndex types.CommitteeIndex
|
||||
var committeeValidatorIndices []types.ValidatorIndex
|
||||
|
||||
if committeeMappingKey, ok := attesterDutiesMapping[validatorIndex]; ok {
|
||||
committeeIndex = committeeMappingKey.committeeIndex
|
||||
attesterSlot = committeeMappingKey.slot
|
||||
|
||||
if committeeValidatorIndices, ok = committeeMapping[committeeMappingKey]; !ok {
|
||||
return nil, errors.Errorf("failed to find validators for committee index `%d` and slot `%d`", committeeIndex, attesterSlot)
|
||||
}
|
||||
}
|
||||
|
||||
duties[index] = ðpb.DutiesResponse_Duty{
|
||||
Committee: committeeValidatorIndices,
|
||||
CommitteeIndex: committeeIndex,
|
||||
AttesterSlot: attesterSlot,
|
||||
ProposerSlots: proposerDutySlots[types.ValidatorIndex(validatorIndex)],
|
||||
PublicKey: pubkey,
|
||||
Status: validatorStatus.Status,
|
||||
ValidatorIndex: types.ValidatorIndex(validatorIndex),
|
||||
IsSyncCommittee: syncDutiesMapping[types.ValidatorIndex(validatorIndex)],
|
||||
}
|
||||
}
|
||||
|
||||
return duties, nil
|
||||
}
|
||||
|
||||
// GetCommittees retrieves the committees for the given epoch
|
||||
func (c beaconApiDutiesProvider) GetCommittees(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.CommitteeJson, error) {
|
||||
committeeParams := url.Values{}
|
||||
committeeParams.Add("epoch", strconv.FormatUint(uint64(epoch), 10))
|
||||
committeesRequest := buildURL("/eth/v1/beacon/states/head/committees", committeeParams)
|
||||
|
||||
var stateCommittees apimiddleware.StateCommitteesResponseJson
|
||||
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, committeesRequest, &stateCommittees); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query committees for epoch `%d`", epoch)
|
||||
}
|
||||
|
||||
if stateCommittees.Data == nil {
|
||||
return nil, errors.New("state committees data is nil")
|
||||
}
|
||||
|
||||
for index, committee := range stateCommittees.Data {
|
||||
if committee == nil {
|
||||
return nil, errors.Errorf("committee at index `%d` is nil", index)
|
||||
}
|
||||
}
|
||||
|
||||
return stateCommittees.Data, nil
|
||||
}
|
||||
|
||||
// GetAttesterDuties retrieves the attester duties for the given epoch and validatorIndices
|
||||
func (c beaconApiDutiesProvider) GetAttesterDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error) {
|
||||
|
||||
jsonValidatorIndices := make([]string, len(validatorIndices))
|
||||
@@ -45,3 +248,53 @@ func (c beaconApiDutiesProvider) GetAttesterDuties(ctx context.Context, epoch ty
|
||||
|
||||
return attesterDuties.Data, nil
|
||||
}
|
||||
|
||||
// GetProposerDuties retrieves the proposer duties for the given epoch
|
||||
func (c beaconApiDutiesProvider) GetProposerDuties(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.ProposerDutyJson, error) {
|
||||
proposerDuties := apimiddleware.ProposerDutiesResponseJson{}
|
||||
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, fmt.Sprintf("/eth/v1/validator/duties/proposer/%d", epoch), &proposerDuties); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query proposer duties for epoch `%d`", epoch)
|
||||
}
|
||||
|
||||
if proposerDuties.Data == nil {
|
||||
return nil, errors.New("proposer duties data is nil")
|
||||
}
|
||||
|
||||
for index, proposerDuty := range proposerDuties.Data {
|
||||
if proposerDuty == nil {
|
||||
return nil, errors.Errorf("proposer duty at index `%d` is nil", index)
|
||||
}
|
||||
}
|
||||
|
||||
return proposerDuties.Data, nil
|
||||
}
|
||||
|
||||
// GetSyncDuties retrieves the sync committee duties for the given epoch and validatorIndices
|
||||
func (c beaconApiDutiesProvider) GetSyncDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error) {
|
||||
jsonValidatorIndices := make([]string, len(validatorIndices))
|
||||
for index, validatorIndex := range validatorIndices {
|
||||
jsonValidatorIndices[index] = strconv.FormatUint(uint64(validatorIndex), 10)
|
||||
}
|
||||
|
||||
validatorIndicesBytes, err := json.Marshal(jsonValidatorIndices)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal validator indices")
|
||||
}
|
||||
|
||||
syncDuties := apimiddleware.SyncCommitteeDutiesResponseJson{}
|
||||
if _, err := c.jsonRestHandler.PostRestJson(ctx, fmt.Sprintf("/eth/v1/validator/duties/sync/%d", epoch), nil, bytes.NewBuffer(validatorIndicesBytes), &syncDuties); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to send POST data to REST endpoint")
|
||||
}
|
||||
|
||||
if syncDuties.Data == nil {
|
||||
return nil, errors.New("sync duties data is nil")
|
||||
}
|
||||
|
||||
for index, syncDuty := range syncDuties.Data {
|
||||
if syncDuty == nil {
|
||||
return nil, errors.Errorf("sync duty at index `%d` is nil", index)
|
||||
}
|
||||
}
|
||||
|
||||
return syncDuties.Data, nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
45
validator/client/beacon-api/mock/duties_mock.go
generated
45
validator/client/beacon-api/mock/duties_mock.go
generated
@@ -50,3 +50,48 @@ func (mr *MockdutiesProviderMockRecorder) GetAttesterDuties(ctx, epoch, validato
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttesterDuties", reflect.TypeOf((*MockdutiesProvider)(nil).GetAttesterDuties), ctx, epoch, validatorIndices)
|
||||
}
|
||||
|
||||
// GetCommittees mocks base method.
|
||||
func (m *MockdutiesProvider) GetCommittees(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.CommitteeJson, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCommittees", ctx, epoch)
|
||||
ret0, _ := ret[0].([]*apimiddleware.CommitteeJson)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCommittees indicates an expected call of GetCommittees.
|
||||
func (mr *MockdutiesProviderMockRecorder) GetCommittees(ctx, epoch interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommittees", reflect.TypeOf((*MockdutiesProvider)(nil).GetCommittees), ctx, epoch)
|
||||
}
|
||||
|
||||
// GetProposerDuties mocks base method.
|
||||
func (m *MockdutiesProvider) GetProposerDuties(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.ProposerDutyJson, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetProposerDuties", ctx, epoch)
|
||||
ret0, _ := ret[0].([]*apimiddleware.ProposerDutyJson)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetProposerDuties indicates an expected call of GetProposerDuties.
|
||||
func (mr *MockdutiesProviderMockRecorder) GetProposerDuties(ctx, epoch interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposerDuties", reflect.TypeOf((*MockdutiesProvider)(nil).GetProposerDuties), ctx, epoch)
|
||||
}
|
||||
|
||||
// GetSyncDuties mocks base method.
|
||||
func (m *MockdutiesProvider) GetSyncDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSyncDuties", ctx, epoch, validatorIndices)
|
||||
ret0, _ := ret[0].([]*apimiddleware.SyncCommitteeDuty)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSyncDuties indicates an expected call of GetSyncDuties.
|
||||
func (mr *MockdutiesProviderMockRecorder) GetSyncDuties(ctx, epoch, validatorIndices interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSyncDuties", reflect.TypeOf((*MockdutiesProvider)(nil).GetSyncDuties), ctx, epoch, validatorIndices)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user