Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't throw ErrProducerEquivocated if authored blocks have different parents #2709

Merged
merged 52 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
992f5da
Is it block producer equivocation if different parents
kishansagathiya Jul 28, 2022
8a3d238
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Aug 12, 2022
89088bf
re-wrote block equivocation logic
kishansagathiya Aug 12, 2022
10aa5b7
implement GetBlocksBySlot to mock block state
kishansagathiya Aug 16, 2022
626ce7b
wrapping some errors
kishansagathiya Aug 16, 2022
6cf664c
temp
kishansagathiya Sep 9, 2022
1437f11
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Oct 12, 2022
f248663
fix tests
kishansagathiya Oct 13, 2022
5f20448
more fixes
kishansagathiya Oct 14, 2022
208650a
temp
kishansagathiya Oct 18, 2022
591247d
Update dot/state/block.go
kishansagathiya Oct 19, 2022
2f5fc93
Update lib/babe/verify.go
kishansagathiya Oct 19, 2022
9025f7d
Update lib/babe/verify.go
kishansagathiya Oct 19, 2022
450262a
Update lib/blocktree/blocktree.go
kishansagathiya Oct 19, 2022
3a3ce0d
Merge branch 'kishan/fix/err-equivocatory-blocks' of github.com:Chain…
kishansagathiya Oct 19, 2022
6ceeffe
added tests and addressed some review comments
kishansagathiya Oct 20, 2022
8c7b81b
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Oct 20, 2022
5259e5d
temp
kishansagathiya Oct 25, 2022
305fd28
temp
kishansagathiya Oct 25, 2022
d1b3047
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Oct 25, 2022
e5904f6
Update lib/babe/verify.go
kishansagathiya Oct 26, 2022
60c8377
Update lib/babe/verify.go
kishansagathiya Oct 26, 2022
db73ab5
addressed reviews
kishansagathiya Oct 26, 2022
1bf011f
Merge branch 'kishan/fix/err-equivocatory-blocks' of github.com:Chain…
kishansagathiya Oct 26, 2022
e5138f3
addressed some comment
kishansagathiya Oct 26, 2022
f62ee9f
fixing lint
kishansagathiya Oct 26, 2022
6b0e1f3
fixing lint
kishansagathiya Oct 26, 2022
33a0341
small change
kishansagathiya Oct 28, 2022
239ead0
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Oct 28, 2022
63e541f
Update lib/blocktree/blocktree_test.go
kishansagathiya Oct 29, 2022
3b72b7d
Update lib/blocktree/blocktree.go
kishansagathiya Oct 29, 2022
3d5569a
Update dot/state/block.go
kishansagathiya Oct 29, 2022
5f1679c
Update dot/state/block_test.go
kishansagathiya Oct 29, 2022
f25b702
Update lib/babe/verify_test.go
kishansagathiya Oct 29, 2022
3906343
Update lib/babe/verify_test.go
kishansagathiya Oct 29, 2022
9e798df
Update lib/babe/verify_test.go
kishansagathiya Oct 29, 2022
de936cd
Update lib/babe/verify_test.go
kishansagathiya Oct 29, 2022
0995416
Update lib/babe/verify_test.go
kishansagathiya Oct 29, 2022
a99e5dd
addressed some more reviews
kishansagathiya Oct 29, 2022
be53db3
Merge branch 'kishan/fix/err-equivocatory-blocks' of github.com:Chain…
kishansagathiya Oct 29, 2022
004551e
test fix
kishansagathiya Oct 29, 2022
d6dd615
Update lib/babe/state.go
kishansagathiya Oct 29, 2022
eb60722
one more test fix
kishansagathiya Oct 29, 2022
c35f97d
Merge branch 'kishan/fix/err-equivocatory-blocks' of github.com:Chain…
kishansagathiya Oct 29, 2022
ee8d7ff
test for verifyBlockEquivocation
kishansagathiya Oct 31, 2022
ad630b9
fix lint
kishansagathiya Oct 31, 2022
03e2707
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Nov 1, 2022
fb70ad2
Merge branch 'development' into kishan/fix/err-equivocatory-blocks
kishansagathiya Nov 1, 2022
9766245
Update dot/state/block.go
kishansagathiya Nov 1, 2022
95531c9
Update lib/babe/verify.go
kishansagathiya Nov 1, 2022
2dcb8cd
addressed qdm12's comments
kishansagathiya Nov 2, 2022
40d0c1f
Merge branch 'kishan/fix/err-equivocatory-blocks' of github.com:Chain…
kishansagathiya Nov 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion dot/state/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,37 @@ func (bs *BlockState) GetHashByNumber(num uint) (common.Hash, error) {
return common.NewHash(bh), nil
}

