mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-05-02 03:02:54 -04:00
<!-- Thanks for sending a PR! Before submitting: 1. If this is your first PR, check out our contribution guide here https://docs.prylabs.network/docs/contribute/contribution-guidelines You will then need to sign our Contributor License Agreement (CLA), which will show up as a comment from a bot in this pull request after you open it. We cannot review code without a signed CLA. 2. Please file an associated tracking issue if this pull request is non-trivial and requires context for our team to understand. All features and most bug fixes should have an associated issue with a design discussed and decided upon. Small bug fixes and documentation improvements don't need issues. 3. New features and bug fixes must have tests. Documentation may need to be updated. If you're unsure what to update, send the PR, and we'll discuss in review. 4. Note that PRs updating dependencies and new Go versions are not accepted. Please file an issue instead. 5. A changelog entry is required for user facing issues. --> **What type of PR is this?** Feature **What does this PR do? Why is it needed?** This PR defines the validator proposer paths for Gloas, namely adding gloas fork support on the validator client for propose function, and adding the following gRPC endpoint support: get Gloas Block, publish Gloas Block, Get Payload Envelope, Publish Payload envelope. we propose in this order 1. Get block (cache payload) 2. Submit block 3. Get payload (get post state of 2 via chain info getter) // needs the block to be recieved and processed 4. Submit payload There are several things still missing in the PR depending on https://github.com/OffchainLabs/prysm/pull/15656 as well as future ones, for now I have left those areas as TODO remarks **Which issues(s) does this PR fix?** Fixes # **Other notes for review** **Acknowledgements** - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [x] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description with sufficient context for reviewers to understand this PR. - [ ] I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable). --------- Co-authored-by: terence <terence@prysmaticlabs.com>
616 lines
20 KiB
Go
616 lines
20 KiB
Go
package client
|
|
|
|
// Validator client proposer functions.
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/async"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
|
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
|
"github.com/OffchainLabs/prysm/v7/config/proposer"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
|
"github.com/OffchainLabs/prysm/v7/crypto/rand"
|
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
validatorpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/validator-client"
|
|
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
|
"github.com/OffchainLabs/prysm/v7/validator/client/iface"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const (
|
|
domainDataErr = "could not get domain data"
|
|
signingRootErr = "could not get signing root"
|
|
signExitErr = "could not sign voluntary exit proposal"
|
|
failedBlockSignLocalErr = "block rejected by local protection"
|
|
)
|
|
|
|
// ProposeBlock proposes a new beacon block for a given slot. This method collects the
|
|
// previous beacon block, any pending deposits, and ETH1 data from the beacon
|
|
// chain node to construct the new block. The new block is then processed with
|
|
// the state root computation, and finally signed by the validator before being
|
|
// sent back to the beacon node for broadcasting.
|
|
func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubKey [fieldparams.BLSPubkeyLength]byte) {
|
|
if slot == 0 {
|
|
log.Debug("Assigned to genesis slot, skipping proposal")
|
|
return
|
|
}
|
|
ctx, span := trace.StartSpan(ctx, "validator.ProposeBlock")
|
|
defer span.End()
|
|
|
|
lock := async.NewMultilock(fmt.Sprint(iface.RoleProposer), string(pubKey[:]))
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
fmtKey := fmt.Sprintf("%#x", pubKey[:])
|
|
span.SetAttributes(trace.StringAttribute("validator", fmtKey))
|
|
log := log.WithField("pubkey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:])))
|
|
|
|
// Sign randao reveal, it's used to request block from beacon node
|
|
epoch := primitives.Epoch(slot / params.BeaconConfig().SlotsPerEpoch)
|
|
randaoReveal, err := v.signRandaoReveal(ctx, pubKey, epoch, slot)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to sign randao reveal")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
g, err := v.Graffiti(ctx, pubKey)
|
|
if err != nil {
|
|
// Graffiti is not a critical enough to fail block production and cause
|
|
// validator to miss block reward. When failed, validator should continue
|
|
// to produce the block.
|
|
log.WithError(err).Warn("Could not get graffiti")
|
|
}
|
|
|
|
// Request block from beacon node
|
|
b, err := v.validatorClient.BeaconBlock(ctx, ðpb.BlockRequest{
|
|
Slot: slot,
|
|
RandaoReveal: randaoReveal,
|
|
Graffiti: g,
|
|
})
|
|
if err != nil {
|
|
log.WithField("slot", slot).WithError(err).Error("Failed to request block from beacon node")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Sign returned block from beacon node
|
|
wb, err := blocks.NewBeaconBlock(b.Block)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to wrap block")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
sig, signingRoot, err := v.signBlock(ctx, pubKey, epoch, slot, wb)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to sign block")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
blk, err := blocks.BuildSignedBeaconBlock(wb, sig)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to build signed beacon block")
|
|
return
|
|
}
|
|
|
|
if err := v.db.SlashableProposalCheck(ctx, pubKey, blk, signingRoot, v.emitAccountMetrics, ValidatorProposeFailVec); err != nil {
|
|
log.WithFields(
|
|
blockLogFields(pubKey, wb, nil),
|
|
).WithError(err).Error("Failed block slashing protection check")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
var genericSignedBlock *ethpb.GenericSignedBeaconBlock
|
|
// Special handling for Deneb blocks and later version because of blob side cars.
|
|
// Gloas blocks are handled differently - no blobs in block, execution payload is separate.
|
|
if blk.Version() >= version.Deneb && blk.Version() < version.Gloas && !blk.IsBlinded() {
|
|
pb, err := blk.Proto()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get deneb block")
|
|
return
|
|
}
|
|
switch blk.Version() {
|
|
case version.Deneb:
|
|
genericSignedBlock, err = buildGenericSignedBlockDenebWithBlobs(pb, b)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to build generic signed block")
|
|
return
|
|
}
|
|
case version.Electra:
|
|
genericSignedBlock, err = buildGenericSignedBlockElectraWithBlobs(pb, b)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to build generic signed block")
|
|
return
|
|
}
|
|
case version.Fulu:
|
|
genericSignedBlock, err = buildGenericSignedBlockFuluWithBlobs(pb, b)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to build generic signed block")
|
|
return
|
|
}
|
|
default:
|
|
log.Errorf("Unsupported block version %s", version.String(blk.Version()))
|
|
}
|
|
} else {
|
|
genericSignedBlock, err = blk.PbGenericBlock()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create proposal request")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
blkResp, err := v.validatorClient.ProposeBeaconBlock(ctx, genericSignedBlock)
|
|
if err != nil {
|
|
log.WithField("slot", slot).WithError(err).Error("Failed to propose block")
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeFailVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := v.proposeSelfBuildEnvelope(ctx, slot, pubKey, blk); err != nil {
|
|
log.WithError(err).Error("Failed to propose self-build envelope")
|
|
return
|
|
}
|
|
|
|
span.SetAttributes(
|
|
trace.StringAttribute("blockRoot", fmt.Sprintf("%#x", blkResp.BlockRoot)),
|
|
trace.Int64Attribute("numDeposits", int64(len(blk.Block().Body().Deposits()))),
|
|
trace.Int64Attribute("numAttestations", int64(len(blk.Block().Body().Attestations()))),
|
|
)
|
|
|
|
if err := logProposedBlock(log, blk, blkResp.BlockRoot); err != nil {
|
|
log.WithError(err).Error("Failed to log proposed block")
|
|
}
|
|
|
|
if v.emitAccountMetrics {
|
|
ValidatorProposeSuccessVec.WithLabelValues(fmtKey).Inc()
|
|
}
|
|
}
|
|
|
|
func logProposedBlock(log *logrus.Entry, blk interfaces.SignedBeaconBlock, blkRoot []byte) error {
|
|
if blk.Version() >= version.Bellatrix && blk.Version() < version.Gloas {
|
|
p, err := blk.Block().Body().Execution()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get execution payload")
|
|
}
|
|
log = log.WithFields(logrus.Fields{
|
|
"payloadHash": fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash())),
|
|
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(p.ParentHash())),
|
|
"blockNumber": p.BlockNumber(),
|
|
})
|
|
if !blk.IsBlinded() {
|
|
txs, err := p.Transactions()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get execution payload transactions")
|
|
}
|
|
log = log.WithField("txCount", len(txs))
|
|
}
|
|
if p.GasLimit() != 0 {
|
|
log = log.WithField("gasUtilized", float64(p.GasUsed())/float64(p.GasLimit()))
|
|
}
|
|
if blk.Version() >= version.Capella && !blk.IsBlinded() {
|
|
withdrawals, err := p.Withdrawals()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get execution payload withdrawals")
|
|
}
|
|
log = log.WithField("withdrawalCount", len(withdrawals))
|
|
}
|
|
if blk.Version() >= version.Deneb {
|
|
kzgs, err := blk.Block().Body().BlobKzgCommitments()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get kzg commitments")
|
|
} else if len(kzgs) != 0 {
|
|
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
|
}
|
|
}
|
|
}
|
|
if blk.Version() >= version.Gloas {
|
|
bid, err := blk.Block().Body().SignedExecutionPayloadBid()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get execution payload bid")
|
|
}
|
|
if bid != nil && bid.Message != nil {
|
|
msg := bid.Message
|
|
log = log.WithFields(logrus.Fields{
|
|
"builderIndex": msg.BuilderIndex,
|
|
"bidValue": msg.Value,
|
|
"blockHash": fmt.Sprintf("%#x", bytesutil.Trunc(msg.BlockHash)),
|
|
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(msg.ParentBlockHash)),
|
|
"gasLimit": msg.GasLimit,
|
|
})
|
|
if len(msg.BlobKzgCommitments) != 0 {
|
|
log = log.WithField("kzgCommitmentCount", len(msg.BlobKzgCommitments))
|
|
}
|
|
}
|
|
payloadAtts, err := blk.Block().Body().PayloadAttestations()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get payload attestations")
|
|
}
|
|
log = log.WithField("payloadAttestationCount", len(payloadAtts))
|
|
}
|
|
|
|
br := fmt.Sprintf("%#x", bytesutil.Trunc(blkRoot))
|
|
graffiti := blk.Block().Body().Graffiti()
|
|
log.WithFields(logrus.Fields{
|
|
"slot": blk.Block().Slot(),
|
|
"blockRoot": br,
|
|
"attestationCount": len(blk.Block().Body().Attestations()),
|
|
"depositCount": len(blk.Block().Body().Deposits()),
|
|
"graffiti": string(graffiti[:]),
|
|
"fork": version.String(blk.Block().Version()),
|
|
}).Info("Submitted new block")
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildGenericSignedBlockDenebWithBlobs(pb proto.Message, b *ethpb.GenericBeaconBlock) (*ethpb.GenericSignedBeaconBlock, error) {
|
|
denebBlock, ok := pb.(*ethpb.SignedBeaconBlockDeneb)
|
|
if !ok {
|
|
return nil, errors.New("could cast to deneb block")
|
|
}
|
|
return ðpb.GenericSignedBeaconBlock{
|
|
Block: ðpb.GenericSignedBeaconBlock_Deneb{
|
|
Deneb: ðpb.SignedBeaconBlockContentsDeneb{
|
|
Block: denebBlock,
|
|
KzgProofs: b.GetDeneb().KzgProofs,
|
|
Blobs: b.GetDeneb().Blobs,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func buildGenericSignedBlockElectraWithBlobs(pb proto.Message, b *ethpb.GenericBeaconBlock) (*ethpb.GenericSignedBeaconBlock, error) {
|
|
electraBlock, ok := pb.(*ethpb.SignedBeaconBlockElectra)
|
|
if !ok {
|
|
return nil, errors.New("could cast to electra block")
|
|
}
|
|
return ðpb.GenericSignedBeaconBlock{
|
|
Block: ðpb.GenericSignedBeaconBlock_Electra{
|
|
Electra: ðpb.SignedBeaconBlockContentsElectra{
|
|
Block: electraBlock,
|
|
KzgProofs: b.GetElectra().KzgProofs,
|
|
Blobs: b.GetElectra().Blobs,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func buildGenericSignedBlockFuluWithBlobs(pb proto.Message, b *ethpb.GenericBeaconBlock) (*ethpb.GenericSignedBeaconBlock, error) {
|
|
fuluBlock, ok := pb.(*ethpb.SignedBeaconBlockFulu)
|
|
if !ok {
|
|
return nil, errors.New("could cast to fulu block")
|
|
}
|
|
return ðpb.GenericSignedBeaconBlock{
|
|
Block: ðpb.GenericSignedBeaconBlock_Fulu{
|
|
Fulu: ðpb.SignedBeaconBlockContentsFulu{
|
|
Block: fuluBlock,
|
|
KzgProofs: b.GetFulu().KzgProofs,
|
|
Blobs: b.GetFulu().Blobs,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// ProposeExit performs a voluntary exit on a validator.
|
|
// The exit is signed by the validator before being sent to the beacon node for broadcasting.
|
|
func ProposeExit(
|
|
ctx context.Context,
|
|
validatorClient iface.ValidatorClient,
|
|
signer iface.SigningFunc,
|
|
pubKey []byte,
|
|
epoch primitives.Epoch,
|
|
) error {
|
|
ctx, span := trace.StartSpan(ctx, "validator.ProposeExit")
|
|
defer span.End()
|
|
|
|
signedExit, err := CreateSignedVoluntaryExit(ctx, validatorClient, signer, pubKey, epoch)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create signed voluntary exit")
|
|
}
|
|
exitResp, err := validatorClient.ProposeExit(ctx, signedExit)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to propose voluntary exit")
|
|
}
|
|
|
|
span.SetAttributes(
|
|
trace.StringAttribute("exitRoot", fmt.Sprintf("%#x", exitResp.ExitRoot)),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func CurrentEpoch(genesisTime *timestamp.Timestamp) (primitives.Epoch, error) {
|
|
currentSlot := slots.CurrentSlot(genesisTime.AsTime())
|
|
currentEpoch := slots.ToEpoch(currentSlot)
|
|
return currentEpoch, nil
|
|
}
|
|
|
|
func CreateSignedVoluntaryExit(
|
|
ctx context.Context,
|
|
validatorClient iface.ValidatorClient,
|
|
signer iface.SigningFunc,
|
|
pubKey []byte,
|
|
epoch primitives.Epoch,
|
|
) (*ethpb.SignedVoluntaryExit, error) {
|
|
ctx, span := trace.StartSpan(ctx, "validator.CreateSignedVoluntaryExit")
|
|
defer span.End()
|
|
|
|
indexResponse, err := validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubKey})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gRPC call to get validator index failed")
|
|
}
|
|
exit := ðpb.VoluntaryExit{Epoch: epoch, ValidatorIndex: indexResponse.Index}
|
|
slot, err := slots.EpochStart(epoch)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to retrieve slot")
|
|
}
|
|
sig, err := signVoluntaryExit(ctx, validatorClient, signer, pubKey, exit, slot)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to sign voluntary exit")
|
|
}
|
|
|
|
return ðpb.SignedVoluntaryExit{Exit: exit, Signature: sig}, nil
|
|
}
|
|
|
|
// Sign randao reveal with randao domain and private key.
|
|
func (v *validator) signRandaoReveal(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, epoch primitives.Epoch, slot primitives.Slot) ([]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "validator.signRandaoReveal")
|
|
defer span.End()
|
|
|
|
domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainRandao[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, domainDataErr)
|
|
}
|
|
if domain == nil {
|
|
return nil, errors.New(domainDataErr)
|
|
}
|
|
|
|
var randaoReveal bls.Signature
|
|
sszUint := primitives.SSZUint64(epoch)
|
|
root, err := signing.ComputeSigningRoot(&sszUint, domain.SignatureDomain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
randaoReveal, err = v.km.Sign(ctx, &validatorpb.SignRequest{
|
|
PublicKey: pubKey[:],
|
|
SigningRoot: root[:],
|
|
SignatureDomain: domain.SignatureDomain,
|
|
Object: &validatorpb.SignRequest_Epoch{Epoch: epoch},
|
|
SigningSlot: slot,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return randaoReveal.Marshal(), nil
|
|
}
|
|
|
|
// Sign block with proposer domain and private key.
|
|
// Returns the signature, block signing root, and any error.
|
|
func (v *validator) signBlock(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, epoch primitives.Epoch, slot primitives.Slot, b interfaces.ReadOnlyBeaconBlock) ([]byte, [32]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "validator.signBlock")
|
|
defer span.End()
|
|
|
|
domain, err := v.domainData(ctx, epoch, params.BeaconConfig().DomainBeaconProposer[:])
|
|
if err != nil {
|
|
return nil, [32]byte{}, errors.Wrap(err, domainDataErr)
|
|
}
|
|
if domain == nil {
|
|
return nil, [32]byte{}, errors.New(domainDataErr)
|
|
}
|
|
|
|
blockRoot, err := signing.ComputeSigningRoot(b, domain.SignatureDomain)
|
|
if err != nil {
|
|
return nil, [32]byte{}, errors.Wrap(err, signingRootErr)
|
|
}
|
|
sro, err := b.AsSignRequestObject()
|
|
if err != nil {
|
|
return nil, [32]byte{}, err
|
|
}
|
|
sig, err := v.km.Sign(ctx, &validatorpb.SignRequest{
|
|
PublicKey: pubKey[:],
|
|
SigningRoot: blockRoot[:],
|
|
SignatureDomain: domain.SignatureDomain,
|
|
Object: sro,
|
|
SigningSlot: slot,
|
|
})
|
|
if err != nil {
|
|
return nil, [32]byte{}, errors.Wrap(err, "could not sign block proposal")
|
|
}
|
|
return sig.Marshal(), blockRoot, nil
|
|
}
|
|
|
|
// Sign voluntary exit with proposer domain and private key.
|
|
func signVoluntaryExit(
|
|
ctx context.Context,
|
|
validatorClient iface.ValidatorClient,
|
|
signer iface.SigningFunc,
|
|
pubKey []byte,
|
|
exit *ethpb.VoluntaryExit,
|
|
slot primitives.Slot,
|
|
) ([]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "validator.signVoluntaryExit")
|
|
defer span.End()
|
|
|
|
req := ðpb.DomainRequest{
|
|
Epoch: exit.Epoch,
|
|
Domain: params.BeaconConfig().DomainVoluntaryExit[:],
|
|
}
|
|
|
|
domain, err := validatorClient.DomainData(ctx, req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, domainDataErr)
|
|
}
|
|
if domain == nil {
|
|
return nil, errors.New(domainDataErr)
|
|
}
|
|
|
|
exitRoot, err := signing.ComputeSigningRoot(exit, domain.SignatureDomain)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, signingRootErr)
|
|
}
|
|
|
|
sig, err := signer(ctx, &validatorpb.SignRequest{
|
|
PublicKey: pubKey,
|
|
SigningRoot: exitRoot[:],
|
|
SignatureDomain: domain.SignatureDomain,
|
|
Object: &validatorpb.SignRequest_Exit{Exit: exit},
|
|
SigningSlot: slot,
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, signExitErr)
|
|
}
|
|
return sig.Marshal(), nil
|
|
}
|
|
|
|
// Graffiti gets the graffiti from cli or file for the validator public key.
|
|
func (v *validator) Graffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
|
ctx, span := trace.StartSpan(ctx, "validator.Graffiti")
|
|
defer span.End()
|
|
|
|
if v.proposerSettings != nil {
|
|
// Check proposer settings for specific key first
|
|
if v.proposerSettings.ProposeConfig != nil {
|
|
option, ok := v.proposerSettings.ProposeConfig[pubKey]
|
|
if ok && option.GraffitiConfig != nil {
|
|
return []byte(option.GraffitiConfig.Graffiti), nil
|
|
}
|
|
}
|
|
// Check proposer settings for default settings second
|
|
if v.proposerSettings.DefaultConfig != nil {
|
|
if v.proposerSettings.DefaultConfig.GraffitiConfig != nil {
|
|
return []byte(v.proposerSettings.DefaultConfig.GraffitiConfig.Graffiti), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// When specified, use default graffiti from the command line.
|
|
if len(v.graffiti) != 0 {
|
|
return bytesutil.PadTo(v.graffiti, 32), nil
|
|
}
|
|
|
|
if v.graffitiStruct == nil {
|
|
return nil, errors.New("graffitiStruct can't be nil")
|
|
}
|
|
|
|
// When specified, individual validator specified graffiti takes the third priority.
|
|
idx, err := v.validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g, ok := v.graffitiStruct.Specific[idx.Index]
|
|
if ok {
|
|
return bytesutil.PadTo([]byte(g), 32), nil
|
|
}
|
|
|
|
// When specified, a graffiti from the ordered list in the file take fourth priority.
|
|
if v.graffitiOrderedIndex < uint64(len(v.graffitiStruct.Ordered)) {
|
|
graffiti := v.graffitiStruct.Ordered[v.graffitiOrderedIndex]
|
|
v.graffitiOrderedIndex = v.graffitiOrderedIndex + 1
|
|
err := v.db.SaveGraffitiOrderedIndex(ctx, v.graffitiOrderedIndex)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to update graffiti ordered index")
|
|
}
|
|
return bytesutil.PadTo([]byte(graffiti), 32), nil
|
|
}
|
|
|
|
// When specified, a graffiti from the random list in the file take Fifth priority.
|
|
if len(v.graffitiStruct.Random) != 0 {
|
|
r := rand.NewGenerator()
|
|
r.Seed(time.Now().Unix())
|
|
i := r.Uint64() % uint64(len(v.graffitiStruct.Random))
|
|
return bytesutil.PadTo([]byte(v.graffitiStruct.Random[i]), 32), nil
|
|
}
|
|
|
|
// Finally, default graffiti if specified in the file will be used.
|
|
if v.graffitiStruct.Default != "" {
|
|
return bytesutil.PadTo([]byte(v.graffitiStruct.Default), 32), nil
|
|
}
|
|
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (v *validator) SetGraffiti(ctx context.Context, pubkey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "validator.SetGraffiti")
|
|
defer span.End()
|
|
|
|
if graffiti == nil {
|
|
return nil
|
|
}
|
|
settings := &proposer.Settings{}
|
|
if v.proposerSettings != nil {
|
|
settings = v.proposerSettings.Clone()
|
|
}
|
|
if settings.ProposeConfig == nil {
|
|
settings.ProposeConfig = map[[48]byte]*proposer.Option{pubkey: {GraffitiConfig: &proposer.GraffitiConfig{Graffiti: string(graffiti)}}}
|
|
return v.SetProposerSettings(ctx, settings)
|
|
}
|
|
option, ok := settings.ProposeConfig[pubkey]
|
|
if !ok || option == nil {
|
|
settings.ProposeConfig[pubkey] = &proposer.Option{GraffitiConfig: &proposer.GraffitiConfig{
|
|
Graffiti: string(graffiti),
|
|
}}
|
|
} else {
|
|
option.GraffitiConfig = &proposer.GraffitiConfig{
|
|
Graffiti: string(graffiti),
|
|
}
|
|
}
|
|
return v.SetProposerSettings(ctx, settings) // save the proposer settings
|
|
}
|
|
|
|
func (v *validator) DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error {
|
|
ctx, span := trace.StartSpan(ctx, "validator.DeleteGraffiti")
|
|
defer span.End()
|
|
|
|
if v.proposerSettings == nil || v.proposerSettings.ProposeConfig == nil {
|
|
return errors.New("attempted to delete graffiti without proposer settings, graffiti will default to flag options")
|
|
}
|
|
ps := v.proposerSettings.Clone()
|
|
option, ok := ps.ProposeConfig[pubKey]
|
|
if !ok || option == nil {
|
|
return fmt.Errorf("graffiti not found in proposer settings for pubkey:%s", hexutil.Encode(pubKey[:]))
|
|
}
|
|
option.GraffitiConfig = nil
|
|
return v.SetProposerSettings(ctx, ps) // save the proposer settings
|
|
}
|
|
|
|
func blockLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, blk interfaces.ReadOnlyBeaconBlock, sig []byte) logrus.Fields {
|
|
fields := logrus.Fields{
|
|
"proposerPublicKey": fmt.Sprintf("%#x", pubKey),
|
|
"proposerIndex": blk.ProposerIndex(),
|
|
"blockSlot": blk.Slot(),
|
|
}
|
|
if sig != nil {
|
|
fields["signature"] = fmt.Sprintf("%#x", sig)
|
|
}
|
|
return fields
|
|
}
|