Skip to content

Commit d770bee

Browse files
Merge branch 'development' into eclesio/solve-block-announcement
2 parents 3f44d45 + c331361 commit d770bee

File tree

13 files changed

+392
-107
lines changed

13 files changed

+392
-107
lines changed

dot/state/block.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,37 @@ func (bs *BlockState) GetHashByNumber(num uint) (common.Hash, error) {
250250
return common.NewHash(bh), nil
251251
}
252252

253+
// GetBlockHashesBySlot gets all block hashes that were produced in the given slot.
254+
func (bs *BlockState) GetBlockHashesBySlot(slotNum uint64) ([]common.Hash, error) {
255+
highestFinalisedHash, err := bs.GetHighestFinalisedHash()
256+
if err != nil {
257+
return nil, fmt.Errorf("failed to get highest finalised hash: %w", err)
258+
}
259+
260+
descendants, err := bs.bt.GetAllDescendants(highestFinalisedHash)
261+
if err != nil {
262+
return nil, fmt.Errorf("failed to get descendants: %w", err)
263+
}
264+
265+
blocksWithGivenSlot := []common.Hash{}
266+
267+
for _, desc := range descendants {
268+
descSlot, err := bs.GetSlotForBlock(desc)
269+
if errors.Is(err, types.ErrGenesisHeader) {
270+
continue
271+
}
272+
if err != nil {
273+
return nil, fmt.Errorf("could not get slot for block: %w", err)
274+
}
275+
276+
if descSlot == slotNum {
277+
blocksWithGivenSlot = append(blocksWithGivenSlot, desc)
278+
}
279+
}
280+
281+
return blocksWithGivenSlot, nil
282+
}
283+
253284
// GetHeaderByNumber returns the block header on our best chain with the given number
254285
func (bs *BlockState) GetHeaderByNumber(num uint) (*types.Header, error) {
255286
hash, err := bs.GetHashByNumber(num)
@@ -505,7 +536,7 @@ func (bs *BlockState) BestBlock() (*types.Block, error) {
505536
func (bs *BlockState) GetSlotForBlock(hash common.Hash) (uint64, error) {
506537
header, err := bs.GetHeader(hash)
507538
if err != nil {
508-
return 0, err
539+
return 0, fmt.Errorf("getting header for hash %s: %w", hash, err)
509540
}
510541

511542
return types.GetSlotFromHeader(header)

dot/state/block_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,62 @@ func TestGetSlotForBlock(t *testing.T) {
195195
require.Equal(t, expectedSlot, res)
196196
}
197197

198+
func TestGetBlockHashesBySlot(t *testing.T) {
199+
t.Parallel()
200+
201+
// create two block in the same slot and test if GetBlockHashesBySlot gets us
202+
// both the blocks
203+
bs := newTestBlockState(t, newTriesEmpty())
204+
slot := uint64(77)
205+
206+
babeHeader := types.NewBabeDigest()
207+
err := babeHeader.Set(*types.NewBabePrimaryPreDigest(0, slot, [32]byte{}, [64]byte{}))
208+
require.NoError(t, err)
209+
data, err := scale.Marshal(babeHeader)
210+
require.NoError(t, err)
211+
preDigest := types.NewBABEPreRuntimeDigest(data)
212+
213+
digest := types.NewDigest()
214+
err = digest.Add(*preDigest)
215+
require.NoError(t, err)
216+
block := &types.Block{
217+
Header: types.Header{
218+
ParentHash: testGenesisHeader.Hash(),
219+
Number: 1,
220+
Digest: digest,
221+
},
222+
Body: types.Body{},
223+
}
224+
225+
err = bs.AddBlock(block)
226+
require.NoError(t, err)
227+
228+
babeHeader2 := types.NewBabeDigest()
229+
err = babeHeader2.Set(*types.NewBabePrimaryPreDigest(1, slot, [32]byte{}, [64]byte{}))
230+
require.NoError(t, err)
231+
data2, err := scale.Marshal(babeHeader2)
232+
require.NoError(t, err)
233+
preDigest2 := types.NewBABEPreRuntimeDigest(data2)
234+
235+
digest2 := types.NewDigest()
236+
err = digest2.Add(*preDigest2)
237+
require.NoError(t, err)
238+
block2 := &types.Block{
239+
Header: types.Header{
240+
ParentHash: testGenesisHeader.Hash(),
241+
Number: 1,
242+
Digest: digest2,
243+
},
244+
Body: types.Body{},
245+
}
246+
err = bs.AddBlock(block2)
247+
require.NoError(t, err)
248+
249+
blocks, err := bs.GetBlockHashesBySlot(slot)
250+
require.NoError(t, err)
251+
require.ElementsMatch(t, blocks, []common.Hash{block.Header.Hash(), block2.Header.Hash()})
252+
}
253+
198254
func TestIsBlockOnCurrentChain(t *testing.T) {
199255
bs := newTestBlockState(t, newTriesEmpty())
200256
currChain, branchChains := AddBlocksToState(t, bs, 3, false)

dot/types/babe.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828

2929
var (
3030
ErrChainHeadMissingDigest = errors.New("chain head missing digest")
31+
ErrGenesisHeader = errors.New("genesis header doesn't have a slot")
3132
)
3233

3334
// BabeConfiguration contains the genesis data for BABE
@@ -108,6 +109,10 @@ type ConfigData struct {
108109

109110
// GetSlotFromHeader returns the BABE slot from the given header
110111
func GetSlotFromHeader(header *Header) (uint64, error) {
112+
if header.Number == 0 {
113+
return 0, ErrGenesisHeader
114+
}
115+
111116
if len(header.Digest.Types) == 0 {
112117
return 0, ErrChainHeadMissingDigest
113118
}

lib/babe/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ var (
7272
errNoBABEAuthorityKeyProvided = errors.New("cannot create BABE service as authority; no keypair provided")
7373
errLastDigestItemNotSeal = errors.New("last digest item is not seal")
7474
errLaggingSlot = errors.New("current slot is smaller than slot of best block")
75+
errNoDigest = errors.New("no digest provided")
7576

7677
other Other
7778
invalidCustom InvalidCustom

lib/babe/mock_state_test.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/babe/state.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type BlockState interface {
2828
GetHeader(common.Hash) (*types.Header, error)
2929
GetBlockByNumber(blockNumber uint) (*types.Block, error)
3030
GetBlockByHash(common.Hash) (*types.Block, error)
31+
GetBlockHashesBySlot(slot uint64) (blockHashes []common.Hash, err error)
3132
GetArrivalTime(common.Hash) (time.Time, error)
3233
GenesisHash() common.Hash
3334
GetSlotForBlock(common.Hash) (uint64, error)

lib/babe/verify.go

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error {
138138
if !block1IsFinal {
139139
firstSlot, err := types.GetSlotFromHeader(header)
140140
if err != nil {
141-
return fmt.Errorf("failed to get slot from block 1: %w", err)
141+
return fmt.Errorf("failed to get slot from header of block 1: %w", err)
142142
}
143143

144144
logger.Debugf("syncing block 1, setting first slot as %d", firstSlot)
@@ -327,62 +327,58 @@ func (b *verifier) verifyAuthorshipRight(header *types.Header) error {
327327
return ErrBadSignature
328328
}
329329

330-
// check if the producer has equivocated, ie. have they produced a conflicting block?
331-
// hashes is hashes of all blocks with same block number as header.Number
332-
hashes := b.blockState.GetAllBlocksAtDepth(header.ParentHash)
330+
equivocated, err := b.verifyBlockEquivocation(header)
331+
if err != nil {
332+
return fmt.Errorf("could not verify block equivocation: %w", err)
333+
}
334+
if equivocated {
335+
return fmt.Errorf("%w for block header %s", ErrProducerEquivocated, header.Hash())
336+
}
333337

334-
for _, currentHash := range hashes {
335-
currentHeader, err := b.blockState.GetHeader(currentHash)
336-
if err != nil {
337-
return fmt.Errorf("failed get header %s", err)
338-
}
338+
return nil
339+
}
339340

340-
currentBlockProducerIndex, err := getAuthorityIndex(currentHeader)
341-
if err != nil {
342-
return fmt.Errorf("failed to get authority index %s", err)
343-
}
341+
// verifyBlockEquivocation checks if the given block's author has occupied the corresponding slot more than once.
342+
// It returns true if the block was equivocated.
343+
func (b *verifier) verifyBlockEquivocation(header *types.Header) (bool, error) {
344+
author, err := getAuthorityIndex(header)
345+
if err != nil {
346+
return false, fmt.Errorf("failed to get authority index: %w", err)
347+
}
344348

345-
if len(currentHeader.Digest.Types) == 0 {
346-
return fmt.Errorf("current header missing digest")
349+
currentHash := header.Hash()
350+
slot, err := types.GetSlotFromHeader(header)
351+
if err != nil {
352+
return false, fmt.Errorf("failed to get slot from header of block %s: %w", currentHash, err)
353+
}
354+
355+
blockHashesInSlot, err := b.blockState.GetBlockHashesBySlot(slot)
356+
if err != nil {
357+
return false, fmt.Errorf("failed to get blocks produced in slot: %w", err)
358+
}
359+
360+
for _, blockHashInSlot := range blockHashesInSlot {
361+
if blockHashInSlot.Equal(currentHash) {
362+
continue
347363
}
348364

349-
currentPreDigestItemValue, err := currentHeader.Digest.Types[0].Value()
365+
existingHeader, err := b.blockState.GetHeader(blockHashInSlot)
350366
if err != nil {
351-
return fmt.Errorf("getting current header first digest type value: %w", err)
352-
}
353-
currentPreDigest, ok := currentPreDigestItemValue.(types.PreRuntimeDigest)
354-
if !ok {
355-
return fmt.Errorf("%w: got %T", types.ErrNoFirstPreDigest, currentPreDigestItemValue)
367+
return false, fmt.Errorf("failed to get header for block: %w", err)
356368
}
357369

358-
currentBabePreDigest, err := b.verifyPreRuntimeDigest(&currentPreDigest)
370+
authorOfExistingHeader, err := getAuthorityIndex(existingHeader)
359371
if err != nil {
360-
return fmt.Errorf("failed to verify pre-runtime digest: %w", err)
372+
return false, fmt.Errorf("failed to get authority index for block %s: %w", blockHashInSlot, err)
361373
}
362-
363-
_, isCurrentBlockProducerPrimary := currentBabePreDigest.(types.BabePrimaryPreDigest)
364-
365-
var isExistingBlockProducerPrimary bool
366-
var existingBlockProducerIndex uint32
367-
switch d := babePreDigest.(type) {
368-
case types.BabePrimaryPreDigest:
369-
existingBlockProducerIndex = d.AuthorityIndex
370-
isExistingBlockProducerPrimary = true
371-
case types.BabeSecondaryVRFPreDigest:
372-
existingBlockProducerIndex = d.AuthorityIndex
373-
case types.BabeSecondaryPlainPreDigest:
374-
existingBlockProducerIndex = d.AuthorityIndex
374+
if authorOfExistingHeader != author {
375+
continue
375376
}
376377

377-
// same authority won't produce two different blocks at the same block number as primary block producer
378-
if currentBlockProducerIndex == existingBlockProducerIndex &&
379-
!currentHash.Equal(header.Hash()) &&
380-
isCurrentBlockProducerPrimary == isExistingBlockProducerPrimary {
381-
return ErrProducerEquivocated
382-
}
378+
return true, nil
383379
}
384380

385-
return nil
381+
return false, nil
386382
}
387383

388384
func (b *verifier) verifyPreRuntimeDigest(digest *types.PreRuntimeDigest) (scale.VaryingDataTypeValue, error) {
@@ -493,7 +489,7 @@ func (b *verifier) verifyPrimarySlotWinner(authorityIndex uint32,
493489

494490
func getAuthorityIndex(header *types.Header) (uint32, error) {
495491
if len(header.Digest.Types) == 0 {
496-
return 0, fmt.Errorf("no digest provided")
492+
return 0, fmt.Errorf("for block hash %s: %w", header.Hash(), errNoDigest)
497493
}
498494

499495
digestValue, err := header.Digest.Types[0].Value()

lib/babe/verify_integration_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ func TestVerifyAuthorshipRight_Equivocation(t *testing.T) {
466466
require.NoError(t, err)
467467

468468
err = verifier.verifyAuthorshipRight(&block2.Header)
469-
require.Equal(t, ErrProducerEquivocated, err)
469+
require.ErrorIs(t, err, ErrProducerEquivocated)
470+
require.EqualError(t, err, fmt.Sprintf("%s for block header %s", ErrProducerEquivocated, block2.Header.Hash()))
470471
}
471472

472473
func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) {

0 commit comments

Comments
 (0)