From 68d0c09daf437383dc92900a550e07564f554bf3 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Sat, 26 Sep 2020 01:04:07 -0700 Subject: [PATCH] Add, use and test `VerifyWeakSubjectivityRoot` (#7344) * Add, use and test `VerifyWeakSubjectivityRoot` * Comments * Make bazel test happy * Merge branch 'master' into verify-ws * Add flag to help * Merge branch 'verify-ws' of github.com:prysmaticlabs/prysm into verify-ws * Merge refs/heads/master into verify-ws * Merge refs/heads/master into verify-ws * Merge refs/heads/master into verify-ws * Merge branch 'master' of github.com:prysmaticlabs/prysm into verify-ws * Update beacon-chain/blockchain/service.go Co-authored-by: Raul Jordan * Update beacon-chain/blockchain/weak_subjectivity_checks.go Co-authored-by: Raul Jordan * Merge branch 'verify-ws' of github.com:prysmaticlabs/prysm into verify-ws * s/&&/|| for VerifyWeakSubjectivityRoot circuit breaker * Merge refs/heads/master into verify-ws * Merge refs/heads/master into verify-ws --- beacon-chain/blockchain/BUILD.bazel | 3 + beacon-chain/blockchain/receive_block.go | 5 ++ beacon-chain/blockchain/service.go | 12 +++ .../blockchain/weak_subjectivity_checks.go | 60 +++++++++++++ .../weak_subjectivity_checks_test.go | 85 +++++++++++++++++++ beacon-chain/main.go | 1 + beacon-chain/node/node.go | 16 ++-- beacon-chain/sync/initial-sync/service.go | 2 - beacon-chain/usage.go | 1 + 9 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 beacon-chain/blockchain/weak_subjectivity_checks.go create mode 100644 beacon-chain/blockchain/weak_subjectivity_checks_test.go diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index 1c78c72e5..f942aed1d 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "receive_attestation.go", "receive_block.go", "service.go", + "weak_subjectivity_checks.go", ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/blockchain", visibility = [ @@ -34,6 +35,7 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/state:go_default_library", "//beacon-chain/db:go_default_library", + "//beacon-chain/db/filters:go_default_library", "//beacon-chain/flags:go_default_library", "//beacon-chain/forkchoice:go_default_library", "//beacon-chain/forkchoice/protoarray:go_default_library", @@ -86,6 +88,7 @@ go_test( "receive_attestation_test.go", "receive_block_test.go", "service_test.go", + "weak_subjectivity_checks_test.go", ], embed = [":go_default_library"], deps = [ diff --git a/beacon-chain/blockchain/receive_block.go b/beacon-chain/blockchain/receive_block.go index 27448202f..b1c032659 100644 --- a/beacon-chain/blockchain/receive_block.go +++ b/beacon-chain/blockchain/receive_block.go @@ -142,6 +142,11 @@ func (s *Service) ReceiveBlockBatch(ctx context.Context, blocks []*ethpb.SignedB reportSlotMetrics(blockCopy.Block.Slot, s.headSlot(), s.CurrentSlot(), s.finalizedCheckpt) } + if err := s.VerifyWeakSubjectivityRoot(s.ctx); err != nil { + // Exit run time if the node failed to verify weak subjectivity checkpoint. + log.Fatalf("Could not verify weak subjectivity checkpoint: %v", err) + } + return nil } diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index 0d0b9f687..2023c7f7e 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -75,6 +75,9 @@ type Service struct { justifiedBalances []uint64 justifiedBalancesLock sync.RWMutex checkPtInfoCache *checkPtInfoCache + wsEpoch uint64 + wsRoot []byte + wsVerified bool } // Config options for the service. @@ -92,6 +95,8 @@ type Config struct { ForkChoiceStore f.ForkChoicer OpsService *attestations.Service StateGen *stategen.State + WspBlockRoot []byte + WspEpoch uint64 } // NewService instantiates a new block service instance that will @@ -120,6 +125,8 @@ func NewService(ctx context.Context, cfg *Config) (*Service, error) { recentCanonicalBlocks: make(map[[32]byte]bool), justifiedBalances: make([]uint64, 0), checkPtInfoCache: newCheckPointInfoCache(), + wsEpoch: cfg.WspEpoch, + wsRoot: cfg.WspBlockRoot, }, nil } @@ -184,6 +191,11 @@ func (s *Service) Start() { s.prevFinalizedCheckpt = stateTrie.CopyCheckpoint(finalizedCheckpoint) s.resumeForkChoice(justifiedCheckpoint, finalizedCheckpoint) + if err := s.VerifyWeakSubjectivityRoot(s.ctx); err != nil { + // Exit run time if the node failed to verify weak subjectivity checkpoint. + log.Fatalf("Could not verify weak subjectivity checkpoint: %v", err) + } + s.stateNotifier.StateFeed().Send(&feed.Event{ Type: statefeed.Initialized, Data: &statefeed.InitializedData{ diff --git a/beacon-chain/blockchain/weak_subjectivity_checks.go b/beacon-chain/blockchain/weak_subjectivity_checks.go new file mode 100644 index 000000000..d66e49bcb --- /dev/null +++ b/beacon-chain/blockchain/weak_subjectivity_checks.go @@ -0,0 +1,60 @@ +package blockchain + +import ( + "context" + "fmt" + + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/beacon-chain/db/filters" + "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/params" +) + +// VerifyWeakSubjectivityRoot verifies the weak subjectivity root in the service struct. +// Reference design: https://github.com/ethereum/eth2.0-specs/blob/master/specs/phase0/weak-subjectivity.md#weak-subjectivity-sync-procedure +func (s *Service) VerifyWeakSubjectivityRoot(ctx context.Context) error { + // TODO(7342): Remove the following to fully use weak subjectivity in production. + if len(s.wsRoot) == 0 || s.wsEpoch == 0 { + return nil + } + + // Do nothing if the weak subjectivity has previously been verified, + // or weak subjectivity epoch is higher than last finalized epoch. + if s.wsVerified { + return nil + } + if s.wsEpoch > s.finalizedCheckpt.Epoch { + return nil + } + + r := bytesutil.ToBytes32(s.wsRoot) + log.Infof("Performing weak subjectivity check for root %#x in epoch %d", r, s.wsEpoch) + // Save initial sync cached blocks to DB. + if err := s.beaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil { + return err + } + // A node should have the weak subjectivity block in the DB. + if !s.beaconDB.HasBlock(ctx, r) { + return fmt.Errorf("node does not have root in DB: %#x", r) + } + + startSlot, err := helpers.StartSlot(s.wsEpoch) + if err != nil { + return err + } + // A node should have the weak subjectivity block corresponds to the correct epoch in the DB. + filter := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(startSlot + params.BeaconConfig().SlotsPerEpoch) + roots, err := s.beaconDB.BlockRoots(ctx, filter) + if err != nil { + return err + } + for _, root := range roots { + if r == root { + log.Info("Weak subjectivity check has passed") + s.wsVerified = true + return nil + } + } + + return fmt.Errorf("node does not have root in db corresponding to epoch: %#x %d", r, s.wsEpoch) +} diff --git a/beacon-chain/blockchain/weak_subjectivity_checks_test.go b/beacon-chain/blockchain/weak_subjectivity_checks_test.go new file mode 100644 index 000000000..d07ea09b1 --- /dev/null +++ b/beacon-chain/blockchain/weak_subjectivity_checks_test.go @@ -0,0 +1,85 @@ +package blockchain + +import ( + "context" + "testing" + + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" + "github.com/prysmaticlabs/prysm/shared/testutil" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestService_VerifyWeakSubjectivityRoot(t *testing.T) { + db, _ := testDB.SetupDB(t) + + b := testutil.NewBeaconBlock() + b.Block.Slot = 32 + require.NoError(t, db.SaveBlock(context.Background(), b)) + r, err := b.Block.HashTreeRoot() + require.NoError(t, err) + tests := []struct { + wsVerified bool + wantErr bool + wsRoot [32]byte + wsEpoch uint64 + finalizedEpoch uint64 + errString string + name string + }{ + { + name: "nil root and epoch", + wantErr: false, + }, + { + name: "already verified", + wsEpoch: 2, + finalizedEpoch: 2, + wsVerified: true, + wantErr: false, + }, + { + name: "not yet to verify, ws epoch higher than finalized epoch", + wsEpoch: 2, + finalizedEpoch: 1, + wantErr: false, + }, + { + name: "can't find the block in DB", + wsEpoch: 1, + wsRoot: [32]byte{'a'}, + finalizedEpoch: 3, + wantErr: true, + errString: "node does not have root in DB", + }, + { + name: "can't find the block corresponds to ws epoch in DB", + wsEpoch: 2, + wsRoot: r, // Root belongs in epoch 1. + finalizedEpoch: 3, + wantErr: true, + errString: "node does not have root in db corresponding to epoch", + }, + { + name: "can verify and pass", + wsEpoch: 1, + wsRoot: r, + finalizedEpoch: 3, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + beaconDB: db, + wsRoot: tt.wsRoot[:], + wsEpoch: tt.wsEpoch, + wsVerified: tt.wsVerified, + finalizedCheckpt: ðpb.Checkpoint{Epoch: tt.finalizedEpoch}, + } + if err := s.VerifyWeakSubjectivityRoot(context.Background()); (err != nil) != tt.wantErr { + require.ErrorContains(t, tt.errString, err) + } + }) + } +} diff --git a/beacon-chain/main.go b/beacon-chain/main.go index fc8ad4fe7..137009c7f 100644 --- a/beacon-chain/main.go +++ b/beacon-chain/main.go @@ -52,6 +52,7 @@ var appFlags = []cli.Flag{ flags.HistoricalSlasherNode, flags.ChainID, flags.NetworkID, + flags.WeakSubjectivityCheckpt, cmd.MinimalConfigFlag, cmd.E2EConfigFlag, cmd.RPCMaxPageSizeFlag, diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 737922f0e..348c4024f 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -435,6 +435,12 @@ func (b *BeaconNode) registerBlockchainService() error { return err } + wsp := b.cliCtx.String(flags.WeakSubjectivityCheckpt.Name) + bRoot, epoch, err := convertWspInput(wsp) + if err != nil { + return err + } + maxRoutines := b.cliCtx.Int(cmd.MaxGoroutines.Name) blockchainService, err := blockchain.NewService(b.ctx, &blockchain.Config{ BeaconDB: b.db, @@ -449,6 +455,8 @@ func (b *BeaconNode) registerBlockchainService() error { ForkChoiceStore: b.forkChoiceStore, OpsService: opsService, StateGen: b.stateGen, + WspBlockRoot: bRoot, + WspEpoch: epoch, }) if err != nil { return errors.Wrap(err, "could not register blockchain service") @@ -535,12 +543,6 @@ func (b *BeaconNode) registerSyncService() error { } func (b *BeaconNode) registerInitialSyncService() error { - wsp := b.cliCtx.String(flags.WeakSubjectivityCheckpt.Name) - bRoot, epoch, err := convertWspInput(wsp) - if err != nil { - return err - } - var chainService *blockchain.Service if err := b.services.FetchService(&chainService); err != nil { return err @@ -552,8 +554,6 @@ func (b *BeaconNode) registerInitialSyncService() error { P2P: b.fetchP2P(), StateNotifier: b, BlockNotifier: b, - WspBlockRoot: bRoot, - WspEpoch: epoch, }) return b.services.RegisterService(is) } diff --git a/beacon-chain/sync/initial-sync/service.go b/beacon-chain/sync/initial-sync/service.go index 216ea0485..a8a7cb149 100644 --- a/beacon-chain/sync/initial-sync/service.go +++ b/beacon-chain/sync/initial-sync/service.go @@ -40,8 +40,6 @@ type Config struct { Chain blockchainService StateNotifier statefeed.Notifier BlockNotifier blockfeed.Notifier - WspBlockRoot []byte - WspEpoch uint64 } // Service service. diff --git a/beacon-chain/usage.go b/beacon-chain/usage.go index 7aee0ab98..32c81e2d2 100644 --- a/beacon-chain/usage.go +++ b/beacon-chain/usage.go @@ -108,6 +108,7 @@ var appHelpFlagGroups = []flagGroup{ flags.HistoricalSlasherNode, flags.ChainID, flags.NetworkID, + flags.WeakSubjectivityCheckpt, }, }, {