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 }