mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
More data for epoch summaries.
This commit is contained in:
@@ -1,3 +1,12 @@
|
|||||||
|
1.36.1:
|
||||||
|
- more JSON data for epoch summary
|
||||||
|
|
||||||
|
1.36.0:
|
||||||
|
- support keystore wallets
|
||||||
|
|
||||||
|
1.35.6:
|
||||||
|
- provide more JSON data in "epoch summary"
|
||||||
|
|
||||||
1.35.5:
|
1.35.5:
|
||||||
- allow keystore to be output to the console
|
- allow keystore to be output to the console
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ func init() {
|
|||||||
RootCmd.AddCommand(epochCmd)
|
RootCmd.AddCommand(epochCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func epochFlags(_ *cobra.Command) {
|
func epochFlags(cmd *cobra.Command) {
|
||||||
epochSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
cmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func epochBindings(cmd *cobra.Command) {
|
func epochBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ type command struct {
|
|||||||
allowInsecureConnections bool
|
allowInsecureConnections bool
|
||||||
|
|
||||||
// Operation.
|
// Operation.
|
||||||
epoch string
|
epoch string
|
||||||
stream bool
|
validatorsStr []string
|
||||||
jsonOutput bool
|
validators map[phase0.ValidatorIndex]struct{}
|
||||||
|
stream bool
|
||||||
|
jsonOutput bool
|
||||||
|
|
||||||
// Data access.
|
// Data access.
|
||||||
eth2Client eth2client.Service
|
eth2Client eth2client.Service
|
||||||
@@ -58,45 +60,61 @@ type command struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type epochSummary struct {
|
type epochSummary struct {
|
||||||
Epoch phase0.Epoch `json:"epoch"`
|
Epoch phase0.Epoch `json:"epoch"`
|
||||||
FirstSlot phase0.Slot `json:"first_slot"`
|
FirstSlot phase0.Slot `json:"first_slot"`
|
||||||
LastSlot phase0.Slot `json:"last_slot"`
|
LastSlot phase0.Slot `json:"last_slot"`
|
||||||
Proposals []*epochProposal `json:"proposals"`
|
Blocks int `json:"blocks"`
|
||||||
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
Proposals []*epochProposal `json:"proposals"`
|
||||||
ActiveValidators int `json:"active_validators"`
|
SyncCommitteeValidators int `json:"sync_committee_validators"`
|
||||||
ParticipatingValidators int `json:"participating_validators"`
|
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
||||||
HeadCorrectValidators int `json:"head_correct_validators"`
|
ActiveValidators int `json:"active_validators"`
|
||||||
HeadTimelyValidators int `json:"head_timely_validators"`
|
ParticipatingValidators int `json:"participating_validators"`
|
||||||
SourceTimelyValidators int `json:"source_timely_validators"`
|
HeadCorrectValidators int `json:"head_correct_validators"`
|
||||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
HeadTimelyValidators int `json:"head_timely_validators"`
|
||||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
SourceTimelyValidators int `json:"source_timely_validators"`
|
||||||
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
|
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||||
Blobs int `json:"blobs"`
|
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||||
|
NonParticipatingValidators []*attestingValidator `json:"nonparticipating_validators"`
|
||||||
|
NonHeadCorrectValidators []*attestingValidator `json:"nonheadcorrect_validators"`
|
||||||
|
NonHeadTimelyValidators []*attestingValidator `json:"nonheadtimely_validators"`
|
||||||
|
NonTargetCorrectValidators []*attestingValidator `json:"nontargetcorrect_validators"`
|
||||||
|
NonSourceTimelyValidators []*attestingValidator `json:"nonsourcetimely_validators"`
|
||||||
|
Blobs int `json:"blobs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type epochProposal struct {
|
type epochProposal struct {
|
||||||
Slot phase0.Slot `json:"slot"`
|
ValidatorIndex phase0.ValidatorIndex `json:"validator_index"`
|
||||||
Proposer phase0.ValidatorIndex `json:"proposer"`
|
Slot phase0.Slot `json:"slot"`
|
||||||
Block bool `json:"block"`
|
Block bool `json:"block"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type epochSyncCommittee struct {
|
type epochSyncCommittee struct {
|
||||||
Index phase0.ValidatorIndex `json:"index"`
|
ValidatorIndex phase0.ValidatorIndex `json:"validator_index"`
|
||||||
Missed int `json:"missed"`
|
Missed int `json:"missed"`
|
||||||
|
MissedSlots []phase0.Slot `json:"missed_slots"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type nonParticipatingValidator struct {
|
type attestingValidator struct {
|
||||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||||
Slot phase0.Slot `json:"slot"`
|
Slot phase0.Slot `json:"slot"`
|
||||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||||
|
HeadVote *phase0.Root `json:"head_vote,omitempty"`
|
||||||
|
Head *phase0.Root `json:"head,omitempty"`
|
||||||
|
TargetVote *phase0.Root `json:"target_vote,omitempty"`
|
||||||
|
Target *phase0.Root `json:"target,omitempty"`
|
||||||
|
InclusionSlot phase0.Slot `json:"inclusion_slot,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommand(_ context.Context) (*command, error) {
|
func newCommand(_ context.Context) (*command, error) {
|
||||||
c := &command{
|
c := &command{
|
||||||
quiet: viper.GetBool("quiet"),
|
quiet: viper.GetBool("quiet"),
|
||||||
verbose: viper.GetBool("verbose"),
|
verbose: viper.GetBool("verbose"),
|
||||||
debug: viper.GetBool("debug"),
|
debug: viper.GetBool("debug"),
|
||||||
summary: &epochSummary{},
|
validatorsStr: viper.GetStringSlice("validators"),
|
||||||
|
summary: &epochSummary{
|
||||||
|
Proposals: make([]*epochProposal, 0),
|
||||||
|
},
|
||||||
|
validators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
|||||||
missedProposals := make([]string, 0, len(c.summary.Proposals))
|
missedProposals := make([]string, 0, len(c.summary.Proposals))
|
||||||
for _, proposal := range c.summary.Proposals {
|
for _, proposal := range c.summary.Proposals {
|
||||||
if !proposal.Block {
|
if !proposal.Block {
|
||||||
missedProposals = append(missedProposals, fmt.Sprintf("\n Slot %d (validator %d)", proposal.Slot, proposal.Proposer))
|
missedProposals = append(missedProposals, fmt.Sprintf("\n Slot %d (validator %d)", proposal.Slot, proposal.ValidatorIndex))
|
||||||
} else {
|
} else {
|
||||||
proposedBlocks++
|
proposedBlocks++
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
|||||||
builder.WriteString("\n Slot ")
|
builder.WriteString("\n Slot ")
|
||||||
builder.WriteString(fmt.Sprintf("%d (%d/%d)", proposal.Slot, uint64(proposal.Slot)%uint64(len(c.summary.Proposals)), len(c.summary.Proposals)))
|
builder.WriteString(fmt.Sprintf("%d (%d/%d)", proposal.Slot, uint64(proposal.Slot)%uint64(len(c.summary.Proposals)), len(c.summary.Proposals)))
|
||||||
builder.WriteString(" validator ")
|
builder.WriteString(" validator ")
|
||||||
builder.WriteString(fmt.Sprintf("%d", proposal.Proposer))
|
builder.WriteString(fmt.Sprintf("%d", proposal.ValidatorIndex))
|
||||||
builder.WriteString(" not proposed or not included")
|
builder.WriteString(" not proposed or not included")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
|||||||
if c.verbose {
|
if c.verbose {
|
||||||
for _, syncCommittee := range c.summary.SyncCommittee {
|
for _, syncCommittee := range c.summary.SyncCommittee {
|
||||||
builder.WriteString("\n Validator ")
|
builder.WriteString("\n Validator ")
|
||||||
builder.WriteString(fmt.Sprintf("%d", syncCommittee.Index))
|
builder.WriteString(fmt.Sprintf("%d", syncCommittee.ValidatorIndex))
|
||||||
builder.WriteString(" included ")
|
builder.WriteString(" included ")
|
||||||
builder.WriteString(fmt.Sprintf("%d/%d", proposedBlocks-syncCommittee.Missed, proposedBlocks))
|
builder.WriteString(fmt.Sprintf("%d/%d", proposedBlocks-syncCommittee.Missed, proposedBlocks))
|
||||||
builder.WriteString(fmt.Sprintf(" (%0.2f%%)", 100.0*float64(proposedBlocks-syncCommittee.Missed)/float64(proposedBlocks)))
|
builder.WriteString(fmt.Sprintf(" (%0.2f%%)", 100.0*float64(proposedBlocks-syncCommittee.Missed)/float64(proposedBlocks)))
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
c.summary.FirstSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)
|
c.summary.FirstSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)
|
||||||
c.summary.LastSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) - 1
|
c.summary.LastSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) - 1
|
||||||
|
|
||||||
|
validators, err := util.ParseValidators(ctx, c.validatorsProvider, c.validatorsStr, "head")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to parse validators")
|
||||||
|
}
|
||||||
|
for _, validator := range validators {
|
||||||
|
c.validators[validator.Index] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.processProposerDuties(ctx); err != nil {
|
if err := c.processProposerDuties(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -52,6 +60,7 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.processBlobs(ctx)
|
return c.processBlobs(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +78,20 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
|||||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
||||||
}
|
}
|
||||||
present := block != nil
|
present := block != nil
|
||||||
|
if present {
|
||||||
|
c.summary.Blocks++
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := c.validators[duty.ValidatorIndex]
|
||||||
|
if len(c.validators) > 0 && !exists {
|
||||||
|
// Not one of ours.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
c.summary.Proposals = append(c.summary.Proposals, &epochProposal{
|
c.summary.Proposals = append(c.summary.Proposals, &epochProposal{
|
||||||
Slot: duty.Slot,
|
Slot: duty.Slot,
|
||||||
Proposer: duty.ValidatorIndex,
|
ValidatorIndex: duty.ValidatorIndex,
|
||||||
Block: present,
|
Block: present,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +99,25 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
|
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
|
||||||
|
validatorIndices := make([]phase0.ValidatorIndex, 0, len(c.validators))
|
||||||
|
for validator := range c.validators {
|
||||||
|
validatorIndices = append(validatorIndices, validator)
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
|
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
|
||||||
State: fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)),
|
State: "head",
|
||||||
|
Indices: validatorIndices,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
|
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
|
||||||
}
|
}
|
||||||
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
||||||
for _, validator := range response.Data {
|
for _, validator := range response.Data {
|
||||||
|
_, exists := c.validators[validator.Index]
|
||||||
|
if len(c.validators) > 0 && !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
|
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
|
||||||
activeValidators[validator.Index] = validator
|
activeValidators[validator.Index] = validator
|
||||||
}
|
}
|
||||||
@@ -112,20 +142,45 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
|||||||
lastSlot = c.chainTime.CurrentSlot()
|
lastSlot = c.chainTime.CurrentSlot()
|
||||||
}
|
}
|
||||||
|
|
||||||
var votes map[phase0.ValidatorIndex]struct{}
|
participatingValidators, headCorrectValidators, headTimelyValidators, sourceTimelyValidators, targetCorrectValidators, targetTimelyValidators, participations, err := c.processSlots(ctx, firstSlot, lastSlot)
|
||||||
var participations map[phase0.ValidatorIndex]*nonParticipatingValidator
|
|
||||||
c.summary.ParticipatingValidators, c.summary.HeadCorrectValidators, c.summary.HeadTimelyValidators, c.summary.SourceTimelyValidators, c.summary.TargetCorrectValidators, c.summary.TargetTimelyValidators, votes, participations, err = c.processSlots(ctx, firstSlot, lastSlot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0, len(activeValidators)-len(votes))
|
c.summary.ParticipatingValidators = len(participatingValidators)
|
||||||
|
c.summary.HeadCorrectValidators = len(headCorrectValidators)
|
||||||
|
c.summary.HeadTimelyValidators = len(headTimelyValidators)
|
||||||
|
c.summary.SourceTimelyValidators = len(sourceTimelyValidators)
|
||||||
|
c.summary.TargetCorrectValidators = len(targetCorrectValidators)
|
||||||
|
c.summary.TargetTimelyValidators = len(targetTimelyValidators)
|
||||||
|
|
||||||
|
c.summary.NonParticipatingValidators = make([]*attestingValidator, 0, len(activeValidators)-len(participatingValidators))
|
||||||
for activeValidatorIndex := range activeValidators {
|
for activeValidatorIndex := range activeValidators {
|
||||||
if _, exists := votes[activeValidatorIndex]; !exists {
|
if _, exists := participatingValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
|
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, exists := headCorrectValidators[activeValidatorIndex]; !exists {
|
||||||
|
if _, exists := participations[activeValidatorIndex]; exists {
|
||||||
|
c.summary.NonHeadCorrectValidators = append(c.summary.NonHeadCorrectValidators, participations[activeValidatorIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := headTimelyValidators[activeValidatorIndex]; !exists {
|
||||||
|
if _, exists := participations[activeValidatorIndex]; exists {
|
||||||
|
c.summary.NonHeadTimelyValidators = append(c.summary.NonHeadTimelyValidators, participations[activeValidatorIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := targetCorrectValidators[activeValidatorIndex]; !exists {
|
||||||
|
if _, exists := participations[activeValidatorIndex]; exists {
|
||||||
|
c.summary.NonTargetCorrectValidators = append(c.summary.NonTargetCorrectValidators, participations[activeValidatorIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, exists := sourceTimelyValidators[activeValidatorIndex]; !exists {
|
||||||
|
if _, exists := participations[activeValidatorIndex]; exists {
|
||||||
|
c.summary.NonSourceTimelyValidators = append(c.summary.NonSourceTimelyValidators, participations[activeValidatorIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
|
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
|
||||||
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
|
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
|
||||||
@@ -140,18 +195,18 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func (c *command) processSlots(ctx context.Context,
|
func (c *command) processSlots(ctx context.Context,
|
||||||
firstSlot phase0.Slot,
|
firstSlot phase0.Slot,
|
||||||
lastSlot phase0.Slot,
|
lastSlot phase0.Slot,
|
||||||
) (
|
) (
|
||||||
int,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
int,
|
|
||||||
map[phase0.ValidatorIndex]struct{},
|
map[phase0.ValidatorIndex]struct{},
|
||||||
map[phase0.ValidatorIndex]*nonParticipatingValidator,
|
map[phase0.ValidatorIndex]struct{},
|
||||||
|
map[phase0.ValidatorIndex]struct{},
|
||||||
|
map[phase0.ValidatorIndex]struct{},
|
||||||
|
map[phase0.ValidatorIndex]struct{},
|
||||||
|
map[phase0.ValidatorIndex]struct{},
|
||||||
|
map[phase0.ValidatorIndex]*attestingValidator,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
votes := make(map[phase0.ValidatorIndex]struct{})
|
votes := make(map[phase0.ValidatorIndex]struct{})
|
||||||
@@ -161,7 +216,7 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
|
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
|
||||||
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
|
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
|
||||||
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||||
participations := make(map[phase0.ValidatorIndex]*nonParticipatingValidator)
|
participations := make(map[phase0.ValidatorIndex]*attestingValidator)
|
||||||
|
|
||||||
// Need a cache of beacon block headers to reduce lookup times.
|
// Need a cache of beacon block headers to reduce lookup times.
|
||||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||||
@@ -169,7 +224,7 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||||
}
|
}
|
||||||
if block == nil {
|
if block == nil {
|
||||||
// No block at this slot; that's fine.
|
// No block at this slot; that's fine.
|
||||||
@@ -177,11 +232,11 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
}
|
}
|
||||||
slot, err := block.Slot()
|
slot, err := block.Slot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
attestations, err := block.Attestations()
|
attestations, err := block.Attestations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
for _, attestation := range attestations {
|
for _, attestation := range attestations {
|
||||||
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
||||||
@@ -194,18 +249,29 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
State: fmt.Sprintf("%d", attestation.Data.Slot),
|
State: fmt.Sprintf("%d", attestation.Data.Slot),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
|
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
|
||||||
}
|
}
|
||||||
for _, beaconCommittee := range response.Data {
|
for _, beaconCommittee := range response.Data {
|
||||||
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
||||||
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
allCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
allCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
||||||
|
|
||||||
for _, index := range beaconCommittee.Validators {
|
for _, index := range beaconCommittee.Validators {
|
||||||
participations[index] = &nonParticipatingValidator{
|
if len(c.validators) > 0 {
|
||||||
Validator: index,
|
if _, exists := c.validators[index]; !exists {
|
||||||
Slot: beaconCommittee.Slot,
|
// Not one of our validators.
|
||||||
Committee: beaconCommittee.Index,
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := participations[index]; !exists {
|
||||||
|
participations[index] = &attestingValidator{
|
||||||
|
Validator: index,
|
||||||
|
Slot: beaconCommittee.Slot,
|
||||||
|
Committee: beaconCommittee.Index,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,44 +280,70 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
committee := slotCommittees[attestation.Data.Index]
|
committee := slotCommittees[attestation.Data.Index]
|
||||||
|
|
||||||
inclusionDistance := slot - attestation.Data.Slot
|
inclusionDistance := slot - attestation.Data.Slot
|
||||||
|
|
||||||
|
head, err := util.AttestationHead(ctx, headersCache, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
target, err := util.AttestationTarget(ctx, headersCache, c.chainTime, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
|
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
|
||||||
if attestation.AggregationBits.BitAt(i) {
|
if attestation.AggregationBits.BitAt(i) {
|
||||||
votes[committee[int(i)]] = struct{}{}
|
validatorIndex := committee[int(i)]
|
||||||
if _, exists := headCorrects[committee[int(i)]]; !exists && headCorrect {
|
if len(c.validators) > 0 {
|
||||||
headCorrects[committee[int(i)]] = struct{}{}
|
if _, exists := c.validators[validatorIndex]; !exists {
|
||||||
|
// Not one of our validators.
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if _, exists := headTimelys[committee[int(i)]]; !exists && headCorrect && inclusionDistance == 1 {
|
|
||||||
headTimelys[committee[int(i)]] = struct{}{}
|
// Only set the information from the first attestation we find for this validator.
|
||||||
|
if participations[validatorIndex].InclusionSlot == 0 {
|
||||||
|
participations[validatorIndex].HeadVote = &attestation.Data.BeaconBlockRoot
|
||||||
|
participations[validatorIndex].Head = &head
|
||||||
|
participations[validatorIndex].TargetVote = &attestation.Data.Target.Root
|
||||||
|
participations[validatorIndex].Target = &target
|
||||||
|
participations[validatorIndex].InclusionSlot = slot
|
||||||
}
|
}
|
||||||
if _, exists := sourceTimelys[committee[int(i)]]; !exists && inclusionDistance <= 5 {
|
|
||||||
sourceTimelys[committee[int(i)]] = struct{}{}
|
votes[validatorIndex] = struct{}{}
|
||||||
|
if _, exists := headCorrects[validatorIndex]; !exists && headCorrect {
|
||||||
|
headCorrects[validatorIndex] = struct{}{}
|
||||||
}
|
}
|
||||||
if _, exists := targetCorrects[committee[int(i)]]; !exists && targetCorrect {
|
if _, exists := headTimelys[validatorIndex]; !exists && headCorrect && inclusionDistance == 1 {
|
||||||
targetCorrects[committee[int(i)]] = struct{}{}
|
headTimelys[validatorIndex] = struct{}{}
|
||||||
}
|
}
|
||||||
if _, exists := targetTimelys[committee[int(i)]]; !exists && targetCorrect && inclusionDistance <= 32 {
|
if _, exists := sourceTimelys[validatorIndex]; !exists && inclusionDistance <= 5 {
|
||||||
targetTimelys[committee[int(i)]] = struct{}{}
|
sourceTimelys[validatorIndex] = struct{}{}
|
||||||
|
}
|
||||||
|
if _, exists := targetCorrects[validatorIndex]; !exists && targetCorrect {
|
||||||
|
targetCorrects[validatorIndex] = struct{}{}
|
||||||
|
}
|
||||||
|
if _, exists := targetTimelys[validatorIndex]; !exists && targetCorrect && inclusionDistance <= 32 {
|
||||||
|
targetTimelys[validatorIndex] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(votes),
|
|
||||||
len(headCorrects),
|
return votes,
|
||||||
len(headTimelys),
|
headCorrects,
|
||||||
len(sourceTimelys),
|
headTimelys,
|
||||||
len(targetCorrects),
|
sourceTimelys,
|
||||||
len(targetTimelys),
|
targetCorrects,
|
||||||
votes,
|
targetTimelys,
|
||||||
participations,
|
participations,
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
@@ -273,7 +365,18 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
|||||||
return errors.Wrap(err, "empty sync committee")
|
return errors.Wrap(err, "empty sync committee")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, validatorIndex := range committee.Validators {
|
||||||
|
if len(c.validators) == 0 {
|
||||||
|
c.summary.SyncCommitteeValidators++
|
||||||
|
} else {
|
||||||
|
if _, exists := c.validators[validatorIndex]; exists {
|
||||||
|
c.summary.SyncCommitteeValidators++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
missed := make(map[phase0.ValidatorIndex]int)
|
missed := make(map[phase0.ValidatorIndex]int)
|
||||||
|
missedSlots := make(map[phase0.ValidatorIndex][]phase0.Slot)
|
||||||
for _, index := range committee.Validators {
|
for _, index := range committee.Validators {
|
||||||
missed[index] = 0
|
missed[index] = 0
|
||||||
}
|
}
|
||||||
@@ -297,8 +400,14 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
|||||||
return errors.Wrapf(err, "failed to obtain sync aggregate for slot %d", slot)
|
return errors.Wrapf(err, "failed to obtain sync aggregate for slot %d", slot)
|
||||||
}
|
}
|
||||||
for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
|
for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
|
||||||
|
validatorIndex := committee.Validators[int(i)]
|
||||||
|
if _, exists := c.validators[validatorIndex]; !exists {
|
||||||
|
// Not one of ours.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !aggregate.SyncCommitteeBits.BitAt(i) {
|
if !aggregate.SyncCommitteeBits.BitAt(i) {
|
||||||
missed[committee.Validators[int(i)]]++
|
missed[validatorIndex]++
|
||||||
|
missedSlots[validatorIndex] = append(missedSlots[validatorIndex], slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,8 +416,9 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
|||||||
for index, count := range missed {
|
for index, count := range missed {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
c.summary.SyncCommittee = append(c.summary.SyncCommittee, &epochSyncCommittee{
|
c.summary.SyncCommittee = append(c.summary.SyncCommittee, &epochSyncCommittee{
|
||||||
Index: index,
|
ValidatorIndex: index,
|
||||||
Missed: count,
|
Missed: count,
|
||||||
|
MissedSlots: missedSlots[index],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,7 +430,7 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
|||||||
return missedDiff > 0
|
return missedDiff > 0
|
||||||
}
|
}
|
||||||
// Then order by validator index.
|
// Then order by validator index.
|
||||||
return c.summary.SyncCommittee[i].Index < c.summary.SyncCommittee[j].Index
|
return c.summary.SyncCommittee[i].ValidatorIndex < c.summary.SyncCommittee[j].ValidatorIndex
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -378,10 +488,10 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *command) processBlobs(ctx context.Context) error {
|
func (c *command) processBlobs(ctx context.Context) error {
|
||||||
for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
|
for _, proposal := range c.summary.Proposals {
|
||||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", proposal.Slot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", proposal.Slot))
|
||||||
}
|
}
|
||||||
if block == nil {
|
if block == nil {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -46,9 +46,17 @@ In quiet mode this will return 0 if information for the epoch is found, otherwis
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
epochCmd.AddCommand(epochSummaryCmd)
|
epochCmd.AddCommand(epochSummaryCmd)
|
||||||
epochFlags(epochSummaryCmd)
|
epochSummaryFlags(epochSummaryCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func epochSummaryFlags(cmd *cobra.Command) {
|
||||||
|
epochFlags(cmd)
|
||||||
|
cmd.Flags().StringSlice("validators", nil, "the validators for which to obtain a summary")
|
||||||
}
|
}
|
||||||
|
|
||||||
func epochSummaryBindings(cmd *cobra.Command) {
|
func epochSummaryBindings(cmd *cobra.Command) {
|
||||||
epochBindings(cmd)
|
epochBindings(cmd)
|
||||||
|
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,35 @@ import (
|
|||||||
"github.com/wealdtech/ethdo/services/chaintime"
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AttestationHead returns the head for which the attestation should have voted.
|
||||||
|
func AttestationHead(ctx context.Context,
|
||||||
|
headersCache *BeaconBlockHeaderCache,
|
||||||
|
attestation *phase0.Attestation,
|
||||||
|
) (
|
||||||
|
phase0.Root,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
slot := attestation.Data.Slot
|
||||||
|
for {
|
||||||
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
|
if err != nil {
|
||||||
|
return phase0.Root{}, err
|
||||||
|
}
|
||||||
|
if header == nil {
|
||||||
|
// No block.
|
||||||
|
slot--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !header.Canonical {
|
||||||
|
// Not canonical.
|
||||||
|
slot--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return header.Root, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AttestationHeadCorrect returns true if the given attestation had the correct head.
|
// AttestationHeadCorrect returns true if the given attestation had the correct head.
|
||||||
func AttestationHeadCorrect(ctx context.Context,
|
func AttestationHeadCorrect(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
@@ -49,6 +78,37 @@ func AttestationHeadCorrect(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttestationTarget returns the target for which the attestation should have voted.
|
||||||
|
func AttestationTarget(ctx context.Context,
|
||||||
|
headersCache *BeaconBlockHeaderCache,
|
||||||
|
chainTime chaintime.Service,
|
||||||
|
attestation *phase0.Attestation,
|
||||||
|
) (
|
||||||
|
phase0.Root,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
// Start with first slot of the target epoch.
|
||||||
|
slot := chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
||||||
|
for {
|
||||||
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
|
if err != nil {
|
||||||
|
return phase0.Root{}, err
|
||||||
|
}
|
||||||
|
if header == nil {
|
||||||
|
// No block.
|
||||||
|
slot--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !header.Canonical {
|
||||||
|
// Not canonical.
|
||||||
|
slot--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return header.Root, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AttestationTargetCorrect returns true if the given attestation had the correct target.
|
// AttestationTargetCorrect returns true if the given attestation had the correct target.
|
||||||
func AttestationTargetCorrect(ctx context.Context,
|
func AttestationTargetCorrect(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
|
|||||||
Reference in New Issue
Block a user