Skip to content

Commit

Permalink
Add slot option for proposer duties.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdee committed Aug 16, 2023
1 parent 92d964f commit 1f1ddc1
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 24 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
dev:
- add "slot" to "proposer duties" command

1.33.0:
- show all slots with 'synccommittee inclusion'
- add "wallet batch" command
Expand Down
26 changes: 13 additions & 13 deletions cmd/proposer/duties/command.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2022, 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -37,6 +37,7 @@ type command struct {

// Operation.
epoch string
slot string
jsonOutput bool

// Data access.
Expand All @@ -55,23 +56,22 @@ type results struct {

func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
results: &results{},
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
timeout: viper.GetDuration("timeout"),
connection: viper.GetString("connection"),
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
epoch: viper.GetString("epoch"),
slot: viper.GetString("slot"),
jsonOutput: viper.GetBool("json"),
results: &results{},
}

// Timeout.
if viper.GetDuration("timeout") == 0 {
if c.timeout == 0 {
return nil, errors.New("timeout is required")
}
c.timeout = viper.GetDuration("timeout")

c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

c.epoch = viper.GetString("epoch")
c.jsonOutput = viper.GetBool("json")

return c, nil
}
29 changes: 21 additions & 8 deletions cmd/proposer/duties/output.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2022, 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -43,19 +43,32 @@ func (c *command) outputJSON(_ context.Context) (string, error) {
func (c *command) outputTxt(_ context.Context) (string, error) {
builder := strings.Builder{}

builder.WriteString("Epoch ")
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))

for _, duty := range c.results.Duties {
builder.WriteString(" Slot ")
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
builder.WriteString("validator ")
if len(c.results.Duties) == 1 {
// Only have a single slot, just print the validator.
duty := c.results.Duties[0]
builder.WriteString("Validator ")
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
if c.verbose {
builder.WriteString(" (pubkey ")
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
}
builder.WriteString("\n")
} else {
// Have multiple slots, print per-slot information.
builder.WriteString("Epoch ")
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))

for _, duty := range c.results.Duties {
builder.WriteString(" Slot ")
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
builder.WriteString("validator ")
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
if c.verbose {
builder.WriteString(" (pubkey ")
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
}
builder.WriteString("\n")
}
}

return strings.TrimSuffix(builder.String(), "\n"), nil
Expand Down
41 changes: 38 additions & 3 deletions cmd/proposer/duties/process.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2022, 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -17,18 +17,53 @@ import (
"context"

eth2client "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
)

func (c *command) process(ctx context.Context) error {
// Obtain information we need to process.
err := c.setup(ctx)
if err != nil {
if err := c.setup(ctx); err != nil {
return err
}

if c.slot != "" {
return c.processSlot(ctx)
}

return c.processEpoch(ctx)
}

func (c *command) processSlot(ctx context.Context) error {
var err error
slot, err := util.ParseSlot(ctx, c.chainTime, c.slot)
if err != nil {
return errors.Wrap(err, "failed to parse slot")
}

c.results.Epoch = c.chainTime.SlotToEpoch(slot)

duties, err := c.proposerDutiesProvider.ProposerDuties(ctx, c.results.Epoch, nil)
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}

c.results.Duties = make([]*apiv1.ProposerDuty, 0, 1)

for _, duty := range duties {
if duty.Slot == slot {
c.results.Duties = append(c.results.Duties, duty)
break
}
}

return nil
}

func (c *command) processEpoch(ctx context.Context) error {
var err error
c.results.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
if err != nil {
return errors.Wrap(err, "failed to parse epoch")
Expand Down
4 changes: 4 additions & 0 deletions cmd/proposerduties.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ func init() {
proposerCmd.AddCommand(proposerDutiesCmd)
proposerFlags(proposerDutiesCmd)
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
proposerDutiesCmd.Flags().String("slot", "", "the slot for which to fetch duties")
}

func proposerDutiesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
panic(err)
}
}
1 change: 1 addition & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ Proposer commands focus on Ethereum consensus validators' actions as proposers.
`ethdo proposer duties` provides information on the proposal duties for a given epoch. Options include:

- `epoch` the epoch in which to obtain the duties (defaults to current epoch)
- `slot` the slot in which to obtain the duties (overrides epoch if present)
- `json` obtain detailed information in JSON format

```sh
Expand Down
49 changes: 49 additions & 0 deletions util/slot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright © 2023 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
"context"
"strconv"

"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/services/chaintime"
)

// ParseSlot parses input to calculate the desired slot.
func ParseSlot(_ context.Context, chainTime chaintime.Service, slotStr string) (phase0.Slot, error) {
currentSlot := chainTime.CurrentSlot()
switch slotStr {
case "", "current", "head", "-0":
return currentSlot, nil
case "last":
if currentSlot > 0 {
currentSlot--
}
return currentSlot, nil
default:
val, err := strconv.ParseInt(slotStr, 10, 64)
if err != nil {
return 0, errors.Wrap(err, "failed to parse slot")
}
if val >= 0 {
return phase0.Slot(val), nil
}
if phase0.Slot(-val) > currentSlot {
return 0, nil
}
return currentSlot + phase0.Slot(val), nil
}
}
105 changes: 105 additions & 0 deletions util/slot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util_test

import (
"context"
"testing"
"time"

"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/testing/mock"
"github.com/wealdtech/ethdo/util"
)

func TestParseSlot(t *testing.T) {
ctx := context.Background()

// genesis is 1 day ago.
genesisTime := time.Now().AddDate(0, 0, -1)
slotDuration := 12 * time.Second
slotsPerSlot := uint64(32)
epochsPerSyncCommitteePeriod := uint64(256)
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerSlot, epochsPerSyncCommitteePeriod)
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithLogLevel(zerolog.Disabled),
standardchaintime.WithGenesisTimeProvider(mockGenesisTimeProvider),
standardchaintime.WithSpecProvider(mockSpecProvider),
)
require.NoError(t, err)

tests := []struct {
name string
input string
err string
expected phase0.Slot
}{
{
name: "Genesis",
input: "0",
expected: 0,
},
{
name: "Invalid",
input: "invalid",
err: `failed to parse slot: strconv.ParseInt: parsing "invalid": invalid syntax`,
},
{
name: "Absolute",
input: "15",
expected: 15,
},
{
name: "Current",
input: "current",
expected: 7200,
},
{
name: "Last",
input: "last",
expected: 7199,
},
{
name: "RelativeZero",
input: "-0",
expected: 7200,
},
{
name: "Relative",
input: "-5",
expected: 7195,
},
{
name: "RelativeFar",
input: "-50000",
expected: 0,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
slot, err := util.ParseSlot(ctx, chainTime, test.input)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, slot)
}
})
}
}

0 comments on commit 1f1ddc1

Please sign in to comment.