Skip to content

Commit

Permalink
ensure conflictng statments treated as misbehavior
Browse files Browse the repository at this point in the history
  • Loading branch information
axaysagathiya committed Sep 6, 2024
1 parent cd5a2bf commit 6f5d059
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 12 deletions.
192 changes: 189 additions & 3 deletions dot/parachain/backing/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ func TestCandidateReachesQuorum(t *testing.T) {
Return(&validationCode, nil)
mockRuntime.EXPECT().
ParachainHostSessionExecutorParams(gomock.AssignableToTypeOf(parachaintypes.SessionIndex(0))).
Return(nil, wazero_runtime.ErrExportFunctionNotFound).Times(1)
Return(nil, wazero_runtime.ErrExportFunctionNotFound)

//mock ImplicitView
mockImplicitView.EXPECT().AllAllowedRelayParents().
Expand Down Expand Up @@ -673,7 +673,7 @@ func TestValidationFailDoesNotStopSubsystem(t *testing.T) {
Return(&validationCode, nil)
mockRuntime.EXPECT().
ParachainHostSessionExecutorParams(gomock.AssignableToTypeOf(parachaintypes.SessionIndex(0))).
Return(nil, wazero_runtime.ErrExportFunctionNotFound).Times(1)
Return(nil, wazero_runtime.ErrExportFunctionNotFound)

//mock ImplicitView
mockImplicitView.EXPECT().AllAllowedRelayParents().
Expand Down Expand Up @@ -952,7 +952,7 @@ func TestNewLeafDoesNotClobberOld(t *testing.T) {
Return(&validationCode, nil)
mockRuntime.EXPECT().
ParachainHostSessionExecutorParams(gomock.AssignableToTypeOf(parachaintypes.SessionIndex(0))).
Return(nil, wazero_runtime.ErrExportFunctionNotFound).Times(1)
Return(nil, wazero_runtime.ErrExportFunctionNotFound)

//mock ImplicitView
mockImplicitView.EXPECT().AllAllowedRelayParents().
Expand Down Expand Up @@ -1034,3 +1034,189 @@ func TestNewLeafDoesNotClobberOld(t *testing.T) {

time.Sleep(1 * time.Second)
}

// Issuing conflicting statements on the same candidate should be a misbehaviour.
func TestConflictingStatementIsMisbehavior(t *testing.T) {
candidateBacking, overseer := initBackingAndOverseerMock(t)
defer stopOverseerAndWaitForCompletion(overseer)

paraValidators := parachainValidators(t, candidateBacking.Keystore)
numOfValidators := uint(len(paraValidators))
relayParent := getDummyHash(t, 5)
paraID := uint32(1)

pov := parachaintypes.PoV{BlockData: []byte{1, 2, 3}}
povHash, err := pov.Hash()
require.NoError(t, err)

pvd := dummyPVD(t)
validationCode := parachaintypes.ValidationCode{1, 2, 3}

signingContext := signingContext(t)

ctrl := gomock.NewController(t)
mockBlockState := backing.NewMockBlockState(ctrl)
mockRuntime := backing.NewMockInstance(ctrl)
mockImplicitView := backing.NewMockImplicitView(ctrl)

candidateBacking.BlockState = mockBlockState
candidateBacking.ImplicitView = mockImplicitView

// mock BlockState methods
mockBlockState.EXPECT().GetRuntime(gomock.AssignableToTypeOf(common.Hash{})).
Return(mockRuntime, nil).Times(3)

// mock Runtime Instance methods
mockRuntime.EXPECT().ParachainHostAsyncBackingParams().
Return(nil, wazero_runtime.ErrExportFunctionNotFound)
mockRuntime.EXPECT().ParachainHostSessionIndexForChild().
Return(parachaintypes.SessionIndex(1), nil).Times(2)
mockRuntime.EXPECT().ParachainHostValidators().
Return(paraValidators, nil)
mockRuntime.EXPECT().ParachainHostValidatorGroups().
Return(validatorGroups(t), nil)
mockRuntime.EXPECT().ParachainHostAvailabilityCores().
Return(availabilityCores(t), nil)
mockRuntime.EXPECT().ParachainHostMinimumBackingVotes().
Return(backing.LEGACY_MIN_BACKING_VOTES, nil)
mockRuntime.EXPECT().ParachainHostValidationCodeByHash(gomock.AssignableToTypeOf(common.Hash{})).
Return(&validationCode, nil)
mockRuntime.EXPECT().
ParachainHostSessionExecutorParams(gomock.AssignableToTypeOf(parachaintypes.SessionIndex(0))).
Return(nil, wazero_runtime.ErrExportFunctionNotFound)

//mock ImplicitView
mockImplicitView.EXPECT().AllAllowedRelayParents().
Return([]common.Hash{})

// to make entry in perRelayParent map
overseer.ReceiveMessage(parachaintypes.ActiveLeavesUpdateSignal{
Activated: &parachaintypes.ActivatedLeaf{Hash: relayParent, Number: 1},
})
time.Sleep(500 * time.Millisecond)

headData := parachaintypes.HeadData{Data: []byte{4, 5, 6}}

candidate := newCommittedCandidate(
t,
paraID,
headData,
povHash,
relayParent,
makeErasureRoot(t, numOfValidators, pov, pvd),
common.Hash{},
validationCode,
)

statementSeconded := parachaintypes.NewStatementVDT()
err = statementSeconded.SetValue(parachaintypes.Seconded(candidate))
require.NoError(t, err)

statementSecondedSign, err := statementSeconded.Sign(candidateBacking.Keystore, signingContext, paraValidators[2])
require.NoError(t, err)

signedStatementSeconded := parachaintypes.SignedFullStatementWithPVD{
SignedFullStatement: parachaintypes.SignedFullStatement{
Payload: statementSeconded,
ValidatorIndex: 2,
Signature: *statementSecondedSign,
},
PersistedValidationData: &pvd,
}

fetchPov := func(msg any) bool {
fetch, ok := msg.(parachaintypes.AvailabilityDistributionMessageFetchPoV)
if !ok {
return false
}

fetch.PovCh <- parachaintypes.OverseerFuncRes[parachaintypes.PoV]{Data: pov}
return true
}

validate := validResponseForValidateFromExhaustive(headData, pvd)

distribute := func(msg any) bool {
_, ok := msg.(parachaintypes.StatementDistributionMessageShare)
return ok
}

provisionerMessageProvisionableData := func(msg any) bool {
_, ok := msg.(parachaintypes.ProvisionerMessageProvisionableData)
return ok
}

// set expected actions for overseer messages we send from the subsystem.
overseer.ExpectActions(fetchPov, validate, storeAvailableData, distribute, provisionerMessageProvisionableData)

// receive statement message from overseer to candidate backing subsystem containing `Seconded` statement
overseer.ReceiveMessage(backing.StatementMessage{
RelayParent: relayParent,
SignedFullStatement: signedStatementSeconded,
})
time.Sleep(1 * time.Second)

candidateHash, err := parachaintypes.GetCandidateHash(candidate)
require.NoError(t, err)

statementValid := parachaintypes.NewStatementVDT()
err = statementValid.SetValue(parachaintypes.Valid(candidateHash))
require.NoError(t, err)

statementValidSign, err := statementValid.Sign(candidateBacking.Keystore, signingContext, paraValidators[2])
require.NoError(t, err)

signedStatementValid := parachaintypes.SignedFullStatementWithPVD{
SignedFullStatement: parachaintypes.SignedFullStatement{
Payload: statementValid,
ValidatorIndex: 2,
Signature: *statementValidSign,
},
}

reportMisbehavior := func(msg any) bool {
provisionerMessage, ok := msg.(parachaintypes.ProvisionerMessageProvisionableData)
if !ok {
return false
}

require.Equal(t, relayParent, provisionerMessage.RelayParent)
misbehvaiorReport, ok := provisionerMessage.ProvisionableData.(parachaintypes.ProvisionableDataMisbehaviorReport)
require.True(t, ok)

require.Equal(t, parachaintypes.ValidatorIndex(2), misbehvaiorReport.ValidatorIndex)
doubleVote, ok := misbehvaiorReport.Misbehaviour.(parachaintypes.ValidityDoubleVoteIssuedAndValidity)
require.True(t, ok)

signForSeconded := doubleVote.CommittedCandidateReceiptAndSign.Signature
statementSeconded := parachaintypes.NewStatementVDT()
err := statementSeconded.SetValue(
parachaintypes.Seconded(doubleVote.CommittedCandidateReceiptAndSign.CommittedCandidateReceipt))
require.NoError(t, err)

ok, err = statementSeconded.VerifySignature(paraValidators[2], signingContext, signForSeconded)
require.NoError(t, err)
require.True(t, ok)

signForValid := doubleVote.CandidateHashAndSign.Signature
statementValid := parachaintypes.NewStatementVDT()
err = statementValid.SetValue(parachaintypes.Valid(doubleVote.CandidateHashAndSign.CandidateHash))
require.NoError(t, err)

ok, err = statementValid.VerifySignature(paraValidators[2], signingContext, signForValid)
require.NoError(t, err)
require.True(t, ok)

return true
}

overseer.ExpectActions(reportMisbehavior)

// receive statement message from overseer to candidate backing subsystem containing `Valid` statement.
// this candidate is already seconded by the same validator So, it is a misbehaviour for conflicting statements.
overseer.ReceiveMessage(backing.StatementMessage{
RelayParent: relayParent,
SignedFullStatement: signedStatementValid,
})
time.Sleep(1 * time.Second)
}
32 changes: 31 additions & 1 deletion dot/parachain/types/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ func (s *StatementVDT) Sign(
return &valSign, nil
}

// VerifySignature verifies the validator signature for the statement.
func (s *StatementVDT) VerifySignature(
validator ValidatorID,
signingContext SigningContext,
validatorSignature ValidatorSignature,
) (bool, error) {
encodedMsg, err := scale.Marshal(s)
if err != nil {
return false, fmt.Errorf("marshalling statementVDT: %w", err)
}

signingContextBytes, err := scale.Marshal(signingContext)
if err != nil {
return false, fmt.Errorf("marshalling signing context: %w", err)
}

encodedMsg = append(encodedMsg, signingContextBytes...)

publicKey, err := sr25519.NewPublicKey(validator[:])
if err != nil {
return false, fmt.Errorf("getting public key: %w", err)
}

ok, err := publicKey.Verify(encodedMsg, validatorSignature[:])
return ok, err
}

// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified.
type UncheckedSignedFullStatement struct {
// The payload is part of the signed data. The rest is the signing context,
Expand Down Expand Up @@ -143,6 +170,9 @@ type SignedFullStatement UncheckedSignedFullStatement

// SignedFullStatementWithPVD represents a signed full statement along with associated Persisted Validation Data (PVD).
type SignedFullStatementWithPVD struct {
SignedFullStatement SignedFullStatement
SignedFullStatement SignedFullStatement

// PersistedValidationData must be set only for `Seconded` statement.
// otherwise, it should be nil.
PersistedValidationData *PersistedValidationData
}
9 changes: 1 addition & 8 deletions dot/parachain/types/statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,7 @@ func TestStatementVDT_Sign(t *testing.T) {
valSign, err := statement.Sign(ks, signingContext, validatorID)
require.NoError(t, err)

encodedMsg, err := scale.Marshal(statement)
require.NoError(t, err)

signingContextBytes, err := scale.Marshal(signingContext)
require.NoError(t, err)

encodedMsg = append(encodedMsg, signingContextBytes...)
ok, err := keyPair.Public().Verify(encodedMsg, valSign[:])
ok, err := statement.VerifySignature(validatorID, signingContext, *valSign)
require.NoError(t, err)
require.True(t, ok)
}

0 comments on commit 6f5d059

Please sign in to comment.