mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-23 11:57:18 +00:00
d17996f8b0
* Update V3 from V4 * Fix build v3 -> v4 * Update ssz * Update beacon_chain.pb.go * Fix formatter import * Update update-mockgen.sh comment to v4 * Fix conflicts. Pass build and tests * Fix test
201 lines
7.5 KiB
Go
201 lines
7.5 KiB
Go
package history
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
|
"github.com/prysmaticlabs/prysm/v4/config/params"
|
|
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/v4/monitoring/progress"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/db"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
|
|
)
|
|
|
|
// ExportStandardProtectionJSON extracts all slashing protection data from a validator database
|
|
// and packages it into an EIP-3076 compliant, standard
|
|
func ExportStandardProtectionJSON(
|
|
ctx context.Context,
|
|
validatorDB db.Database,
|
|
filteredKeys ...[]byte,
|
|
) (*format.EIPSlashingProtectionFormat, error) {
|
|
interchangeJSON := &format.EIPSlashingProtectionFormat{}
|
|
genesisValidatorsRoot, err := validatorDB.GenesisValidatorsRoot(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not get genesis validators root from DB")
|
|
}
|
|
if genesisValidatorsRoot == nil || !bytesutil.IsValidRoot(genesisValidatorsRoot) {
|
|
return nil, errors.New(
|
|
"genesis validators root is empty, perhaps you are not connected to your beacon node",
|
|
)
|
|
}
|
|
genesisRootHex, err := rootToHexString(genesisValidatorsRoot)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not convert genesis validators root to hex string")
|
|
}
|
|
interchangeJSON.Metadata.GenesisValidatorsRoot = genesisRootHex
|
|
interchangeJSON.Metadata.InterchangeFormatVersion = format.InterchangeFormatVersion
|
|
|
|
// Allow for filtering data for the keys we wish to export.
|
|
filteredKeysMap := make(map[string]bool, len(filteredKeys))
|
|
for _, k := range filteredKeys {
|
|
filteredKeysMap[string(k)] = true
|
|
}
|
|
|
|
// Extract the existing public keys in our database.
|
|
proposedPublicKeys, err := validatorDB.ProposedPublicKeys(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not retrieve proposer public keys from DB")
|
|
}
|
|
attestedPublicKeys, err := validatorDB.AttestedPublicKeys(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not retrieve attested public keys from DB")
|
|
}
|
|
dataByPubKey := make(map[[fieldparams.BLSPubkeyLength]byte]*format.ProtectionData)
|
|
|
|
// Extract the signed proposals by public key.
|
|
bar := progress.InitializeProgressBar(
|
|
len(proposedPublicKeys), "Extracting signed blocks by validator public key",
|
|
)
|
|
for _, pubKey := range proposedPublicKeys {
|
|
if _, ok := filteredKeysMap[string(pubKey[:])]; len(filteredKeys) > 0 && !ok {
|
|
continue
|
|
}
|
|
pubKeyHex, err := pubKeyToHexString(pubKey[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not convert public key to hex string")
|
|
}
|
|
signedBlocks, err := signedBlocksByPubKey(ctx, validatorDB, pubKey)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not retrieve signed blocks for public key %s", pubKeyHex)
|
|
}
|
|
dataByPubKey[pubKey] = &format.ProtectionData{
|
|
Pubkey: pubKeyHex,
|
|
SignedBlocks: signedBlocks,
|
|
SignedAttestations: nil,
|
|
}
|
|
if err := bar.Add(1); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Extract the signed attestations by public key.
|
|
bar = progress.InitializeProgressBar(
|
|
len(attestedPublicKeys), "Extracting signed attestations by validator public key",
|
|
)
|
|
for _, pubKey := range attestedPublicKeys {
|
|
if _, ok := filteredKeysMap[string(pubKey[:])]; len(filteredKeys) > 0 && !ok {
|
|
continue
|
|
}
|
|
pubKeyHex, err := pubKeyToHexString(pubKey[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not convert public key to hex string")
|
|
}
|
|
signedAttestations, err := signedAttestationsByPubKey(ctx, validatorDB, pubKey)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not retrieve signed attestations for public key %s", pubKeyHex)
|
|
}
|
|
if _, ok := dataByPubKey[pubKey]; ok {
|
|
dataByPubKey[pubKey].SignedAttestations = signedAttestations
|
|
} else {
|
|
dataByPubKey[pubKey] = &format.ProtectionData{
|
|
Pubkey: pubKeyHex,
|
|
SignedBlocks: nil,
|
|
SignedAttestations: signedAttestations,
|
|
}
|
|
}
|
|
if err := bar.Add(1); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Next we turn our map into a slice as expected by the EIP-3076 JSON standard.
|
|
dataList := make([]*format.ProtectionData, 0)
|
|
for _, item := range dataByPubKey {
|
|
if item.SignedAttestations == nil {
|
|
item.SignedAttestations = make([]*format.SignedAttestation, 0)
|
|
}
|
|
if item.SignedBlocks == nil {
|
|
item.SignedBlocks = make([]*format.SignedBlock, 0)
|
|
}
|
|
dataList = append(dataList, item)
|
|
}
|
|
sort.Slice(dataList, func(i, j int) bool {
|
|
return strings.Compare(dataList[i].Pubkey, dataList[j].Pubkey) < 0
|
|
})
|
|
interchangeJSON.Data = dataList
|
|
return interchangeJSON, nil
|
|
}
|
|
|
|
func signedAttestationsByPubKey(ctx context.Context, validatorDB db.Database, pubKey [fieldparams.BLSPubkeyLength]byte) ([]*format.SignedAttestation, error) {
|
|
// If a key does not have an attestation history in our database, we return nil.
|
|
// This way, a user will be able to export their slashing protection history
|
|
// even if one of their keys does not have a history of signed attestations.
|
|
history, err := validatorDB.AttestationHistoryForPubKey(ctx, pubKey)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "cannot get attestation history for public key")
|
|
}
|
|
if history == nil {
|
|
return nil, nil
|
|
}
|
|
signedAttestations := make([]*format.SignedAttestation, 0)
|
|
for i := 0; i < len(history); i++ {
|
|
att := history[i]
|
|
// Special edge case due to a bug in Prysm's old slashing protection schema. The bug
|
|
// manifests itself as the first entry in attester slashing protection history
|
|
// having a target epoch greater than the next entry in the list. If this manifests,
|
|
// we skip it to protect users. This check is the best trade-off we can make at
|
|
// the moment without creating any false positive slashable attestation exports.
|
|
// More information on the bug can found in https://github.com/prysmaticlabs/prysm/issues/8893.
|
|
if i == 0 && len(history) > 1 {
|
|
nextEntryTargetEpoch := history[1].Target
|
|
if att.Target > nextEntryTargetEpoch && att.Source == 0 {
|
|
continue
|
|
}
|
|
}
|
|
var root string
|
|
if !bytes.Equal(att.SigningRoot[:], params.BeaconConfig().ZeroHash[:]) {
|
|
root, err = rootToHexString(att.SigningRoot[:])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not convert signing root to hex string")
|
|
}
|
|
}
|
|
signedAttestations = append(signedAttestations, &format.SignedAttestation{
|
|
TargetEpoch: fmt.Sprintf("%d", att.Target),
|
|
SourceEpoch: fmt.Sprintf("%d", att.Source),
|
|
SigningRoot: root,
|
|
})
|
|
}
|
|
return signedAttestations, nil
|
|
}
|
|
|
|
func signedBlocksByPubKey(ctx context.Context, validatorDB db.Database, pubKey [fieldparams.BLSPubkeyLength]byte) ([]*format.SignedBlock, error) {
|
|
// If a key does not have a lowest or highest signed proposal history
|
|
// in our database, we return nil. This way, a user will be able to export their
|
|
// slashing protection history even if one of their keys does not have a history
|
|
// of signed blocks.
|
|
proposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, pubKey)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not get proposal history for public key: %#x", pubKey)
|
|
}
|
|
signedBlocks := make([]*format.SignedBlock, 0)
|
|
for _, proposal := range proposalHistory {
|
|
if ctx.Err() != nil {
|
|
return nil, errors.Wrap(err, "context canceled")
|
|
}
|
|
signingRootHex, err := rootToHexString(proposal.SigningRoot)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not convert signing root to hex string")
|
|
}
|
|
signedBlocks = append(signedBlocks, &format.SignedBlock{
|
|
Slot: fmt.Sprintf("%d", proposal.Slot),
|
|
SigningRoot: signingRootHex,
|
|
})
|
|
}
|
|
return signedBlocks, nil
|
|
}
|