Mark block as invalid in gossip if it fails signature check (#15847)

* Mark block as invalid in gossip if it fails signature check

* Add tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Potuz
2025-10-13 17:29:27 -03:00
committed by GitHub
parent 5f8eb69201
commit e463bcd1e1
6 changed files with 120 additions and 4 deletions

View File

@@ -6,3 +6,4 @@ var errNilSignedWithdrawalMessage = errors.New("nil SignedBLSToExecutionChange m
var errNilWithdrawalMessage = errors.New("nil BLSToExecutionChange message") var errNilWithdrawalMessage = errors.New("nil BLSToExecutionChange message")
var errInvalidBLSPrefix = errors.New("withdrawal credential prefix is not a BLS prefix") var errInvalidBLSPrefix = errors.New("withdrawal credential prefix is not a BLS prefix")
var errInvalidWithdrawalCredentials = errors.New("withdrawal credentials do not match") var errInvalidWithdrawalCredentials = errors.New("withdrawal credentials do not match")
var ErrInvalidSignature = errors.New("invalid signature")

View File

@@ -114,9 +114,12 @@ func VerifyBlockSignatureUsingCurrentFork(beaconState state.ReadOnlyBeaconState,
} }
proposerPubKey := proposer.PublicKey proposerPubKey := proposer.PublicKey
sig := blk.Signature() sig := blk.Signature()
return signing.VerifyBlockSigningRoot(proposerPubKey, sig[:], domain, func() ([32]byte, error) { if err := signing.VerifyBlockSigningRoot(proposerPubKey, sig[:], domain, func() ([32]byte, error) {
return blkRoot, nil return blkRoot, nil
}) }); err != nil {
return ErrInvalidSignature
}
return nil
} }
// BlockSignatureBatch retrieves the block signature batch from the provided block and its corresponding state. // BlockSignatureBatch retrieves the block signature batch from the provided block and its corresponding state.

View File

@@ -89,3 +89,36 @@ func TestVerifyBlockSignatureUsingCurrentFork(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.NoError(t, blocks.VerifyBlockSignatureUsingCurrentFork(bState, wsb, blkRoot)) assert.NoError(t, blocks.VerifyBlockSignatureUsingCurrentFork(bState, wsb, blkRoot))
} }
func TestVerifyBlockSignatureUsingCurrentFork_InvalidSignature(t *testing.T) {
params.SetupTestConfigCleanup(t)
bCfg := params.BeaconConfig()
bCfg.AltairForkEpoch = 100
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.AltairForkVersion)] = 100
params.OverrideBeaconConfig(bCfg)
bState, keys := util.DeterministicGenesisState(t, 100)
altairBlk := util.NewBeaconBlockAltair()
altairBlk.Block.ProposerIndex = 0
altairBlk.Block.Slot = params.BeaconConfig().SlotsPerEpoch * 100
blkRoot, err := altairBlk.Block.HashTreeRoot()
assert.NoError(t, err)
// Sign with wrong key (proposer index 0, but using key 1)
fData := &ethpb.Fork{
Epoch: 100,
CurrentVersion: params.BeaconConfig().AltairForkVersion,
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
}
domain, err := signing.Domain(fData, 100, params.BeaconConfig().DomainBeaconProposer, bState.GenesisValidatorsRoot())
assert.NoError(t, err)
rt, err := signing.ComputeSigningRoot(altairBlk.Block, domain)
assert.NoError(t, err)
wrongSig := keys[1].Sign(rt[:]).Marshal()
altairBlk.Signature = wrongSig
wsb, err := consensusblocks.NewSignedBeaconBlock(altairBlk)
require.NoError(t, err)
err = blocks.VerifyBlockSignatureUsingCurrentFork(bState, wsb, blkRoot)
require.ErrorIs(t, err, blocks.ErrInvalidSignature, "Expected ErrInvalidSignature for invalid signature")
}

View File

@@ -294,6 +294,9 @@ func (s *Service) validatePhase0Block(ctx context.Context, blk interfaces.ReadOn
} }
if err := blocks.VerifyBlockSignatureUsingCurrentFork(parentState, blk, blockRoot); err != nil { if err := blocks.VerifyBlockSignatureUsingCurrentFork(parentState, blk, blockRoot); err != nil {
if errors.Is(err, blocks.ErrInvalidSignature) {
s.setBadBlock(ctx, blockRoot)
}
return nil, err return nil, err
} }
// In the event the block is more than an epoch ahead from its // In the event the block is more than an epoch ahead from its

