Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use checkpoint info cache #7098

Merged
merged 4 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions beacon-chain/blockchain/process_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/shared/attestationutil"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/roughtime"
"go.opencensus.io/trace"
Expand Down Expand Up @@ -66,6 +69,53 @@ func (s *Service) onAttestation(ctx context.Context, a *ethpb.Attestation) ([]ui
return nil, ErrTargetRootNotInDB
}

if featureconfig.Get().UseCheckPointInfoCache {
c, err := s.AttestationCheckPtInfo(ctx, a)
if err != nil {
return nil, err
}
if err := s.verifyAttTargetEpoch(ctx, params.BeaconConfig().GenesisTime, uint64(roughtime.Now().Unix()), tgt); err != nil {
return nil, err
}
if err := s.verifyBeaconBlock(ctx, a.Data); err != nil {
return nil, errors.Wrap(err, "could not verify attestation beacon block")
}
if err := s.verifyLMDFFGConsistent(ctx, a.Data.Target.Epoch, a.Data.Target.Root, a.Data.BeaconBlockRoot); err != nil {
return nil, errors.Wrap(err, "could not verify attestation beacon block")
}
if err := helpers.VerifySlotTime(params.BeaconConfig().GenesisTime, a.Data.Slot+1, params.BeaconNetworkConfig().MaximumGossipClockDisparity); err != nil {
return nil, err
}
committee, err := helpers.BeaconCommittee(c.ActiveIndices, bytesutil.ToBytes32(c.Seed), a.Data.Slot, a.Data.CommitteeIndex)
if err != nil {
return nil, err
}
indexedAtt := attestationutil.ConvertToIndexed(ctx, a, committee)
if err := attestationutil.IsValidAttestationIndices(ctx, indexedAtt); err != nil {
return nil, err
}
domain, err := helpers.Domain(c.Fork, indexedAtt.Data.Target.Epoch, params.BeaconConfig().DomainBeaconAttester, c.GenesisRoot)
if err != nil {
return nil, err
}
indices := indexedAtt.AttestingIndices
pubkeys := []bls.PublicKey{}
for i := 0; i < len(indices); i++ {
pubkeyAtIdx := c.PubKeys[indices[i]]
pk, err := bls.PublicKeyFromBytes(pubkeyAtIdx)
if err != nil {
return nil, err
}
pubkeys = append(pubkeys, pk)
}
if err := attestationutil.VerifyIndexedAttestationSig(ctx, indexedAtt, pubkeys, domain); err != nil {
return nil, err
}
s.forkChoiceStore.ProcessAttestation(ctx, indexedAtt.AttestingIndices, bytesutil.ToBytes32(a.Data.BeaconBlockRoot), a.Data.Target.Epoch)

return indexedAtt.AttestingIndices, nil
}

// Retrieve attestation's data beacon block pre state. Advance pre state to latest epoch if necessary and
// save it to the cache.
baseState, err := s.getAttPreState(ctx, tgt)
Expand Down
58 changes: 58 additions & 0 deletions beacon-chain/blockchain/process_attestation_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/attestationutil"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/params"
Expand Down Expand Up @@ -59,6 +60,63 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (*sta

}

// getAttCheckPtInfo retrieves the check point info given a check point. Check point info enables the node
// to efficiently verify attestation signature without using beacon state. This function utilizes
// the checkpoint info cache and will update the check point info cache on miss.
func (s *Service) getAttCheckPtInfo(ctx context.Context, c *ethpb.Checkpoint, e uint64) (*pb.CheckPtInfo, error) {
// Return checkpoint info if exists in cache.
info, err := s.checkPtInfoCache.get(c)
if err != nil {
return nil, errors.Wrap(err, "could not get cached checkpoint state")
}
if info != nil {
return info, nil
}

// Retrieve checkpoint state to compute checkpoint info.
baseState, err := s.stateGen.StateByRoot(ctx, bytesutil.ToBytes32(c.Root))
if err != nil {
return nil, errors.Wrapf(err, "could not get pre state for slot %d", helpers.StartSlot(c.Epoch))
}
if helpers.StartSlot(c.Epoch) > baseState.Slot() {
baseState = baseState.Copy()
baseState, err = state.ProcessSlots(ctx, baseState, helpers.StartSlot(c.Epoch))
if err != nil {
return nil, errors.Wrapf(err, "could not process slots up to %d", helpers.StartSlot(c.Epoch))
}
}
f := baseState.Fork()
g := bytesutil.ToBytes32(baseState.GenesisValidatorRoot())
seed, err := helpers.Seed(baseState, e, params.BeaconConfig().DomainBeaconAttester)
if err != nil {
return nil, err
}
indices, err := helpers.ActiveValidatorIndices(baseState, e)
if err != nil {
return nil, err
}
validators := baseState.ValidatorsReadOnly()
pks := make([][]byte, len(validators))
for i := 0; i < len(pks); i++ {
pk := validators[i].PublicKey()
pks[i] = pk[:]
}

// Cache and return the checkpoint info.
info = &pb.CheckPtInfo{
Fork: f,
GenesisRoot: g[:],
Seed: seed[:],
ActiveIndices: indices,
PubKeys: pks,
}
if err := s.checkPtInfoCache.put(c, info); err != nil {
return nil, err
}

return info, nil
}

