github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/slasherkv/slasher.go (about) 1 package slasherkv 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "fmt" 8 "sort" 9 10 ssz "github.com/ferranbt/fastssz" 11 "github.com/golang/snappy" 12 "github.com/pkg/errors" 13 types "github.com/prysmaticlabs/eth2-types" 14 slashertypes "github.com/prysmaticlabs/prysm/beacon-chain/slasher/types" 15 slashpb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" 16 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 17 "github.com/prysmaticlabs/prysm/shared/bytesutil" 18 "github.com/prysmaticlabs/prysm/shared/hashutil" 19 bolt "go.etcd.io/bbolt" 20 "go.opencensus.io/trace" 21 ) 22 23 const ( 24 // Signing root (32 bytes) + fastsum64(attesting_indices) (8 bytes). 25 attestationRecordKeySize = 40 26 signingRootSize = 32 // Bytes. 27 ) 28 29 // LastEpochWrittenForValidators given a list of validator indices returns the latest 30 // epoch we have recorded the validators writing data for. 31 func (s *Store) LastEpochWrittenForValidators( 32 ctx context.Context, validatorIndices []types.ValidatorIndex, 33 ) ([]*slashertypes.AttestedEpochForValidator, error) { 34 ctx, span := trace.StartSpan(ctx, "BeaconDB.LastEpochWrittenForValidators") 35 defer span.End() 36 attestedEpochs := make([]*slashertypes.AttestedEpochForValidator, 0) 37 encodedIndices := make([][]byte, len(validatorIndices)) 38 for i, valIdx := range validatorIndices { 39 encodedIndices[i] = encodeValidatorIndex(valIdx) 40 } 41 err := s.db.View(func(tx *bolt.Tx) error { 42 bkt := tx.Bucket(attestedEpochsByValidator) 43 for i, encodedIndex := range encodedIndices { 44 epochBytes := bkt.Get(encodedIndex) 45 if epochBytes != nil { 46 var epoch types.Epoch 47 if err := epoch.UnmarshalSSZ(epochBytes); err != nil { 48 return err 49 } 50 attestedEpochs = append(attestedEpochs, &slashertypes.AttestedEpochForValidator{ 51 ValidatorIndex: validatorIndices[i], 52 Epoch: epoch, 53 }) 54 } 55 } 56 return nil 57 }) 58 return attestedEpochs, err 59 } 60 61 // SaveLastEpochWrittenForValidators updates the latest epoch a slice 62 // of validator indices has attested to. 63 func (s *Store) SaveLastEpochWrittenForValidators( 64 ctx context.Context, validatorIndices []types.ValidatorIndex, epoch types.Epoch, 65 ) error { 66 ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveLastEpochWrittenForValidators") 67 defer span.End() 68 encodedIndices := make([][]byte, len(validatorIndices)) 69 for i, valIdx := range validatorIndices { 70 encodedIndices[i] = encodeValidatorIndex(valIdx) 71 } 72 encodedEpoch, err := epoch.MarshalSSZ() 73 if err != nil { 74 return err 75 } 76 return s.db.Update(func(tx *bolt.Tx) error { 77 bkt := tx.Bucket(attestedEpochsByValidator) 78 for _, encodedIndex := range encodedIndices { 79 if err = bkt.Put(encodedIndex, encodedEpoch); err != nil { 80 return err 81 } 82 } 83 return nil 84 }) 85 } 86 87 // CheckAttesterDoubleVotes retries any slashable double votes that exist 88 // for a series of input attestations. 89 func (s *Store) CheckAttesterDoubleVotes( 90 ctx context.Context, attestations []*slashertypes.IndexedAttestationWrapper, 91 ) ([]*slashertypes.AttesterDoubleVote, error) { 92 ctx, span := trace.StartSpan(ctx, "BeaconDB.CheckAttesterDoubleVotes") 93 defer span.End() 94 doubleVotes := make([]*slashertypes.AttesterDoubleVote, 0) 95 err := s.db.View(func(tx *bolt.Tx) error { 96 signingRootsBkt := tx.Bucket(attestationDataRootsBucket) 97 attRecordsBkt := tx.Bucket(attestationRecordsBucket) 98 for _, att := range attestations { 99 encEpoch := encodeTargetEpoch(att.IndexedAttestation.Data.Target.Epoch) 100 for _, valIdx := range att.IndexedAttestation.AttestingIndices { 101 encIdx := encodeValidatorIndex(types.ValidatorIndex(valIdx)) 102 validatorEpochKey := append(encEpoch, encIdx...) 103 attRecordsKey := signingRootsBkt.Get(validatorEpochKey) 104 105 // An attestation record key is comprised of a signing root (32 bytes) 106 // and a fast sum hash of the attesting indices (8 bytes). 107 if len(attRecordsKey) < attestationRecordKeySize { 108 continue 109 } 110 encExistingAttRecord := attRecordsBkt.Get(attRecordsKey) 111 if encExistingAttRecord == nil { 112 continue 113 } 114 existingSigningRoot := bytesutil.ToBytes32(attRecordsKey[:signingRootSize]) 115 if existingSigningRoot != att.SigningRoot { 116 existingAttRecord, err := decodeAttestationRecord(encExistingAttRecord) 117 if err != nil { 118 return err 119 } 120 doubleVotes = append(doubleVotes, &slashertypes.AttesterDoubleVote{ 121 ValidatorIndex: types.ValidatorIndex(valIdx), 122 Target: att.IndexedAttestation.Data.Target.Epoch, 123 PrevAttestationWrapper: existingAttRecord, 124 AttestationWrapper: att, 125 }) 126 } 127 } 128 } 129 return nil 130 }) 131 return doubleVotes, err 132 } 133 134 // AttestationRecordForValidator given a validator index and a target epoch, 135 // retrieves an existing attestation record we have stored in the database. 136 func (s *Store) AttestationRecordForValidator( 137 ctx context.Context, validatorIdx types.ValidatorIndex, targetEpoch types.Epoch, 138 ) (*slashertypes.IndexedAttestationWrapper, error) { 139 ctx, span := trace.StartSpan(ctx, "BeaconDB.AttestationRecordForValidator") 140 defer span.End() 141 var record *slashertypes.IndexedAttestationWrapper 142 encIdx := encodeValidatorIndex(validatorIdx) 143 encEpoch := encodeTargetEpoch(targetEpoch) 144 key := append(encEpoch, encIdx...) 145 err := s.db.View(func(tx *bolt.Tx) error { 146 signingRootsBkt := tx.Bucket(attestationDataRootsBucket) 147 attRecordKey := signingRootsBkt.Get(key) 148 if attRecordKey == nil { 149 return nil 150 } 151 attRecordsBkt := tx.Bucket(attestationRecordsBucket) 152 indexedAttBytes := attRecordsBkt.Get(attRecordKey) 153 if indexedAttBytes == nil { 154 return nil 155 } 156 decoded, err := decodeAttestationRecord(indexedAttBytes) 157 if err != nil { 158 return err 159 } 160 record = decoded 161 return nil 162 }) 163 return record, err 164 } 165 166 // SaveAttestationRecordsForValidators saves attestation records for the specified indices. 167 func (s *Store) SaveAttestationRecordsForValidators( 168 ctx context.Context, 169 attestations []*slashertypes.IndexedAttestationWrapper, 170 ) error { 171 ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveAttestationRecordsForValidators") 172 defer span.End() 173 encodedTargetEpoch := make([][]byte, len(attestations)) 174 encodedRecords := make([][]byte, len(attestations)) 175 encodedIndices := make([][]byte, len(attestations)) 176 for i, att := range attestations { 177 encEpoch := encodeTargetEpoch(att.IndexedAttestation.Data.Target.Epoch) 178 value, err := encodeAttestationRecord(att) 179 if err != nil { 180 return err 181 } 182 indicesBytes := make([]byte, len(att.IndexedAttestation.AttestingIndices)*8) 183 for _, idx := range att.IndexedAttestation.AttestingIndices { 184 encodedIdx := encodeValidatorIndex(types.ValidatorIndex(idx)) 185 indicesBytes = append(indicesBytes, encodedIdx...) 186 } 187 encodedIndices[i] = indicesBytes 188 encodedTargetEpoch[i] = encEpoch 189 encodedRecords[i] = value 190 } 191 return s.db.Update(func(tx *bolt.Tx) error { 192 attRecordsBkt := tx.Bucket(attestationRecordsBucket) 193 signingRootsBkt := tx.Bucket(attestationDataRootsBucket) 194 for i, att := range attestations { 195 // An attestation record key is comprised of the signing root (32 bytes) 196 // and a fastsum64 of the attesting indices (8 bytes). This is used 197 // to have a more optimal schema 198 attIndicesHash := hashutil.FastSum64(encodedIndices[i]) 199 attRecordKey := append( 200 att.SigningRoot[:], ssz.MarshalUint64(make([]byte, 0), attIndicesHash)..., 201 ) 202 if err := attRecordsBkt.Put(attRecordKey, encodedRecords[i]); err != nil { 203 return err 204 } 205 for _, valIdx := range att.IndexedAttestation.AttestingIndices { 206 encIdx := encodeValidatorIndex(types.ValidatorIndex(valIdx)) 207 key := append(encodedTargetEpoch[i], encIdx...) 208 if err := signingRootsBkt.Put(key, attRecordKey); err != nil { 209 return err 210 } 211 } 212 } 213 return nil 214 }) 215 } 216 217 // LoadSlasherChunks given a chunk kind and a disk keys, retrieves chunks for a validator 218 // min or max span used by slasher from our database. 219 func (s *Store) LoadSlasherChunks( 220 ctx context.Context, kind slashertypes.ChunkKind, diskKeys [][]byte, 221 ) ([][]uint16, []bool, error) { 222 ctx, span := trace.StartSpan(ctx, "BeaconDB.LoadSlasherChunk") 223 defer span.End() 224 chunks := make([][]uint16, 0) 225 var exists []bool 226 err := s.db.View(func(tx *bolt.Tx) error { 227 bkt := tx.Bucket(slasherChunksBucket) 228 for _, diskKey := range diskKeys { 229 key := append(ssz.MarshalUint8(make([]byte, 0), uint8(kind)), diskKey...) 230 chunkBytes := bkt.Get(key) 231 if chunkBytes == nil { 232 chunks = append(chunks, []uint16{}) 233 exists = append(exists, false) 234 continue 235 } 236 chunk, err := decodeSlasherChunk(chunkBytes) 237 if err != nil { 238 return err 239 } 240 chunks = append(chunks, chunk) 241 exists = append(exists, true) 242 } 243 return nil 244 }) 245 return chunks, exists, err 246 } 247 248 // SaveSlasherChunks given a chunk kind, list of disk keys, and list of chunks, 249 // saves the chunks to our database for use by slasher in slashing detection. 250 func (s *Store) SaveSlasherChunks( 251 ctx context.Context, kind slashertypes.ChunkKind, chunkKeys [][]byte, chunks [][]uint16, 252 ) error { 253 ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveSlasherChunks") 254 defer span.End() 255 encodedKeys := make([][]byte, len(chunkKeys)) 256 encodedChunks := make([][]byte, len(chunkKeys)) 257 for i := 0; i < len(chunkKeys); i++ { 258 encodedKeys[i] = append(ssz.MarshalUint8(make([]byte, 0), uint8(kind)), chunkKeys[i]...) 259 encodedChunks[i] = encodeSlasherChunk(chunks[i]) 260 } 261 return s.db.Update(func(tx *bolt.Tx) error { 262 bkt := tx.Bucket(slasherChunksBucket) 263 for i := 0; i < len(chunkKeys); i++ { 264 if err := bkt.Put(encodedKeys[i], encodedChunks[i]); err != nil { 265 return err 266 } 267 } 268 return nil 269 }) 270 } 271 272 // CheckDoubleBlockProposals takes in a list of proposals and for each, 273 // checks if there already exists a proposal at the same slot+validatorIndex combination. If so, 274 // We check if the existing signing root is not-empty and is different than the incoming 275 // proposal signing root. If so, we return a double block proposal object. 276 func (s *Store) CheckDoubleBlockProposals( 277 ctx context.Context, proposals []*slashertypes.SignedBlockHeaderWrapper, 278 ) ([]*ethpb.ProposerSlashing, error) { 279 ctx, span := trace.StartSpan(ctx, "BeaconDB.CheckDoubleBlockProposals") 280 defer span.End() 281 proposerSlashings := make([]*ethpb.ProposerSlashing, 0, len(proposals)) 282 err := s.db.View(func(tx *bolt.Tx) error { 283 bkt := tx.Bucket(proposalRecordsBucket) 284 for _, proposal := range proposals { 285 key, err := keyForValidatorProposal(proposal) 286 if err != nil { 287 return err 288 } 289 encExistingProposalWrapper := bkt.Get(key) 290 if len(encExistingProposalWrapper) < signingRootSize { 291 continue 292 } 293 existingSigningRoot := bytesutil.ToBytes32(encExistingProposalWrapper[:signingRootSize]) 294 if existingSigningRoot != proposal.SigningRoot { 295 existingProposalWrapper, err := decodeProposalRecord(encExistingProposalWrapper) 296 if err != nil { 297 return err 298 } 299 proposerSlashings = append(proposerSlashings, ðpb.ProposerSlashing{ 300 Header_1: existingProposalWrapper.SignedBeaconBlockHeader, 301 Header_2: proposal.SignedBeaconBlockHeader, 302 }) 303 } 304 } 305 return nil 306 }) 307 return proposerSlashings, err 308 } 309 310 // SaveBlockProposals takes in a list of block proposals and saves them to our 311 // proposal records bucket in the database. 312 func (s *Store) SaveBlockProposals( 313 ctx context.Context, proposals []*slashertypes.SignedBlockHeaderWrapper, 314 ) error { 315 ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlockProposals") 316 defer span.End() 317 encodedKeys := make([][]byte, len(proposals)) 318 encodedProposals := make([][]byte, len(proposals)) 319 for i, proposal := range proposals { 320 key, err := keyForValidatorProposal(proposal) 321 if err != nil { 322 return err 323 } 324 enc, err := encodeProposalRecord(proposal) 325 if err != nil { 326 return err 327 } 328 encodedKeys[i] = key 329 encodedProposals[i] = enc 330 } 331 return s.db.Update(func(tx *bolt.Tx) error { 332 bkt := tx.Bucket(proposalRecordsBucket) 333 for i := range proposals { 334 if err := bkt.Put(encodedKeys[i], encodedProposals[i]); err != nil { 335 return err 336 } 337 } 338 return nil 339 }) 340 } 341 342 // HighestAttestations retrieves the last attestation data from the database for all indices. 343 func (s *Store) HighestAttestations( 344 ctx context.Context, 345 indices []types.ValidatorIndex, 346 ) ([]*slashpb.HighestAttestation, error) { 347 if len(indices) == 0 { 348 return nil, nil 349 } 350 // Sort indices to keep DB interactions short. 351 sort.SliceStable(indices, func(i, j int) bool { 352 return uint64(indices[i]) < uint64(indices[j]) 353 }) 354 355 var err error 356 encodedIndices := make([][]byte, len(indices)) 357 for i, valIdx := range indices { 358 encodedIndices[i] = encodeValidatorIndex(valIdx) 359 } 360 361 history := make([]*slashpb.HighestAttestation, 0, len(encodedIndices)) 362 err = s.db.View(func(tx *bolt.Tx) error { 363 signingRootsBkt := tx.Bucket(attestationDataRootsBucket) 364 attRecordsBkt := tx.Bucket(attestationRecordsBucket) 365 for i := 0; i < len(encodedIndices); i++ { 366 c := signingRootsBkt.Cursor() 367 for k, v := c.Last(); k != nil; k, v = c.Prev() { 368 if suffixForAttestationRecordsKey(k, encodedIndices[i]) { 369 encodedAttRecord := attRecordsBkt.Get(v) 370 if encodedAttRecord == nil { 371 continue 372 } 373 attWrapper, err := decodeAttestationRecord(encodedAttRecord) 374 if err != nil { 375 return err 376 } 377 highestAtt := &slashpb.HighestAttestation{ 378 ValidatorIndex: uint64(indices[i]), 379 HighestSourceEpoch: attWrapper.IndexedAttestation.Data.Source.Epoch, 380 HighestTargetEpoch: attWrapper.IndexedAttestation.Data.Target.Epoch, 381 } 382 history = append(history, highestAtt) 383 break 384 } 385 } 386 } 387 return nil 388 }) 389 return history, err 390 } 391 392 func suffixForAttestationRecordsKey(key, encodedValidatorIndex []byte) bool { 393 encIdx := key[8:] 394 return bytes.Equal(encIdx, encodedValidatorIndex) 395 } 396 397 // Disk key for a validator proposal, including a slot+validatorIndex as a byte slice. 398 func keyForValidatorProposal(proposal *slashertypes.SignedBlockHeaderWrapper) ([]byte, error) { 399 encSlot, err := proposal.SignedBeaconBlockHeader.Header.Slot.MarshalSSZ() 400 if err != nil { 401 return nil, err 402 } 403 encValidatorIdx := encodeValidatorIndex(proposal.SignedBeaconBlockHeader.Header.ProposerIndex) 404 return append(encSlot, encValidatorIdx...), nil 405 } 406 407 func encodeSlasherChunk(chunk []uint16) []byte { 408 val := make([]byte, 0) 409 for i := 0; i < len(chunk); i++ { 410 val = append(val, ssz.MarshalUint16(make([]byte, 0), chunk[i])...) 411 } 412 return snappy.Encode(nil, val) 413 } 414 415 func decodeSlasherChunk(enc []byte) ([]uint16, error) { 416 chunkBytes, err := snappy.Decode(nil, enc) 417 if err != nil { 418 return nil, err 419 } 420 chunk := make([]uint16, 0) 421 for i := 0; i < len(chunkBytes); i += 2 { 422 distance := ssz.UnmarshallUint16(chunkBytes[i : i+2]) 423 chunk = append(chunk, distance) 424 } 425 return chunk, nil 426 } 427 428 // Decode attestation record from bytes. 429 func encodeAttestationRecord(att *slashertypes.IndexedAttestationWrapper) ([]byte, error) { 430 if att == nil || att.IndexedAttestation == nil { 431 return []byte{}, errors.New("nil proposal record") 432 } 433 encodedAtt, err := att.IndexedAttestation.MarshalSSZ() 434 if err != nil { 435 return nil, err 436 } 437 compressedAtt := snappy.Encode(nil, encodedAtt) 438 return append(att.SigningRoot[:], compressedAtt...), nil 439 } 440 441 // Decode attestation record from bytes. 442 func decodeAttestationRecord(encoded []byte) (*slashertypes.IndexedAttestationWrapper, error) { 443 if len(encoded) < signingRootSize { 444 return nil, fmt.Errorf("wrong length for encoded attestation record, want 32, got %d", len(encoded)) 445 } 446 signingRoot := encoded[:signingRootSize] 447 decodedAtt := ðpb.IndexedAttestation{} 448 decodedAttBytes, err := snappy.Decode(nil, encoded[signingRootSize:]) 449 if err != nil { 450 return nil, err 451 } 452 if err := decodedAtt.UnmarshalSSZ(decodedAttBytes); err != nil { 453 return nil, err 454 } 455 return &slashertypes.IndexedAttestationWrapper{ 456 IndexedAttestation: decodedAtt, 457 SigningRoot: bytesutil.ToBytes32(signingRoot), 458 }, nil 459 } 460 461 func encodeProposalRecord(blkHdr *slashertypes.SignedBlockHeaderWrapper) ([]byte, error) { 462 if blkHdr == nil || blkHdr.SignedBeaconBlockHeader == nil { 463 return []byte{}, errors.New("nil proposal record") 464 } 465 encodedHdr, err := blkHdr.SignedBeaconBlockHeader.MarshalSSZ() 466 if err != nil { 467 return nil, err 468 } 469 compressedHdr := snappy.Encode(nil, encodedHdr) 470 return append(blkHdr.SigningRoot[:], compressedHdr...), nil 471 } 472 473 func decodeProposalRecord(encoded []byte) (*slashertypes.SignedBlockHeaderWrapper, error) { 474 if len(encoded) < signingRootSize { 475 return nil, fmt.Errorf( 476 "wrong length for encoded proposal record, want %d, got %d", signingRootSize, len(encoded), 477 ) 478 } 479 signingRoot := encoded[:signingRootSize] 480 decodedBlkHdr := ðpb.SignedBeaconBlockHeader{} 481 decodedHdrBytes, err := snappy.Decode(nil, encoded[signingRootSize:]) 482 if err != nil { 483 return nil, err 484 } 485 if err := decodedBlkHdr.UnmarshalSSZ(decodedHdrBytes); err != nil { 486 return nil, err 487 } 488 return &slashertypes.SignedBlockHeaderWrapper{ 489 SignedBeaconBlockHeader: decodedBlkHdr, 490 SigningRoot: bytesutil.ToBytes32(signingRoot), 491 }, nil 492 } 493 494 // Encodes an epoch into little-endian bytes. 495 func encodeTargetEpoch(epoch types.Epoch) []byte { 496 buf := make([]byte, 8) 497 binary.LittleEndian.PutUint64(buf, uint64(epoch)) 498 return buf 499 } 500 501 // Encodes a validator index using 5 bytes instead of 8 as a 502 // client optimization to save space in the database. Because the max validator 503 // registry size is 2**40, this is a safe optimization. 504 func encodeValidatorIndex(index types.ValidatorIndex) []byte { 505 buf := make([]byte, 5) 506 v := uint64(index) 507 buf[0] = byte(v) 508 buf[1] = byte(v >> 8) 509 buf[2] = byte(v >> 16) 510 buf[3] = byte(v >> 24) 511 buf[4] = byte(v >> 32) 512 return buf 513 }