diff --git a/lib/parachain/available_data_fetching.go b/lib/parachain/available_data_fetching.go index 7cd973a5d7..8b6d989041 100644 --- a/lib/parachain/available_data_fetching.go +++ b/lib/parachain/available_data_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/available_data_fetching_test.go b/lib/parachain/available_data_fetching_test.go index 8a1507268c..6cfc6e0849 100644 --- a/lib/parachain/available_data_fetching_test.go +++ b/lib/parachain/available_data_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/candidate_validation.go b/lib/parachain/candidate_validation.go new file mode 100644 index 0000000000..b91f6e5fd8 --- /dev/null +++ b/lib/parachain/candidate_validation.go @@ -0,0 +1,139 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + parachainruntime "github.com/ChainSafe/gossamer/lib/parachain/runtime" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +var ( + ErrValidationCodeMismatch = errors.New("validation code hash does not match") + ErrValidationInputOverLimit = errors.New("validation input is over the limit") +) + +// PoVRequestor gets proof of validity by issuing network requests to validators of the current backing group. +// TODO: Implement PoV requestor +type PoVRequestor interface { + RequestPoV(povHash common.Hash) PoV +} + +func getValidationData(runtimeInstance parachainruntime.RuntimeInstance, paraID uint32, +) (*parachaintypes.PersistedValidationData, *parachaintypes.ValidationCode, error) { + + var mergedError error + + for _, assumptionValue := range []any{ + parachaintypes.IncludedOccupiedCoreAssumption{}, + parachaintypes.TimedOutOccupiedCoreAssumption{}, + parachaintypes.Free{}, + } { + assumption := parachaintypes.NewOccupiedCoreAssumption() + err := assumption.SetValue(assumptionValue) + if err != nil { + return nil, nil, fmt.Errorf("getting assumption: %w", err) + } + persistedValidationData, err := runtimeInstance.ParachainHostPersistedValidationData(paraID, assumption) + if err != nil { + mergedError = errors.Join(mergedError, err) + continue + } + + validationCode, err := runtimeInstance.ParachainHostValidationCode(paraID, assumption) + if err != nil { + return nil, nil, fmt.Errorf("getting validation code: %w", err) + } + + return persistedValidationData, validationCode, nil + } + + return nil, nil, fmt.Errorf("getting persisted validation data: %w", mergedError) +} + +// ValidateFromChainState validates a candidate parachain block with provided parameters using relay-chain +// state and using the parachain runtime. +func ValidateFromChainState(runtimeInstance parachainruntime.RuntimeInstance, povRequestor PoVRequestor, + candidateReceipt parachaintypes.CandidateReceipt) ( + *parachaintypes.CandidateCommitments, *parachaintypes.PersistedValidationData, bool, error) { + + persistedValidationData, validationCode, err := getValidationData(runtimeInstance, candidateReceipt.Descriptor.ParaID) + if err != nil { + return nil, nil, false, fmt.Errorf("getting validation data: %w", err) + } + + // check that the candidate does not exceed any parameters in the persisted validation data + pov := povRequestor.RequestPoV(candidateReceipt.Descriptor.PovHash) + + // basic checks + + // check if encoded size of pov is less than max pov size + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err = encoder.Encode(pov) + if err != nil { + return nil, nil, false, fmt.Errorf("encoding pov: %w", err) + } + encodedPoVSize := buffer.Len() + if encodedPoVSize > int(persistedValidationData.MaxPovSize) { + return nil, nil, false, fmt.Errorf("%w, limit: %d, got: %d", ErrValidationInputOverLimit, + persistedValidationData.MaxPovSize, encodedPoVSize) + } + + validationCodeHash, err := common.Blake2bHash([]byte(*validationCode)) + if err != nil { + return nil, nil, false, fmt.Errorf("hashing validation code: %w", err) + } + + if validationCodeHash != common.Hash(candidateReceipt.Descriptor.ValidationCodeHash) { + return nil, nil, false, fmt.Errorf("%w, expected: %s, got %s", ErrValidationCodeMismatch, + candidateReceipt.Descriptor.ValidationCodeHash, validationCodeHash) + } + + // check candidate signature + err = candidateReceipt.Descriptor.CheckCollatorSignature() + if err != nil { + return nil, nil, false, fmt.Errorf("verifying collator signature: %w", err) + } + + validationParams := parachainruntime.ValidationParameters{ + ParentHeadData: persistedValidationData.ParentHead, + BlockData: pov.BlockData, + RelayParentNumber: persistedValidationData.RelayParentNumber, + RelayParentStorageRoot: persistedValidationData.RelayParentStorageRoot, + } + + parachainRuntimeInstance, err := parachainruntime.SetupVM(*validationCode) + if err != nil { + return nil, nil, false, fmt.Errorf("setting up VM: %w", err) + } + + validationResults, err := parachainRuntimeInstance.ValidateBlock(validationParams) + if err != nil { + return nil, nil, false, fmt.Errorf("executing validate_block: %w", err) + } + + candidateCommitments := parachaintypes.CandidateCommitments{ + UpwardMessages: validationResults.UpwardMessages, + HorizontalMessages: validationResults.HorizontalMessages, + NewValidationCode: validationResults.NewValidationCode, + HeadData: validationResults.HeadData, + ProcessedDownwardMessages: validationResults.ProcessedDownwardMessages, + HrmpWatermark: validationResults.HrmpWatermark, + } + + isValid, err := runtimeInstance.ParachainHostCheckValidationOutputs( + candidateReceipt.Descriptor.ParaID, candidateCommitments) + if err != nil { + return nil, nil, false, fmt.Errorf("executing validate_block: %w", err) + } + + return &candidateCommitments, persistedValidationData, isValid, nil +} diff --git a/lib/parachain/candidate_validation_test.go b/lib/parachain/candidate_validation_test.go new file mode 100644 index 0000000000..9728c54865 --- /dev/null +++ b/lib/parachain/candidate_validation_test.go @@ -0,0 +1,134 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "os" + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func createTestCandidateReceiptAndValidationCode(t *testing.T) ( + parachaintypes.CandidateReceipt, parachaintypes.ValidationCode) { + // this wasm was achieved by building polkadot's adder test parachain + runtimeFilePath := "./testdata/test_parachain_adder.wasm" + validationCodeBytes, err := os.ReadFile(runtimeFilePath) + require.NoError(t, err) + + validationCode := parachaintypes.ValidationCode(validationCodeBytes) + + validationCodeHashV := common.MustBlake2bHash(validationCodeBytes) + + collatorKeypair, err := sr25519.GenerateKeypair() + require.NoError(t, err) + collatorID, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) + require.NoError(t, err) + + candidateReceipt := parachaintypes.CandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: uint32(1000), + RelayParent: common.MustHexToHash("0xded542bacb3ca6c033a57676f94ae7c8f36834511deb44e3164256fd3b1c0de0"), //nolint:lll + Collator: collatorID.AsBytes(), + PersistedValidationDataHash: common.MustHexToHash("0x690d8f252ef66ab0f969c3f518f90012b849aa5ac94e1752c5e5ae5a8996de37"), //nolint:lll + PovHash: common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274"), //nolint:lll + ErasureRoot: common.MustHexToHash("0xc07f658163e93c45a6f0288d229698f09c1252e41076f4caa71c8cbc12f118a1"), //nolint:lll + ParaHead: common.MustHexToHash("0x9a8a7107426ef873ab89fc8af390ec36bdb2f744a9ff71ad7f18a12d55a7f4f5"), //nolint:lll + ValidationCodeHash: parachaintypes.ValidationCodeHash(validationCodeHashV), + }, + + CommitmentsHash: common.MustHexToHash("0xa54a8dce5fd2a27e3715f99e4241f674a48f4529f77949a4474f5b283b823535"), + } + + payload, err := candidateReceipt.Descriptor.CreateSignaturePayload() + require.NoError(t, err) + + signatureBytes, err := collatorKeypair.Sign(payload) + require.NoError(t, err) + + signature := [sr25519.SignatureLength]byte{} + copy(signature[:], signatureBytes) + + candidateReceipt.Descriptor.Signature = parachaintypes.CollatorSignature(signature) + + return candidateReceipt, validationCode +} + +func TestValidateFromChainState(t *testing.T) { + + candidateReceipt, validationCode := createTestCandidateReceiptAndValidationCode(t) + + bd, err := scale.Marshal(BlockDataInAdderParachain{ + State: uint64(1), + Add: uint64(1), + }) + require.NoError(t, err) + + pov := PoV{ + BlockData: bd, + } + + // 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) + + expectedPersistedValidationData := parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{Data: hd}, + RelayParentNumber: uint32(1), + RelayParentStorageRoot: common.MustHexToHash("0x50c969706800c0e9c3c4565dc2babb25e4a73d1db0dee1bcf7745535a32e7ca1"), + MaxPovSize: uint32(2048), + } + + ctrl := gomock.NewController(t) + + mockInstance := NewMockRuntimeInstance(ctrl) + mockInstance.EXPECT(). + ParachainHostPersistedValidationData( + uint32(1000), + gomock.AssignableToTypeOf(parachaintypes.OccupiedCoreAssumption{})). + Return(&expectedPersistedValidationData, nil) + mockInstance.EXPECT(). + ParachainHostValidationCode(uint32(1000), gomock.AssignableToTypeOf(parachaintypes.OccupiedCoreAssumption{})). + Return(&validationCode, nil) + mockInstance.EXPECT(). + ParachainHostCheckValidationOutputs(uint32(1000), gomock.AssignableToTypeOf(parachaintypes.CandidateCommitments{})). + Return(true, nil) + + mockPoVRequestor := NewMockPoVRequestor(ctrl) + mockPoVRequestor.EXPECT(). + RequestPoV(common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274")).Return(pov) + + candidateCommitments, persistedValidationData, isValid, err := ValidateFromChainState( + mockInstance, mockPoVRequestor, candidateReceipt) + require.NoError(t, err) + require.True(t, isValid) + require.NotNil(t, candidateCommitments) + require.Equal(t, expectedPersistedValidationData, *persistedValidationData) +} + +type HeadDataInAdderParachain struct { + Number uint64 + ParentHash [32]byte + PostState [32]byte +} + +type BlockDataInAdderParachain struct { + State uint64 + Add uint64 +} diff --git a/lib/parachain/chunk_fetching.go b/lib/parachain/chunk_fetching.go index f69c9b687e..fa015cc4cf 100644 --- a/lib/parachain/chunk_fetching.go +++ b/lib/parachain/chunk_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/chunk_fetching_test.go b/lib/parachain/chunk_fetching_test.go index e2aeb55789..ee565c8909 100644 --- a/lib/parachain/chunk_fetching_test.go +++ b/lib/parachain/chunk_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_fetching.go b/lib/parachain/collation_fetching.go index 7f5cec1c2b..5229c736d6 100644 --- a/lib/parachain/collation_fetching.go +++ b/lib/parachain/collation_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_fetching_test.go b/lib/parachain/collation_fetching_test.go index 47d819f2fc..023a2fdbfd 100644 --- a/lib/parachain/collation_fetching_test.go +++ b/lib/parachain/collation_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_protocol.go b/lib/parachain/collation_protocol.go index 53560b5623..d94d3ea5da 100644 --- a/lib/parachain/collation_protocol.go +++ b/lib/parachain/collation_protocol.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go index a2d1b03c97..b31a006a15 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/lib/parachain/collation_protocol_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -55,9 +58,11 @@ func TestCollationProtocol(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/mocks_generate_test.go b/lib/parachain/mocks_generate_test.go new file mode 100644 index 0000000000..8eb3c600ed --- /dev/null +++ b/lib/parachain/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . RuntimeInstance,PoVRequestor diff --git a/lib/parachain/mocks_test.go b/lib/parachain/mocks_test.go new file mode 100644 index 0000000000..18bb48db5f --- /dev/null +++ b/lib/parachain/mocks_test.go @@ -0,0 +1,118 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/lib/parachain (interfaces: RuntimeInstance,PoVRequestor) + +// Package parachain is a generated GoMock package. +package parachain + +import ( + reflect "reflect" + + common "github.com/ChainSafe/gossamer/lib/common" + types "github.com/ChainSafe/gossamer/lib/parachain/types" + gomock "go.uber.org/mock/gomock" +) + +// MockRuntimeInstance is a mock of RuntimeInstance interface. +type MockRuntimeInstance struct { + ctrl *gomock.Controller + recorder *MockRuntimeInstanceMockRecorder +} + +// MockRuntimeInstanceMockRecorder is the mock recorder for MockRuntimeInstance. +type MockRuntimeInstanceMockRecorder struct { + mock *MockRuntimeInstance +} + +// NewMockRuntimeInstance creates a new mock instance. +func NewMockRuntimeInstance(ctrl *gomock.Controller) *MockRuntimeInstance { + mock := &MockRuntimeInstance{ctrl: ctrl} + mock.recorder = &MockRuntimeInstanceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRuntimeInstance) EXPECT() *MockRuntimeInstanceMockRecorder { + return m.recorder +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockRuntimeInstance) ParachainHostCheckValidationOutputs(arg0 uint32, arg1 types.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostPersistedValidationData mocks base method. +func (m *MockRuntimeInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*types.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + +// ParachainHostValidationCode mocks base method. +func (m *MockRuntimeInstance) ParachainHostValidationCode(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*types.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + +// MockPoVRequestor is a mock of PoVRequestor interface. +type MockPoVRequestor struct { + ctrl *gomock.Controller + recorder *MockPoVRequestorMockRecorder +} + +// MockPoVRequestorMockRecorder is the mock recorder for MockPoVRequestor. +type MockPoVRequestorMockRecorder struct { + mock *MockPoVRequestor +} + +// NewMockPoVRequestor creates a new mock instance. +func NewMockPoVRequestor(ctrl *gomock.Controller) *MockPoVRequestor { + mock := &MockPoVRequestor{ctrl: ctrl} + mock.recorder = &MockPoVRequestorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPoVRequestor) EXPECT() *MockPoVRequestorMockRecorder { + return m.recorder +} + +// RequestPoV mocks base method. +func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) PoV { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestPoV", arg0) + ret0, _ := ret[0].(PoV) + return ret0 +} + +// RequestPoV indicates an expected call of RequestPoV. +func (mr *MockPoVRequestorMockRecorder) RequestPoV(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestPoV", reflect.TypeOf((*MockPoVRequestor)(nil).RequestPoV), arg0) +} diff --git a/lib/parachain/pov_fetching.go b/lib/parachain/pov_fetching.go index 72f1589810..4993438d41 100644 --- a/lib/parachain/pov_fetching.go +++ b/lib/parachain/pov_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/pov_fetching_test.go b/lib/parachain/pov_fetching_test.go index 08b708e4ec..a2334b3114 100644 --- a/lib/parachain/pov_fetching_test.go +++ b/lib/parachain/pov_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/runtime/instance.go b/lib/parachain/runtime/instance.go new file mode 100644 index 0000000000..017431cd81 --- /dev/null +++ b/lib/parachain/runtime/instance.go @@ -0,0 +1,98 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wasmer" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +var ( + ErrCodeEmpty = errors.New("code is empty") + ErrWASMDecompress = errors.New("wasm decompression failed") + ErrInstanceIsStopped = errors.New("instance is stopped") +) + +// ValidationResult is result received from validate_block. It is similar to CandidateCommitments, but different order. +type ValidationResult struct { + // The head-data is the new head data that should be included in the relay chain state. + HeadData parachaintypes.HeadData `scale:"1"` + // NewValidationCode is an update to the validation code that should be scheduled in the relay chain. + NewValidationCode *parachaintypes.ValidationCode `scale:"2"` + // UpwardMessages are upward messages send by the Parachain. + UpwardMessages []parachaintypes.UpwardMessage `scale:"3"` + // HorizontalMessages are Outbound horizontal messages sent by the parachain. + HorizontalMessages []parachaintypes.OutboundHrmpMessage `scale:"4"` + + // The number of messages processed from the DMQ. It is expected that the Parachain processes them from first to last. + ProcessedDownwardMessages uint32 `scale:"5"` + // The mark which specifies the block number up to which all inbound HRMP messages are processed. + HrmpWatermark uint32 `scale:"6"` +} + +// ValidationParameters contains parameters for evaluating the parachain validity function. +type ValidationParameters struct { + // Previous head-data. + ParentHeadData parachaintypes.HeadData + // The collation body. + BlockData []byte //types.BlockData + // The current relay-chain block number. + RelayParentNumber uint32 + // The relay-chain block's storage root. + RelayParentStorageRoot common.Hash +} + +// Instance is a wrapper around the wasmer runtime instance. +type Instance struct { + *runtimewasmer.Instance +} + +func SetupVM(code []byte) (*Instance, error) { + cfg := runtimewasmer.Config{} + + instance, err := runtimewasmer.NewInstance(code, cfg) + if err != nil { + return nil, fmt.Errorf("creating instance: %w", err) + } + return &Instance{instance}, nil +} + +// ValidateBlock validates a block by calling parachain runtime's validate_block call and returns the result. +func (in *Instance) ValidateBlock(params ValidationParameters) ( + *ValidationResult, error) { + + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(params) + if err != nil { + return nil, fmt.Errorf("encoding validation parameters: %w", err) + } + + encodedValidationResult, err := in.Exec("validate_block", buffer.Bytes()) + if err != nil { + return nil, err + } + + validationResult := ValidationResult{} + err = scale.Unmarshal(encodedValidationResult, &validationResult) + if err != nil { + return nil, fmt.Errorf("scale decoding: %w", err) + } + return &validationResult, nil +} + +// RuntimeInstance for runtime methods +type RuntimeInstance interface { + ParachainHostPersistedValidationData(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.PersistedValidationData, error) + ParachainHostValidationCode(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.ValidationCode, error) + ParachainHostCheckValidationOutputs(parachainID uint32, outputs parachaintypes.CandidateCommitments) (bool, error) +} diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go index e6c6274e8c..96c1939d72 100644 --- a/lib/parachain/statement.go +++ b/lib/parachain/statement.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go index 4793ef8c96..bdc2d8a492 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/lib/parachain/statement_distribution_message.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go index cca48e568f..dd0e491a58 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/lib/parachain/statement_distribution_message_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -58,9 +61,11 @@ func TestStatementDistributionMessage(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/statement_fetching.go b/lib/parachain/statement_fetching.go index de0c82ca2a..4649bdddb6 100644 --- a/lib/parachain/statement_fetching.go +++ b/lib/parachain/statement_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_fetching_test.go b/lib/parachain/statement_fetching_test.go index c70cf33bd2..268936410a 100644 --- a/lib/parachain/statement_fetching_test.go +++ b/lib/parachain/statement_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -93,9 +96,11 @@ func TestStatementFetchingResponse(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(testHash), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go index f6488a5a31..8ba8e7714b 100644 --- a/lib/parachain/statement_test.go +++ b/lib/parachain/statement_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -43,9 +46,11 @@ func TestStatement(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/testdata/test_parachain_adder.wasm b/lib/parachain/testdata/test_parachain_adder.wasm new file mode 100755 index 0000000000..7dfaccd460 Binary files /dev/null and b/lib/parachain/testdata/test_parachain_adder.wasm differ diff --git a/lib/parachain/types/types.go b/lib/parachain/types/types.go index 3feb4007a4..3e932af3d4 100644 --- a/lib/parachain/types/types.go +++ b/lib/parachain/types/types.go @@ -4,6 +4,7 @@ package parachaintypes import ( + "bytes" "fmt" "github.com/ChainSafe/gossamer/lib/common" @@ -90,6 +91,36 @@ type CandidateDescriptor struct { ValidationCodeHash ValidationCodeHash `scale:"9"` } +func (cd CandidateDescriptor) CreateSignaturePayload() ([]byte, error) { + var payload [132]byte + copy(payload[0:32], cd.RelayParent.ToBytes()) + + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(cd.ParaID) + if err != nil { + return nil, fmt.Errorf("encoding parachain id: %w", err) + } + if len(buffer.Bytes()) != 4 { + return nil, fmt.Errorf("invalid length of encoded parachain id") + } + copy(payload[32:36], buffer.Bytes()) + copy(payload[36:68], cd.PersistedValidationDataHash.ToBytes()) + copy(payload[68:100], cd.PovHash.ToBytes()) + copy(payload[100:132], common.Hash(cd.ValidationCodeHash).ToBytes()) + + return payload[:], nil +} + +func (cd CandidateDescriptor) CheckCollatorSignature() error { + payload, err := cd.CreateSignaturePayload() + if err != nil { + return fmt.Errorf("creating signature payload: %w", err) + } + + return sr25519.VerifySignature(cd.Collator[:], cd.Signature[:], payload) +} + // OccupiedCore Information about a core which is currently occupied. type OccupiedCore struct { // NOTE: this has no ParaId as it can be deduced from the candidate descriptor. @@ -203,9 +234,6 @@ type OutboundHrmpMessage struct { // ValidationCode is Parachain validation code. type ValidationCode []byte -// headData is Parachain head data included in the chain. -type headData []byte - // CandidateCommitments are Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. type CandidateCommitments struct { // Messages destined to be interpreted by the Relay chain itself. @@ -215,7 +243,7 @@ type CandidateCommitments struct { // New validation code. NewValidationCode *ValidationCode `scale:"3"` // The head-data produced as a result of execution. - HeadData headData `scale:"4"` + HeadData HeadData `scale:"4"` // The number of messages processed from the DMQ. ProcessedDownwardMessages uint32 `scale:"5"` // The mark which specifies the block number up to which all inbound HRMP messages are processed.