// verifyAttTargetEpoch validates attestation is from the current or previous epoch.
func (s *Service) verifyAttTargetEpoch(ctx context.Context, genesisTime uint64, nowTime uint64, c *ethpb.Checkpoint) error {
currentSlot := (nowTime - genesisTime) / params.BeaconConfig().SecondsPerSlot
Expand Down
169 changes: 169 additions & 0 deletions beacon-chain/blockchain/process_attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/gogo/protobuf/proto"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
Expand Down Expand Up @@ -139,6 +141,129 @@ func TestStore_OnAttestation(t *testing.T) {
}
}

func TestStore_OnAttestationUsingCheckptCache(t *testing.T) {
resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{UseCheckPointInfoCache: true})
defer resetCfg()

ctx := context.Background()
db, sc := testDB.SetupDB(t)

cfg := &Config{
BeaconDB: db,
ForkChoiceStore: protoarray.New(0, 0, [32]byte{}),
StateGen: stategen.New(db, sc),
}
service, err := NewService(ctx, cfg)
require.NoError(t, err)

_, err = blockTree1(db, []byte{'g'})
require.NoError(t, err)

BlkWithOutState := testutil.NewBeaconBlock()
BlkWithOutState.Block.Slot = 0
require.NoError(t, db.SaveBlock(ctx, BlkWithOutState))
BlkWithOutStateRoot, err := stateutil.BlockRoot(BlkWithOutState.Block)
require.NoError(t, err)

BlkWithStateBadAtt := testutil.NewBeaconBlock()
BlkWithStateBadAtt.Block.Slot = 1
require.NoError(t, db.SaveBlock(ctx, BlkWithStateBadAtt))
BlkWithStateBadAttRoot, err := stateutil.BlockRoot(BlkWithStateBadAtt.Block)
require.NoError(t, err)

s := testutil.NewBeaconState()
require.NoError(t, s.SetSlot(100*params.BeaconConfig().SlotsPerEpoch))
require.NoError(t, service.beaconDB.SaveState(ctx, s, BlkWithStateBadAttRoot))

BlkWithValidState := testutil.NewBeaconBlock()
BlkWithValidState.Block.Slot = 2
require.NoError(t, db.SaveBlock(ctx, BlkWithValidState))

BlkWithValidStateRoot, err := stateutil.BlockRoot(BlkWithValidState.Block)
require.NoError(t, err)
s = testutil.NewBeaconState()
if err := s.SetFork(&pb.Fork{
Epoch: 0,
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
}); err != nil {
t.Fatal(err)
}
require.NoError(t, service.beaconDB.SaveState(ctx, s, BlkWithValidStateRoot))

tests := []struct {
name string
a *ethpb.Attestation
wantErr bool
wantErrString string
}{
{
name: "attestation's data slot not aligned with target vote",
a: &ethpb.Attestation{Data: &ethpb.AttestationData{Slot: params.BeaconConfig().SlotsPerEpoch, Target: &ethpb.Checkpoint{Root: make([]byte, 32)}}},
wantErr: true,
wantErrString: "data slot is not in the same epoch as target 1 != 0",
},
{
name: "attestation's target root not in db",
a: &ethpb.Attestation{Data: &ethpb.AttestationData{Target: &ethpb.Checkpoint{Root: bytesutil.PadTo([]byte{'A'}, 32)}}},
wantErr: true,
wantErrString: "target root does not exist in db",
},
{
name: "no pre state for attestations's target block",
a: &ethpb.Attestation{Data: &ethpb.AttestationData{Target: &ethpb.Checkpoint{Root: BlkWithOutStateRoot[:]}}},
wantErr: true,
wantErrString: "could not get pre state for slot 0",
},
{
name: "process attestation doesn't match current epoch",
a: &ethpb.Attestation{Data: &ethpb.AttestationData{Slot: 100 * params.BeaconConfig().SlotsPerEpoch, Target: &ethpb.Checkpoint{Epoch: 100,
Root: BlkWithStateBadAttRoot[:]}}},
wantErr: true,
wantErrString: "target epoch 100 does not match current epoch",
},
{
name: "process nil attestation",
a: nil,
wantErr: true,
wantErrString: "nil attestation",
},
{
name: "process nil field (a.Data) in attestation",
a: &ethpb.Attestation{},
wantErr: true,
wantErrString: "nil attestation.Data field",
},
{
name: "process nil field (a.Target) in attestation",
a: &ethpb.Attestation{
Data: &ethpb.AttestationData{
BeaconBlockRoot: make([]byte, 32),
Target: nil,
Source: &ethpb.Checkpoint{Root: make([]byte, 32)},
},
AggregationBits: make([]byte, 1),
Signature: make([]byte, 96),
},
wantErr: true,
wantErrString: "nil attestation.Data.Target field",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := service.onAttestation(ctx, tt.a)
if tt.wantErr {
if err == nil || !strings.Contains(err.Error(), tt.wantErrString) {
t.Errorf("Store.onAttestation() error = %v, wantErr = %v", err, tt.wantErrString)
}
} else {
t.Error(err)
}
})
}
}

