prysm-pulse/beacon-chain/blockchain/core.go

764 lines
25 KiB
Go

package blockchain
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/gogo/protobuf/proto"
"github.com/prysmaticlabs/prysm/beacon-chain/casper"
"github.com/prysmaticlabs/prysm/beacon-chain/params"
"github.com/prysmaticlabs/prysm/beacon-chain/types"
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"golang.org/x/crypto/blake2b"
)
var clock utils.Clock = &utils.RealClock{}
// BeaconChain represents the core PoS blockchain object containing
// both a crystallized and active state.
type BeaconChain struct {
state *beaconState
lock sync.Mutex
db ethdb.Database
}
type beaconState struct {
// ActiveState captures the beacon state at block processing level,
// it focuses on verifying aggregated signatures and pending attestations.
ActiveState *types.ActiveState
// CrystallizedState captures the beacon state at cycle transition level,
// it focuses on changes to the validator set, processing cross links and
// setting up FFG checkpoints.
CrystallizedState *types.CrystallizedState
}
// NewBeaconChain initializes a beacon chain using genesis state parameters if
// none provided.
func NewBeaconChain(db ethdb.Database) (*BeaconChain, error) {
beaconChain := &BeaconChain{
db: db,
state: &beaconState{},
}
hasCrystallized, err := db.Has(crystallizedStateLookupKey)
if err != nil {
return nil, err
}
hasGenesis, err := db.Has(genesisLookupKey)
if err != nil {
return nil, err
}
active, crystallized, err := types.NewGenesisStates()
if err != nil {
return nil, err
}
beaconChain.state.ActiveState = active
if !hasGenesis {
log.Info("No genesis block found on disk, initializing genesis block")
genesisBlock, err := types.NewGenesisBlock()
if err != nil {
return nil, err
}
genesisMarshall, err := proto.Marshal(genesisBlock.Proto())
if err != nil {
return nil, err
}
if err := beaconChain.db.Put(genesisLookupKey, genesisMarshall); err != nil {
return nil, err
}
}
if !hasCrystallized {
log.Info("No chainstate found on disk, initializing beacon from genesis")
beaconChain.state.CrystallizedState = crystallized
return beaconChain, nil
}
enc, err := db.Get(crystallizedStateLookupKey)
if err != nil {
return nil, err
}
crystallizedData := &pb.CrystallizedState{}
err = proto.Unmarshal(enc, crystallizedData)
if err != nil {
return nil, err
}
beaconChain.state.CrystallizedState = types.NewCrystallizedState(crystallizedData)
return beaconChain, nil
}
// GenesisBlock returns the canonical, genesis block.
func (b *BeaconChain) GenesisBlock() (*types.Block, error) {
genesisExists, err := b.db.Has(genesisLookupKey)
if err != nil {
return nil, err
}
if genesisExists {
bytes, err := b.db.Get(genesisLookupKey)
if err != nil {
return nil, err
}
block := &pb.BeaconBlock{}
if err := proto.Unmarshal(bytes, block); err != nil {
return nil, err
}
return types.NewBlock(block), nil
}
return types.NewGenesisBlock()
}
// CanonicalHead fetches the latest head stored in persistent storage.
func (b *BeaconChain) CanonicalHead() (*types.Block, error) {
bytes, err := b.db.Get(canonicalHeadLookupKey)
if err != nil {
return nil, err
}
block := &pb.BeaconBlock{}
if err := proto.Unmarshal(bytes, block); err != nil {
return nil, fmt.Errorf("cannot unmarshal proto: %v", err)
}
return types.NewBlock(block), nil
}
// ActiveState contains the current state of attestations and changes every block.
func (b *BeaconChain) ActiveState() *types.ActiveState {
return b.state.ActiveState
}
// CrystallizedState contains cycle dependent validator information, changes every cycle.
func (b *BeaconChain) CrystallizedState() *types.CrystallizedState {
return b.state.CrystallizedState
}
// SetActiveState is a convenience method which sets and persists the active state on the beacon chain.
func (b *BeaconChain) SetActiveState(activeState *types.ActiveState) error {
b.lock.Lock()
defer b.lock.Unlock()
b.state.ActiveState = activeState
return b.PersistActiveState()
}
// SetCrystallizedState is a convenience method which sets and persists the crystallized state on the beacon chain.
func (b *BeaconChain) SetCrystallizedState(crystallizedState *types.CrystallizedState) error {
b.lock.Lock()
defer b.lock.Unlock()
b.state.CrystallizedState = crystallizedState
return b.PersistCrystallizedState()
}
// PersistActiveState stores proto encoding of the current beacon chain active state into the db.
func (b *BeaconChain) PersistActiveState() error {
encodedState, err := b.ActiveState().Marshal()
if err != nil {
return err
}
return b.db.Put(activeStateLookupKey, encodedState)
}
// PersistCrystallizedState stores proto encoding of the current beacon chain crystallized state into the db.
func (b *BeaconChain) PersistCrystallizedState() error {
encodedState, err := b.CrystallizedState().Marshal()
if err != nil {
return err
}
return b.db.Put(crystallizedStateLookupKey, encodedState)
}
// IsCycleTransition checks if a new cycle has been reached. At that point,
// a new crystallized state and active state transition will occur.
func (b *BeaconChain) IsCycleTransition(slotNumber uint64) bool {
return slotNumber >= b.CrystallizedState().LastStateRecalc()+params.CycleLength
}
// CanProcessBlock is called to decide if an incoming p2p block can be processed into the chain's block trie,
// it checks time stamp, beacon chain parent block hash. It also checks pow chain reference hash if it's a validator.
func (b *BeaconChain) CanProcessBlock(fetcher types.POWBlockFetcher, block *types.Block, isValidator bool) (bool, error) {
if isValidator {
if _, err := fetcher.BlockByHash(context.Background(), block.PowChainRef()); err != nil {
return false, fmt.Errorf("fetching PoW block corresponding to mainchain reference failed: %v", err)
}
}
canProcess, err := b.verifyBlockTimeStamp(block)
if err != nil {
return false, fmt.Errorf("unable to process block: %v", err)
}
if !canProcess {
return false, fmt.Errorf("time stamp verification for beacon block %v failed", block.SlotNumber())
}
return canProcess, nil
}
// verifyBlockTimeStamp verifies node's local time is greater than or equal to
// min timestamp as computed by GENESIS_TIME + slot_number * SLOT_DURATION.
func (b *BeaconChain) verifyBlockTimeStamp(block *types.Block) (bool, error) {
slotDuration := time.Duration(block.SlotNumber()*params.SlotDuration) * time.Second
genesis, err := b.GenesisBlock()
if err != nil {
return false, err
}
genesisTime, err := genesis.Timestamp()
if err != nil {
return false, err
}
if clock.Now().Before(genesisTime.Add(slotDuration)) {
return false, nil
}
return true, nil
}
// computeNewActiveState for every newly processed beacon block.
func (b *BeaconChain) computeNewActiveState(attestations []*pb.AttestationRecord, activeState *types.ActiveState, blockVoteCache map[[32]byte]*types.VoteCache, blockHash [32]byte) (*types.ActiveState, error) {
// TODO: Insert recent block hash.
activeState.SetBlockVoteCache(blockVoteCache)
activeState.NewPendingAttestation(attestations)
blockHashes := activeState.RecentBlockHashes()
blockHashes = append(blockHashes, blockHash)
for len(blockHashes) > 2*params.CycleLength {
blockHashes = blockHashes[1:]
}
activeState.ReplaceBlockHashes(blockHashes)
return activeState, nil
}
// processAttestation processes the attestations for one shard in an incoming block.
func (b *BeaconChain) processAttestation(attestationIndex int, block *types.Block) error {
// Validate attestation's slot number has is within range of incoming block number.
slotNumber := int(block.SlotNumber())
attestation := block.Attestations()[attestationIndex]
if int(attestation.Slot) > slotNumber {
return fmt.Errorf("attestation slot number can't be higher than block slot number. Found: %d, Needed lower than: %d",
attestation.Slot,
slotNumber)
}
if int(attestation.Slot) < slotNumber-params.CycleLength {
return fmt.Errorf("attestation slot number can't be lower than block slot number by one CycleLength. Found: %v, Needed greater than: %v",
attestation.Slot,
slotNumber-params.CycleLength)
}
if attestation.JustifiedSlot != b.CrystallizedState().LastJustifiedSlot() {
return fmt.Errorf("attestation's last justified slot has to match crystallied state's last justified slot. Found: %d. Want: %d",
attestation.JustifiedSlot,
b.CrystallizedState().LastJustifiedSlot())
}
// TODO: Validate last justified block hash matches in the crystallizedState.
// Get all the block hashes up to cycle length.
parentHashes := b.getSignedParentHashes(block, attestation)
attesterIndices, err := b.getAttesterIndices(attestation)
if err != nil {
return fmt.Errorf("unable to get validator committee: %v", attesterIndices)
}
// Verify attester bitfields matches crystallized state's prev computed bitfield.
if err := b.validateAttesterBitfields(attestation, attesterIndices); err != nil {
return err
}
// TODO: Generate validators aggregated pub key.
// Hash parentHashes + shardID + slotNumber + shardBlockHash into a message to use to
// to verify with aggregated public key and aggregated attestation signature.
msg := make([]byte, binary.MaxVarintLen64)
var signedHashesStr []byte
for _, parentHash := range parentHashes {
signedHashesStr = append(signedHashesStr, parentHash[:]...)
signedHashesStr = append(signedHashesStr, byte(' '))
}
binary.PutUvarint(msg, attestation.Slot%params.CycleLength)
msg = append(msg, signedHashesStr...)
binary.PutUvarint(msg, attestation.ShardId)
msg = append(msg, attestation.ShardBlockHash...)
msgHash := blake2b.Sum512(msg)
log.Debugf("Attestation message for shard: %v, slot %v, block hash %v is: %v",
attestation.ShardId, attestation.Slot, attestation.ShardBlockHash, msgHash)
// TODO: Verify msgHash against aggregated pub key and aggregated signature.
return nil
}
// calculateBlockVoteCache calculates and updates active state's block vote cache.
func (b *BeaconChain) calculateBlockVoteCache(attestationIndex int, block *types.Block) (map[[32]byte]*types.VoteCache, error) {
attestation := block.Attestations()[attestationIndex]
newVoteCache := b.ActiveState().GetBlockVoteCache()
parentHashes := b.getSignedParentHashes(block, attestation)
attesterIndices, err := b.getAttesterIndices(attestation)
if err != nil {
return nil, err
}
for _, h := range parentHashes {
// Skip calculating for this hash if the hash is part of oblique parent hashes.
var skip bool
for _, obliqueParentHash := range attestation.ObliqueParentHashes {
if bytes.Equal(h[:], obliqueParentHash) {
skip = true
}
}
if skip {
continue
}
// Initialize vote cache of a given block hash if it doesn't exist already.
if !b.ActiveState().IsVoteCacheEmpty(h) {
newVoteCache[h] = &types.VoteCache{VoterIndices: []uint32{}, VoteTotalDeposit: 0}
}
// Loop through attester indices, if the attester has voted but was not accounted for
// in the cache, then we add attester's index and balance to the block cache.
for i, attesterIndex := range attesterIndices {
var existingAttester bool
if !utils.CheckBit(attestation.AttesterBitfield, i) {
continue
}
for _, indexInCache := range newVoteCache[h].VoterIndices {
if attesterIndex == indexInCache {
existingAttester = true
}
}
if !existingAttester {
newVoteCache[h].VoterIndices = append(newVoteCache[h].VoterIndices, attesterIndex)
newVoteCache[h].VoteTotalDeposit += b.CrystallizedState().Validators()[attesterIndex].Balance
}
}
}
return newVoteCache, nil
}
// getSignedParentHashes returns all the parent hashes stored in active state up to last cycle length.
func (b *BeaconChain) getSignedParentHashes(block *types.Block, attestation *pb.AttestationRecord) [][32]byte {
var signedParentHashes [][32]byte
start := block.SlotNumber() - attestation.Slot
end := block.SlotNumber() - attestation.Slot - uint64(len(attestation.ObliqueParentHashes)) + params.CycleLength
signedParentHashes = append(signedParentHashes, b.ActiveState().RecentBlockHashes()[start:end]...)
for _, obliqueParentHashes := range attestation.ObliqueParentHashes {
hashes := common.BytesToHash(obliqueParentHashes)
signedParentHashes = append(signedParentHashes, hashes)
}
return signedParentHashes
}
// getAttesterIndices returns the attester committee of based from attestation's shard ID and slot number.
func (b *BeaconChain) getAttesterIndices(attestation *pb.AttestationRecord) ([]uint32, error) {
lastStateRecalc := b.CrystallizedState().LastStateRecalc()
// TODO: IndicesForSlots will return default value because the spec for dynasty transition is not finalized.
shardCommitteeArray := b.CrystallizedState().IndicesForSlots()
shardCommittee := shardCommitteeArray[attestation.Slot-lastStateRecalc].ArrayShardAndCommittee
for i := 0; i < len(shardCommittee); i++ {
if attestation.ShardId == shardCommittee[i].ShardId {
return shardCommittee[i].Committee, nil
}
}
return nil, fmt.Errorf("unable to find attestation based on slot: %v, shardID: %v", attestation.Slot, attestation.ShardId)
}
// validateAttesterBitfields validates the attester bitfields are equal between attestation and crystallized state's calculation.
func (b *BeaconChain) validateAttesterBitfields(attestation *pb.AttestationRecord, attesterIndices []uint32) error {
// Validate attester bit field has the correct length.
if utils.BitLength(len(attesterIndices)) != len(attestation.AttesterBitfield) {
return fmt.Errorf("attestation has incorrect bitfield length. Found %v, expected %v",
len(attestation.AttesterBitfield), utils.BitLength(len(attesterIndices)))
}
// Valid attestation can not have non-zero trailing bits.
lastBit := len(attesterIndices)
if lastBit%8 != 0 {
for i := 0; i < 8-lastBit%8; i++ {
if utils.CheckBit(attestation.AttesterBitfield, lastBit+i) {
return errors.New("attestation has non-zero trailing bits")
}
}
}
return nil
}
// stateRecalc is called when a new cycle has been reached, beacon node
// will re-compute active state and crystallized state during init cycle transition.
func (b *BeaconChain) stateRecalc(
cState *types.CrystallizedState,
aState *types.ActiveState,
block *types.Block) (*types.CrystallizedState, *types.ActiveState, error) {
var blockVoteBalance uint64
justifiedStreak := cState.JustifiedStreak()
justifiedSlot := cState.LastJustifiedSlot()
finalizedSlot := cState.LastFinalizedSlot()
lastStateRecalc := cState.LastStateRecalc()
blockVoteCache := aState.GetBlockVoteCache()
// walk through all the slots from LastStateRecalc - cycleLength to LastStateRecalc - 1.
for i := uint64(0); i < params.CycleLength; i++ {
slot := lastStateRecalc - params.CycleLength + i
blockHash := aState.RecentBlockHashes()[i]
if _, ok := blockVoteCache[blockHash]; ok {
blockVoteBalance = blockVoteCache[blockHash].VoteTotalDeposit
} else {
blockVoteBalance = 0
}
if 3*blockVoteBalance >= 2*cState.TotalDeposits() {
if slot > justifiedSlot {
justifiedSlot = slot
}
justifiedStreak++
} else {
justifiedStreak = 0
}
if justifiedStreak >= params.CycleLength+1 && slot-params.CycleLength > finalizedSlot {
finalizedSlot = slot - params.CycleLength
}
}
newCrossLinkRecords, err := b.processCrosslinks(
cState.CrosslinkRecords(),
cState.Validators(),
aState.PendingAttestations(),
cState.CurrentDynasty(),
block.SlotNumber(),
)
if err != nil {
return nil, nil, err
}
// Remove attestations older than LastStateRecalc.
var newPendingAttestations []*pb.AttestationRecord
for _, attestation := range aState.PendingAttestations() {
if attestation.Slot > lastStateRecalc {
newPendingAttestations = append(newPendingAttestations, attestation)
}
}
// TODO: Full rewards and penalties design is not finalized according to the spec.
rewardedValidators, _ := casper.CalculateRewards(
aState.PendingAttestations(),
cState.Validators(),
cState.CurrentDynasty(),
cState.TotalDeposits())
// Get all active validators and calculate total balance for next cycle.
var nextCycleBalance uint64
nextCycleValidators := casper.ActiveValidatorIndices(cState.Validators(), cState.CurrentDynasty())
for _, index := range nextCycleValidators {
nextCycleBalance += cState.Validators()[index].Balance
}
// Construct new crystallized state for cycle transition.
newCrystallizedState := types.NewCrystallizedState(&pb.CrystallizedState{
Validators: rewardedValidators, // TODO: Stub. Static validator set because dynasty transition is not finalized according to the spec.
LastStateRecalc: lastStateRecalc + params.CycleLength,
IndicesForSlots: cState.IndicesForSlots(), // TODO: Stub. This will be addresses by shuffling during dynasty transition.
LastJustifiedSlot: justifiedSlot,
JustifiedStreak: justifiedStreak,
LastFinalizedSlot: finalizedSlot,
CrosslinkingStartShard: 0, // TODO: Stub. Need to see where this epoch left off.
CrosslinkRecords: newCrossLinkRecords,
DynastySeedLastReset: cState.DynastySeedLastReset(), // TODO: Stub. Dynasty transition is not finalized according to the spec.
TotalDeposits: nextCycleBalance,
})
var recentBlockHashes [][]byte
for _, blockHashes := range aState.RecentBlockHashes() {
recentBlockHashes = append(recentBlockHashes, blockHashes[:])
// Drop the oldest block hash if the recent block hashes length is more than 2 * cycle length.
for len(recentBlockHashes) > 2*params.CycleLength {
// Delete attestation hash list that's corresponding to the block hash.
recentBlockHashes = recentBlockHashes[1:]
}
}
// Construct new active state for cycle transition.
newActiveState := types.NewActiveState(&pb.ActiveState{
PendingAttestations: newPendingAttestations,
RecentBlockHashes: recentBlockHashes,
}, aState.GetBlockVoteCache())
return newCrystallizedState, newActiveState, nil
}
// processCrosslinks checks if the proposed shard block has recevied
// 2/3 of the votes. If yes, we update crosslink record to point to
// the proposed shard block with latest dynasty and slot numbers.
func (b *BeaconChain) processCrosslinks(
crosslinkRecords []*pb.CrosslinkRecord,
validators []*pb.ValidatorRecord,
pendingAttestations []*pb.AttestationRecord,
dynasty uint64,
slot uint64) ([]*pb.CrosslinkRecord, error) {
type shardBlockHash struct {
id uint64
blockHash []byte
}
shardBlockHashBalance := make(map[*shardBlockHash]uint64)
for _, attestation := range pendingAttestations {
sbh := shardBlockHash{
id: attestation.ShardId,
blockHash: attestation.ShardBlockHash,
}
var keyExists bool
if _, ok := shardBlockHashBalance[&sbh]; ok {
keyExists = true
}
if !keyExists {
shardBlockHashBalance[&sbh] = 0
}
indices, err := b.getAttesterIndices(attestation)
if err != nil {
return nil, err
}
// find the total balance of the shard committee.
var totalBalances uint64
for _, attesterIndex := range indices {
totalBalances += validators[attesterIndex].Balance
}
// find the balance of votes cast in shard attestation.
var voteBalances uint64
for i, attesterIndex := range indices {
if utils.CheckBit(attestation.AttesterBitfield, i) {
voteBalances += validators[attesterIndex].Balance
}
}
// if 2/3 of committee voted on this crosslink, update the crosslink
// with latest dynasty number, shard block hash, and slot number.
if 3*voteBalances >= 2*totalBalances && dynasty > crosslinkRecords[attestation.ShardId].Dynasty {
crosslinkRecords[attestation.ShardId] = &pb.CrosslinkRecord{
Dynasty: dynasty,
Blockhash: attestation.ShardBlockHash,
Slot: slot,
}
}
}
return crosslinkRecords, nil
}
func (b *BeaconChain) hasBlock(blockhash [32]byte) (bool, error) {
return b.db.Has(blockKey(blockhash))
}
// saveBlock puts the passed block into the beacon chain db.
func (b *BeaconChain) saveBlock(block *types.Block) error {
hash, err := block.Hash()
if err != nil {
return err
}
key := blockKey(hash)
encodedState, err := block.Marshal()
if err != nil {
return err
}
return b.db.Put(key, encodedState)
}
// saveCanonicalSlotNumber saves the slotnumber and blockhash of a canonical block
// saved in the db. This will alow for canonical blocks to be retrieved from the db
// by using their slotnumber as a key, and then using the retrieved blockhash to get
// the block from the db.
// prefix + slotnumber -> blockhash
// prefix + blockhash -> block
func (b *BeaconChain) saveCanonicalSlotNumber(slotnumber uint64, hash [32]byte) error {
return b.db.Put(canonicalBlockKey(slotnumber), hash[:])
}
// saveCanonicalBlock puts the passed block into the beacon chain db
// and also saves a "latest-head" key mapping to the block in the db.
func (b *BeaconChain) saveCanonicalBlock(block *types.Block) error {
if err := b.saveBlock(block); err != nil {
return err
}
enc, err := block.Marshal()
if err != nil {
return err
}
return b.db.Put(canonicalHeadLookupKey, enc)
}
// getBlock retrieves a block from the db using its hash.
func (b *BeaconChain) getBlock(hash [32]byte) (*types.Block, error) {
key := blockKey(hash)
enc, err := b.db.Get(key)
if err != nil {
return nil, err
}
block := &pb.BeaconBlock{}
err = proto.Unmarshal(enc, block)
return types.NewBlock(block), err
}
// removeBlock removes the block from the db.
func (b *BeaconChain) removeBlock(hash [32]byte) error {
return b.db.Delete(blockKey(hash))
}
// hasCanonicalBlockForSlot checks the db if the canonical block for
// this slot exists.
func (b *BeaconChain) hasCanonicalBlockForSlot(slotnumber uint64) (bool, error) {
return b.db.Has(canonicalBlockKey(slotnumber))
}
// getCanonicalBlockForSlot retrieves the canonical block which is saved in the db
// for that required slot number.
func (b *BeaconChain) getCanonicalBlockForSlot(slotNumber uint64) (*types.Block, error) {
enc, err := b.db.Get(canonicalBlockKey(slotNumber))
if err != nil {
return nil, err
}
var blockhash [32]byte
copy(blockhash[:], enc)
block, err := b.getBlock(blockhash)
return block, err
}
func (b *BeaconChain) hasAttestation(attestationHash [32]byte) (bool, error) {
return b.db.Has(attestationKey(attestationHash))
}
// saveAttestation puts the attestation record into the beacon chain db.
func (b *BeaconChain) saveAttestation(attestation *types.Attestation) error {
hash := attestation.Key()
key := attestationKey(hash)
encodedState, err := attestation.Marshal()
if err != nil {
return err
}
return b.db.Put(key, encodedState)
}
// getAttestation retrieves an attestation record from the db using its hash.
func (b *BeaconChain) getAttestation(hash [32]byte) (*types.Attestation, error) {
key := attestationKey(hash)
enc, err := b.db.Get(key)
if err != nil {
return nil, err
}
attestation := &pb.AttestationRecord{}
err = proto.Unmarshal(enc, attestation)
return types.NewAttestation(attestation), err
}
// removeAttestation removes the attestation from the db.
func (b *BeaconChain) removeAttestation(blockHash [32]byte) error {
return b.db.Delete(attestationKey(blockHash))
}
// hasAttestationHash checks if the beacon block has the attestation.
func (b *BeaconChain) hasAttestationHash(blockHash [32]byte, attestationHash [32]byte) (bool, error) {
enc, err := b.db.Get(attestationHashListKey(blockHash))
if err != nil {
return false, err
}
attestationHashes := &pb.AttestationHashes{}
if err := proto.Unmarshal(enc, attestationHashes); err != nil {
return false, err
}
for _, hash := range attestationHashes.AttestationHash {
if bytes.Equal(hash, attestationHash[:]) {
return true, nil
}
}
return false, nil
}
// hasAttestationHashList checks if the attestation hash list is available.
func (b *BeaconChain) hasAttestationHashList(blockHash [32]byte) (bool, error) {
key := attestationHashListKey(blockHash)
hasKey, err := b.db.Has(key)
if err != nil {
return false, err
}
if !hasKey {
return false, nil
}
return true, nil
}
// getAttestationHashList gets the attestation hash list of the beacon block from the db.
func (b *BeaconChain) getAttestationHashList(blockHash [32]byte) ([][]byte, error) {
key := attestationHashListKey(blockHash)
hasList, err := b.hasAttestationHashList(blockHash)
if err != nil {
return [][]byte{}, err
}
if !hasList {
if err := b.db.Put(key, []byte{}); err != nil {
return [][]byte{}, err
}
}
enc, err := b.db.Get(key)
if err != nil {
return [][]byte{}, err
}
attestationHashes := &pb.AttestationHashes{}
if err := proto.Unmarshal(enc, attestationHashes); err != nil {
return [][]byte{}, err
}
return attestationHashes.AttestationHash, nil
}
// removeAttestationHashList removes the attestation hash list of the beacon block from the db.
func (b *BeaconChain) removeAttestationHashList(blockHash [32]byte) error {
return b.db.Delete(attestationHashListKey(blockHash))
}
// saveAttestationHash saves the attestation hash into the attestation hash list of the corresponding beacon block.
func (b *BeaconChain) saveAttestationHash(blockHash [32]byte, attestationHash [32]byte) error {
key := attestationHashListKey(blockHash)
hashes, err := b.getAttestationHashList(blockHash)
if err != nil {
return err
}
hashes = append(hashes, attestationHash[:])
attestationHashes := &pb.AttestationHashes{}
attestationHashes.AttestationHash = hashes
encodedState, err := proto.Marshal(attestationHashes)
if err != nil {
return err
}
return b.db.Put(key, encodedState)
}