diff --git a/beacon-chain/blockchain/service.go b/beacon-chain/blockchain/service.go index 8094e035746a..001eb6013ff4 100644 --- a/beacon-chain/blockchain/service.go +++ b/beacon-chain/blockchain/service.go @@ -69,21 +69,20 @@ type Service struct { // Config options for the service. type Config struct { - BeaconBlockBuf int - ChainStartFetcher powchain.ChainStartFetcher - BeaconDB db.HeadAccessDatabase - DepositCache *depositcache.DepositCache - AttPool attestations.Pool - ExitPool voluntaryexits.PoolManager - SlashingPool slashings.PoolManager - P2p p2p.Broadcaster - MaxRoutines int - StateNotifier statefeed.Notifier - ForkChoiceStore f.ForkChoicer - OpsService *attestations.Service - StateGen *stategen.State - WspBlockRoot []byte - WspEpoch types.Epoch + BeaconBlockBuf int + ChainStartFetcher powchain.ChainStartFetcher + BeaconDB db.HeadAccessDatabase + DepositCache *depositcache.DepositCache + AttPool attestations.Pool + ExitPool voluntaryexits.PoolManager + SlashingPool slashings.PoolManager + P2p p2p.Broadcaster + MaxRoutines int + StateNotifier statefeed.Notifier + ForkChoiceStore f.ForkChoicer + OpsService *attestations.Service + StateGen *stategen.State + WeakSubjectivityCheckpt *ethpb.Checkpoint } // NewService instantiates a new block service instance that will diff --git a/beacon-chain/blockchain/weak_subjectivity_checks.go b/beacon-chain/blockchain/weak_subjectivity_checks.go index dc5b9a53a423..24a00cb58470 100644 --- a/beacon-chain/blockchain/weak_subjectivity_checks.go +++ b/beacon-chain/blockchain/weak_subjectivity_checks.go @@ -14,7 +14,7 @@ import ( // 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.cfg.WspBlockRoot) == 0 || s.cfg.WspEpoch == 0 { + if s.cfg.WeakSubjectivityCheckpt == nil || len(s.cfg.WeakSubjectivityCheckpt.Root) == 0 || s.cfg.WeakSubjectivityCheckpt.Epoch == 0 { return nil } @@ -23,12 +23,12 @@ func (s *Service) VerifyWeakSubjectivityRoot(ctx context.Context) error { if s.wsVerified { return nil } - if s.cfg.WspEpoch > s.finalizedCheckpt.Epoch { + if s.cfg.WeakSubjectivityCheckpt.Epoch > s.finalizedCheckpt.Epoch { return nil } - r := bytesutil.ToBytes32(s.cfg.WspBlockRoot) - log.Infof("Performing weak subjectivity check for root %#x in epoch %d", r, s.cfg.WspEpoch) + r := bytesutil.ToBytes32(s.cfg.WeakSubjectivityCheckpt.Root) + log.Infof("Performing weak subjectivity check for root %#x in epoch %d", r, s.cfg.WeakSubjectivityCheckpt.Epoch) // Save initial sync cached blocks to DB. if err := s.cfg.BeaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil { return err @@ -38,7 +38,7 @@ func (s *Service) VerifyWeakSubjectivityRoot(ctx context.Context) error { return fmt.Errorf("node does not have root in DB: %#x", r) } - startSlot, err := helpers.StartSlot(s.cfg.WspEpoch) + startSlot, err := helpers.StartSlot(s.cfg.WeakSubjectivityCheckpt.Epoch) if err != nil { return err } @@ -56,5 +56,5 @@ func (s *Service) VerifyWeakSubjectivityRoot(ctx context.Context) error { } } - return fmt.Errorf("node does not have root in db corresponding to epoch: %#x %d", r, s.cfg.WspEpoch) + return fmt.Errorf("node does not have root in db corresponding to epoch: %#x %d", r, s.cfg.WeakSubjectivityCheckpt.Epoch) } diff --git a/beacon-chain/blockchain/weak_subjectivity_checks_test.go b/beacon-chain/blockchain/weak_subjectivity_checks_test.go index cc3c0807b4ee..391c6bfa2a8b 100644 --- a/beacon-chain/blockchain/weak_subjectivity_checks_test.go +++ b/beacon-chain/blockchain/weak_subjectivity_checks_test.go @@ -7,6 +7,7 @@ import ( types "github.com/prysmaticlabs/eth2-types" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" + "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/prysmaticlabs/prysm/shared/testutil/require" ) @@ -22,8 +23,7 @@ func TestService_VerifyWeakSubjectivityRoot(t *testing.T) { tests := []struct { wsVerified bool wantErr bool - wsRoot [32]byte - wsEpoch types.Epoch + checkpt *ethpb.Checkpoint finalizedEpoch types.Epoch errString string name string @@ -34,37 +34,34 @@ func TestService_VerifyWeakSubjectivityRoot(t *testing.T) { }, { name: "already verified", - wsEpoch: 2, + checkpt: ðpb.Checkpoint{Epoch: 2}, finalizedEpoch: 2, wsVerified: true, wantErr: false, }, { name: "not yet to verify, ws epoch higher than finalized epoch", - wsEpoch: 2, + checkpt: ðpb.Checkpoint{Epoch: 2}, finalizedEpoch: 1, wantErr: false, }, { name: "can't find the block in DB", - wsEpoch: 1, - wsRoot: [32]byte{'a'}, + checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: 1}, 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. + checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 2}, // 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, + checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 1}, finalizedEpoch: 3, wantErr: false, }, @@ -72,7 +69,7 @@ func TestService_VerifyWeakSubjectivityRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Service{ - cfg: &Config{BeaconDB: beaconDB, WspBlockRoot: tt.wsRoot[:], WspEpoch: tt.wsEpoch}, + cfg: &Config{BeaconDB: beaconDB, WeakSubjectivityCheckpt: tt.checkpt}, wsVerified: tt.wsVerified, finalizedCheckpt: ðpb.Checkpoint{Epoch: tt.finalizedEpoch}, } diff --git a/beacon-chain/core/helpers/weak_subjectivity.go b/beacon-chain/core/helpers/weak_subjectivity.go index a8981c04c1f1..490be1cc2f38 100644 --- a/beacon-chain/core/helpers/weak_subjectivity.go +++ b/beacon-chain/core/helpers/weak_subjectivity.go @@ -2,8 +2,11 @@ package helpers import ( "bytes" + "encoding/hex" "errors" "fmt" + "strconv" + "strings" types "github.com/prysmaticlabs/eth2-types" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" @@ -156,3 +159,43 @@ func LatestWeakSubjectivityEpoch(st iface.ReadOnlyBeaconState) (types.Epoch, err finalizedEpoch := st.FinalizedCheckpointEpoch() return finalizedEpoch - (finalizedEpoch % wsPeriod), nil } + +// ParseWeakSubjectivityInputString parses "blocks_root:epoch_number" string into a checkpoint. +func ParseWeakSubjectivityInputString(wsCheckpointString string) (*eth.Checkpoint, error) { + if wsCheckpointString == "" { + return nil, nil + } + + // Weak subjectivity input string must contain ":" to separate epoch and block root. + if !strings.Contains(wsCheckpointString, ":") { + return nil, fmt.Errorf("%s did not contain column", wsCheckpointString) + } + + // Strip prefix "0x" if it's part of the input string. + wsCheckpointString = strings.TrimPrefix(wsCheckpointString, "0x") + + // Get the hexadecimal block root from input string. + s := strings.Split(wsCheckpointString, ":") + if len(s) != 2 { + return nil, errors.New("weak subjectivity checkpoint input should be in `block_root:epoch_number` format") + } + + bRoot, err := hex.DecodeString(s[0]) + if err != nil { + return nil, err + } + if len(bRoot) != 32 { + return nil, errors.New("block root is not length of 32") + } + + // Get the epoch number from input string. + epoch, err := strconv.ParseUint(s[1], 10, 64) + if err != nil { + return nil, err + } + + return ð.Checkpoint{ + Epoch: types.Epoch(epoch), + Root: bRoot, + }, nil +} diff --git a/beacon-chain/core/helpers/weak_subjectivity_test.go b/beacon-chain/core/helpers/weak_subjectivity_test.go index 91718b4e8f93..5c82a763dac5 100644 --- a/beacon-chain/core/helpers/weak_subjectivity_test.go +++ b/beacon-chain/core/helpers/weak_subjectivity_test.go @@ -204,6 +204,76 @@ func TestWeakSubjectivity_IsWithinWeakSubjectivityPeriod(t *testing.T) { } } +func TestWeakSubjectivity_ParseWeakSubjectivityInputString(t *testing.T) { + tests := []struct { + name string + input string + checkpt *ethpb.Checkpoint + wantedErr string + }{ + { + name: "No column in string", + input: "0x111111;123", + wantedErr: "did not contain column", + }, + { + name: "Too many columns in string", + input: "0x010203:123:456", + wantedErr: "weak subjectivity checkpoint input should be in `block_root:epoch_number` format", + }, + { + name: "Incorrect block root length", + input: "0x010203:987", + wantedErr: "block root is not length of 32", + }, + { + name: "Correct input", + input: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", + checkpt: ðpb.Checkpoint{ + Root: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + Epoch: types.Epoch(123456789), + }, + }, + { + name: "Correct input without 0x", + input: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", + checkpt: ðpb.Checkpoint{ + Root: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + Epoch: types.Epoch(123456789), + }, + }, + { + name: "Correct input", + input: "0xF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", + checkpt: ðpb.Checkpoint{ + Root: []byte{0xf0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + Epoch: types.Epoch(123456789), + }, + }, + { + name: "Correct input without 0x", + input: "F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", + checkpt: ðpb.Checkpoint{ + Root: []byte{0xf0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + Epoch: types.Epoch(123456789), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + wsCheckpt, err := helpers.ParseWeakSubjectivityInputString(tt.input) + if tt.wantedErr != "" { + require.ErrorContains(t, tt.wantedErr, err) + return + } + require.NoError(t, err) + require.NotNil(t, wsCheckpt) + require.DeepEqual(t, tt.checkpt.Root, wsCheckpt.Root, "Roots do not match") + require.Equal(t, tt.checkpt.Epoch, wsCheckpt.Epoch, "Epochs do not match") + }) + } +} + func genState(t *testing.T, valCount uint64, avgBalance uint64) iface.BeaconState { beaconState, err := testutil.NewBeaconState() require.NoError(t, err) diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index 885944641107..276a406f8b15 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -4,7 +4,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") go_library( name = "go_default_library", srcs = [ - "helper.go", "log.go", "node.go", ], @@ -16,6 +15,7 @@ go_library( deps = [ "//beacon-chain/blockchain:go_default_library", "//beacon-chain/cache/depositcache:go_default_library", + "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/db:go_default_library", "//beacon-chain/db/kv:go_default_library", "//beacon-chain/forkchoice:go_default_library", @@ -56,17 +56,13 @@ go_library( go_test( name = "go_default_test", size = "small", - srcs = [ - "helper_test.go", - "node_test.go", - ], + srcs = ["node_test.go"], embed = [":go_default_library"], deps = [ "//beacon-chain/core/feed/state:go_default_library", "//shared/cmd:go_default_library", "//shared/testutil/assert:go_default_library", "//shared/testutil/require:go_default_library", - "@com_github_prysmaticlabs_eth2_types//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", ], diff --git a/beacon-chain/node/helper.go b/beacon-chain/node/helper.go deleted file mode 100644 index 8738d8c80603..000000000000 --- a/beacon-chain/node/helper.go +++ /dev/null @@ -1,49 +0,0 @@ -package node - -import ( - "encoding/hex" - "errors" - "fmt" - "strconv" - "strings" - - types "github.com/prysmaticlabs/eth2-types" -) - -// Given input string `block_root:epoch_number`, this verifies the input string is valid, and -// returns the block root as bytes and epoch number as unsigned integers. -func convertWspInput(wsp string) ([]byte, types.Epoch, error) { - if wsp == "" { - return nil, 0, nil - } - - // Weak subjectivity input string must contain ":" to separate epoch and block root. - if !strings.Contains(wsp, ":") { - return nil, 0, fmt.Errorf("%s did not contain column", wsp) - } - - // Strip prefix "0x" if it's part of the input string. - wsp = strings.TrimPrefix(wsp, "0x") - - // Get the hexadecimal block root from input string. - s := strings.Split(wsp, ":") - if len(s) != 2 { - return nil, 0, errors.New("weak subjectivity checkpoint input should be in `block_root:epoch_number` format") - } - - bRoot, err := hex.DecodeString(s[0]) - if err != nil { - return nil, 0, err - } - if len(bRoot) != 32 { - return nil, 0, errors.New("block root is not length of 32") - } - - // Get the epoch number from input string. - epoch, err := strconv.ParseUint(s[1], 10, 64) - if err != nil { - return nil, 0, err - } - - return bRoot, types.Epoch(epoch), nil -} diff --git a/beacon-chain/node/helper_test.go b/beacon-chain/node/helper_test.go deleted file mode 100644 index 6e79a2446463..000000000000 --- a/beacon-chain/node/helper_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package node - -import ( - "reflect" - "testing" - - types "github.com/prysmaticlabs/eth2-types" - "github.com/prysmaticlabs/prysm/shared/testutil/require" -) - -func TestConvertWspInput(t *testing.T) { - tests := []struct { - name string - input string - bRoot []byte - epoch types.Epoch - wantErr bool - errStr string - }{ - { - name: "No column in string", - input: "0x111111;123", - wantErr: true, - errStr: "did not contain column", - }, - { - name: "Too many columns in string", - input: "0x010203:123:456", - wantErr: false, - errStr: "weak subjectivity checkpoint input should be in `block_root:epoch_number` format", - }, - { - name: "Incorrect block root length", - input: "0x010203:987", - wantErr: false, - errStr: "block root is not length of 32", - }, - { - name: "Correct input", - input: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", - bRoot: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, - epoch: 123456789, - wantErr: false, - }, - { - name: "Correct input without 0x", - input: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:123456789", - bRoot: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, - epoch: 123456789, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bRoot, epoch, err := convertWspInput(tt.input) - if (err != nil) != tt.wantErr { - require.ErrorContains(t, tt.errStr, err) - return - } - if !reflect.DeepEqual(bRoot, tt.bRoot) { - t.Errorf("convertWspInput() block root = %v, want %v", bRoot, tt.bRoot) - } - if epoch != tt.epoch { - t.Errorf("convertWspInput() epoch = %v, want %v", epoch, tt.epoch) - } - }) - } -} diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 0745dad1b4dc..d09791569251 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -20,6 +20,7 @@ import ( types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/beacon-chain/blockchain" "github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/db" "github.com/prysmaticlabs/prysm/beacon-chain/db/kv" "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice" @@ -468,27 +469,26 @@ func (b *BeaconNode) registerBlockchainService() error { } wsp := b.cliCtx.String(flags.WeakSubjectivityCheckpt.Name) - bRoot, epoch, err := convertWspInput(wsp) + wsCheckpt, err := helpers.ParseWeakSubjectivityInputString(wsp) if err != nil { return err } maxRoutines := b.cliCtx.Int(cmd.MaxGoroutines.Name) blockchainService, err := blockchain.NewService(b.ctx, &blockchain.Config{ - BeaconDB: b.db, - DepositCache: b.depositCache, - ChainStartFetcher: web3Service, - AttPool: b.attestationPool, - ExitPool: b.exitPool, - SlashingPool: b.slashingsPool, - P2p: b.fetchP2P(), - MaxRoutines: maxRoutines, - StateNotifier: b, - ForkChoiceStore: b.forkChoiceStore, - OpsService: opsService, - StateGen: b.stateGen, - WspBlockRoot: bRoot, - WspEpoch: epoch, + BeaconDB: b.db, + DepositCache: b.depositCache, + ChainStartFetcher: web3Service, + AttPool: b.attestationPool, + ExitPool: b.exitPool, + SlashingPool: b.slashingsPool, + P2p: b.fetchP2P(), + MaxRoutines: maxRoutines, + StateNotifier: b, + ForkChoiceStore: b.forkChoiceStore, + OpsService: opsService, + StateGen: b.stateGen, + WeakSubjectivityCheckpt: wsCheckpt, }) if err != nil { return errors.Wrap(err, "could not register blockchain service") diff --git a/cmd/beacon-chain/flags/base.go b/cmd/beacon-chain/flags/base.go index 8f1fb2c8dafb..7d13ddbbbd67 100644 --- a/cmd/beacon-chain/flags/base.go +++ b/cmd/beacon-chain/flags/base.go @@ -156,7 +156,7 @@ var ( // WeakSubjectivityCheckpt defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks. WeakSubjectivityCheckpt = &cli.StringFlag{ Name: "weak-subjectivity-checkpoint", - Usage: "Input in `block_root:epoch_number` format. This guarantee that syncing leads to the given Weak Subjectivity Checkpoint being in the canonical chain. " + + Usage: "Input in `block_root:epoch_number` format. This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " + "If such a sync is not possible, the node will treat it a critical and irrecoverable failure", Value: "", }