-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lib/parachain): introduced parachain candidate validation (#3249)
This commit adds all the functions required for parachain candidate validations. With this commit, we are able to take candidate receipts and - get validation data for it, - perform basic checks on it, - run respective parachain's validate_block on parachain's runtime, - get validate results from parachain's runtime and verify those validation results again relaychain runtime, - and declare candidate as valid or invalid in the end. This commit also includes tests for candidate validation.
- Loading branch information
1 parent
74a8c52
commit 1c35f41
Showing
23 changed files
with
595 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 []scale.VaryingDataTypeValue{ | ||
parachaintypes.IncludedOccupiedCoreAssumption{}, | ||
parachaintypes.TimedOutOccupiedCoreAssumption{}, | ||
parachaintypes.Free{}, | ||
} { | ||
assumption := parachaintypes.NewOccupiedCoreAssumption() | ||
err := assumption.Set(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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/golang/mock/gomock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
// Copyright 2023 ChainSafe Systems (ON) | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
|
||
package parachain | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.