Skip to content

Commit

Permalink
feat(prospective-parachains): Implement `HandleIntroduceSecondedCandi…
Browse files Browse the repository at this point in the history
…date` (#4350)
  • Loading branch information
rebonat0 authored Jan 16, 2025
1 parent 84c3df0 commit e0be44f
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 7 deletions.
12 changes: 7 additions & 5 deletions dot/parachain/prospective-parachains/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
)

var (
errCandidateAlreadyKnown = errors.New("candidate already known")
errZeroLengthCycle = errors.New("candidate's parent head is equal to its output head. Would introduce a cycle") //nolint:lll
errCycle = errors.New("candidate would introduce a cycle")
errMultiplePaths = errors.New("candidate would introduce two paths to the same output state")
errIntroduceBackedCandidate = errors.New("attempting to directly introduce a Backed candidate. It should first be introduced as Seconded") //nolint:lll,unused
errCandidateAlreadyKnown = errors.New("candidate already known")
errZeroLengthCycle = errors.New("candidate's parent head is equal to its output head. Would introduce a cycle") //nolint:lll
errCycle = errors.New("candidate would introduce a cycle")
errMultiplePaths = errors.New("candidate would introduce two paths to the same output state")
errIntroduceBackedCandidate = errors.New(
"attempting to directly introduce a Backed candidate. It should first be introduced as Seconded",
)
errParentCandidateNotFound = errors.New("could not find parent of the candidate")
errRelayParentMovedBackwards = errors.New("relay parent would move backwards from the latest candidate in the chain") //nolint:lll
errPersistedValidationDataMismatch = errors.New("candidate does not match the persisted validation data provided alongside it") //nolint:lll
Expand Down
2 changes: 1 addition & 1 deletion dot/parachain/prospective-parachains/fragment_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ func (f *fragmentChain) canAddCandidateAsPotential(entry *candidateEntry) error
// tryAddingSecondedCandidate tries to add a candidate as a seconded candidate, if the
// candidate has potential. It will never be added to the chain directly in the seconded
// state, it will only be part of the unconnected storage
func (f *fragmentChain) tryAddingSecondedCandidate(entry *candidateEntry) error { //nolint:unused
func (f *fragmentChain) tryAddingSecondedCandidate(entry *candidateEntry) error {
if entry.state == backed {
return errIntroduceBackedCandidate
}
Expand Down
110 changes: 109 additions & 1 deletion dot/parachain/prospective-parachains/prospective-parachains.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ import (
"context"
"errors"

"github.com/ChainSafe/gossamer/dot/parachain/backing"
parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
"github.com/ChainSafe/gossamer/internal/log"
"github.com/ChainSafe/gossamer/lib/common"
)

var logger = log.NewFromGlobal(log.AddContext("pkg", "prospective_parachains"), log.SetLevel(log.Debug))

// Initialize with empty values.
func NewView() *view {
//nolint:lll
return &view{
perRelayParent: make(map[common.Hash]*relayParentData),
activeLeaves: make(map[common.Hash]bool),
implicitView: nil, // TODO: currently there's no implementation for ImplicitView, reference is: //nolint:lll
// https://github.com/paritytech/polkadot-sdk/blob/028e61be43f05f6f6c88c5cca94160f8db075585/polkadot/node/subsystem-util/src/backing_implicit_view.rs#L40 //nolint:lll
}
}

type ProspectiveParachains struct {
SubsystemToOverseer chan<- any
View *view
Expand All @@ -19,6 +31,7 @@ type ProspectiveParachains struct {
type view struct {
activeLeaves map[common.Hash]bool
perRelayParent map[common.Hash]*relayParentData
implicitView backing.ImplicitView
}

type relayParentData struct {
Expand All @@ -34,6 +47,7 @@ func (*ProspectiveParachains) Name() parachaintypes.SubSystemName {
func NewProspectiveParachains(overseerChan chan<- any) *ProspectiveParachains {
prospectiveParachain := ProspectiveParachains{
SubsystemToOverseer: overseerChan,
View: NewView(),
}
return &prospectiveParachain
}
Expand Down Expand Up @@ -64,7 +78,11 @@ func (pp *ProspectiveParachains) processMessage(msg any) {
case parachaintypes.BlockFinalizedSignal:
_ = pp.ProcessBlockFinalizedSignal(msg)
case IntroduceSecondedCandidate:
panic("not implemented yet: see issue #4308")
pp.introduceSecondedCandidate(
pp.View,
msg.IntroduceSecondedCandidateRequest,
msg.Response,
)
case CandidateBacked:
panic("not implemented yet: see issue #4309")
case GetBackableCandidates:
Expand All @@ -82,6 +100,96 @@ func (pp *ProspectiveParachains) processMessage(msg any) {

}

func (pp *ProspectiveParachains) introduceSecondedCandidate(
view *view,
request IntroduceSecondedCandidateRequest,
response chan bool,
) {
defer close(response)

para := request.CandidateParaID
candidate := request.CandidateReceipt
pvd := request.PersistedValidationData

hash, err := candidate.Hash()

if err != nil {
logger.Tracef("hashing candidate: %s", err.Error())
response <- false
return
}

candidateHash := parachaintypes.CandidateHash{Value: hash}

entry, err := newCandidateEntry(
candidateHash,
candidate,
pvd,
seconded,
)

if err != nil {
logger.Tracef("adding seconded candidate error: %s para: %v", err.Error(), para)
response <- false
return
}

added := make([]common.Hash, 0, len(view.perRelayParent))
paraScheduled := false

for relayParent, rpData := range view.perRelayParent {
chain, exists := rpData.fragmentChains[para]
if !exists {
continue
}

_, isActiveLeaf := view.activeLeaves[relayParent]

paraScheduled = true

err = chain.tryAddingSecondedCandidate(entry)
if err != nil {
if errors.Is(err, errCandidateAlreadyKnown) {
logger.Tracef(
"attempting to introduce an already known candidate with hash: %s, para: %v relayParent: %v isActiveLeaf: %v",
candidateHash,
para,
relayParent,
isActiveLeaf,
)
added = append(added, relayParent)
} else {
logger.Tracef(
"adding seconded candidate with hash: %s error: %s para: %v relayParent: %v isActiveLeaf: %v",
candidateHash,
err.Error(),
para,
relayParent,
isActiveLeaf,
)
}
} else {
added = append(added, relayParent)
}
}

if !paraScheduled {
logger.Warnf(
"received seconded candidate with hash: %s for inactive para: %v",
candidateHash,
para,
)
}

if len(added) == 0 {
logger.Debugf("newly-seconded candidate cannot be kept under any relay parent: %s", candidateHash)
} else {
logger.Tracef("added seconded candidate to %d relay parents: %s", len(added), candidateHash)
}

response <- len(added) > 0
}

// ProcessActiveLeavesUpdateSignal processes active leaves update signal
func (pp *ProspectiveParachains) ProcessActiveLeavesUpdateSignal(parachaintypes.ActiveLeavesUpdateSignal) error {
panic("not implemented yet: see issue #4305")
Expand Down
193 changes: 193 additions & 0 deletions dot/parachain/prospective-parachains/prospective_parachains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,206 @@ package prospectiveparachains

import (
"bytes"
"context"
"testing"

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

func introduceSecondedCandidate(
t *testing.T,
overseerToSubsystem chan any,
candidate parachaintypes.CommittedCandidateReceipt,
pvd parachaintypes.PersistedValidationData,
) {
req := IntroduceSecondedCandidateRequest{
CandidateParaID: candidate.Descriptor.ParaID,
CandidateReceipt: candidate,
PersistedValidationData: pvd,
}

response := make(chan bool)

msg := IntroduceSecondedCandidate{
IntroduceSecondedCandidateRequest: req,
Response: response,
}

overseerToSubsystem <- msg

assert.True(t, <-response)
}

func introduceSecondedCandidateFailed(
t *testing.T,
overseerToSubsystem chan any,
candidate parachaintypes.CommittedCandidateReceipt,
pvd parachaintypes.PersistedValidationData,
) {
req := IntroduceSecondedCandidateRequest{
CandidateParaID: candidate.Descriptor.ParaID,
CandidateReceipt: candidate,
PersistedValidationData: pvd,
}

response := make(chan bool)

msg := IntroduceSecondedCandidate{
IntroduceSecondedCandidateRequest: req,
Response: response,
}

overseerToSubsystem <- msg

assert.False(t, <-response)
}

func TestFailedIntroduceSecondedCandidateWhenMissingViewPerRelayParent(
t *testing.T,
) {
candidateRelayParent := common.Hash{0x01}
paraId := parachaintypes.ParaID(1)
parentHead := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x01}, 32),
}
headData := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x02}, 32),
}
validationCodeHash := parachaintypes.ValidationCodeHash{0x01}
candidateRelayParentNumber := uint32(1)

candidate := makeCandidate(
candidateRelayParent,
candidateRelayParentNumber,
paraId,
parentHead,
headData,
validationCodeHash,
)

pvd := dummyPVD(parentHead, candidateRelayParentNumber)

subsystemToOverseer := make(chan any)
overseerToSubsystem := make(chan any)

prospectiveParachains := NewProspectiveParachains(subsystemToOverseer)

go prospectiveParachains.Run(context.Background(), overseerToSubsystem)

introduceSecondedCandidateFailed(t, overseerToSubsystem, candidate, pvd)
}

func TestFailedIntroduceSecondedCandidateWhenParentHeadAndHeadDataEquals(
t *testing.T,
) {
candidateRelayParent := common.Hash{0x01}
paraId := parachaintypes.ParaID(1)
parentHead := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x01}, 32),
}
headData := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x01}, 32),
}
validationCodeHash := parachaintypes.ValidationCodeHash{0x01}
candidateRelayParentNumber := uint32(1)