View File

@@ -103,11 +103,84 @@ func TestValidateBeaconBlockPubSub_InvalidSignature(t *testing.T) {
}, },
} }
res, err := r.validateBeaconBlockPubSub(ctx, "", m) res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorIs(t, err, signing.ErrSigFailedToVerify) require.ErrorContains(t, "invalid signature", err)
result := res == pubsub.ValidationReject result := res == pubsub.ValidationReject
assert.Equal(t, true, result) assert.Equal(t, true, result)
} }
func TestValidateBeaconBlockPubSub_InvalidSignature_MarksBlockAsBad(t *testing.T) {
db := dbtest.SetupDB(t)
p := p2ptest.NewTestP2P(t)
ctx := t.Context()
beaconState, privKeys := util.DeterministicGenesisState(t, 100)
parentBlock := util.NewBeaconBlock()
util.SaveBlock(t, ctx, db, parentBlock)
bRoot, err := parentBlock.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveState(ctx, beaconState, bRoot))
require.NoError(t, db.SaveStateSummary(ctx, &ethpb.StateSummary{Root: bRoot[:]}))
copied := beaconState.Copy()
require.NoError(t, copied.SetSlot(1))
proposerIdx, err := helpers.BeaconProposerIndex(ctx, copied)
require.NoError(t, err)
msg := util.NewBeaconBlock()
msg.Block.ParentRoot = bRoot[:]
msg.Block.Slot = 1
msg.Block.ProposerIndex = proposerIdx
badPrivKeyIdx := proposerIdx + 1 // We generate a valid signature from a wrong private key which fails to verify
msg.Signature, err = signing.ComputeDomainAndSign(beaconState, 0, msg.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[badPrivKeyIdx])
require.NoError(t, err)
stateGen := stategen.New(db, doublylinkedtree.New())
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0),
FinalizedCheckPoint: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
},
DB: db,
}
r := &Service{
cfg: &config{
beaconDB: db,
p2p: p,
initialSync: &mockSync.Sync{IsSyncing: false},
chain: chainService,
clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot),
blockNotifier: chainService.BlockNotifier(),
stateGen: stateGen,
},
seenBlockCache: lruwrpr.New(10),
badBlockCache: lruwrpr.New(10),
}
blockRoot, err := msg.Block.HashTreeRoot()
require.NoError(t, err)
// Verify block is not marked as bad initially
assert.Equal(t, false, r.hasBadBlock(blockRoot), "block should not be marked as bad initially")
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
Topic: &topic,
},
}
res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorContains(t, "invalid signature", err)
result := res == pubsub.ValidationReject
assert.Equal(t, true, result)
// Verify block is now marked as bad after invalid signature
assert.Equal(t, true, r.hasBadBlock(blockRoot), "block should be marked as bad after invalid signature")
}
func TestValidateBeaconBlockPubSub_BlockAlreadyPresentInDB(t *testing.T) { func TestValidateBeaconBlockPubSub_BlockAlreadyPresentInDB(t *testing.T) {
db := dbtest.SetupDB(t) db := dbtest.SetupDB(t)
ctx := t.Context() ctx := t.Context()
@@ -976,7 +1049,7 @@ func TestValidateBeaconBlockPubSub_InvalidParentBlock(t *testing.T) {
}, },
} }
res, err := r.validateBeaconBlockPubSub(ctx, "", m) res, err := r.validateBeaconBlockPubSub(ctx, "", m)
require.ErrorContains(t, "could not unmarshal bytes into signature", err) require.ErrorContains(t, "invalid signature", err)
assert.Equal(t, res, pubsub.ValidationReject, "block with invalid signature should be rejected") assert.Equal(t, res, pubsub.ValidationReject, "block with invalid signature should be rejected")
require.NoError(t, copied.SetSlot(2)) require.NoError(t, copied.SetSlot(2))

View File

@@ -0,0 +1,3 @@
### Fixed
- Mark the block as invalid if it has an invalid signature.