From fe1e2f0b33500a19bc46902af7bfe27380f98ef3 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Tue, 12 Jul 2022 13:18:22 +0530 Subject: [PATCH] throw ErrProducerEquivocated only when both equivocatory blocks are created by primary block producer (#2596) --- dot/types/babe.go | 7 +- lib/babe/errors.go | 1 + lib/babe/verify.go | 37 +++++++-- lib/babe/verify_test.go | 165 ++++++++++++++++++++++++++++++---------- 4 files changed, 160 insertions(+), 50 deletions(-) diff --git a/dot/types/babe.go b/dot/types/babe.go index e0ebd30cf1..ab30b0f119 100644 --- a/dot/types/babe.go +++ b/dot/types/babe.go @@ -4,9 +4,12 @@ package types import ( + "errors" "fmt" ) +var ErrNoFirstPreDigest = errors.New("first digest item is not pre-digest") + // RandomnessLength is the length of the epoch randomness (32 bytes) const RandomnessLength = 32 @@ -107,7 +110,7 @@ func GetSlotFromHeader(header *Header) (uint64, error) { preDigest, ok := header.Digest.Types[0].Value().(PreRuntimeDigest) if !ok { - return 0, fmt.Errorf("first digest item is not pre-digest") + return 0, fmt.Errorf("%w: got %T", ErrNoFirstPreDigest, header.Digest.Types[0].Value()) } digest, err := DecodeBabePreDigest(preDigest.Data) @@ -140,7 +143,7 @@ func IsPrimary(header *Header) (bool, error) { preDigest, ok := header.Digest.Types[0].Value().(PreRuntimeDigest) if !ok { - return false, fmt.Errorf("first digest item is not pre-digest: type=%T", header.Digest.Types[0].Value()) + return false, fmt.Errorf("%w: got %T", ErrNoFirstPreDigest, header.Digest.Types[0].Value()) } digest, err := DecodeBabePreDigest(preDigest.Data) diff --git a/lib/babe/errors.go b/lib/babe/errors.go index b851f2c05a..3f50917477 100644 --- a/lib/babe/errors.go +++ b/lib/babe/errors.go @@ -84,6 +84,7 @@ var ( errServicePaused = errors.New("service paused") errInvalidSlotTechnique = errors.New("invalid slot claiming technique") errNoBABEAuthorityKeyProvided = errors.New("cannot create BABE service as authority; no keypair provided") + errLastDigestItemNotSeal = errors.New("last digest item is not seal") other Other invalidCustom InvalidCustom diff --git a/lib/babe/verify.go b/lib/babe/verify.go index 50b59c2211..8957dd9995 100644 --- a/lib/babe/verify.go +++ b/lib/babe/verify.go @@ -262,12 +262,12 @@ func (b *verifier) verifyAuthorshipRight(header *types.Header) error { preDigest, ok := preDigestItem.Value().(types.PreRuntimeDigest) if !ok { - return fmt.Errorf("first digest item is not pre-digest") + return fmt.Errorf("%w: got %T", types.ErrNoFirstPreDigest, preDigestItem.Value()) } seal, ok := sealItem.Value().(types.SealDigest) if !ok { - return fmt.Errorf("last digest item is not seal") + return fmt.Errorf("%w: got %T", errLastDigestItemNotSeal, sealItem.Value()) } babePreDigest, err := b.verifyPreRuntimeDigest(&preDigest) @@ -329,29 +329,50 @@ func (b *verifier) verifyAuthorshipRight(header *types.Header) error { // hashes is hashes of all blocks with same block number as header.Number hashes := b.blockState.GetAllBlocksAtDepth(header.ParentHash) - for _, hash := range hashes { - currentHeader, err := b.blockState.GetHeader(hash) + for _, currentHash := range hashes { + currentHeader, err := b.blockState.GetHeader(currentHash) if err != nil { - continue + return fmt.Errorf("failed get header %s", err) } currentBlockProducerIndex, err := getAuthorityIndex(currentHeader) if err != nil { - continue + return fmt.Errorf("failed to get authority index %s", err) } + if len(currentHeader.Digest.Types) == 0 { + return fmt.Errorf("current header missing digest") + } + + currentPreDigestItem := currentHeader.Digest.Types[0] + currentPreDigest, ok := currentPreDigestItem.Value().(types.PreRuntimeDigest) + if !ok { + return fmt.Errorf("%w: got %T", types.ErrNoFirstPreDigest, currentPreDigestItem.Value()) + } + + currentBabePreDigest, err := b.verifyPreRuntimeDigest(¤tPreDigest) + if err != nil { + return fmt.Errorf("failed to verify pre-runtime digest: %w", 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 } - // same authority won't produce two different blocks at the same block number - if currentBlockProducerIndex == existingBlockProducerIndex && hash != header.Hash() { + // 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 } } diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index d8c8432ebf..9b2adf9e8b 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -470,9 +470,6 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { ctrl := gomock.NewController(t) mockBlockState := NewMockBlockState(ctrl) mockBlockStateErr := NewMockBlockState(ctrl) - mockBlockStateEquiv1 := NewMockBlockState(ctrl) - mockBlockStateEquiv2 := NewMockBlockState(ctrl) - mockBlockStateEquiv3 := NewMockBlockState(ctrl) //Generate keys kp, err := sr25519.GenerateKeypair() @@ -547,14 +544,6 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { mockBlockStateErr.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) mockBlockStateErr.EXPECT().GetHeader(h).Return(nil, errors.New("get header error")) - mockBlockStateEquiv1.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) - mockBlockStateEquiv1.EXPECT().GetHeader(h).Return(testHeaderPrimary, nil) - - mockBlockStateEquiv2.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) - mockBlockStateEquiv2.EXPECT().GetHeader(h).Return(testSecPlainHeader, nil) - mockBlockStateEquiv3.EXPECT().GetAllBlocksAtDepth(gomock.Any()).Return(h1) - mockBlockStateEquiv3.EXPECT().GetHeader(h).Return(testSecVrfHeader, nil) - // Case 0: First element not preruntime digest header0 := newTestHeader(t, testInvalidSeal, testInvalidSeal) @@ -613,27 +602,6 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { //// Case 8: Get header error babeVerifier6 := newTestVerifier(t, kp, mockBlockStateErr, scale.MaxUint128, false) - // Case 9: Equivocate case primary - babeVerifier7 := newTestVerifier(t, kp, mockBlockStateEquiv1, scale.MaxUint128, false) - - // Case 10: Equivocate case secondary plain - babeSecPlainPrd2, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() - assert.NoError(t, err) - header8 := newTestHeader(t, *babeSecPlainPrd2) - - hash2 := encodeAndHashHeader(t, header8) - signAndAddSeal(t, kp, header8, hash2[:]) - babeVerifier8 := newTestVerifier(t, kp, mockBlockStateEquiv2, scale.MaxUint128, true) - - // Case 11: equivocation case secondary VRF - encVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) - assert.NoError(t, err) - header9 := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfDigest)) - - hash3 := encodeAndHashHeader(t, header9) - signAndAddSeal(t, kp, header9, hash3[:]) - babeVerifier9 := newTestVerifier(t, kp, mockBlockStateEquiv3, scale.MaxUint128, true) - tests := []struct { name string verifier verifier @@ -644,19 +612,19 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { name: "missing digest", verifier: verifier{}, header: types.NewEmptyHeader(), - expErr: errors.New("block header is missing digest items"), + expErr: errMissingDigestItems, }, { name: "first digest invalid", verifier: verifier{}, header: header0, - expErr: errors.New("first digest item is not pre-digest"), + expErr: fmt.Errorf("%w: got types.SealDigest", types.ErrNoFirstPreDigest), }, { name: "last digest invalid", verifier: verifier{}, header: header1, - expErr: errors.New("last digest item is not seal"), + expErr: fmt.Errorf("%w: got types.PreRuntimeDigest", errLastDigestItemNotSeal), }, { name: "invalid preruntime digest data", @@ -692,28 +660,145 @@ func Test_verifier_verifyAuthorshipRight(t *testing.T) { name: "valid digest items, getAuthorityIndex error", verifier: *babeVerifier5, header: header7, + expErr: errors.New("failed to get authority index no digest provided"), }, { name: "get header err", verifier: *babeVerifier6, header: header7, + expErr: errors.New("failed get header get header error"), }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &tt.verifier + err := b.verifyAuthorshipRight(tt.header) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErr.Error()) + } else { + assert.NoError(t, err) + } + + }) + } +} + +func Test_verifier_verifyAuthorshipRightEquivocatory(t *testing.T) { + ctrl := gomock.NewController(t) + + mockBlockStateEquiv1 := NewMockBlockState(ctrl) + mockBlockStateEquiv2 := NewMockBlockState(ctrl) + mockBlockStateEquiv3 := NewMockBlockState(ctrl) + + //Generate keys + kp, err := sr25519.GenerateKeypair() + assert.NoError(t, err) + + output, proof, err := kp.VrfSign(makeTranscript(Randomness{}, uint64(1), 1)) + assert.NoError(t, err) + + testBabeSecondaryPlainPreDigest := types.BabeSecondaryPlainPreDigest{ + AuthorityIndex: 1, + SlotNumber: 1, + } + testBabeSecondaryVRFPreDigest := types.BabeSecondaryVRFPreDigest{ + AuthorityIndex: 1, + SlotNumber: 1, + VrfOutput: output, + VrfProof: proof, + } + + //BabePrimaryPreDigest case + secDigest1 := types.BabePrimaryPreDigest{ + SlotNumber: 1, + VRFOutput: output, + VRFProof: proof, + } + prd1, err := secDigest1.ToPreRuntimeDigest() + assert.NoError(t, err) + + auth := types.NewAuthority(kp.Public(), uint64(1)) + vi := &verifierInfo{ + authorities: []types.Authority{*auth, *auth}, + threshold: scale.MaxUint128, + } + + verifierEquivocatoryPrimary, err := newVerifier(mockBlockStateEquiv1, 1, vi) + assert.NoError(t, err) + + headerEquivocatoryPrimary := newTestHeader(t, *prd1) + hashEquivocatoryPrimary := encodeAndHashHeader(t, headerEquivocatoryPrimary) + signAndAddSeal(t, kp, headerEquivocatoryPrimary, hashEquivocatoryPrimary[:]) + + mockBlockStateEquiv1.EXPECT().GetAllBlocksAtDepth(headerEquivocatoryPrimary.ParentHash).Return( + []common.Hash{hashEquivocatoryPrimary}) + mockBlockStateEquiv1.EXPECT().GetHeader(hashEquivocatoryPrimary).Return(headerEquivocatoryPrimary, nil) + + // Secondary Plain Test Header + testParentPrd, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + testParentHeader := newTestHeader(t, *testParentPrd) + + testParentHash := encodeAndHashHeader(t, testParentHeader) + testSecondaryPrd, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + testSecPlainHeader := newTestHeader(t, *testSecondaryPrd) + testSecPlainHeader.ParentHash = testParentHash + + babeSecPlainPrd2, err := testBabeSecondaryPlainPreDigest.ToPreRuntimeDigest() + assert.NoError(t, err) + headerEquivocatorySecondaryPlain := newTestHeader(t, *babeSecPlainPrd2) + + hashEquivocatorySecondaryPlain := encodeAndHashHeader(t, headerEquivocatorySecondaryPlain) + signAndAddSeal(t, kp, headerEquivocatorySecondaryPlain, hashEquivocatorySecondaryPlain[:]) + babeVerifier8 := newTestVerifier(t, kp, mockBlockStateEquiv2, scale.MaxUint128, true) + + mockBlockStateEquiv2.EXPECT().GetAllBlocksAtDepth(headerEquivocatorySecondaryPlain.ParentHash).Return( + []common.Hash{hashEquivocatorySecondaryPlain}) + mockBlockStateEquiv2.EXPECT().GetHeader(hashEquivocatorySecondaryPlain).Return(headerEquivocatorySecondaryPlain, nil) + + // Secondary Vrf Test Header + encParentVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + testParentVrfHeader := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encParentVrfDigest)) + + testVrfParentHash := encodeAndHashHeader(t, testParentVrfHeader) + encVrfHeader := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + testSecVrfHeader := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfHeader)) + testSecVrfHeader.ParentHash = testVrfParentHash + + encVrfDigest := newEncodedBabeDigest(t, testBabeSecondaryVRFPreDigest) + assert.NoError(t, err) + headerEquivocatorySecondaryVRF := newTestHeader(t, *types.NewBABEPreRuntimeDigest(encVrfDigest)) + + hashEquivocatorySecondaryVRF := encodeAndHashHeader(t, headerEquivocatorySecondaryVRF) + signAndAddSeal(t, kp, headerEquivocatorySecondaryVRF, hashEquivocatorySecondaryVRF[:]) + babeVerifierEquivocatorySecondaryVRF := newTestVerifier(t, kp, mockBlockStateEquiv3, scale.MaxUint128, true) + mockBlockStateEquiv3.EXPECT().GetAllBlocksAtDepth(headerEquivocatorySecondaryVRF.ParentHash).Return( + []common.Hash{hashEquivocatorySecondaryVRF}) + mockBlockStateEquiv3.EXPECT().GetHeader(hashEquivocatorySecondaryVRF).Return(headerEquivocatorySecondaryVRF, nil) + + tests := []struct { + name string + verifier verifier + header *types.Header + expErr error + }{ { name: "equivocate - primary", - verifier: *babeVerifier7, - header: header7, + verifier: *verifierEquivocatoryPrimary, + header: headerEquivocatoryPrimary, expErr: ErrProducerEquivocated, }, { name: "equivocate - secondary plain", verifier: *babeVerifier8, - header: header8, + header: headerEquivocatorySecondaryPlain, expErr: ErrProducerEquivocated, }, { name: "equivocate - secondary vrf", - verifier: *babeVerifier9, - header: header9, + verifier: *babeVerifierEquivocatorySecondaryVRF, + header: headerEquivocatorySecondaryVRF, expErr: ErrProducerEquivocated, }, }