diff --git a/dot/parachain/prospective-parachains/errors.go b/dot/parachain/prospective-parachains/errors.go index 2b20aeadeb..255a5c8e13 100644 --- a/dot/parachain/prospective-parachains/errors.go +++ b/dot/parachain/prospective-parachains/errors.go @@ -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 diff --git a/dot/parachain/prospective-parachains/fragment_chain.go b/dot/parachain/prospective-parachains/fragment_chain.go index 407d6b88d7..8e5443b6e0 100644 --- a/dot/parachain/prospective-parachains/fragment_chain.go +++ b/dot/parachain/prospective-parachains/fragment_chain.go @@ -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 } diff --git a/dot/parachain/prospective-parachains/prospective-parachains.go b/dot/parachain/prospective-parachains/prospective-parachains.go index 13c0a396c3..eedca8d53e 100644 --- a/dot/parachain/prospective-parachains/prospective-parachains.go +++ b/dot/parachain/prospective-parachains/prospective-parachains.go @@ -4,6 +4,7 @@ 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" @@ -11,6 +12,16 @@ import ( var logger = log.NewFromGlobal(log.AddContext("pkg", "prospective_parachains"), log.SetLevel(log.Debug)) +// Initialize with empty values. +func NewView() *view { + 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: + // https://github.com/ChainSafe/gossamer/blob/main/lib/prospective_parachains/view.go#L10 + } +} + type ProspectiveParachains struct { SubsystemToOverseer chan<- any View *view @@ -19,6 +30,7 @@ type ProspectiveParachains struct { type view struct { activeLeaves map[common.Hash]bool perRelayParent map[common.Hash]*relayParentData + implicitView backing.ImplicitView } type relayParentData struct { @@ -34,6 +46,7 @@ func (*ProspectiveParachains) Name() parachaintypes.SubSystemName { func NewProspectiveParachains(overseerChan chan<- any) *ProspectiveParachains { prospectiveParachain := ProspectiveParachains{ SubsystemToOverseer: overseerChan, + View: NewView(), } return &prospectiveParachain } @@ -64,7 +77,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: @@ -82,6 +99,94 @@ func (pp *ProspectiveParachains) processMessage(msg any) { } +func (pp *ProspectiveParachains) introduceSecondedCandidate( + view *view, + request IntroduceSecondedCandidateRequest, + response chan bool, +) { + 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") diff --git a/dot/parachain/prospective-parachains/prospective_parachains_test.go b/dot/parachain/prospective-parachains/prospective_parachains_test.go index 8b5b8bb7d0..806d6dfd17 100644 --- a/dot/parachain/prospective-parachains/prospective_parachains_test.go +++ b/dot/parachain/prospective-parachains/prospective_parachains_test.go @@ -2,6 +2,7 @@ package prospectiveparachains import ( "bytes" + "context" "testing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -9,6 +10,198 @@ import ( "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 := ¶chaintypes.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 := ¶chaintypes.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 {