diff --git a/core/types/block.go b/core/types/block.go index 03bcc6c703..c47c564dd0 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -501,18 +501,18 @@ func (b *Block) GetTxDependency() [][]uint64 { } // GetValidatorBytes extracts validator bytes from the header's Extra field. +// Returns nil if len(h.Extra) is shorter than ExtraVanityLength+ExtraSealLength. // If you need multiple fields from BlockExtraData, prefer DecodeBlockExtraData // to avoid redundant RLP decodes. func (h *Header) GetValidatorBytes(chainConfig *params.ChainConfig) []byte { - if !chainConfig.IsCancun(h.Number) { - return h.Extra[ExtraVanityLength : len(h.Extra)-ExtraSealLength] - } - if len(h.Extra) < ExtraVanityLength+ExtraSealLength { - log.Error("length of extra less is than vanity and seal") return nil } + if !chainConfig.IsCancun(h.Number) { + return h.Extra[ExtraVanityLength : len(h.Extra)-ExtraSealLength] + } + var blockExtraData BlockExtraData if err := rlp.DecodeBytes(h.Extra[ExtraVanityLength:len(h.Extra)-ExtraSealLength], &blockExtraData); err != nil { log.Debug("error while decoding block extra data", "err", err) diff --git a/core/types/block_test.go b/core/types/block_test.go index 1703b103d1..a9100a3358 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -931,3 +931,75 @@ func TestDecodeBlockExtraData(t *testing.T) { t.Errorf("TxDependency mismatch: got %v, want %v", result.TxDependency, txDep) } } + +// TestGetValidatorBytesShortExtra is a regression test for the pre-Cancun +// branch of (*Header).GetValidatorBytes panicking with +// `runtime error: slice bounds out of range` when len(Extra) < ExtraVanityLength+ExtraSealLength. +// +// Prior to the fix the pre-Cancun branch performed +// +// return h.Extra[ExtraVanityLength : len(h.Extra)-ExtraSealLength] +// +// without any length guard. The post-Cancun branch already guarded this +// path; this test exercises both branches across a range of short Extra +// lengths plus a happy-path case. +func TestGetValidatorBytesShortExtra(t *testing.T) { + t.Parallel() + + preCancunCfg := ¶ms.ChainConfig{ + ChainID: big.NewInt(137), + } + postCancunCfg := ¶ms.ChainConfig{ + ChainID: big.NewInt(137), + CancunBlock: big.NewInt(100), + } + + cases := []struct { + name string + cfg *params.ChainConfig + blockNumber *big.Int + }{ + {"pre-cancun", preCancunCfg, big.NewInt(50)}, + {"post-cancun", postCancunCfg, big.NewInt(200)}, + } + + shortLens := []int{0, 1, 32, 64, 65, 80, 96} + + for _, c := range cases { + for _, n := range shortLens { + h := &Header{ + Number: c.blockNumber, + Extra: make([]byte, n), + } + var got []byte + func() { + defer func() { + if r := recover(); r != nil { + t.Errorf("%s len(Extra)=%d: unexpected panic: %v", c.name, n, r) + } + }() + got = h.GetValidatorBytes(c.cfg) + }() + if got != nil { + t.Errorf("%s len(Extra)=%d: expected nil, got %x (len %d)", + c.name, n, got, len(got)) + } + } + } + + // Happy path (pre-Cancun): vanity + 40-byte validator entry + seal. + // Confirms the existing slice operation is preserved for valid input. + extra := make([]byte, ExtraVanityLength+40+ExtraSealLength) + for i := 0; i < 40; i++ { + extra[ExtraVanityLength+i] = byte(i + 1) + } + h := &Header{Number: big.NewInt(50), Extra: extra} + got := h.GetValidatorBytes(preCancunCfg) + if len(got) != 40 { + t.Fatalf("happy path: expected 40 bytes, got %d", len(got)) + } + if !bytes.Equal(got, extra[ExtraVanityLength:ExtraVanityLength+40]) { + t.Errorf("happy path: validator bytes mismatch: got %x, want %x", + got, extra[ExtraVanityLength:ExtraVanityLength+40]) + } +}