mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-22 19:40:37 +00:00
1112e01c06
* `TestStore_GenesisValidatorsRoot_ReadAndWrite`: Make all test cases independents. In a test with multiple test cases, each test case should be independents. (aka: Removing test case `A` should not impact test case `B`) * `SaveGenesisValidatorsRoot`: Allow to overwrite the genesis validator root if the root is the same. * `ProposalHistoryForSlot`: Add `signingRootExists` Currently, it is not possible with `ProposalHistoryForSlot` to know if a proposal is stored with and `0x00000....` signing root or with an empty signing root. Both cases result to `proposalExists == true` and `signingRoot == 0x00000`. This commit adds a new return boolean: `signingRootExists`. If a proposal has been saved with a `0x00000...` signing root, then: - `proposalExists` is set to `true`, and - `signingRootExists` is set to `true`, and - `signingRoot` is set to `0x00000...` If a proposal has been saved with an empty signing root, then: - `proposalExists` is set to `true`, and - `signingRootExists` is set to `false`, and - (`signingRoot` is set to `0x00000...`) * `ImportStandardProtectionJSON`: When importing EIP-3076 Slashing Protection Interchange Format, do not filter any more slashable keys. Note: Those keys are still saved into the black-listed public keys list. There is two reason not to do so: - The EIP-3076 test cases do not know about Prysm's internal black-listed public keys list. Tests will expect, without looking into this internal black-listed public keys list, to deny a further signature. If we filter these keys from the DB (even if we keep them into the black-listed keys list), then some tests will fail. - If we import a interchange file containing slashable keys and we filter them, then, if we re-export the DB, those slashing offences won't appear in the exported interchange file. * `transformSignedBlocks`: Store an 0-len byte slice When importing an EIP-3076 interchange format, and when no signing root is specified into the file, we currently store a `0x00000.....` signing root. In such a case, instead storing `0x00000...`, this commit stores a 0-len byte array, so we can differentiate real `0x000.....` signing root and no signing-root at all. * `slashableProposalCheck`: Manage lack of sign root Currently, `slashableProposalCheck` does not really make a difference between a `0x0000.....` signing root and a missing signing root. (Signing roots can be missing when importing an EIP-3076 interchange file.) This commit differentiate, for `slashableProposalCheck`, `0x0000....` signing root and a missing signing root. * `AttestationRecord.SigningRoot`: ==> `[]byte` When importing attestations from EIP-3076 interchange format, the signing root of an attestation may be missing. Currently, Prysm consider any missing attestation signing root as `0x000...`. However, it may conflict with signing root which really are equal to `0x000...`. This commit transforms `AttestationRecord.SigningRoot` from `[32]byte` to `[]byte`, and change the minimal set of functions (sic) to support this new type. * `CheckSlashableAttestation`: Empty signing root Regarding slashing roots, 2 attestations are slashable, if: - both signing roots are defined and differs, or - one attestation exists, but without a signing root * `filterSlashablePubKeysFromAttestations`: Err sort Rergarding `CheckSlashableAttestation`, we consider that: - If slashable == NotSlashable and err != nil, then CheckSlashableAttestation failed. - If slashable != NotSlashable, then err contains the reason why the attestation is slashable. * `setupEIP3076SpecTests`: Update to `v5.3.0` This commit: - Updates the version of EIP-3076 tests to `v.5.2.1`. - Setups on anti-slashing DB per test case, instead per step. * `ImportStandardProtectionJSON`: Reduce cycl cmplxt * `AttestationHistoryForPubKey`: copy signing root BoltDB documentation specifies: | Byte slices returned from Bolt are only valid during a transaction. | Once the transaction has been committed or rolled back then the memory | they point to can be reused by a new page or can be unmapped | from virtual memory and you'll see an unexpected fault address panic | when accessing it.
199 lines
7.4 KiB
Go
199 lines
7.4 KiB
Go
package history
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
|
"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 len(att.SigningRoot) != 0 {
|
|
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
|
|
}
|