mirror of https://github.com/certusone/vouch.git
200 lines
6.8 KiB
Go
200 lines
6.8 KiB
Go
// Copyright © 2020 Attestant Limited.
|
|
// 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 standard
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
eth2client "github.com/attestantio/go-eth2-client"
|
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
|
"github.com/attestantio/vouch/services/accountmanager"
|
|
"github.com/attestantio/vouch/services/beaconblockproposer"
|
|
"github.com/attestantio/vouch/services/chaintime"
|
|
"github.com/attestantio/vouch/services/graffitiprovider"
|
|
"github.com/attestantio/vouch/services/metrics"
|
|
"github.com/attestantio/vouch/services/signer"
|
|
"github.com/attestantio/vouch/services/submitter"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog"
|
|
zerologger "github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// Service is a beacon block proposer.
|
|
type Service struct {
|
|
monitor metrics.BeaconBlockProposalMonitor
|
|
chainTimeService chaintime.Service
|
|
proposalProvider eth2client.BeaconBlockProposalProvider
|
|
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
|
|
graffitiProvider graffitiprovider.Service
|
|
beaconBlockSubmitter submitter.BeaconBlockSubmitter
|
|
randaoRevealSigner signer.RANDAORevealSigner
|
|
beaconBlockSigner signer.BeaconBlockSigner
|
|
}
|
|
|
|
// module-wide log.
|
|
var log zerolog.Logger
|
|
|
|
// New creates a new beacon block proposer.
|
|
func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
|
parameters, err := parseAndCheckParameters(params...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "problem with parameters")
|
|
}
|
|
|
|
// Set logging.
|
|
log = zerologger.With().Str("service", "beaconblockproposer").Str("impl", "standard").Logger()
|
|
if parameters.logLevel != log.GetLevel() {
|
|
log = log.Level(parameters.logLevel)
|
|
}
|
|
|
|
s := &Service{
|
|
monitor: parameters.monitor,
|
|
chainTimeService: parameters.chainTimeService,
|
|
proposalProvider: parameters.proposalProvider,
|
|
validatingAccountsProvider: parameters.validatingAccountsProvider,
|
|
graffitiProvider: parameters.graffitiProvider,
|
|
beaconBlockSubmitter: parameters.beaconBlockSubmitter,
|
|
randaoRevealSigner: parameters.randaoRevealSigner,
|
|
beaconBlockSigner: parameters.beaconBlockSigner,
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Prepare prepares for a beacon block proposal, carrying out activities that
|
|
// can be undertaken before the time the proposal is required.
|
|
func (s *Service) Prepare(ctx context.Context, data interface{}) error {
|
|
started := time.Now()
|
|
|
|
duty, ok := data.(*beaconblockproposer.Duty)
|
|
if !ok {
|
|
return errors.New("passed invalid data structure")
|
|
}
|
|
log := log.With().Uint64("proposing_slot", uint64(duty.Slot())).Uint64("validator_index", uint64(duty.ValidatorIndex())).Logger()
|
|
log.Trace().Msg("Preparing")
|
|
|
|
dutyEpoch := s.chainTimeService.SlotToEpoch(duty.Slot())
|
|
// Fetch the validating account.
|
|
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx,
|
|
dutyEpoch,
|
|
[]phase0.ValidatorIndex{duty.ValidatorIndex()},
|
|
)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to obtain proposing validator account")
|
|
}
|
|
if len(accounts) != 1 {
|
|
return fmt.Errorf("unknown proposing validator account %d", duty.ValidatorIndex())
|
|
}
|
|
account := accounts[duty.ValidatorIndex()]
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained proposing account")
|
|
duty.SetAccount(account)
|
|
|
|
randaoReveal, err := s.randaoRevealSigner.SignRANDAOReveal(ctx, account, duty.Slot())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to sign RANDAO reveal")
|
|
}
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Signed RANDAO reveal")
|
|
|
|
duty.SetRandaoReveal(randaoReveal)
|
|
return nil
|
|
}
|
|
|
|
// Propose proposes a block.
|
|
func (s *Service) Propose(ctx context.Context, data interface{}) {
|
|
started := time.Now()
|
|
|
|
duty, ok := data.(*beaconblockproposer.Duty)
|
|
if !ok {
|
|
log.Error().Msg("Passed invalid data structure")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
log := log.With().Uint64("proposing_slot", uint64(duty.Slot())).Uint64("validator_index", uint64(duty.ValidatorIndex())).Logger()
|
|
log.Trace().Msg("Proposing")
|
|
|
|
var zeroSig phase0.BLSSignature
|
|
if duty.RANDAOReveal() == zeroSig {
|
|
log.Error().Msg("Missing RANDAO reveal")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
|
|
var graffiti []byte
|
|
var err error
|
|
if s.graffitiProvider != nil {
|
|
graffiti, err = s.graffitiProvider.Graffiti(ctx, duty.Slot(), duty.ValidatorIndex())
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("Failed to obtain graffiti")
|
|
graffiti = nil
|
|
}
|
|
}
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained graffiti")
|
|
|
|
proposal, err := s.proposalProvider.BeaconBlockProposal(ctx, duty.Slot(), duty.RANDAOReveal(), graffiti)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to obtain proposal data")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
if proposal == nil {
|
|
log.Error().Msg("Provider did not return beacon block proposal")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained proposal")
|
|
|
|
if proposal.Slot != duty.Slot() {
|
|
log.Error().Uint64("proposal_slot", uint64(proposal.Slot)).Msg("Proposal data for incorrect slot; not proceeding")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
|
|
bodyRoot, err := proposal.Body.HashTreeRoot()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to calculate hash tree root of block")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
|
|
sig, err := s.beaconBlockSigner.SignBeaconBlockProposal(ctx,
|
|
duty.Account(),
|
|
proposal.Slot,
|
|
duty.ValidatorIndex(),
|
|
proposal.ParentRoot,
|
|
proposal.StateRoot,
|
|
bodyRoot)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to sign beacon block proposal")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Signed proposal")
|
|
|
|
signedBlock := &phase0.SignedBeaconBlock{
|
|
Message: proposal,
|
|
Signature: sig,
|
|
}
|
|
|
|
// Submit the block.
|
|
if err := s.beaconBlockSubmitter.SubmitBeaconBlock(ctx, signedBlock); err != nil {
|
|
log.Error().Err(err).Msg("Failed to submit beacon block proposal")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "failed")
|
|
return
|
|
}
|
|
log.Trace().Dur("elapsed", time.Since(started)).Msg("Submitted proposal")
|
|
s.monitor.BeaconBlockProposalCompleted(started, "succeeded")
|
|
}
|