Skip to content

Commit

Permalink
feat(dot/parachain/backing): handle second incoming overseer message (
Browse files Browse the repository at this point in the history
#3682)

The `Second` message is a message received from the overseer.
Candidate Backing subsystem should second the given candidate in the context of the given relay parent.
This candidate must be validated.

Sign and dispatch a `Seconded` statement only if we have not signed a Valid statement for the requested candidate.
  • Loading branch information
axaysagathiya authored and timwu20 committed Jun 20, 2024
1 parent d093b34 commit 40ae213
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 6 deletions.
6 changes: 1 addition & 5 deletions dot/parachain/backing/candidate_backing.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func (cb *CandidateBacking) processMessage(msg any, chRelayParentAndCommand chan
logger.Debug(fmt.Sprintf("can't second the candidate: %s", err))
}
case SecondMessage:
cb.handleSecondMessage()
return cb.handleSecondMessage(msg.CandidateReceipt, msg.PersistedValidationData, msg.PoV, chRelayParentAndCommand)
case StatementMessage:
return cb.handleStatementMessage(msg.RelayParent, msg.SignedFullStatement, chRelayParentAndCommand)
case parachaintypes.ActiveLeavesUpdateSignal:
Expand All @@ -249,10 +249,6 @@ func (cb *CandidateBacking) handleGetBackedCandidatesMessage() {
// TODO: Implement this #3504
}

func (cb *CandidateBacking) handleSecondMessage() {
// TODO: Implement this #3506
}

// Import the statement and kick off validation work if it is a part of our assignment.
func (cb *CandidateBacking) handleStatementMessage(
relayParent common.Hash,
Expand Down
88 changes: 88 additions & 0 deletions dot/parachain/backing/second.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package backing

import (
"errors"
"fmt"

parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
)

var (
errWrongPVDForSecondingCandidate = errors.New(
"incorrect persisted validation data provided for seconding candidate")
errUnknownRelayParentForSecondingCandidate = errors.New(
"attempted to second a candidate with an unknown relay parent")
errParaOutsideAssignmentForSeconding = errors.New(
"subsystem requested to second for parachain beyond our assignment scope")
errAlreadySignedValidStatement = errors.New("already signed a valid statement for this candidate")
)

func (cb *CandidateBacking) handleSecondMessage(
candidateReceipt parachaintypes.CandidateReceipt,
pvd parachaintypes.PersistedValidationData,
pov parachaintypes.PoV,
chRelayParentAndCommand chan relayParentAndCommand,
) error {
hash, err := candidateReceipt.Hash()
if err != nil {
return fmt.Errorf("hashing candidate receipt: %w", err)
}
candidateHash := parachaintypes.CandidateHash{Value: hash}

pvdHash, err := pvd.Hash()
if err != nil {
return fmt.Errorf("hashing persisted validation data: %w", err)
}

if candidateReceipt.Descriptor.PersistedValidationDataHash != pvdHash {
return fmt.Errorf("%w; mismatch between persisted validation data hash in candidate descriptor: %s "+
"and calculated hash of given persisted validation data: %s",
errWrongPVDForSecondingCandidate,
candidateReceipt.Descriptor.PersistedValidationDataHash,
pvdHash,
)
}

rpState, ok := cb.perRelayParent[candidateReceipt.Descriptor.RelayParent]
if !ok {
return fmt.Errorf("%w; candidate hash: %s; relay parent: %s",
errUnknownRelayParentForSecondingCandidate,
candidateHash,
candidateReceipt.Descriptor.RelayParent,
)
}

// Sanity check that candidate is from our assignment.
if candidateReceipt.Descriptor.ParaID != uint32(rpState.assignment) {
return fmt.Errorf("%w: candidate hash: %s; candidate paraID: %d; assignment: %d",
errParaOutsideAssignmentForSeconding,
candidateHash,
candidateReceipt.Descriptor.ParaID,
rpState.assignment,
)
}

// If the message is a `CandidateBackingMessage::Second`, sign and dispatch a
// Seconded statement only if we have not signed a Valid statement for the requested candidate.
if rpState.issuedStatements[candidateHash] {
return fmt.Errorf("%w: candidate hash: %s", errAlreadySignedValidStatement, candidateHash)
}

// Kick off background validation with intent to second.
logger.Debugf("validate and second candidate: %s", candidateHash)
return rpState.validateAndMakeAvailable(
executorParamsAtRelayParent,
cb.SubSystemToOverseer,
chRelayParentAndCommand,
candidateReceipt,
rpState.relayParent,
pvd,
pov,
uint32(len(rpState.tableContext.validators)),
second,
candidateHash,
)
}
133 changes: 133 additions & 0 deletions dot/parachain/backing/second_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package backing

import (
"testing"

parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/stretchr/testify/require"
)

func TestHandleSecondMessage(t *testing.T) {
t.Parallel()

testCases := []struct {
description string
cb *CandidateBacking
candidateReceipt parachaintypes.CandidateReceipt
pvd parachaintypes.PersistedValidationData
err error
}{
{
description: "wrong_persisted_validation_data_for_seconding_candidate",
cb: &CandidateBacking{},
candidateReceipt: parachaintypes.CandidateReceipt{},
pvd: parachaintypes.PersistedValidationData{},
err: errWrongPVDForSecondingCandidate,
},
{
description: "unknown_relay_parent_for_seconding_candidate",
cb: &CandidateBacking{},
candidateReceipt: dummyCandidateReceipt(t),
pvd: dummyPVD(t),
err: errUnknownRelayParentForSecondingCandidate,
},
{
description: "parachain_outside_assignment_for_seconding",
cb: &CandidateBacking{
perRelayParent: map[common.Hash]*perRelayParentState{
getDummyHash(t, 6): {
assignment: 10,
},
},
},
candidateReceipt: dummyCandidateReceipt(t),
pvd: dummyPVD(t),
err: errParaOutsideAssignmentForSeconding,
},
{
description: "already_signed_valid_statement_for_candidate",
cb: &CandidateBacking{
perRelayParent: map[common.Hash]*perRelayParentState{
getDummyHash(t, 6): {
assignment: 1,
issuedStatements: map[parachaintypes.CandidateHash]bool{
dummyCandidateHash(t): true,
},
},
},
},
candidateReceipt: dummyCandidateReceipt(t),
pvd: dummyPVD(t),
err: errAlreadySignedValidStatement,
},
{
description: "kick_off_background_validation_with_intent_to_second",
cb: &CandidateBacking{
perRelayParent: map[common.Hash]*perRelayParentState{
getDummyHash(t, 6): {
assignment: 1,
awaitingValidation: map[parachaintypes.CandidateHash]bool{
dummyCandidateHash(t): true,
},
},
},
},
candidateReceipt: dummyCandidateReceipt(t),
pvd: dummyPVD(t),
err: nil,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
t.Parallel()

err := tc.cb.handleSecondMessage(tc.candidateReceipt, tc.pvd, parachaintypes.PoV{}, nil)
if err == nil {
require.NoError(t, err)
} else {
require.ErrorIs(t, err, tc.err)
}
})
}
}

func dummyPVD(t *testing.T) parachaintypes.PersistedValidationData {
t.Helper()

return parachaintypes.PersistedValidationData{
ParentHead: parachaintypes.HeadData{
Data: []byte{1, 2, 3},
},
RelayParentNumber: 5,
RelayParentStorageRoot: getDummyHash(t, 5),
MaxPovSize: 3,
}
}

func dummyCandidateReceipt(t *testing.T) parachaintypes.CandidateReceipt {
t.Helper()

cr := getDummyCommittedCandidateReceipt(t).ToPlain()

// blake2bhash of PVD in dummyPVD(t *testing.T) function
cr.Descriptor.PersistedValidationDataHash =
common.MustHexToHash("0x3544fbcdcb094751a5e044a30b994b2586ffc0b50e8b88c381461fe023a7242f")

return cr
}

func dummyCandidateHash(t *testing.T) parachaintypes.CandidateHash {
t.Helper()

cr := dummyCandidateReceipt(t)
hash, err := cr.Hash()
require.NoError(t, err)

return parachaintypes.CandidateHash{Value: hash}
}
2 changes: 1 addition & 1 deletion dot/parachain/backing/validated_candidate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type validatedCandidateCommand byte

const (
// We were instructed to second the candidate that has been already validated.
second = validatedCandidateCommand(iota) //nolint:unused
second = validatedCandidateCommand(iota)
// We were instructed to validate the candidate.
attest
// We were not able to `Attest` because backing validator did not send us the PoV.
Expand Down
13 changes: 13 additions & 0 deletions dot/parachain/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,15 @@ type PersistedValidationData struct {
MaxPovSize uint32 `scale:"4"`
}

func (pvd PersistedValidationData) Hash() (common.Hash, error) {
bytes, err := scale.Marshal(pvd)
if err != nil {
return common.Hash{}, fmt.Errorf("marshalling PersistedValidationData: %w", err)
}

return common.Blake2bHash(bytes)
}

type OccupiedCoreAssumptionValues interface {
IncludedOccupiedCoreAssumption | TimedOutOccupiedCoreAssumption | FreeOccupiedCoreAssumption
}
Expand Down Expand Up @@ -571,6 +580,10 @@ type CandidateHash struct {
Value common.Hash `scale:"1"`
}

func (ch CandidateHash) String() string {
return ch.Value.String()
}

// PoV represents a Proof-of-Validity block (PoV block) or a parachain block.
// It contains the necessary data for the parachain specific state transition logic.
type PoV struct {
Expand Down

0 comments on commit 40ae213

Please sign in to comment.