candidate := makeCandidate(
candidateRelayParent,
candidateRelayParentNumber,
paraId,
parentHead,
headData,
validationCodeHash,
)

pvd := dummyPVD(parentHead, candidateRelayParentNumber)

subsystemToOverseer := make(chan any)
overseerToSubsystem := make(chan any)

prospectiveParachains := NewProspectiveParachains(subsystemToOverseer)

relayParent := relayChainBlockInfo{
Hash: candidateRelayParent,
Number: 0,
StorageRoot: common.Hash{0x00},
}

baseConstraints := &parachaintypes.Constraints{
RequiredParent: parachaintypes.HeadData{Data: []byte{byte(0)}},
MinRelayParentNumber: 0,
ValidationCodeHash: parachaintypes.ValidationCodeHash(common.Hash{0x03}),
}
scope, err := newScopeWithAncestors(relayParent, baseConstraints, nil, 10, nil)
assert.NoError(t, err)

prospectiveParachains.View.perRelayParent[candidateRelayParent] = &relayParentData{
fragmentChains: map[parachaintypes.ParaID]*fragmentChain{
paraId: newFragmentChain(scope, newCandidateStorage()),
},
}
go prospectiveParachains.Run(context.Background(), overseerToSubsystem)

introduceSecondedCandidateFailed(t, overseerToSubsystem, candidate, pvd)
}

