Skip to content

Commit

Permalink
throw ErrProducerEquivocated only when both equivocatory blocks are c…
Browse files Browse the repository at this point in the history
…reated by primary block producer (#2596)
  • Loading branch information
kishansagathiya authored Jul 12, 2022
1 parent 8c16d4e commit fe1e2f0
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 50 deletions.
7 changes: 5 additions & 2 deletions dot/types/babe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions lib/babe/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 29 additions & 8 deletions lib/babe/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(&currentPreDigest)
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
}
}
Expand Down
165 changes: 125 additions & 40 deletions lib/babe/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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,
},
}
Expand Down

0 comments on commit fe1e2f0

Please sign in to comment.