github.com/prysmaticlabs/prysm@v1.4.4/validator/slashing-protection/local/standard-protection-format/export.go (about)

     1  package interchangeformat
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/prysmaticlabs/prysm/shared/params"
    12  	"github.com/prysmaticlabs/prysm/shared/progressutil"
    13  	"github.com/prysmaticlabs/prysm/validator/db"
    14  	"github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format/format"
    15  )
    16  
    17  // ExportStandardProtectionJSON extracts all slashing protection data from a validator database
    18  // and packages it into an EIP-3076 compliant, standard
    19  func ExportStandardProtectionJSON(ctx context.Context, validatorDB db.Database) (*format.EIPSlashingProtectionFormat, error) {
    20  	interchangeJSON := &format.EIPSlashingProtectionFormat{}
    21  	genesisValidatorsRoot, err := validatorDB.GenesisValidatorsRoot(ctx)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  	genesisRootHex, err := rootToHexString(genesisValidatorsRoot)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	interchangeJSON.Metadata.GenesisValidatorsRoot = genesisRootHex
    30  	interchangeJSON.Metadata.InterchangeFormatVersion = format.InterchangeFormatVersion
    31  
    32  	// Extract the existing public keys in our database.
    33  	proposedPublicKeys, err := validatorDB.ProposedPublicKeys(ctx)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	attestedPublicKeys, err := validatorDB.AttestedPublicKeys(ctx)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	dataByPubKey := make(map[[48]byte]*format.ProtectionData)
    42  
    43  	// Extract the signed proposals by public key.
    44  	progress := progressutil.InitializeProgressBar(
    45  		len(proposedPublicKeys), "Extracting signed blocks by validator public key",
    46  	)
    47  	for _, pubKey := range proposedPublicKeys {
    48  		pubKeyHex, err := pubKeyToHexString(pubKey[:])
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  		signedBlocks, err := signedBlocksByPubKey(ctx, validatorDB, pubKey)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  		dataByPubKey[pubKey] = &format.ProtectionData{
    57  			Pubkey:             pubKeyHex,
    58  			SignedBlocks:       signedBlocks,
    59  			SignedAttestations: nil,
    60  		}
    61  		if err := progress.Add(1); err != nil {
    62  			return nil, err
    63  		}
    64  	}
    65  
    66  	// Extract the signed attestations by public key.
    67  	progress = progressutil.InitializeProgressBar(
    68  		len(proposedPublicKeys), "Extracting signed attestations by validator public key",
    69  	)
    70  	for _, pubKey := range attestedPublicKeys {
    71  		pubKeyHex, err := pubKeyToHexString(pubKey[:])
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  		signedAttestations, err := signedAttestationsByPubKey(ctx, validatorDB, pubKey)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		if _, ok := dataByPubKey[pubKey]; ok {
    80  			dataByPubKey[pubKey].SignedAttestations = signedAttestations
    81  		} else {
    82  			dataByPubKey[pubKey] = &format.ProtectionData{
    83  				Pubkey:             pubKeyHex,
    84  				SignedBlocks:       nil,
    85  				SignedAttestations: signedAttestations,
    86  			}
    87  		}
    88  		if err := progress.Add(1); err != nil {
    89  			return nil, err
    90  		}
    91  	}
    92  
    93  	// Next we turn our map into a slice as expected by the EIP-3076 JSON standard.
    94  	dataList := make([]*format.ProtectionData, 0)
    95  	for _, item := range dataByPubKey {
    96  		if item.SignedAttestations == nil {
    97  			item.SignedAttestations = make([]*format.SignedAttestation, 0)
    98  		}
    99  		if item.SignedBlocks == nil {
   100  			item.SignedBlocks = make([]*format.SignedBlock, 0)
   101  		}
   102  		dataList = append(dataList, item)
   103  	}
   104  	sort.Slice(dataList, func(i, j int) bool {
   105  		return strings.Compare(dataList[i].Pubkey, dataList[j].Pubkey) < 0
   106  	})
   107  	interchangeJSON.Data = dataList
   108  	return interchangeJSON, nil
   109  }
   110  
   111  func signedAttestationsByPubKey(ctx context.Context, validatorDB db.Database, pubKey [48]byte) ([]*format.SignedAttestation, error) {
   112  	// If a key does not have an attestation history in our database, we return nil.
   113  	// This way, a user will be able to export their slashing protection history
   114  	// even if one of their keys does not have a history of signed attestations.
   115  	history, err := validatorDB.AttestationHistoryForPubKey(ctx, pubKey)
   116  	if err != nil {
   117  		return nil, errors.Wrap(err, "cannot get attestation history for public key")
   118  	}
   119  	if history == nil {
   120  		return nil, nil
   121  	}
   122  	signedAttestations := make([]*format.SignedAttestation, 0)
   123  	for i := 0; i < len(history); i++ {
   124  		att := history[i]
   125  		// Special edge case due to a bug in Prysm's old slashing protection schema. The bug
   126  		// manifests itself as the first entry in attester slashing protection history
   127  		// having a target epoch greater than the next entry in the list. If this manifests,
   128  		// we skip it to protect users. This check is the best trade-off we can make at
   129  		// the moment without creating any false positive slashable attestation exports.
   130  		// More information on the bug can found in https://github.com/prysmaticlabs/prysm/issues/8893.
   131  		if i == 0 && len(history) > 1 {
   132  			nextEntryTargetEpoch := history[1].Target
   133  			if att.Target > nextEntryTargetEpoch && att.Source == 0 {
   134  				continue
   135  			}
   136  		}
   137  		var root string
   138  		if !bytes.Equal(att.SigningRoot[:], params.BeaconConfig().ZeroHash[:]) {
   139  			root, err = rootToHexString(att.SigningRoot[:])
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  		}
   144  		signedAttestations = append(signedAttestations, &format.SignedAttestation{
   145  			TargetEpoch: fmt.Sprintf("%d", att.Target),
   146  			SourceEpoch: fmt.Sprintf("%d", att.Source),
   147  			SigningRoot: root,
   148  		})
   149  	}
   150  	return signedAttestations, nil
   151  }
   152  
   153  func signedBlocksByPubKey(ctx context.Context, validatorDB db.Database, pubKey [48]byte) ([]*format.SignedBlock, error) {
   154  	// If a key does not have a lowest or highest signed proposal history
   155  	// in our database, we return nil. This way, a user will be able to export their
   156  	// slashing protection history even if one of their keys does not have a history
   157  	// of signed blocks.
   158  	proposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, pubKey)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	signedBlocks := make([]*format.SignedBlock, 0)
   163  	for _, proposal := range proposalHistory {
   164  		if ctx.Err() != nil {
   165  			return nil, ctx.Err()
   166  		}
   167  		signingRootHex, err := rootToHexString(proposal.SigningRoot)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		signedBlocks = append(signedBlocks, &format.SignedBlock{
   172  			Slot:        fmt.Sprintf("%d", proposal.Slot),
   173  			SigningRoot: signingRootHex,
   174  		})
   175  	}
   176  	return signedBlocks, nil
   177  }