func TestStore_SaveCheckpointState(t *testing.T) {
ctx := context.Background()
db, sc := testDB.SetupDB(t)
Expand Down Expand Up @@ -395,3 +520,47 @@ func TestVerifyLMDFFGConsistent_OK(t *testing.T) {
err = service.verifyLMDFFGConsistent(context.Background(), 1, r32[:], r33[:])
assert.NoError(t, err, "Could not verify LMD and FFG votes to be consistent")
}

func TestGetAttCheckptInfo(t *testing.T) {
ctx := context.Background()
db, _ := testDB.SetupDB(t)
cfg := &Config{BeaconDB: db, StateGen: stategen.New(db, cache.NewStateSummaryCache())}
service, err := NewService(ctx, cfg)
require.NoError(t, err)

baseState, _ := testutil.DeterministicGenesisState(t, 128)
b := testutil.NewBeaconBlock()
r, err := stateutil.BlockRoot(b.Block)
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveState(ctx, baseState, r))
require.NoError(t, service.beaconDB.SaveBlock(ctx, b))
require.NoError(t, service.beaconDB.SaveGenesisBlockRoot(ctx, r))
checkpoint := &ethpb.Checkpoint{Root: r[:]}

returned, err := service.getAttCheckPtInfo(ctx, checkpoint, 0)
require.NoError(t, err)

seed, err := helpers.Seed(baseState, 0, params.BeaconConfig().DomainBeaconAttester)
require.NoError(t, err)
indices, err := helpers.ActiveValidatorIndices(baseState, 0)
require.NoError(t, err)
validators := baseState.ValidatorsReadOnly()
pks := make([][]byte, len(validators))
for i := 0; i < len(pks); i++ {
pk := validators[i].PublicKey()
pks[i] = pk[:]
}

wanted := &pb.CheckPtInfo{
Fork: baseState.Fork(),
GenesisRoot: baseState.GenesisValidatorRoot(),
Seed: seed[:],
ActiveIndices: indices,
PubKeys: pks,
}
require.DeepEqual(t, wanted, returned)

cached, err := service.checkPtInfoCache.get(checkpoint)
require.NoError(t, err)
require.DeepEqual(t, wanted, cached)
}
8 changes: 8 additions & 0 deletions beacon-chain/blockchain/receive_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
Expand All @@ -24,6 +25,7 @@ type AttestationReceiver interface {
ReceiveAttestationNoPubsub(ctx context.Context, att *ethpb.Attestation) error
IsValidAttestation(ctx context.Context, att *ethpb.Attestation) bool
AttestationPreState(ctx context.Context, att *ethpb.Attestation) (*state.BeaconState, error)
AttestationCheckPtInfo(ctx context.Context, att *ethpb.Attestation) (*pb.CheckPtInfo, error)
}

// ReceiveAttestationNoPubsub is a function that defines the operations that are performed on
Expand Down Expand Up @@ -74,6 +76,12 @@ func (s *Service) AttestationPreState(ctx context.Context, att *ethpb.Attestatio
return s.getAttPreState(ctx, att.Data.Target)
}

// AttestationCheckPtInfo returns the check point info of attestation that can be used to verify the attestation
// contents and signatures.
func (s *Service) AttestationCheckPtInfo(ctx context.Context, att *ethpb.Attestation) (*pb.CheckPtInfo, error) {
return s.getAttCheckPtInfo(ctx, att.Data.Target, helpers.SlotToEpoch(att.Data.Slot))
}

// This processes attestations from the attestation pool to account for validator votes and fork choice.
func (s *Service) processAttestation(subscribedToStateEvents chan struct{}) {
// Wait for state to be initialized.
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/blockchain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type Service struct {
recentCanonicalBlocksLock sync.RWMutex
justifiedBalances []uint64
justifiedBalancesLock sync.RWMutex
checkPtInfoCache *checkPtInfoCache
}

// Config options for the service.
Expand Down Expand Up @@ -119,6 +120,7 @@ func NewService(ctx context.Context, cfg *Config) (*Service, error) {
initSyncBlocks: make(map[[32]byte]*ethpb.SignedBeaconBlock),
recentCanonicalBlocks: make(map[[32]byte]bool),
justifiedBalances: make([]uint64, 0),
checkPtInfoCache: newCheckPointInfoCache(),
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions beacon-chain/blockchain/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stateutil:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/event:go_default_library",
"//shared/params:go_default_library",
"@com_github_pkg_errors//:go_default_library",
Expand Down
Loading