func TestHandleIntroduceSecondedCandidate(
t *testing.T,
) {
candidateRelayParent := common.Hash{0x01}
paraId := parachaintypes.ParaID(1)
parentHead := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x01}, 32),
}
headData := parachaintypes.HeadData{
Data: bytes.Repeat([]byte{0x02}, 32),
}
validationCodeHash := parachaintypes.ValidationCodeHash{0x01}
candidateRelayParentNumber := uint32(1)

candidate := makeCandidate(
candidateRelayParent,
candidateRelayParentNumber,
paraId,
parentHead,
headData,
validationCodeHash,
)

pvd := dummyPVD(parentHead, candidateRelayParentNumber)

subsystemToOverseer := make(chan any)
overseerToSubsystem := make(chan any)

prospectiveParachains := NewProspectiveParachains(subsystemToOverseer)

relayParent := relayChainBlockInfo{
Hash: candidateRelayParent,
Number: 0,
StorageRoot: common.Hash{0x00},
}

baseConstraints := &parachaintypes.Constraints{
RequiredParent: parachaintypes.HeadData{Data: []byte{byte(0)}},
MinRelayParentNumber: 0,
ValidationCodeHash: parachaintypes.ValidationCodeHash(common.Hash{0x03}),
}

scope, err := newScopeWithAncestors(relayParent, baseConstraints, nil, 10, nil)
assert.NoError(t, err)

prospectiveParachains.View.perRelayParent[candidateRelayParent] = &relayParentData{
fragmentChains: map[parachaintypes.ParaID]*fragmentChain{
paraId: newFragmentChain(scope, newCandidateStorage()),
},
}
go prospectiveParachains.Run(context.Background(), overseerToSubsystem)

introduceSecondedCandidate(t, overseerToSubsystem, candidate, pvd)
}

const MaxPoVSize = 1_000_000

func dummyPVD(parentHead parachaintypes.HeadData, relayParentNumber uint32) parachaintypes.PersistedValidationData {
Expand Down

0 comments on commit e0be44f

Please sign in to comment.