// GetBlockHashesBySlot gets all block hashes that were produced in the given slot.
func (bs *BlockState) GetBlockHashesBySlot(slotNum uint64) ([]common.Hash, error) {
highestFinalisedHash, err := bs.GetHighestFinalisedHash()
if err != nil {
return nil, fmt.Errorf("failed to get highest finalised hash: %w", err)
}

descendants, err := bs.bt.GetAllDescendants(highestFinalisedHash)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("failed to get descendants: %w", err)
}

blocksWithGivenSlot := []common.Hash{}
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved

for _, desc := range descendants {
descSlot, err := bs.GetSlotForBlock(desc)
if errors.Is(err, types.ErrGenesisHeader) {
continue
}
if err != nil {
return nil, fmt.Errorf("could not get slot for block: %w", err)
}

if descSlot == slotNum {
blocksWithGivenSlot = append(blocksWithGivenSlot, desc)
}
}

return blocksWithGivenSlot, nil
}

// GetHeaderByNumber returns the block header on our best chain with the given number
func (bs *BlockState) GetHeaderByNumber(num uint) (*types.Header, error) {
hash, err := bs.GetHashByNumber(num)
Expand Down Expand Up @@ -505,7 +536,7 @@ func (bs *BlockState) BestBlock() (*types.Block, error) {
func (bs *BlockState) GetSlotForBlock(hash common.Hash) (uint64, error) {
header, err := bs.GetHeader(hash)
if err != nil {
return 0, err
return 0, fmt.Errorf("getting header for hash %s: %w", hash, err)
}

return types.GetSlotFromHeader(header)
Expand Down
56 changes: 56 additions & 0 deletions dot/state/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,62 @@ func TestGetSlotForBlock(t *testing.T) {
require.Equal(t, expectedSlot, res)
}

func TestGetBlockHashesBySlot(t *testing.T) {
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()

// create two block in the same slot and test if GetBlockHashesBySlot gets us
// both the blocks
bs := newTestBlockState(t, newTriesEmpty())
slot := uint64(77)

babeHeader := types.NewBabeDigest()
err := babeHeader.Set(*types.NewBabePrimaryPreDigest(0, slot, [32]byte{}, [64]byte{}))
require.NoError(t, err)
data, err := scale.Marshal(babeHeader)
require.NoError(t, err)
preDigest := types.NewBABEPreRuntimeDigest(data)

digest := types.NewDigest()
err = digest.Add(*preDigest)
require.NoError(t, err)
block := &types.Block{
Header: types.Header{
ParentHash: testGenesisHeader.Hash(),
Number: 1,
Digest: digest,
},
Body: types.Body{},
}

err = bs.AddBlock(block)
require.NoError(t, err)

babeHeader2 := types.NewBabeDigest()
err = babeHeader2.Set(*types.NewBabePrimaryPreDigest(1, slot, [32]byte{}, [64]byte{}))
require.NoError(t, err)
data2, err := scale.Marshal(babeHeader2)
require.NoError(t, err)
preDigest2 := types.NewBABEPreRuntimeDigest(data2)

digest2 := types.NewDigest()
err = digest2.Add(*preDigest2)
require.NoError(t, err)
block2 := &types.Block{
Header: types.Header{
ParentHash: testGenesisHeader.Hash(),
Number: 1,
Digest: digest2,
},
Body: types.Body{},
}
err = bs.AddBlock(block2)
require.NoError(t, err)

blocks, err := bs.GetBlockHashesBySlot(slot)
require.NoError(t, err)
require.ElementsMatch(t, blocks, []common.Hash{block.Header.Hash(), block2.Header.Hash()})
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}

func TestIsBlockOnCurrentChain(t *testing.T) {
bs := newTestBlockState(t, newTriesEmpty())
currChain, branchChains := AddBlocksToState(t, bs, 3, false)
Expand Down
5 changes: 5 additions & 0 deletions dot/types/babe.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (

var (
ErrChainHeadMissingDigest = errors.New("chain head missing digest")
ErrGenesisHeader = errors.New("genesis header doesn't have a slot")
)

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

// GetSlotFromHeader returns the BABE slot from the given header
func GetSlotFromHeader(header *Header) (uint64, error) {
if header.Number == 0 {
return 0, ErrGenesisHeader
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
}

if len(header.Digest.Types) == 0 {
return 0, ErrChainHeadMissingDigest
}
Expand Down
1 change: 1 addition & 0 deletions lib/babe/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ var (
errNoBABEAuthorityKeyProvided = errors.New("cannot create BABE service as authority; no keypair provided")
errLastDigestItemNotSeal = errors.New("last digest item is not seal")
errLaggingSlot = errors.New("current slot is smaller than slot of best block")
errNoDigest = errors.New("no digest provided")
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved

other Other
invalidCustom InvalidCustom
Expand Down
15 changes: 15 additions & 0 deletions lib/babe/mock_state_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/babe/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type BlockState interface {
GetHeader(common.Hash) (*types.Header, error)
GetBlockByNumber(blockNumber uint) (*types.Block, error)
GetBlockByHash(common.Hash) (*types.Block, error)
GetBlockHashesBySlot(slot uint64) (blockHashes []common.Hash, err error)
GetArrivalTime(common.Hash) (time.Time, error)
GenesisHash() common.Hash
GetSlotForBlock(common.Hash) (uint64, error)
Expand Down
84 changes: 40 additions & 44 deletions lib/babe/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (v *VerificationManager) VerifyBlock(header *types.Header) error {
if !block1IsFinal {
firstSlot, err := types.GetSlotFromHeader(header)
if err != nil {
return fmt.Errorf("failed to get slot from block 1: %w", err)
return fmt.Errorf("failed to get slot from header of block 1: %w", err)
}

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

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

for _, currentHash := range hashes {
currentHeader, err := b.blockState.GetHeader(currentHash)
if err != nil {
return fmt.Errorf("failed get header %s", err)
}
return nil
}

currentBlockProducerIndex, err := getAuthorityIndex(currentHeader)
if err != nil {
return fmt.Errorf("failed to get authority index %s", err)
}
// verifyBlockEquivocation checks if the given block's author has occupied the corresponding slot more than once.
// It returns true if the block was equivocated.
func (b *verifier) verifyBlockEquivocation(header *types.Header) (bool, error) {
kishansagathiya marked this conversation as resolved.
Show resolved Hide resolved
author, err := getAuthorityIndex(header)
if err != nil {
return false, fmt.Errorf("failed to get authority index: %w", err)
}

if len(currentHeader.Digest.Types) == 0 {
return fmt.Errorf("current header missing digest")
currentHash := header.Hash()
slot, err := types.GetSlotFromHeader(header)
if err != nil {
return false, fmt.Errorf("failed to get slot from header of block %s: %w", currentHash, err)
}

blockHashesInSlot, err := b.blockState.GetBlockHashesBySlot(slot)
if err != nil {
return false, fmt.Errorf("failed to get blocks produced in slot: %w", err)
}

for _, blockHashInSlot := range blockHashesInSlot {
if blockHashInSlot.Equal(currentHash) {
continue
}

currentPreDigestItemValue, err := currentHeader.Digest.Types[0].Value()
existingHeader, err := b.blockState.GetHeader(blockHashInSlot)
if err != nil {
return fmt.Errorf("getting current header first digest type value: %w", err)
}
currentPreDigest, ok := currentPreDigestItemValue.(types.PreRuntimeDigest)
if !ok {
return fmt.Errorf("%w: got %T", types.ErrNoFirstPreDigest, currentPreDigestItemValue)
return false, fmt.Errorf("failed to get header for block: %w", err)
}

currentBabePreDigest, err := b.verifyPreRuntimeDigest(&currentPreDigest)
authorOfExistingHeader, err := getAuthorityIndex(existingHeader)
if err != nil {
return fmt.Errorf("failed to verify pre-runtime digest: %w", err)
return false, fmt.Errorf("failed to get authority index for block %s: %w", blockHashInSlot, err)
}

_, isCurrentBlockProducerPrimary := currentBabePreDigest.(types.BabePrimaryPreDigest)

var isExistingBlockProducerPrimary bool
var existingBlockProducerIndex uint32
switch d := babePreDigest.(type) {
case types.BabePrimaryPreDigest:
existingBlockProducerIndex = d.AuthorityIndex
isExistingBlockProducerPrimary = true
case types.BabeSecondaryVRFPreDigest:
existingBlockProducerIndex = d.AuthorityIndex
case types.BabeSecondaryPlainPreDigest:
existingBlockProducerIndex = d.AuthorityIndex
if authorOfExistingHeader != author {
continue
}

// same authority won't produce two different blocks at the same block number as primary block producer
if currentBlockProducerIndex == existingBlockProducerIndex &&
!currentHash.Equal(header.Hash()) &&
isCurrentBlockProducerPrimary == isExistingBlockProducerPrimary {
return ErrProducerEquivocated
}
return true, nil
}

return nil
return false, nil
}

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

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

digestValue, err := header.Digest.Types[0].Value()
Expand Down
3 changes: 2 additions & 1 deletion lib/babe/verify_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,8 @@ func TestVerifyAuthorshipRight_Equivocation(t *testing.T) {
require.NoError(t, err)

err = verifier.verifyAuthorshipRight(&block2.Header)
require.Equal(t, ErrProducerEquivocated, err)
require.ErrorIs(t, err, ErrProducerEquivocated)
require.EqualError(t, err, fmt.Sprintf("%s for block header %s", ErrProducerEquivocated, block2.Header.Hash()))
}

func TestVerifyForkBlocksWithRespectiveEpochData(t *testing.T) {
Expand Down
Loading