From e0e7c71eb5681bb0d4a4ffd4ced83a8ecc4d3a67 Mon Sep 17 00:00:00 2001 From: Potuz Date: Mon, 22 May 2023 14:23:20 -0300 Subject: [PATCH] Fix sandwich attack on honest reorgs (#12418) * Fix sandwich attack on honest reorgs * fix test --------- Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> --- .../doubly-linked-tree/reorg_late_blocks.go | 10 ++++++++++ .../doubly-linked-tree/reorg_late_blocks_test.go | 12 ++++++++++-- beacon-chain/rpc/eth/beacon/config_test.go | 4 +++- config/params/config.go | 1 + config/params/mainnet_config.go | 1 + 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks.go b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks.go index 49362a8a8bac..0c0db069ddb5 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks.go @@ -82,6 +82,11 @@ func (f *ForkChoice) ShouldOverrideFCU() (override bool) { if head.weight*100 > f.store.committeeWeight*params.BeaconConfig().ReorgWeightThreshold { return } + + // Only orphan a block if the parent LMD vote is strong + if parent.weight*100 < f.store.committeeWeight*params.BeaconConfig().ReorgParentWeightThreshold { + panic(f.store.committeeWeight) + } return true } @@ -137,6 +142,11 @@ func (f *ForkChoice) GetProposerHead() [32]byte { return head.root } + // Only orphan a block if the parent LMD vote is strong + if parent.weight*100 < f.store.committeeWeight*params.BeaconConfig().ReorgParentWeightThreshold { + return head.root + } + // Only reorg if we are proposing early secs, err := slots.SecondsSinceSlotStart(head.slot+1, f.store.genesisTime, uint64(time.Now().Unix())) if err != nil { diff --git a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go index b6316fd0ebbd..61780e4cb587 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go @@ -22,7 +22,11 @@ func TestForkChoice_ShouldOverrideFCU(t *testing.T) { st, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, 0, 0) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, st, root)) - f.ProcessAttestation(ctx, []uint64{0, 1, 2}, root, 0) + attesters := make([]uint64, f.numActiveValidators-64) + for i := range attesters { + attesters[i] = uint64(i + 64) + } + f.ProcessAttestation(ctx, attesters, root, 0) driftGenesisTime(f, 2, orphanLateBlockFirstThreshold+1) st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 0, 0) @@ -101,7 +105,11 @@ func TestForkChoice_GetProposerHead(t *testing.T) { st, root, err := prepareForkchoiceState(ctx, 1, parentRoot, [32]byte{}, [32]byte{'A'}, 0, 0) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, st, root)) - f.ProcessAttestation(ctx, []uint64{0, 1, 2}, root, 0) + attesters := make([]uint64, f.numActiveValidators-64) + for i := range attesters { + attesters[i] = uint64(i + 64) + } + f.ProcessAttestation(ctx, attesters, root, 0) driftGenesisTime(f, 3, 1) childRoot := [32]byte{'b'} diff --git a/beacon-chain/rpc/eth/beacon/config_test.go b/beacon-chain/rpc/eth/beacon/config_test.go index 75b616d04609..f9851f56b121 100644 --- a/beacon-chain/rpc/eth/beacon/config_test.go +++ b/beacon-chain/rpc/eth/beacon/config_test.go @@ -136,7 +136,7 @@ func TestGetSpec(t *testing.T) { resp, err := server.GetSpec(context.Background(), &emptypb.Empty{}) require.NoError(t, err) - assert.Equal(t, 105, len(resp.Data)) + assert.Equal(t, 106, len(resp.Data)) for k, v := range resp.Data { switch k { case "CONFIG_NAME": @@ -361,6 +361,8 @@ func TestGetSpec(t *testing.T) { assert.Equal(t, "2", v) case "REORG_WEIGHT_THRESHOLD": assert.Equal(t, "20", v) + case "REORG_PARENT_WEIGHT_THRESHOLD": + assert.Equal(t, "160", v) case "SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY": default: t.Errorf("Incorrect key: %s", k) diff --git a/config/params/config.go b/config/params/config.go index f0b2295f3ee8..7743d0ffbb96 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -69,6 +69,7 @@ type BeaconChainConfig struct { // Fork choice algorithm constants. ProposerScoreBoost uint64 `yaml:"PROPOSER_SCORE_BOOST" spec:"true"` // ProposerScoreBoost defines a value that is a % of the committee weight for fork-choice boosting. ReorgWeightThreshold uint64 `yaml:"REORG_WEIGHT_THRESHOLD" spec:"true"` // ReorgWeightThreshold defines a value that is a % of the committee weight to consider a block weak and subject to being orphaned. + ReorgParentWeightThreshold uint64 `yaml:"REORG_PARENT_WEIGHT_THRESHOLD" spec:"true"` // ReorgParentWeightThreshold defines a value that is a % of the committee weight to consider a parent block strong and subject its child to being orphaned. ReorgMaxEpochsSinceFinalization primitives.Epoch `yaml:"REORG_MAX_EPOCHS_SINCE_FINALIZATION" spec:"true"` // This defines a limit to consider safe to orphan a block if the network is finalizing IntervalsPerSlot uint64 `yaml:"INTERVALS_PER_SLOT" spec:"true"` // IntervalsPerSlot defines the number of fork choice intervals in a slot defined in the fork choice spec. diff --git a/config/params/mainnet_config.go b/config/params/mainnet_config.go index 6a68423ce5db..5b856f3851eb 100644 --- a/config/params/mainnet_config.go +++ b/config/params/mainnet_config.go @@ -115,6 +115,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{ // Fork choice algorithm constants. ProposerScoreBoost: 40, ReorgWeightThreshold: 20, + ReorgParentWeightThreshold: 160, ReorgMaxEpochsSinceFinalization: 2, IntervalsPerSlot: 3,