Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dot/parachain): implement validate candidate from exhaustive #4036

Merged
merged 31 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e11ac42
create stubs to implement Subsystem interface
edwardmack Apr 19, 2024
a5b08bd
create message types, move existing code to candidatevalidation package
edwardmack Apr 23, 2024
5f4d53f
fix rebase conflicts
edwardmack Apr 29, 2024
c7f6eb8
added test wasm file
edwardmack May 3, 2024
ff4498d
address PR comments
edwardmack May 8, 2024
cd14404
fix merge conflicts
edwardmack May 14, 2024
4d6dc56
remove context from candidate validation subsystem, create message st…
edwardmack May 17, 2024
8e97e38
address PR comments, remove redundant structs, cleanup
edwardmack May 24, 2024
72fbc64
implement "happy path" functionality for validate from exhaustive
edwardmack May 21, 2024
303ab23
implement perform basic checks and added tests
edwardmack May 22, 2024
f0f3ea4
address rebase conflicts, implement ValidationResultMessage
edwardmack May 29, 2024
95f2376
add test for performBasicChecks function
edwardmack Jun 4, 2024
7935076
add test helper functions and additional tests
edwardmack Jun 7, 2024
34c60da
cleanup comments
edwardmack Jun 10, 2024
efc6d33
fix message test to init validation host
edwardmack Jun 10, 2024
e6f8bce
fix lint issues
edwardmack Jun 11, 2024
75cc898
add chore use os.MkdirTemp + t.Cleanup fix (from PR#4037)
edwardmack Jun 12, 2024
26375b7
refactor ValidationResultMessage to use VDT
edwardmack Jun 17, 2024
32bb3eb
remove the usage of VDT type for ValidationResult
edwardmack Jun 18, 2024
27f5ea3
fix candidate validation test
timwu20 Jun 19, 2024
b9dbe5d
remove un-used commented code
edwardmack Jun 20, 2024
e0ad0dd
updated `performBasicChecks` to return multiple errors, rename error …
edwardmack Jun 21, 2024
6bbf4e3
add comments
edwardmack Jun 21, 2024
8b53a83
Add type ValidValidationResult and InvalidValidationResult to by used…
edwardmack Jun 24, 2024
57521d0
refactor ValidationResult InvaledResult as an enum to control possabl…
edwardmack Jun 25, 2024
b2715f7
Merge branch 'feat/parachain' into ed/feat/candidate_validation_exhau…
edwardmack Jun 27, 2024
2ba6ef2
rename objects to address PR comments
edwardmack Jul 2, 2024
c8bd1d8
add hash checks to validate candidate from exhaustive
edwardmack Jul 3, 2024
fb14bfa
clearify code, rename varibale and simplify if/else condition
edwardmack Jul 8, 2024
2afed8b
rename variable
edwardmack Jul 8, 2024
3899e21
Merge branch 'feat/parachain' into ed/feat/candidate_validation_exhau…
edwardmack Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions dot/parachain/backing/candidate_backing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,10 +761,10 @@ func TestValidateAndMakeAvailable(t *testing.T) {
Data: parachaintypes.ValidationCode{1, 2, 3},
}
case candidatevalidation.ValidateFromExhaustive:
ci := candidatevalidation.ExecutionError
data.Ch <- parachaintypes.OverseerFuncRes[candidatevalidation.ValidationResult]{
Data: candidatevalidation.ValidationResult{
IsValid: false,
Err: errors.New("mock error validating candidate"),
InvalidResult: &ci,
},
}
default:
Expand All @@ -791,7 +791,7 @@ func TestValidateAndMakeAvailable(t *testing.T) {
case candidatevalidation.ValidateFromExhaustive:
data.Ch <- parachaintypes.OverseerFuncRes[candidatevalidation.ValidationResult]{
Data: candidatevalidation.ValidationResult{
IsValid: true,
ValidResult: &candidatevalidation.ValidValidationResult{},
},
}
case availabilitystore.StoreAvailableData:
Expand Down
11 changes: 5 additions & 6 deletions dot/parachain/backing/per_relay_parent_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,7 @@ func (rpState *perRelayParentState) validateAndMakeAvailable(
}

var bgValidationResult backgroundValidationResult

if validationResultRes.Data.IsValid { // Valid
if validationResultRes.Data.IsValid() { // Valid
// Important: the `av-store` subsystem will check if the erasure root of the `available_data`
// matches `expected_erasure_root` which was provided by the collator in the `CandidateReceipt`.
// This check is consensus critical and the `backing` subsystem relies on it for ensuring
Expand All @@ -343,8 +342,8 @@ func (rpState *perRelayParentState) validateAndMakeAvailable(
bgValidationResult = backgroundValidationResult{
outputs: &backgroundValidationOutputs{
candidateReceipt: candidateReceipt,
candidateCommitments: validationResultRes.Data.CandidateCommitments,
persistedValidationData: validationResultRes.Data.PersistedValidationData,
candidateCommitments: validationResultRes.Data.ValidResult.CandidateCommitments,
persistedValidationData: validationResultRes.Data.ValidResult.PersistedValidationData,
},
candidate: nil,
err: nil,
Expand All @@ -361,11 +360,11 @@ func (rpState *perRelayParentState) validateAndMakeAvailable(
}

} else { // Invalid
logger.Error(validationResultRes.Data.Err.Error())
logger.Error(validationResultRes.Data.InvalidResult.Error())
bgValidationResult = backgroundValidationResult{
outputs: nil,
candidate: &candidateReceipt,
err: validationResultRes.Data.Err,
err: fmt.Errorf(validationResultRes.Data.InvalidResult.Error()),
}
}

Expand Down
113 changes: 111 additions & 2 deletions dot/parachain/candidate-validation/candidate_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,46 +24,54 @@ var (
ErrValidationInputOverLimit = errors.New("validation input is over the limit")
)

// CandidateValidation is a parachain subsystem that validates candidate parachain blocks
type CandidateValidation struct {
wg sync.WaitGroup
stopChan chan struct{}

SubsystemToOverseer chan<- any
OverseerToSubsystem <-chan any
ValidationHost parachainruntime.ValidationHost
}

// NewCandidateValidation creates a new CandidateValidation subsystem
func NewCandidateValidation(overseerChan chan<- any) *CandidateValidation {
candidateValidation := CandidateValidation{
SubsystemToOverseer: overseerChan,
}

return &candidateValidation
}

// Run starts the CandidateValidation subsystem
func (cv *CandidateValidation) Run(context.Context, chan any, chan any) {
cv.wg.Add(1)
go cv.processMessages(&cv.wg)
}

// Name returns the name of the subsystem
func (*CandidateValidation) Name() parachaintypes.SubSystemName {
return parachaintypes.CandidateValidation
}

// ProcessActiveLeavesUpdateSignal processes active leaves update signal
func (*CandidateValidation) ProcessActiveLeavesUpdateSignal(parachaintypes.ActiveLeavesUpdateSignal) error {
// NOTE: this subsystem does not process active leaves update signal
return nil
}

// ProcessBlockFinalizedSignal processes block finalized signal
func (*CandidateValidation) ProcessBlockFinalizedSignal(parachaintypes.BlockFinalizedSignal) error {
// NOTE: this subsystem does not process block finalized signal
return nil
}

// Stop stops the CandidateValidation subsystem
func (cv *CandidateValidation) Stop() {
close(cv.stopChan)
cv.wg.Wait()
}

// processMessages processes messages sent to the CandidateValidation subsystem
func (cv *CandidateValidation) processMessages(wg *sync.WaitGroup) {
defer wg.Done()
for {
Expand All @@ -74,7 +82,20 @@ func (cv *CandidateValidation) processMessages(wg *sync.WaitGroup) {
case ValidateFromChainState:
// TODO: implement functionality to handle ValidateFromChainState, see issue #3919
case ValidateFromExhaustive:
// TODO: implement functionality to handle ValidateFromExhaustive, see issue #3547
result, err := validateFromExhaustive(cv.ValidationHost, msg.PersistedValidationData,
msg.ValidationCode, msg.CandidateReceipt, msg.PoV)
if err != nil {
logger.Errorf("failed to validate from exhaustive: %w", err)
msg.Ch <- parachaintypes.OverseerFuncRes[ValidationResult]{
Data: *result,
Err: err,
}
edwardmack marked this conversation as resolved.
Show resolved Hide resolved
} else {
msg.Ch <- parachaintypes.OverseerFuncRes[ValidationResult]{
Data: *result,
}
}

case PreCheck:
// TODO: implement functionality to handle PreCheck, see issue #3921

Expand All @@ -100,6 +121,7 @@ type PoVRequestor interface {
RequestPoV(povHash common.Hash) parachaintypes.PoV
}

// getValidationData gets validation data for a parachain block from the runtime instance
func getValidationData(runtimeInstance parachainruntime.RuntimeInstance, paraID uint32,
) (*parachaintypes.PersistedValidationData, *parachaintypes.ValidationCode, error) {

Expand Down Expand Up @@ -211,3 +233,90 @@ func validateFromChainState(runtimeInstance parachainruntime.RuntimeInstance, po

return &candidateCommitments, persistedValidationData, isValid, nil
}

// validateFromExhaustive validates a candidate parachain block with provided parameters
func validateFromExhaustive(validationHost parachainruntime.ValidationHost,
edwardmack marked this conversation as resolved.
Show resolved Hide resolved
persistedValidationData parachaintypes.PersistedValidationData,
validationCode parachaintypes.ValidationCode,
candidateReceipt parachaintypes.CandidateReceipt, pov parachaintypes.PoV) (
*ValidationResult, error) {

validationCodeHash := validationCode.Hash()
// basic checks
validationErr, internalErr := performBasicChecks(&candidateReceipt.Descriptor, persistedValidationData.MaxPovSize,
pov,
validationCodeHash)
if validationErr != nil {
validationResult := &ValidationResult{
InvalidResult: validationErr,
}
return validationResult, internalErr
}
edwardmack marked this conversation as resolved.
Show resolved Hide resolved

validationParams := parachainruntime.ValidationParameters{
ParentHeadData: persistedValidationData.ParentHead,
BlockData: pov.BlockData,
RelayParentNumber: persistedValidationData.RelayParentNumber,
RelayParentStorageRoot: persistedValidationData.RelayParentStorageRoot,
}

validationResult, err := validationHost.ValidateBlock(validationParams)
if err != nil {
ci := ExecutionError
return &ValidationResult{InvalidResult: &ci}, fmt.Errorf("executing validate_block: %w", err)
edwardmack marked this conversation as resolved.
Show resolved Hide resolved
}

result := &ValidationResult{
ValidResult: &ValidValidationResult{
CandidateCommitments: parachaintypes.CandidateCommitments{
UpwardMessages: validationResult.UpwardMessages,
HorizontalMessages: validationResult.HorizontalMessages,
NewValidationCode: validationResult.NewValidationCode,
HeadData: validationResult.HeadData,
ProcessedDownwardMessages: validationResult.ProcessedDownwardMessages,
HrmpWatermark: validationResult.HrmpWatermark,
},
PersistedValidationData: persistedValidationData,
},
}
return result, nil
}

// performBasicChecks Does basic checks of a candidate. Provide the encoded PoV-block.
// Returns CandidateInvalidity and internal error if any.
func performBasicChecks(candidate *parachaintypes.CandidateDescriptor, maxPoVSize uint32,
edwardmack marked this conversation as resolved.
Show resolved Hide resolved
pov parachaintypes.PoV, validationCodeHash parachaintypes.ValidationCodeHash) (validationError *CandidateInvalidity,
internalError error) {
povHash, err := pov.Hash()
if err != nil {
return nil, fmt.Errorf("hashing PoV: %w", err)
}

encodedPoV, err := pov.Encode()
if err != nil {
return nil, fmt.Errorf("encoding PoV: %w", err)
}
encodedPoVSize := uint32(len(encodedPoV))

if encodedPoVSize > maxPoVSize {
ci := ParamsTooLarge
return &ci, nil
}

if povHash != candidate.PovHash {
ci := PoVHashMismatch
return &ci, nil
}

if validationCodeHash != candidate.ValidationCodeHash {
ci := CodeHashMismatch
return &ci, nil
}

err = candidate.CheckCollatorSignature()
if err != nil {
ci := BadSignature
return &ci, nil
}
return nil, nil
}
Loading
Loading