diff --git a/dot/parachain/candidate-validation/mocks_generate_test.go b/dot/parachain/candidate-validation/mocks_generate_test.go index 4cab5225ad..c8f6f93b07 100644 --- a/dot/parachain/candidate-validation/mocks_generate_test.go +++ b/dot/parachain/candidate-validation/mocks_generate_test.go @@ -6,3 +6,4 @@ package candidatevalidation //go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . PoVRequestor //go:generate mockgen -destination=mocks_blockstate_test.go -package=$GOPACKAGE . BlockState //go:generate mockgen -destination=mocks_instance_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/lib/runtime Instance +//go:generate mockgen -destination=mocks_validation_instance_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/parachain/runtime ValidatorInstance diff --git a/dot/parachain/candidate-validation/mocks_validation_instance_test.go b/dot/parachain/candidate-validation/mocks_validation_instance_test.go new file mode 100644 index 0000000000..519357cfe7 --- /dev/null +++ b/dot/parachain/candidate-validation/mocks_validation_instance_test.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/runtime (interfaces: ValidatorInstance) +// +// Generated by this command: +// +// mockgen -destination=mocks_validation_instance_test.go -package candidatevalidation github.com/ChainSafe/gossamer/dot/parachain/runtime ValidatorInstance +// + +// Package candidatevalidation is a generated GoMock package. +package candidatevalidation + +import ( + reflect "reflect" + + parachain "github.com/ChainSafe/gossamer/dot/parachain/runtime" + gomock "go.uber.org/mock/gomock" +) + +// MockValidatorInstance is a mock of ValidatorInstance interface. +type MockValidatorInstance struct { + ctrl *gomock.Controller + recorder *MockValidatorInstanceMockRecorder +} + +// MockValidatorInstanceMockRecorder is the mock recorder for MockValidatorInstance. +type MockValidatorInstanceMockRecorder struct { + mock *MockValidatorInstance +} + +// NewMockValidatorInstance creates a new mock instance. +func NewMockValidatorInstance(ctrl *gomock.Controller) *MockValidatorInstance { + mock := &MockValidatorInstance{ctrl: ctrl} + mock.recorder = &MockValidatorInstanceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidatorInstance) EXPECT() *MockValidatorInstanceMockRecorder { + return m.recorder +} + +// ValidateBlock mocks base method. +func (m *MockValidatorInstance) ValidateBlock(arg0 parachain.ValidationParameters) (*parachain.ValidationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateBlock", arg0) + ret0, _ := ret[0].(*parachain.ValidationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateBlock indicates an expected call of ValidateBlock. +func (mr *MockValidatorInstanceMockRecorder) ValidateBlock(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateBlock", reflect.TypeOf((*MockValidatorInstance)(nil).ValidateBlock), arg0) +} diff --git a/dot/parachain/candidate-validation/worker.go b/dot/parachain/candidate-validation/worker.go index 10d0c05596..abd345ce65 100644 --- a/dot/parachain/candidate-validation/worker.go +++ b/dot/parachain/candidate-validation/worker.go @@ -1,6 +1,7 @@ package candidatevalidation import ( + "fmt" "time" parachainruntime "github.com/ChainSafe/gossamer/dot/parachain/runtime" @@ -9,7 +10,7 @@ import ( type worker struct { workerID parachaintypes.ValidationCodeHash - instance *parachainruntime.Instance + instance parachainruntime.ValidatorInstance isProcessed map[parachaintypes.CandidateHash]*ValidationResult } @@ -54,6 +55,9 @@ func determineTimeout(timeoutKind parachaintypes.PvfExecTimeoutKind) time.Durati } func (w *worker) executeRequest(task *workerTask) (*ValidationResult, error) { + if task == nil { + return nil, fmt.Errorf("task is nil") + } logger.Debugf("[EXECUTING] worker %x task %v", w.workerID, task.work) candidateHash, err := parachaintypes.GetCandidateHash(task.candidateReceipt) if err != nil { diff --git a/dot/parachain/candidate-validation/worker_test.go b/dot/parachain/candidate-validation/worker_test.go new file mode 100644 index 0000000000..636cea0bf7 --- /dev/null +++ b/dot/parachain/candidate-validation/worker_test.go @@ -0,0 +1,140 @@ +package candidatevalidation + +import ( + "testing" + "time" + + parachain "github.com/ChainSafe/gossamer/dot/parachain/runtime" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func Test_worker_executeRequest(t *testing.T) { + candidateReceipt, validationCode := createTestCandidateReceiptAndValidationCode(t) + + validationRuntime, err := parachain.SetupVM(validationCode) + require.NoError(t, err) + + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + mockValidationInstance := NewMockValidatorInstance(ctrl) + mockValidationInstance.EXPECT().ValidateBlock(gomock.Any()).DoAndReturn(func(params parachain. + ValidationParameters) (*parachain.ValidationResult, error) { + time.Sleep(3 * time.Second) // sleep to simulate long execution time + return ¶chain.ValidationResult{}, nil + }) + + candidateReceiptCommitmentsMismatch := candidateReceipt + candidateReceiptCommitmentsMismatch.CommitmentsHash = common.MustHexToHash( + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + + // NOTE: adder parachain internally compares postState with bd.State in it's validate_block, + // so following is necessary. + encodedState, err := scale.Marshal(uint64(1)) + require.NoError(t, err) + postState, err := common.Keccak256(encodedState) + require.NoError(t, err) + + hd, err := scale.Marshal(HeadDataInAdderParachain{ + Number: uint64(1), + ParentHash: common.MustHexToHash("0x0102030405060708090001020304050607080900010203040506070809000102"), + PostState: postState, + }) + require.NoError(t, err) + + blockData, err := scale.Marshal(BlockDataInAdderParachain{ + State: uint64(1), + Add: uint64(1), + }) + require.NoError(t, err) + + commitmentsHashMismatch := CommitmentsHashMismatch + timeout := Timeout + + tests := map[string]struct { + instance parachain.ValidatorInstance + task *workerTask + want *ValidationResult + }{ + "commitments_hash_mismatch": { + instance: validationRuntime, + task: &workerTask{ + work: parachain.ValidationParameters{ + ParentHeadData: parachaintypes.HeadData{Data: hd}, + BlockData: blockData, + RelayParentNumber: uint32(1), + RelayParentStorageRoot: common.MustHexToHash("0x50c969706800c0e9c3c4565dc2babb25e4a73d1db0dee1bcf7745535a32e7ca1"), + }, + maxPoVSize: uint32(2048), + candidateReceipt: &candidateReceiptCommitmentsMismatch, + }, + want: &ValidationResult{ + InvalidResult: &commitmentsHashMismatch, + }, + }, + "execution_timeout": { + instance: mockValidationInstance, + task: &workerTask{ + candidateReceipt: &candidateReceipt, + }, + want: &ValidationResult{ + InvalidResult: &timeout, + }, + }, + "happy_path": { + instance: validationRuntime, + task: &workerTask{ + work: parachain.ValidationParameters{ + ParentHeadData: parachaintypes.HeadData{Data: hd}, + BlockData: blockData, + RelayParentNumber: uint32(1), + RelayParentStorageRoot: common.MustHexToHash("0x50c969706800c0e9c3c4565dc2babb25e4a73d1db0dee1bcf7745535a32e7ca1"), + }, + maxPoVSize: uint32(2048), + candidateReceipt: &candidateReceipt, + timeoutKind: parachaintypes.PvfExecTimeoutKind{}, + }, + want: &ValidationResult{ + ValidResult: &Valid{ + CandidateCommitments: parachaintypes.CandidateCommitments{ + UpwardMessages: nil, + HorizontalMessages: nil, + NewValidationCode: nil, + HeadData: parachaintypes.HeadData{Data: []byte{2, 0, 0, 0, 0, 0, 0, 0, 123, 207, 206, 8, 219, 227, + 136, 82, 236, 169, 14, 100, 45, 100, 31, 177, 154, 160, 220, 245, 59, 106, 76, 168, 122, 109, + 164, 169, 22, 46, 144, 39, 103, 92, 31, 78, 66, 72, 252, 64, 24, 194, 129, 162, 128, 1, 77, 147, + 200, 229, 189, 242, 111, 198, 236, 139, 16, 143, 19, 245, 113, 233, 138, 210}}, + ProcessedDownwardMessages: 0, + HrmpWatermark: 1, + }, + PersistedValidationData: parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{Data: []byte{1, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 0, 1, 2, 48, 246, 146, 178, 86, 226, 64, 9, + 188, 179, 77, 14, 232, 77, 167, 60, 41, 138, 250, 204, 9, 36, 224, 17, 5, 226, 235, + 15, 1, 168, 127, 226}}, + RelayParentNumber: 1, + RelayParentStorageRoot: common.MustHexToHash( + "0x50c969706800c0e9c3c4565dc2babb25e4a73d1db0dee1bcf7745535a32e7ca1"), + MaxPovSize: 2048, + }, + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + w := &worker{ + instance: tt.instance, + isProcessed: make(map[parachaintypes.CandidateHash]*ValidationResult), + } + got, err := w.executeRequest(tt.task) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/dot/parachain/runtime/instance.go b/dot/parachain/runtime/instance.go index 41b01bfff5..57dca6606b 100644 --- a/dot/parachain/runtime/instance.go +++ b/dot/parachain/runtime/instance.go @@ -89,6 +89,11 @@ func (in *Instance) ValidateBlock(params ValidationParameters) ( return &validationResult, nil } +// ValidatorInstance +type ValidatorInstance interface { + ValidateBlock(params ValidationParameters) (*ValidationResult, error) +} + // RuntimeInstance for runtime methods type RuntimeInstance interface { ParachainHostPersistedValidationData(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption,