diff --git a/dot/core/interfaces.go b/dot/core/interfaces.go index cab5168e41..33dc42013c 100644 --- a/dot/core/interfaces.go +++ b/dot/core/interfaces.go @@ -15,6 +15,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" rtstorage "github.com/ChainSafe/gossamer/lib/runtime/storage" @@ -54,6 +55,11 @@ type RuntimeInstance interface { RandomSeed() OffchainWorker() GenerateSessionKeys() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error } // BlockState interface for block state methods diff --git a/dot/core/mocks_test.go b/dot/core/mocks_test.go index 7be93f6e29..897530802a 100644 --- a/dot/core/mocks_test.go +++ b/dot/core/mocks_test.go @@ -13,6 +13,7 @@ import ( state "github.com/ChainSafe/gossamer/dot/state" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" storage "github.com/ChainSafe/gossamer/lib/runtime/storage" @@ -691,6 +692,35 @@ func (mr *MockRuntimeInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaAuthorities)) } +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockRuntimeInstance) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockRuntimeInstanceMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockRuntimeInstance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockRuntimeInstanceMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + // InherentExtrinsics mocks base method. func (m *MockRuntimeInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/dot/interfaces.go b/dot/interfaces.go index 44235afff3..0ebf32c907 100644 --- a/dot/interfaces.go +++ b/dot/interfaces.go @@ -8,6 +8,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/services" @@ -69,4 +70,9 @@ type runtimeInterface interface { RandomSeed() OffchainWorker() GenerateSessionKeys() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error } diff --git a/dot/rpc/modules/interfaces_test.go b/dot/rpc/modules/interfaces_test.go index 1acdd3939d..b731759d54 100644 --- a/dot/rpc/modules/interfaces_test.go +++ b/dot/rpc/modules/interfaces_test.go @@ -6,6 +6,7 @@ package modules import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/transaction" @@ -39,4 +40,9 @@ type Runtime interface { RandomSeed() OffchainWorker() GenerateSessionKeys() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error } diff --git a/dot/state/interfaces.go b/dot/state/interfaces.go index c32195e074..f12e2e24d3 100644 --- a/dot/state/interfaces.go +++ b/dot/state/interfaces.go @@ -9,6 +9,7 @@ import ( "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/transaction" @@ -94,6 +95,11 @@ type Runtime interface { RandomSeed() OffchainWorker() GenerateSessionKeys() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error } // BabeConfigurer returns the babe configuration of the runtime. diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index f698777f38..d6c6474c49 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -9,6 +9,7 @@ import ( types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" @@ -210,6 +211,35 @@ func (mr *MockInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockInstance)(nil).GrandpaAuthorities)) } +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockInstance) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockInstanceMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockInstance)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockInstance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockInstanceMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockInstance)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + // InherentExtrinsics mocks base method. func (m *MockInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/dot/types/grandpa.go b/dot/types/grandpa.go index 0dd6067b4c..93e93f72db 100644 --- a/dot/types/grandpa.go +++ b/dot/types/grandpa.go @@ -196,3 +196,66 @@ type GrandpaVote struct { func (v GrandpaVote) String() string { return fmt.Sprintf("hash=%s number=%d", v.Hash, v.Number) } + +// GrandpaEquivocation is used to create a proof of equivocation +// https://github.com/paritytech/finality-grandpa/blob/19d251d0b0105d51a79d3c4532a9aae75a5035bd/src/lib.rs#L213 //nolint:lll +type GrandpaEquivocation struct { + RoundNumber uint64 + ID [32]byte + FirstVote GrandpaVote + FirstSignature [64]byte + SecondVote GrandpaVote + SecondSignature [64]byte +} + +// GrandpaEquivocationEnum is a wrapper object for GRANDPA equivocation proofs, useful for unifying prevote +// and precommit equivocations under a common type. +// https://github.com/paritytech/substrate/blob/fb22096d2ec6bf38e67ce811ad2c31415237a9a5/primitives/finality-grandpa/src/lib.rs#L272 //nolint:lll +type GrandpaEquivocationEnum scale.VaryingDataType + +// Set sets a VaryingDataTypeValue using the underlying VaryingDataType +func (ge *GrandpaEquivocationEnum) Set(value scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*ge) + err = vdt.Set(value) + if err != nil { + return err + } + *ge = GrandpaEquivocationEnum(vdt) + return nil +} + +// Value will return the value from the underlying VaryingDataType +func (ge *GrandpaEquivocationEnum) Value() (value scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*ge) + return vdt.Value() +} + +// NewGrandpaEquivocation returns a new VaryingDataType to represent a grandpa Equivocation +func NewGrandpaEquivocation() *GrandpaEquivocationEnum { + vdt := scale.MustNewVaryingDataType(PreVote{}, PreCommit{}) + ge := GrandpaEquivocationEnum(vdt) + return &ge +} + +// PreVote equivocation type for a prevote +type PreVote GrandpaEquivocation + +// Index returns VDT index +func (PreVote) Index() uint { return 0 } + +// PreCommit equivocation type for a precommit +type PreCommit GrandpaEquivocation + +// Index returns VDT index +func (PreCommit) Index() uint { return 1 } + +// GrandpaOpaqueKeyOwnershipProof contains a key ownership proof for reporting equivocations +// https://github.com/paritytech/substrate/blob/fb22096d2ec6bf38e67ce811ad2c31415237a9a5/primitives/finality-grandpa/src/lib.rs#L533 //nolint:lll +type GrandpaOpaqueKeyOwnershipProof []byte + +// GrandpaEquivocationProof is used to report grandpa equivocations +// https://github.com/paritytech/substrate/blob/fb22096d2ec6bf38e67ce811ad2c31415237a9a5/primitives/finality-grandpa/src/lib.rs#L238 //nolint:lll +type GrandpaEquivocationProof struct { + SetID uint64 + Equivocation GrandpaEquivocationEnum +} diff --git a/dot/types/grandpa_test.go b/dot/types/grandpa_test.go index 1c3184ec6f..6b42382397 100644 --- a/dot/types/grandpa_test.go +++ b/dot/types/grandpa_test.go @@ -7,55 +7,129 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) -func TestEncodeGrandpaVote(t *testing.T) { - exp := common.MustHexToBytes("0x0a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000") - var testVote = GrandpaVote{ +func mustHexTo64BArray(t *testing.T, inputHex string) (outputArray [64]byte) { + t.Helper() + copy(outputArray[:], common.MustHexToBytes(inputHex)) + return outputArray +} + +func Test_OpaqueKeyOwnershipProof_ScaleCodec(t *testing.T) { + t.Parallel() + keyOwnershipProof := GrandpaOpaqueKeyOwnershipProof([]byte{64, 138, 252, 29, 127, 102, 189, 129, 207, 47, 157, + 60, 17, 138, 194, 121, 139, 92, 176, 175, 224, 16, 185, 93, 175, 251, 224, 81, 209, 61, 0, 71}) + encoded := scale.MustMarshal(keyOwnershipProof) + var proof GrandpaOpaqueKeyOwnershipProof + err := scale.Unmarshal(encoded, &proof) + require.NoError(t, err) + require.Equal(t, keyOwnershipProof, proof) +} + +func TestInstance_GrandpaSubmitReportEquivocationUnsignedExtrinsicEncoding(t *testing.T) { + t.Parallel() + // source: + // https://github.com/jimjbrettj/scale-encoding-generator/blob/a111e57d5103a7b5ba863cee09b83b89ba9c29e0/src/main.rs#L52 + expectedEncoding := common.MustHexToBytes("0x010000000000000000010000000000000088dc3417d5058ec4b4503e0c12ea" + + "1a0a89be200fe98922423d4334014fa6b0ee4801b8e62d31167d30c893cc1970f6a0e289420282a4b245b75f2c46fb308af10a0000" + + "00d7292caacc62504365f179892a7399f233944bf261f8a3f66260f70e0016f2db63922726b015c82dc7131f4730fbec61f71672a5" + + "71453e51029bfb469070900fc314327941fdd924bc67fd72651c40aececd485ca3e878c21e02abb40feae5bd0a000000b3c408b749" + + "05dfedfffa66f99f16fe8b938fd8df76a92225228a1ca075230b99a2d9e173c561952e1e378b701915ca188d2c832ef92a3fab8e455" + + "f32570c0807") + identity := common.MustHexToBytes("0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee") + identityPubKey, _ := ed25519.NewPublicKey(identity) + firstVote := GrandpaVote{ + Hash: common.MustHexToHash("0x4801b8e62d31167d30c893cc1970f6a0e289420282a4b245b75f2c46fb308af1"), + Number: uint32(10), + } + secondVote := GrandpaVote{ + Hash: common.MustHexToHash("0xc314327941fdd924bc67fd72651c40aececd485ca3e878c21e02abb40feae5bd"), + Number: uint32(10), + } + + firstSignatureArray := mustHexTo64BArray(t, "0xd7292caacc62504365f179892a7399f233944bf261f8a3f66260f70e0016f2d"+ + "b63922726b015c82dc7131f4730fbec61f71672a571453e51029bfb469070900f") + + secondSignatureArray := mustHexTo64BArray(t, "0xb3c408b74905dfedfffa66f99f16fe8b938fd8df76a92225228a1ca07523"+ + "0b99a2d9e173c561952e1e378b701915ca188d2c832ef92a3fab8e455f32570c0807") + + var authorityID [32]byte + copy(authorityID[:], identityPubKey.Encode()) + + grandpaEquivocation := GrandpaEquivocation{ + RoundNumber: 1, + ID: authorityID, + FirstVote: firstVote, + FirstSignature: firstSignatureArray, + SecondVote: secondVote, + SecondSignature: secondSignatureArray, + } + + preVoteEquivocation := PreVote(grandpaEquivocation) + equivocationEnum := NewGrandpaEquivocation() + err := equivocationEnum.Set(preVoteEquivocation) + require.NoError(t, err) + + equivocationProof := GrandpaEquivocationProof{ + SetID: 1, + Equivocation: *equivocationEnum, + } + + actualEncoding := scale.MustMarshal(equivocationProof) + require.Equal(t, expectedEncoding, actualEncoding) +} + +func Test_GrandpaVote(t *testing.T) { + t.Parallel() + // source: + // https://github.com/jimjbrettj/scale-encoding-generator/blob/a111e57d5103a7b5ba863cee09b83b89ba9c29e0/src/main.rs#L42 + expectedEncoding := []byte{10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 3, 0, 0} + vote := GrandpaVote{ Hash: common.Hash{0xa, 0xb, 0xc, 0xd}, Number: 999, } - enc, err := scale.Marshal(testVote) - require.NoError(t, err) - require.Equal(t, exp, enc) + encoding := scale.MustMarshal(vote) + require.Equal(t, expectedEncoding, encoding) - dec := GrandpaVote{} - err = scale.Unmarshal(enc, &dec) + grandpaVote := GrandpaVote{} + err := scale.Unmarshal(encoding, &grandpaVote) require.NoError(t, err) - require.Equal(t, testVote, dec) - + require.Equal(t, vote, grandpaVote) } func TestEncodeSignedVote(t *testing.T) { - exp := common.MustHexToBytes("0x0a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000") //nolint:lll - var testVote = GrandpaVote{ + t.Parallel() + expectedEncoding := common.MustHexToBytes("0x0a0b0c0d00000000000000000000000000000000000000000000000000000000e7030000010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000") //nolint:lll + vote := GrandpaVote{ Hash: common.Hash{0xa, 0xb, 0xc, 0xd}, Number: 999, } - var testSignature = [64]byte{1, 2, 3, 4} - var testAuthorityID = [32]byte{5, 6, 7, 8} + signature := [64]byte{1, 2, 3, 4} + authorityID := [32]byte{5, 6, 7, 8} - sv := GrandpaSignedVote{ - Vote: testVote, - Signature: testSignature, - AuthorityID: testAuthorityID, + signedVote := GrandpaSignedVote{ + Vote: vote, + Signature: signature, + AuthorityID: authorityID, } - enc, err := scale.Marshal(sv) - require.NoError(t, err) - require.Equal(t, exp, enc) + encoding := scale.MustMarshal(signedVote) + require.Equal(t, expectedEncoding, encoding) - res := GrandpaSignedVote{} - err = scale.Unmarshal(enc, &res) + grandpaSignedVote := GrandpaSignedVote{} + err := scale.Unmarshal(encoding, &grandpaSignedVote) require.NoError(t, err) - require.Equal(t, sv, res) + require.Equal(t, signedVote, grandpaSignedVote) } func TestGrandpaAuthoritiesRawToAuthorities(t *testing.T) { - exp := common.MustHexToBytes("0x08eea1eabcac7d2c8a6459b7322cf997874482bfc3d2ec7a80888a3a7d714103640000000000000000b64994460e59b30364cad3c92e3df6052f9b0ebbb8f88460c194dc5794d6d7170100000000000000") //nolint:lll + t.Parallel() + expectedEncoding := common.MustHexToBytes("0x08eea1eabcac7d2c8a6459b7322cf997874482bfc3d2ec7a80888a3a7d714103640000000000000000b64994460e59b30364cad3c92e3df6052f9b0ebbb8f88460c194dc5794d6d7170100000000000000") //nolint:lll authA, _ := common.HexToHash("0xeea1eabcac7d2c8a6459b7322cf997874482bfc3d2ec7a80888a3a7d71410364") authB, _ := common.HexToHash("0xb64994460e59b30364cad3c92e3df6052f9b0ebbb8f88460c194dc5794d6d717") @@ -64,21 +138,20 @@ func TestGrandpaAuthoritiesRawToAuthorities(t *testing.T) { {Key: authB, ID: 1}, } - enc, err := scale.Marshal(auths) - require.NoError(t, err) - require.Equal(t, exp, enc) + encoding := scale.MustMarshal(auths) + require.Equal(t, expectedEncoding, encoding) - var dec []GrandpaAuthoritiesRaw - err = scale.Unmarshal(enc, &dec) + var grandpaAuthoritiesRaw []GrandpaAuthoritiesRaw + err := scale.Unmarshal(encoding, &grandpaAuthoritiesRaw) require.NoError(t, err) - require.Equal(t, auths, dec) + require.Equal(t, auths, grandpaAuthoritiesRaw) - authoritys, err := GrandpaAuthoritiesRawToAuthorities(dec) + authorities, err := GrandpaAuthoritiesRawToAuthorities(grandpaAuthoritiesRaw) require.NoError(t, err) - require.Equal(t, auths[0].ID, authoritys[0].Weight) + require.Equal(t, auths[0].ID, authorities[0].Weight) - a := Authority{} - err = a.FromRawEd25519(dec[1]) + authority := Authority{} + err = authority.FromRawEd25519(grandpaAuthoritiesRaw[1]) require.NoError(t, err) - require.Equal(t, a, authoritys[1]) + require.Equal(t, authority, authorities[1]) } diff --git a/go.mod b/go.mod index fca53a9024..ba2ff226b7 100644 --- a/go.mod +++ b/go.mod @@ -159,6 +159,7 @@ require ( github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect diff --git a/go.sum b/go.sum index be11d563d8..9be2bd8cb1 100644 --- a/go.sum +++ b/go.sum @@ -750,6 +750,7 @@ github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 305cccae16..6c18d6b903 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -9,6 +9,7 @@ import ( types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" @@ -210,6 +211,35 @@ func (mr *MockRuntimeInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaAuthorities)) } +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockRuntimeInstance) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockRuntimeInstanceMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockRuntimeInstance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockRuntimeInstanceMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockRuntimeInstance)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + // InherentExtrinsics mocks base method. func (m *MockRuntimeInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/lib/blocktree/interfaces.go b/lib/blocktree/interfaces.go index bff6165336..3a4480e170 100644 --- a/lib/blocktree/interfaces.go +++ b/lib/blocktree/interfaces.go @@ -6,6 +6,7 @@ package blocktree import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/transaction" @@ -40,4 +41,9 @@ type Runtime interface { RandomSeed() OffchainWorker() GenerateSessionKeys() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error } diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index 891d75a85a..fe5943fd46 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -9,6 +9,7 @@ import ( types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" @@ -210,6 +211,35 @@ func (mr *MockRuntimeMockRecorder) GrandpaAuthorities() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockRuntime)(nil).GrandpaAuthorities)) } +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockRuntime) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockRuntimeMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockRuntime)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockRuntime) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockRuntimeMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockRuntime)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + // InherentExtrinsics mocks base method. func (m *MockRuntime) InherentExtrinsics(arg0 []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/lib/grandpa/errors.go b/lib/grandpa/errors.go index e6c1a2bb47..801e46d03f 100644 --- a/lib/grandpa/errors.go +++ b/lib/grandpa/errors.go @@ -83,9 +83,10 @@ var ( // ErrAuthorityNotInSet is returned when a precommit within a justification is signed by a key not in the authority set ErrAuthorityNotInSet = errors.New("authority is not in set") - errVoteToSignatureMismatch = errors.New("votes and authority count mismatch") - errVoteBlockMismatch = errors.New("block in vote is not descendant of previously finalised block") - errVoteFromSelf = errors.New("got vote from ourselves") - errRoundOutOfBounds = errors.New("round out of bounds") - errRoundsMismatch = errors.New("rounds mismatch") + errVoteToSignatureMismatch = errors.New("votes and authority count mismatch") + errVoteBlockMismatch = errors.New("block in vote is not descendant of previously finalised block") + errVoteFromSelf = errors.New("got vote from ourselves") + errRoundOutOfBounds = errors.New("round out of bounds") + errRoundsMismatch = errors.New("rounds mismatch") + errInvalidEquivocationStage = errors.New("invalid stage for equivocating") ) diff --git a/lib/grandpa/mocks_generate_test.go b/lib/grandpa/mocks_generate_test.go index 0f01cb2d9e..10a87f88f6 100644 --- a/lib/grandpa/mocks_generate_test.go +++ b/lib/grandpa/mocks_generate_test.go @@ -6,3 +6,4 @@ package grandpa //go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . BlockState,GrandpaState,Network //go:generate mockgen -source=finalisation.go -destination=mock_ephemeral_service_test.go -package $GOPACKAGE . ephemeralService //go:generate mockgen -destination=mock_telemetry_test.go -package $GOPACKAGE . Telemetry +//go:generate mockgen -destination=mocks_runtime_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/lib/runtime Instance diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go new file mode 100644 index 0000000000..28be8acfde --- /dev/null +++ b/lib/grandpa/mocks_runtime_test.go @@ -0,0 +1,447 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/lib/runtime (interfaces: Instance) + +// Package grandpa is a generated GoMock package. +package grandpa + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + keystore "github.com/ChainSafe/gossamer/lib/keystore" + runtime "github.com/ChainSafe/gossamer/lib/runtime" + transaction "github.com/ChainSafe/gossamer/lib/transaction" + gomock "github.com/golang/mock/gomock" +) + +// MockInstance is a mock of Instance interface. +type MockInstance struct { + ctrl *gomock.Controller + recorder *MockInstanceMockRecorder +} + +// MockInstanceMockRecorder is the mock recorder for MockInstance. +type MockInstanceMockRecorder struct { + mock *MockInstance +} + +// NewMockInstance creates a new mock instance. +func NewMockInstance(ctrl *gomock.Controller) *MockInstance { + mock := &MockInstance{ctrl: ctrl} + mock.recorder = &MockInstanceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInstance) EXPECT() *MockInstanceMockRecorder { + return m.recorder +} + +// ApplyExtrinsic mocks base method. +func (m *MockInstance) ApplyExtrinsic(arg0 types.Extrinsic) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyExtrinsic", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ApplyExtrinsic indicates an expected call of ApplyExtrinsic. +func (mr *MockInstanceMockRecorder) ApplyExtrinsic(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyExtrinsic", reflect.TypeOf((*MockInstance)(nil).ApplyExtrinsic), arg0) +} + +// BabeConfiguration mocks base method. +func (m *MockInstance) BabeConfiguration() (*types.BabeConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BabeConfiguration") + ret0, _ := ret[0].(*types.BabeConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BabeConfiguration indicates an expected call of BabeConfiguration. +func (mr *MockInstanceMockRecorder) BabeConfiguration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BabeConfiguration", reflect.TypeOf((*MockInstance)(nil).BabeConfiguration)) +} + +// BabeGenerateKeyOwnershipProof mocks base method. +func (m *MockInstance) BabeGenerateKeyOwnershipProof(arg0 uint64, arg1 [32]byte) (types.OpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BabeGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.OpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BabeGenerateKeyOwnershipProof indicates an expected call of BabeGenerateKeyOwnershipProof. +func (mr *MockInstanceMockRecorder) BabeGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BabeGenerateKeyOwnershipProof", reflect.TypeOf((*MockInstance)(nil).BabeGenerateKeyOwnershipProof), arg0, arg1) +} + +// BabeSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockInstance) BabeSubmitReportEquivocationUnsignedExtrinsic(arg0 types.BabeEquivocationProof, arg1 types.OpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BabeSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// BabeSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of BabeSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockInstanceMockRecorder) BabeSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BabeSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockInstance)(nil).BabeSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + +// CheckInherents mocks base method. +func (m *MockInstance) CheckInherents() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CheckInherents") +} + +// CheckInherents indicates an expected call of CheckInherents. +func (mr *MockInstanceMockRecorder) CheckInherents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckInherents", reflect.TypeOf((*MockInstance)(nil).CheckInherents)) +} + +// DecodeSessionKeys mocks base method. +func (m *MockInstance) DecodeSessionKeys(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecodeSessionKeys", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecodeSessionKeys indicates an expected call of DecodeSessionKeys. +func (mr *MockInstanceMockRecorder) DecodeSessionKeys(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeSessionKeys", reflect.TypeOf((*MockInstance)(nil).DecodeSessionKeys), arg0) +} + +// Exec mocks base method. +func (m *MockInstance) Exec(arg0 string, arg1 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exec", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exec indicates an expected call of Exec. +func (mr *MockInstanceMockRecorder) Exec(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exec", reflect.TypeOf((*MockInstance)(nil).Exec), arg0, arg1) +} + +// ExecuteBlock mocks base method. +func (m *MockInstance) ExecuteBlock(arg0 *types.Block) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteBlock", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteBlock indicates an expected call of ExecuteBlock. +func (mr *MockInstanceMockRecorder) ExecuteBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteBlock", reflect.TypeOf((*MockInstance)(nil).ExecuteBlock), arg0) +} + +// FinalizeBlock mocks base method. +func (m *MockInstance) FinalizeBlock() (*types.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FinalizeBlock") + ret0, _ := ret[0].(*types.Header) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FinalizeBlock indicates an expected call of FinalizeBlock. +func (mr *MockInstanceMockRecorder) FinalizeBlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeBlock", reflect.TypeOf((*MockInstance)(nil).FinalizeBlock)) +} + +// GenerateSessionKeys mocks base method. +func (m *MockInstance) GenerateSessionKeys() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GenerateSessionKeys") +} + +// GenerateSessionKeys indicates an expected call of GenerateSessionKeys. +func (mr *MockInstanceMockRecorder) GenerateSessionKeys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateSessionKeys", reflect.TypeOf((*MockInstance)(nil).GenerateSessionKeys)) +} + +// GetCodeHash mocks base method. +func (m *MockInstance) GetCodeHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCodeHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetCodeHash indicates an expected call of GetCodeHash. +func (mr *MockInstanceMockRecorder) GetCodeHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCodeHash", reflect.TypeOf((*MockInstance)(nil).GetCodeHash)) +} + +// GrandpaAuthorities mocks base method. +func (m *MockInstance) GrandpaAuthorities() ([]types.Authority, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaAuthorities") + ret0, _ := ret[0].([]types.Authority) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaAuthorities indicates an expected call of GrandpaAuthorities. +func (mr *MockInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockInstance)(nil).GrandpaAuthorities)) +} + +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockInstance) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockInstanceMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockInstance)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockInstance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockInstanceMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockInstance)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + +// InherentExtrinsics mocks base method. +func (m *MockInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InherentExtrinsics", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InherentExtrinsics indicates an expected call of InherentExtrinsics. +func (mr *MockInstanceMockRecorder) InherentExtrinsics(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InherentExtrinsics", reflect.TypeOf((*MockInstance)(nil).InherentExtrinsics), arg0) +} + +// InitializeBlock mocks base method. +func (m *MockInstance) InitializeBlock(arg0 *types.Header) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitializeBlock", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// InitializeBlock indicates an expected call of InitializeBlock. +func (mr *MockInstanceMockRecorder) InitializeBlock(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitializeBlock", reflect.TypeOf((*MockInstance)(nil).InitializeBlock), arg0) +} + +// Keystore mocks base method. +func (m *MockInstance) Keystore() *keystore.GlobalKeystore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Keystore") + ret0, _ := ret[0].(*keystore.GlobalKeystore) + return ret0 +} + +// Keystore indicates an expected call of Keystore. +func (mr *MockInstanceMockRecorder) Keystore() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Keystore", reflect.TypeOf((*MockInstance)(nil).Keystore)) +} + +// Metadata mocks base method. +func (m *MockInstance) Metadata() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Metadata") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Metadata indicates an expected call of Metadata. +func (mr *MockInstanceMockRecorder) Metadata() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockInstance)(nil).Metadata)) +} + +// NetworkService mocks base method. +func (m *MockInstance) NetworkService() runtime.BasicNetwork { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetworkService") + ret0, _ := ret[0].(runtime.BasicNetwork) + return ret0 +} + +// NetworkService indicates an expected call of NetworkService. +func (mr *MockInstanceMockRecorder) NetworkService() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetworkService", reflect.TypeOf((*MockInstance)(nil).NetworkService)) +} + +// NodeStorage mocks base method. +func (m *MockInstance) NodeStorage() runtime.NodeStorage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeStorage") + ret0, _ := ret[0].(runtime.NodeStorage) + return ret0 +} + +// NodeStorage indicates an expected call of NodeStorage. +func (mr *MockInstanceMockRecorder) NodeStorage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeStorage", reflect.TypeOf((*MockInstance)(nil).NodeStorage)) +} + +// OffchainWorker mocks base method. +func (m *MockInstance) OffchainWorker() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "OffchainWorker") +} + +// OffchainWorker indicates an expected call of OffchainWorker. +func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) +} + +// PaymentQueryInfo mocks base method. +func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaymentQueryInfo", arg0) + ret0, _ := ret[0].(*types.RuntimeDispatchInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaymentQueryInfo indicates an expected call of PaymentQueryInfo. +func (mr *MockInstanceMockRecorder) PaymentQueryInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaymentQueryInfo", reflect.TypeOf((*MockInstance)(nil).PaymentQueryInfo), arg0) +} + +// RandomSeed mocks base method. +func (m *MockInstance) RandomSeed() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RandomSeed") +} + +// RandomSeed indicates an expected call of RandomSeed. +func (mr *MockInstanceMockRecorder) RandomSeed() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RandomSeed", reflect.TypeOf((*MockInstance)(nil).RandomSeed)) +} + +// SetContextStorage mocks base method. +func (m *MockInstance) SetContextStorage(arg0 runtime.Storage) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetContextStorage", arg0) +} + +// SetContextStorage indicates an expected call of SetContextStorage. +func (mr *MockInstanceMockRecorder) SetContextStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContextStorage", reflect.TypeOf((*MockInstance)(nil).SetContextStorage), arg0) +} + +// Stop mocks base method. +func (m *MockInstance) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockInstanceMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockInstance)(nil).Stop)) +} + +// UpdateRuntimeCode mocks base method. +func (m *MockInstance) UpdateRuntimeCode(arg0 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRuntimeCode", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRuntimeCode indicates an expected call of UpdateRuntimeCode. +func (mr *MockInstanceMockRecorder) UpdateRuntimeCode(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRuntimeCode", reflect.TypeOf((*MockInstance)(nil).UpdateRuntimeCode), arg0) +} + +// ValidateTransaction mocks base method. +func (m *MockInstance) ValidateTransaction(arg0 types.Extrinsic) (*transaction.Validity, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateTransaction", arg0) + ret0, _ := ret[0].(*transaction.Validity) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateTransaction indicates an expected call of ValidateTransaction. +func (mr *MockInstanceMockRecorder) ValidateTransaction(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateTransaction", reflect.TypeOf((*MockInstance)(nil).ValidateTransaction), arg0) +} + +// Validator mocks base method. +func (m *MockInstance) Validator() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validator") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Validator indicates an expected call of Validator. +func (mr *MockInstanceMockRecorder) Validator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validator", reflect.TypeOf((*MockInstance)(nil).Validator)) +} + +// Version mocks base method. +func (m *MockInstance) Version() runtime.Version { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Version") + ret0, _ := ret[0].(runtime.Version) + return ret0 +} + +// Version indicates an expected call of Version. +func (mr *MockInstanceMockRecorder) Version() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockInstance)(nil).Version)) +} diff --git a/lib/grandpa/mocks_test.go b/lib/grandpa/mocks_test.go index 63e0dbe2db..c3bfe8d942 100644 --- a/lib/grandpa/mocks_test.go +++ b/lib/grandpa/mocks_test.go @@ -8,6 +8,7 @@ import ( reflect "reflect" network "github.com/ChainSafe/gossamer/dot/network" + state "github.com/ChainSafe/gossamer/dot/state" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" gomock "github.com/golang/mock/gomock" @@ -38,6 +39,20 @@ func (m *MockBlockState) EXPECT() *MockBlockStateMockRecorder { return m.recorder } +// BestBlockHash mocks base method. +func (m *MockBlockState) BestBlockHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BestBlockHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// BestBlockHash indicates an expected call of BestBlockHash. +func (mr *MockBlockStateMockRecorder) BestBlockHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BestBlockHash", reflect.TypeOf((*MockBlockState)(nil).BestBlockHash)) +} + // BestBlockHeader mocks base method. func (m *MockBlockState) BestBlockHeader() (*types.Header, error) { m.ctrl.T.Helper() @@ -210,6 +225,21 @@ func (mr *MockBlockStateMockRecorder) GetImportedBlockNotifierChannel() *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetImportedBlockNotifierChannel)) } +// GetRuntime mocks base method. +func (m *MockBlockState) GetRuntime(arg0 common.Hash) (state.Runtime, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRuntime", arg0) + ret0, _ := ret[0].(state.Runtime) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRuntime indicates an expected call of GetRuntime. +func (mr *MockBlockStateMockRecorder) GetRuntime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRuntime", reflect.TypeOf((*MockBlockState)(nil).GetRuntime), arg0) +} + // HasFinalisedBlock mocks base method. func (m *MockBlockState) HasFinalisedBlock(arg0, arg1 uint64) (bool, error) { m.ctrl.T.Helper() diff --git a/lib/grandpa/state.go b/lib/grandpa/state.go index c00da7f019..6e9ea8598b 100644 --- a/lib/grandpa/state.go +++ b/lib/grandpa/state.go @@ -4,6 +4,7 @@ package grandpa import ( + "github.com/ChainSafe/gossamer/dot/state" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -32,6 +33,8 @@ type BlockState interface { SetJustification(hash common.Hash, data []byte) error BestBlockNumber() (blockNumber uint, err error) GetHighestRoundAndSetID() (uint64, uint64, error) + BestBlockHash() common.Hash + GetRuntime(blockHash common.Hash) (instance state.Runtime, err error) } // GrandpaState is the interface required by grandpa into the grandpa state diff --git a/lib/grandpa/vote_message.go b/lib/grandpa/vote_message.go index 2886a1374a..43ba96ee60 100644 --- a/lib/grandpa/vote_message.go +++ b/lib/grandpa/vote_message.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/telemetry" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/blocktree" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/pkg/scale" @@ -198,9 +199,9 @@ func (s *Service) validateVoteMessage(from peer.ID, m *VoteMessage) (*Vote, erro AuthorityID: pk.AsBytes(), } - equivocated := s.checkForEquivocation(voter, just, m.Message.Stage) - if equivocated { - return nil, fmt.Errorf("%w", ErrEquivocation) + err = s.checkAndReportEquivocation(voter, just, m.Message.Stage) + if err != nil { + return nil, fmt.Errorf("checking for equivocation: %w", err) } switch m.Message.Stage { @@ -213,10 +214,10 @@ func (s *Service) validateVoteMessage(from peer.ID, m *VoteMessage) (*Vote, erro return vote, nil } -// checkForEquivocation checks if the vote is an equivocatory vote. -// it returns true if so, false otherwise. -// additionally, if the vote is equivocatory, it updates the service's votes and equivocations. -func (s *Service) checkForEquivocation(voter *Voter, vote *SignedVote, stage Subround) bool { +// checkAndReportEquivocation checks if the vote is an equivocatory vote. +// If it is an equivocatory vote, the error `ErrEquivocation` is returned, the service's votes and +// equivocations are updated and the equivocation is reported to the runtime. +func (s *Service) checkAndReportEquivocation(voter *Voter, vote *SignedVote, stage Subround) error { v := voter.Key.AsBytes() // save justification, since equivocatory vote may still be used in justification @@ -236,22 +237,94 @@ func (s *Service) checkForEquivocation(voter *Voter, vote *SignedVote, stage Sub if has { // if the voter has already equivocated, every vote in that round is an equivocatory vote eq[v] = append(eq[v], vote) - return true + return fmt.Errorf("%w: voter %s", + ErrEquivocation, v) } existingVote, has := s.loadVote(v, stage) if !has { - return false + return nil } if has && existingVote.Vote.Hash != vote.Vote.Hash { // the voter has already voted, all their votes are now equivocatory eq[v] = []*SignedVote{existingVote, vote} s.deleteVote(v, stage) - return true + + err := s.reportEquivocation(stage, existingVote, vote) + if err != nil { + logger.Errorf("reporting equivocation: %s", err) + } + return fmt.Errorf("%w: voter %s has existing vote %s and new vote %s", + ErrEquivocation, v, existingVote.Vote.Hash, vote.Vote.Hash) + } + + return nil +} + +func (s *Service) reportEquivocation(stage Subround, existingVote *SignedVote, currentVote *SignedVote) error { + setID, err := s.grandpaState.GetCurrentSetID() + if err != nil { + return fmt.Errorf("getting authority set id: %w", err) + } + + round, err := s.grandpaState.GetLatestRound() + if err != nil { + return fmt.Errorf("getting latest round: %w", err) + } + + pubKey := existingVote.AuthorityID + + bestBlockHash := s.blockState.BestBlockHash() + runtime, err := s.blockState.GetRuntime(bestBlockHash) + if err != nil { + return fmt.Errorf("getting runtime: %w", err) + } + + opaqueKeyOwnershipProof, err := runtime.GrandpaGenerateKeyOwnershipProof(setID, pubKey) + if err != nil { + return fmt.Errorf("getting key ownership proof: %w", err) + } + + grandpaEquivocation := types.GrandpaEquivocation{ + RoundNumber: round, + ID: pubKey, + FirstVote: existingVote.Vote, + FirstSignature: existingVote.Signature, + SecondVote: currentVote.Vote, + SecondSignature: currentVote.Signature, } - return false + equivocationVote := types.NewGrandpaEquivocation() + switch stage { + case prevote: + err = equivocationVote.Set(types.PreVote(grandpaEquivocation)) + if err != nil { + return fmt.Errorf("setting grandpa equivocation VDT as prevote equivocation: %w", err) + } + case precommit: + err = equivocationVote.Set(types.PreCommit(grandpaEquivocation)) + if err != nil { + return fmt.Errorf("setting grandpa equivocation VDT as precommit equivocation: %w", err) + } + case primaryProposal: + return fmt.Errorf("%w: %s (%d)", errInvalidEquivocationStage, stage, stage) + default: + panic(fmt.Sprintf("equivocation stage not implemented: %s (%d)", stage, stage)) + + } + + equivocationProof := types.GrandpaEquivocationProof{ + SetID: setID, + Equivocation: *equivocationVote, + } + + err = runtime.GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof, opaqueKeyOwnershipProof) + if err != nil { + return fmt.Errorf("submitting grandpa equivocation report to runtime: %w", err) + } + + return nil } // validateVote checks if the block that is being voted for exists, and that it is a descendant of a diff --git a/lib/grandpa/vote_message_integration_test.go b/lib/grandpa/vote_message_integration_test.go index f00b055177..829d280838 100644 --- a/lib/grandpa/vote_message_integration_test.go +++ b/lib/grandpa/vote_message_integration_test.go @@ -39,10 +39,10 @@ func TestCheckForEquivocation_NoEquivocation(t *testing.T) { require.NoError(t, err) for _, v := range newTestVoters(t) { - equivocated := gs.checkForEquivocation(&v, &SignedVote{ + err = gs.checkAndReportEquivocation(&v, &SignedVote{ Vote: *vote, }, prevote) - require.False(t, equivocated) + require.NoError(t, err) } } @@ -78,10 +78,10 @@ func TestCheckForEquivocation_WithEquivocation(t *testing.T) { vote2, err := NewVoteFromHash(leaves[1], st.Block) require.NoError(t, err) - equivocated := gs.checkForEquivocation(&voter, &SignedVote{ + err = gs.checkAndReportEquivocation(&voter, &SignedVote{ Vote: *vote2, }, prevote) - require.True(t, equivocated) + require.ErrorIs(t, err, ErrEquivocation) require.Equal(t, 0, gs.lenVotes(prevote)) require.Equal(t, 1, len(gs.pvEquivocations)) @@ -120,20 +120,20 @@ func TestCheckForEquivocation_WithExistingEquivocation(t *testing.T) { vote2, err := NewVoteFromHash(leaves[0], gs.blockState) require.NoError(t, err) - equivocated := gs.checkForEquivocation(&voter, &SignedVote{ + err = gs.checkAndReportEquivocation(&voter, &SignedVote{ Vote: *vote2, }, prevote) - require.True(t, equivocated) + require.ErrorIs(t, err, ErrEquivocation) require.Equal(t, 0, gs.lenVotes(prevote)) require.Equal(t, 1, len(gs.pvEquivocations)) vote3 := vote1 - equivocated = gs.checkForEquivocation(&voter, &SignedVote{ + err = gs.checkAndReportEquivocation(&voter, &SignedVote{ Vote: *vote3, }, prevote) - require.True(t, equivocated) + require.ErrorIs(t, err, ErrEquivocation) require.Equal(t, 0, gs.lenVotes(prevote)) require.Equal(t, 1, len(gs.pvEquivocations)) @@ -283,7 +283,10 @@ func TestValidateMessage_Equivocation(t *testing.T) { _, err = gs.validateVoteMessage("", msg) require.ErrorIs(t, err, ErrEquivocation) - require.EqualError(t, err, ErrEquivocation.Error()) + require.EqualError(t, err, "checking for equivocation: vote is equivocatory: "+ + "voter 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee has "+ + "existing vote 0xfa6648885514eb4e8e5cbcbd18bdcf18a903f71b44769131604304099384e930 "+ + "and new vote 0x3d5687d4ca7f086a0e18c4730cac0e1bcfa0e1ae592b5966dcc1a5330a58d10b") } func TestValidateMessage_BlockDoesNotExist(t *testing.T) { diff --git a/lib/grandpa/vote_message_test.go b/lib/grandpa/vote_message_test.go new file mode 100644 index 0000000000..d94e0e2b20 --- /dev/null +++ b/lib/grandpa/vote_message_test.go @@ -0,0 +1,223 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package grandpa + +import ( + "errors" + "testing" + + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + errTestError = errors.New("test dummy error") + dummyHash = common.NewHash([]byte{1}) +) + +func TestService_reportEquivocation(t *testing.T) { + t.Parallel() + keyOwnershipProof := types.GrandpaOpaqueKeyOwnershipProof{1} + signedVote := &types.GrandpaSignedVote{ + Vote: *testVote, + Signature: testSignature, + AuthorityID: testAuthorityID, + } + + signedVote2 := &types.GrandpaSignedVote{ + Vote: *testVote, + Signature: testSignature, + AuthorityID: testAuthorityID, + } + + grandpaEquivocation := types.GrandpaEquivocation{ + RoundNumber: 1, + ID: testAuthorityID, + FirstVote: *testVote, + FirstSignature: testSignature, + SecondVote: *testVote, + SecondSignature: testSignature, + } + + equivocationVote := types.NewGrandpaEquivocation() + err := equivocationVote.Set(types.PreVote(grandpaEquivocation)) + require.NoError(t, err) + + equivocationProof := types.GrandpaEquivocationProof{ + SetID: uint64(1), + Equivocation: *equivocationVote, + } + type args struct { + stage Subround + existingVote *SignedVote + currentVote *SignedVote + } + tests := []struct { + name string + service *Service + serviceBuilder func(ctrl *gomock.Controller) *Service + args args + expErr error + expErrMsg string + }{ + { + name: "get_setID_error", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockGrandpaStateSetIDErr := NewMockGrandpaState(ctrl) + mockGrandpaStateSetIDErr.EXPECT().GetCurrentSetID().Return(uint64(0), errTestError) + return &Service{grandpaState: mockGrandpaStateSetIDErr} + }, + expErr: errTestError, + expErrMsg: "getting authority set id: test dummy error", + }, + { + name: "get_latest_round_error", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockGrandpaStateRoundErr := NewMockGrandpaState(ctrl) + mockGrandpaStateRoundErr.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateRoundErr.EXPECT().GetLatestRound().Return(uint64(0), errTestError) + return &Service{grandpaState: mockGrandpaStateRoundErr} + }, + expErr: errTestError, + expErrMsg: "getting latest round: test dummy error", + }, + { + name: "get_runtime_error", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockGrandpaStateOk := NewMockGrandpaState(ctrl) + mockGrandpaStateOk.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateOk.EXPECT().GetLatestRound().Return(uint64(1), nil) + mockBlockStateGetRuntimeErr := NewMockBlockState(ctrl) + mockBlockStateGetRuntimeErr.EXPECT().BestBlockHash().Return(dummyHash) + mockBlockStateGetRuntimeErr.EXPECT().GetRuntime(dummyHash).Return(nil, errTestError) + return &Service{ + grandpaState: mockGrandpaStateOk, + blockState: mockBlockStateGetRuntimeErr, + } + }, + args: args{existingVote: signedVote}, + expErr: errTestError, + expErrMsg: "getting runtime: test dummy error", + }, + { + name: "get_key_ownership_proof_error", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockRuntimeInstanceGenerateProofErr := NewMockInstance(ctrl) + mockRuntimeInstanceGenerateProofErr.EXPECT().GrandpaGenerateKeyOwnershipProof(uint64(1), testAuthorityID). + Return(types.GrandpaOpaqueKeyOwnershipProof{}, errTestError) + mockGrandpaStateOk := NewMockGrandpaState(ctrl) + mockGrandpaStateOk.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateOk.EXPECT().GetLatestRound().Return(uint64(1), nil) + mockBlockStateGenerateProofErr := NewMockBlockState(ctrl) + mockBlockStateGenerateProofErr.EXPECT().BestBlockHash().Return(dummyHash) + mockBlockStateGenerateProofErr.EXPECT().GetRuntime(dummyHash). + Return(mockRuntimeInstanceGenerateProofErr, nil) + return &Service{ + grandpaState: mockGrandpaStateOk, + blockState: mockBlockStateGenerateProofErr, + } + }, + args: args{existingVote: signedVote}, + expErr: errTestError, + expErrMsg: "getting key ownership proof: test dummy error", + }, + { + name: "invalid_stage", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockRuntimeInstanceReportEquivocationErr := NewMockInstance(ctrl) + mockRuntimeInstanceReportEquivocationErr.EXPECT().GrandpaGenerateKeyOwnershipProof(uint64(1), testAuthorityID). + Return(keyOwnershipProof, nil) + mockGrandpaStateOk := NewMockGrandpaState(ctrl) + mockGrandpaStateOk.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateOk.EXPECT().GetLatestRound().Return(uint64(1), nil) + mockBlockStateReportEquivocationErr := NewMockBlockState(ctrl) + mockBlockStateReportEquivocationErr.EXPECT().BestBlockHash().Return(dummyHash) + mockBlockStateReportEquivocationErr.EXPECT().GetRuntime(dummyHash). + Return(mockRuntimeInstanceReportEquivocationErr, nil) + return &Service{ + grandpaState: mockGrandpaStateOk, + blockState: mockBlockStateReportEquivocationErr, + } + }, + args: args{ + stage: primaryProposal, + existingVote: signedVote, + currentVote: signedVote2, + }, + expErr: errInvalidEquivocationStage, + expErrMsg: "invalid stage for equivocating: primaryProposal (2)", + }, + { + name: "submit_equivocation_proof_error", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockRuntimeInstanceReportEquivocationErr := NewMockInstance(ctrl) + mockRuntimeInstanceReportEquivocationErr.EXPECT().GrandpaGenerateKeyOwnershipProof(uint64(1), testAuthorityID). + Return(keyOwnershipProof, nil) + mockRuntimeInstanceReportEquivocationErr.EXPECT(). + GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof, keyOwnershipProof). + Return(errTestError) + mockGrandpaStateOk := NewMockGrandpaState(ctrl) + mockGrandpaStateOk.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateOk.EXPECT().GetLatestRound().Return(uint64(1), nil) + mockBlockStateReportEquivocationErr := NewMockBlockState(ctrl) + mockBlockStateReportEquivocationErr.EXPECT().BestBlockHash().Return(dummyHash) + mockBlockStateReportEquivocationErr.EXPECT().GetRuntime(dummyHash). + Return(mockRuntimeInstanceReportEquivocationErr, nil) + return &Service{ + grandpaState: mockGrandpaStateOk, + blockState: mockBlockStateReportEquivocationErr, + } + }, + args: args{ + stage: prevote, + existingVote: signedVote, + currentVote: signedVote2, + }, + expErr: errTestError, + expErrMsg: "submitting grandpa equivocation report to runtime: test dummy error", + }, + { + name: "valid_path", + serviceBuilder: func(ctrl *gomock.Controller) *Service { + mockRuntimeInstanceOk := NewMockInstance(ctrl) + mockRuntimeInstanceOk.EXPECT().GrandpaGenerateKeyOwnershipProof(uint64(1), testAuthorityID). + Return(keyOwnershipProof, nil) + mockRuntimeInstanceOk.EXPECT(). + GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof, keyOwnershipProof). + Return(nil) + mockGrandpaStateOk := NewMockGrandpaState(ctrl) + mockGrandpaStateOk.EXPECT().GetCurrentSetID().Return(uint64(1), nil) + mockGrandpaStateOk.EXPECT().GetLatestRound().Return(uint64(1), nil) + mockBlockStateOk := NewMockBlockState(ctrl) + mockBlockStateOk.EXPECT().BestBlockHash().Return(dummyHash) + mockBlockStateOk.EXPECT().GetRuntime(dummyHash).Return(mockRuntimeInstanceOk, nil) + return &Service{ + grandpaState: mockGrandpaStateOk, + blockState: mockBlockStateOk, + } + }, + args: args{ + stage: prevote, + existingVote: signedVote, + currentVote: signedVote2, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + service := tt.serviceBuilder(ctrl) + err := service.reportEquivocation(tt.args.stage, tt.args.existingVote, tt.args.currentVote) + assert.ErrorIs(t, err, tt.expErr) + if tt.expErr != nil { + assert.EqualError(t, err, tt.expErrMsg) + } + }) + } +} diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index cb6e810238..8a73adc26d 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -76,6 +76,10 @@ const ( // BabeAPISubmitReportEquivocationUnsignedExtrinsic is the runtime API call // BabeApi_submit_report_equivocation_unsigned_extrinsic BabeAPISubmitReportEquivocationUnsignedExtrinsic = "BabeApi_submit_report_equivocation_unsigned_extrinsic" + // GrandpaSubmitReportEquivocation is the runtime API call GrandpaApi_submit_report_equivocation_unsigned_extrinsic + GrandpaSubmitReportEquivocation = "GrandpaApi_submit_report_equivocation_unsigned_extrinsic" + // GrandpaGenerateKeyOwnershipProof is the runtime API call GrandpaApi_generate_key_ownership_proof + GrandpaGenerateKeyOwnershipProof = "GrandpaApi_generate_key_ownership_proof" // BabeAPIConfiguration is the runtime API call BabeApi_configuration BabeAPIConfiguration = "BabeApi_configuration" // BlockBuilderInherentExtrinsics is the runtime API call BlockBuilder_inherent_extrinsics diff --git a/lib/runtime/mocks/instance.go b/lib/runtime/mocks/instance.go new file mode 100644 index 0000000000..d4d0d5e628 --- /dev/null +++ b/lib/runtime/mocks/instance.go @@ -0,0 +1,476 @@ +// Code generated by mockery v2.14.1. DO NOT EDIT. + +package mocks + +import ( + common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + + keystore "github.com/ChainSafe/gossamer/lib/keystore" + + mock "github.com/stretchr/testify/mock" + + runtime "github.com/ChainSafe/gossamer/lib/runtime" + + transaction "github.com/ChainSafe/gossamer/lib/transaction" + + types "github.com/ChainSafe/gossamer/dot/types" +) + +// Instance is an autogenerated mock type for the Instance type +type Instance struct { + mock.Mock +} + +// ApplyExtrinsic provides a mock function with given fields: data +func (_m *Instance) ApplyExtrinsic(data types.Extrinsic) ([]byte, error) { + ret := _m.Called(data) + + var r0 []byte + if rf, ok := ret.Get(0).(func(types.Extrinsic) []byte); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(types.Extrinsic) error); ok { + r1 = rf(data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// BabeConfiguration provides a mock function with given fields: +func (_m *Instance) BabeConfiguration() (*types.BabeConfiguration, error) { + ret := _m.Called() + + var r0 *types.BabeConfiguration + if rf, ok := ret.Get(0).(func() *types.BabeConfiguration); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.BabeConfiguration) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckInherents provides a mock function with given fields: +func (_m *Instance) CheckInherents() { + _m.Called() +} + +// DecodeSessionKeys provides a mock function with given fields: enc +func (_m *Instance) DecodeSessionKeys(enc []byte) ([]byte, error) { + ret := _m.Called(enc) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(enc) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(enc) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Exec provides a mock function with given fields: function, data +func (_m *Instance) Exec(function string, data []byte) ([]byte, error) { + ret := _m.Called(function, data) + + var r0 []byte + if rf, ok := ret.Get(0).(func(string, []byte) []byte); ok { + r0 = rf(function, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, []byte) error); ok { + r1 = rf(function, data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteBlock provides a mock function with given fields: block +func (_m *Instance) ExecuteBlock(block *types.Block) ([]byte, error) { + ret := _m.Called(block) + + var r0 []byte + if rf, ok := ret.Get(0).(func(*types.Block) []byte); ok { + r0 = rf(block) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*types.Block) error); ok { + r1 = rf(block) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FinalizeBlock provides a mock function with given fields: +func (_m *Instance) FinalizeBlock() (*types.Header, error) { + ret := _m.Called() + + var r0 *types.Header + if rf, ok := ret.Get(0).(func() *types.Header); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Header) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateSessionKeys provides a mock function with given fields: +func (_m *Instance) GenerateSessionKeys() { + _m.Called() +} + +// GetCodeHash provides a mock function with given fields: +func (_m *Instance) GetCodeHash() common.Hash { + ret := _m.Called() + + var r0 common.Hash + if rf, ok := ret.Get(0).(func() common.Hash); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + return r0 +} + +// GrandpaAuthorities provides a mock function with given fields: +func (_m *Instance) GrandpaAuthorities() ([]types.Authority, error) { + ret := _m.Called() + + var r0 []types.Authority + if rf, ok := ret.Get(0).(func() []types.Authority); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Authority) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrandpaGenerateKeyOwnershipProof provides a mock function with given fields: authSetID, authorityID +func (_m *Instance) GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + ret := _m.Called(authSetID, authorityID) + + var r0 types.GrandpaOpaqueKeyOwnershipProof + if rf, ok := ret.Get(0).(func(uint64, ed25519.PublicKeyBytes) types.GrandpaOpaqueKeyOwnershipProof); ok { + r0 = rf(authSetID, authorityID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.GrandpaOpaqueKeyOwnershipProof) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(uint64, ed25519.PublicKeyBytes) error); ok { + r1 = rf(authSetID, authorityID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic provides a mock function with given fields: equivocationProof, keyOwnershipProof +func (_m *Instance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof) error { + ret := _m.Called(equivocationProof, keyOwnershipProof) + + var r0 error + if rf, ok := ret.Get(0).(func(types.GrandpaEquivocationProof, types.GrandpaOpaqueKeyOwnershipProof) error); ok { + r0 = rf(equivocationProof, keyOwnershipProof) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InherentExtrinsics provides a mock function with given fields: data +func (_m *Instance) InherentExtrinsics(data []byte) ([]byte, error) { + ret := _m.Called(data) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitializeBlock provides a mock function with given fields: header +func (_m *Instance) InitializeBlock(header *types.Header) error { + ret := _m.Called(header) + + var r0 error + if rf, ok := ret.Get(0).(func(*types.Header) error); ok { + r0 = rf(header) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Keystore provides a mock function with given fields: +func (_m *Instance) Keystore() *keystore.GlobalKeystore { + ret := _m.Called() + + var r0 *keystore.GlobalKeystore + if rf, ok := ret.Get(0).(func() *keystore.GlobalKeystore); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*keystore.GlobalKeystore) + } + } + + return r0 +} + +// Metadata provides a mock function with given fields: +func (_m *Instance) Metadata() ([]byte, error) { + ret := _m.Called() + + var r0 []byte + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NetworkService provides a mock function with given fields: +func (_m *Instance) NetworkService() runtime.BasicNetwork { + ret := _m.Called() + + var r0 runtime.BasicNetwork + if rf, ok := ret.Get(0).(func() runtime.BasicNetwork); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.BasicNetwork) + } + } + + return r0 +} + +// NodeStorage provides a mock function with given fields: +func (_m *Instance) NodeStorage() runtime.NodeStorage { + ret := _m.Called() + + var r0 runtime.NodeStorage + if rf, ok := ret.Get(0).(func() runtime.NodeStorage); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(runtime.NodeStorage) + } + + return r0 +} + +// OffchainWorker provides a mock function with given fields: +func (_m *Instance) OffchainWorker() { + _m.Called() +} + +// PaymentQueryInfo provides a mock function with given fields: ext +func (_m *Instance) PaymentQueryInfo(ext []byte) (*types.RuntimeDispatchInfo, error) { + ret := _m.Called(ext) + + var r0 *types.RuntimeDispatchInfo + if rf, ok := ret.Get(0).(func([]byte) *types.RuntimeDispatchInfo); ok { + r0 = rf(ext) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.RuntimeDispatchInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(ext) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RandomSeed provides a mock function with given fields: +func (_m *Instance) RandomSeed() { + _m.Called() +} + +// SetContextStorage provides a mock function with given fields: s +func (_m *Instance) SetContextStorage(s runtime.Storage) { + _m.Called(s) +} + +// Stop provides a mock function with given fields: +func (_m *Instance) Stop() { + _m.Called() +} + +// UpdateRuntimeCode provides a mock function with given fields: _a0 +func (_m *Instance) UpdateRuntimeCode(_a0 []byte) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func([]byte) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ValidateTransaction provides a mock function with given fields: e +func (_m *Instance) ValidateTransaction(e types.Extrinsic) (*transaction.Validity, error) { + ret := _m.Called(e) + + var r0 *transaction.Validity + if rf, ok := ret.Get(0).(func(types.Extrinsic) *transaction.Validity); ok { + r0 = rf(e) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*transaction.Validity) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(types.Extrinsic) error); ok { + r1 = rf(e) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Validator provides a mock function with given fields: +func (_m *Instance) Validator() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Version provides a mock function with given fields: +func (_m *Instance) Version() runtime.Version { + ret := _m.Called() + + var r0 runtime.Version + if rf, ok := ret.Get(0).(func() runtime.Version); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(runtime.Version) + } + + return r0 +} + +type mockConstructorTestingTNewInstance interface { + mock.TestingT + Cleanup(func()) +} + +// NewInstance creates a new instance of Instance. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewInstance(t mockConstructorTestingTNewInstance) *Instance { + mock := &Instance{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index 1109cdf294..c5f2629105 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -9,6 +9,7 @@ import ( types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" + ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" @@ -210,6 +211,35 @@ func (mr *MockInstanceMockRecorder) GrandpaAuthorities() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaAuthorities", reflect.TypeOf((*MockInstance)(nil).GrandpaAuthorities)) } +// GrandpaGenerateKeyOwnershipProof mocks base method. +func (m *MockInstance) GrandpaGenerateKeyOwnershipProof(arg0 uint64, arg1 ed25519.PublicKeyBytes) (types.GrandpaOpaqueKeyOwnershipProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaGenerateKeyOwnershipProof", arg0, arg1) + ret0, _ := ret[0].(types.GrandpaOpaqueKeyOwnershipProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GrandpaGenerateKeyOwnershipProof indicates an expected call of GrandpaGenerateKeyOwnershipProof. +func (mr *MockInstanceMockRecorder) GrandpaGenerateKeyOwnershipProof(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaGenerateKeyOwnershipProof", reflect.TypeOf((*MockInstance)(nil).GrandpaGenerateKeyOwnershipProof), arg0, arg1) +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic mocks base method. +func (m *MockInstance) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0 types.GrandpaEquivocationProof, arg1 types.GrandpaOpaqueKeyOwnershipProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic indicates an expected call of GrandpaSubmitReportEquivocationUnsignedExtrinsic. +func (mr *MockInstanceMockRecorder) GrandpaSubmitReportEquivocationUnsignedExtrinsic(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrandpaSubmitReportEquivocationUnsignedExtrinsic", reflect.TypeOf((*MockInstance)(nil).GrandpaSubmitReportEquivocationUnsignedExtrinsic), arg0, arg1) +} + // InherentExtrinsics mocks base method. func (m *MockInstance) InherentExtrinsics(arg0 []byte) ([]byte, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/test_helpers.go b/lib/runtime/test_helpers.go index 355c98f05c..e9aea6bcb5 100644 --- a/lib/runtime/test_helpers.go +++ b/lib/runtime/test_helpers.go @@ -289,6 +289,11 @@ type Instance interface { BabeGenerateKeyOwnershipProof(slot uint64, offenderPublicKey [32]byte) (types.OpaqueKeyOwnershipProof, error) BabeSubmitReportEquivocationUnsignedExtrinsic(types.BabeEquivocationProof, types.OpaqueKeyOwnershipProof) error CheckInherents() + GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) + GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, + ) error RandomSeed() OffchainWorker() GenerateSessionKeys() diff --git a/lib/runtime/wasmer/exports.go b/lib/runtime/wasmer/exports.go index a86eefc3cb..b7298cb574 100644 --- a/lib/runtime/wasmer/exports.go +++ b/lib/runtime/wasmer/exports.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/pkg/scale" @@ -277,6 +278,55 @@ func (in *Instance) QueryCallFeeDetails(ext []byte) (*types.FeeDetails, error) { // TODO: use this in block verification process (#1873) func (in *Instance) CheckInherents() {} +// GrandpaGenerateKeyOwnershipProof returns grandpa key ownership proof from the runtime. +func (in *Instance) GrandpaGenerateKeyOwnershipProof(authSetID uint64, authorityID ed25519.PublicKeyBytes) ( + types.GrandpaOpaqueKeyOwnershipProof, error) { + const bufferSize = 8 + 32 // authSetID uint64 + ed25519.PublicKeyBytes + buffer := bytes.NewBuffer(make([]byte, 0, bufferSize)) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(authSetID) + if err != nil { + return nil, fmt.Errorf("encoding auth set id: %w", err) + } + err = encoder.Encode(authorityID) + if err != nil { + return nil, fmt.Errorf("encoding authority id: %w", err) + } + encodedOpaqueKeyOwnershipProof, err := in.Exec(runtime.GrandpaGenerateKeyOwnershipProof, buffer.Bytes()) + if err != nil { + return nil, err + } + + keyOwnershipProof := types.GrandpaOpaqueKeyOwnershipProof{} + err = scale.Unmarshal(encodedOpaqueKeyOwnershipProof, &keyOwnershipProof) + if err != nil { + return nil, fmt.Errorf("scale decoding: %w", err) + } + + return keyOwnershipProof, nil +} + +// GrandpaSubmitReportEquivocationUnsignedExtrinsic reports an equivocation report to the runtime. +func (in *Instance) GrandpaSubmitReportEquivocationUnsignedExtrinsic( + equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, +) error { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(equivocationProof) + if err != nil { + return fmt.Errorf("encoding equivocation proof: %w", err) + } + err = encoder.Encode(keyOwnershipProof) + if err != nil { + return fmt.Errorf("encoding key ownership proof: %w", err) + } + _, err = in.Exec(runtime.GrandpaSubmitReportEquivocation, buffer.Bytes()) + if err != nil { + return err + } + return nil +} + func (in *Instance) RandomSeed() {} //nolint:revive func (in *Instance) OffchainWorker() {} //nolint:revive func (in *Instance) GenerateSessionKeys() {} //nolint:revive diff --git a/lib/runtime/wasmer/exports_test.go b/lib/runtime/wasmer/exports_test.go index b5e63b63cd..af70fdab6e 100644 --- a/lib/runtime/wasmer/exports_test.go +++ b/lib/runtime/wasmer/exports_test.go @@ -1278,3 +1278,66 @@ func TestInstance_TransactionPaymentCallApi_QueryCallFeeDetails(t *testing.T) { require.Equal(t, test.expect, details) } } + +func TestInstance_GrandpaGenerateKeyOwnershipProof(t *testing.T) { + t.Parallel() + instance := NewTestInstance(t, runtime.WESTEND_RUNTIME_v0929) + identity := common.MustHexToBytes("0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee") + identityPubKey, _ := ed25519.NewPublicKey(identity) + authorityID := identityPubKey.AsBytes() + + encodedOpaqueKeyOwnershipProof, err := instance.GrandpaGenerateKeyOwnershipProof(uint64(0), authorityID) + require.NoError(t, err) + // Since the input is not valid with respect to the instance, an empty proof is returned + require.Empty(t, encodedOpaqueKeyOwnershipProof) +} + +func TestInstance_GrandpaSubmitReportEquivocationUnsignedExtrinsic(t *testing.T) { + t.Parallel() + identity := common.MustHexToBytes("0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee") + identityPubKey, _ := ed25519.NewPublicKey(identity) + runtime := NewTestInstance(t, runtime.WESTEND_RUNTIME_v0929) + + keyOwnershipProofRaw := types.GrandpaOpaqueKeyOwnershipProof([]byte{64, 138, 252, 29, 127, 102, 189, 129, 207, 47, + 157, 60, 17, 138, 194, 121, 139, 92, 176, 175, 224, 16, 185, 93, 175, 251, 224, 81, 209, 61, 0, 71}) + keyOwnershipProof := scale.MustMarshal(keyOwnershipProofRaw) + + var opaqueKeyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof + err := scale.Unmarshal(keyOwnershipProof, &opaqueKeyOwnershipProof) + require.NoError(t, err) + + firstVote := types.GrandpaVote{ + Hash: common.MustHexToHash("0x4801b8e62d31167d30c893cc1970f6a0e289420282a4b245b75f2c46fb308af1"), + Number: 10, + } + secondVote := types.GrandpaVote{ + Hash: common.MustHexToHash("0xc314327941fdd924bc67fd72651c40aececd485ca3e878c21e02abb40feae5bd"), + Number: 10, + } + + firstSignatureArray := mustHexTo64BArray(t, "0xd7292caacc62504365f179892a7399f233944bf261f8a3f66260f70e0016f2d"+ + "b63922726b015c82dc7131f4730fbec61f71672a571453e51029bfb469070900f") + + secondSignatureArray := mustHexTo64BArray(t, "0xb3c408b74905dfedfffa66f99f16fe8b938fd8df76a92225228a1ca07523"+ + "0b99a2d9e173c561952e1e378b701915ca188d2c832ef92a3fab8e455f32570c0807") + + grandpaEquivocation := types.GrandpaEquivocation{ + RoundNumber: 1, + ID: identityPubKey.AsBytes(), + FirstVote: firstVote, + FirstSignature: firstSignatureArray, + SecondVote: secondVote, + SecondSignature: secondSignatureArray, + } + preVoteEquivocation := types.PreVote(grandpaEquivocation) + equivocationVote := types.NewGrandpaEquivocation() + err = equivocationVote.Set(preVoteEquivocation) + require.NoError(t, err) + + equivocationProof := types.GrandpaEquivocationProof{ + SetID: 1, + Equivocation: *equivocationVote, + } + err = runtime.GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof, opaqueKeyOwnershipProof) + require.NoError(t, err) +} diff --git a/lib/runtime/wasmer/test_helpers.go b/lib/runtime/wasmer/test_helpers.go index e01a519562..ac5c5a900d 100644 --- a/lib/runtime/wasmer/test_helpers.go +++ b/lib/runtime/wasmer/test_helpers.go @@ -21,6 +21,12 @@ import ( // DefaultTestLogLvl is the log level used for test runtime instances var DefaultTestLogLvl = log.Info +func mustHexTo64BArray(t *testing.T, inputHex string) (outputArray [64]byte) { + t.Helper() + copy(outputArray[:], common.MustHexToBytes(inputHex)) + return outputArray +} + // NewTestInstance will create a new runtime instance using the given target runtime func NewTestInstance(t *testing.T, targetRuntime string) *Instance { t.Helper()