From 8dc6bd9d7df5f47f241ed035f249eb2cd5fb05b5 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 21 Jun 2023 11:13:48 -0400 Subject: [PATCH] feat(parachain): Create struct for Approval Distribution Message (#3326) --- .../approval_distribution_message.go | 216 ++++++++++++++++++ .../approval_distribution_message_test.go | 202 ++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 lib/parachain/approval_distribution_message.go create mode 100644 lib/parachain/approval_distribution_message_test.go diff --git a/lib/parachain/approval_distribution_message.go b/lib/parachain/approval_distribution_message.go new file mode 100644 index 0000000000..a86b5de3ce --- /dev/null +++ b/lib/parachain/approval_distribution_message.go @@ -0,0 +1,216 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +type AssignmentCertKindValues interface { + RelayVRFModulo | RelayVRFDelay +} + +// AssignmentCertKind different kinds of input or criteria that can prove a validator's assignment +// to check a particular parachain. +type AssignmentCertKind struct { + inner any +} + +func setAssignmentCertKind[Value AssignmentCertKindValues](mvdt *AssignmentCertKind, value Value) { + mvdt.inner = value +} + +func (mvdt *AssignmentCertKind) SetValue(value any) (err error) { + switch value := value.(type) { + case RelayVRFModulo: + setAssignmentCertKind(mvdt, value) + return + + case RelayVRFDelay: + setAssignmentCertKind(mvdt, value) + return + + default: + return fmt.Errorf("unsupported type") + } +} + +func (mvdt AssignmentCertKind) IndexValue() (index uint, value any, err error) { + switch mvdt.inner.(type) { + case RelayVRFModulo: + return 0, mvdt.inner, nil + + case RelayVRFDelay: + return 1, mvdt.inner, nil + + } + return 0, nil, scale.ErrUnsupportedVaryingDataTypeValue +} + +func (mvdt AssignmentCertKind) Value() (value any, err error) { + _, value, err = mvdt.IndexValue() + return +} + +func (mvdt AssignmentCertKind) ValueAt(index uint) (value any, err error) { + switch index { + case 0: + return *new(RelayVRFModulo), nil + + case 1: + return *new(RelayVRFDelay), nil + + } + return nil, scale.ErrUnknownVaryingDataTypeValue +} + +// NewAssignmentCertKindVDT constructor for AssignmentCertKind +func NewAssignmentCertKindVDT() AssignmentCertKind { + return AssignmentCertKind{} +} + +// RelayVRFModulo an assignment story based on the VRF that authorized the relay-chain block where the +// candidate was included combined with a sample number. +type RelayVRFModulo struct { + // Sample the sample number used in this cert. + Sample uint32 +} + +// NewRelayVRFModulo constructor for RelayVRFModulo +func NewRelayVRFModulo() RelayVRFModulo { + return RelayVRFModulo{} +} + +// RelayVRFDelay an assignment story based on the VRF that authorized the relay-chain block where the +// candidate was included combined with the index of a particular core. +type RelayVRFDelay struct { + // CoreIndex the unique (during session) index of a core. + CoreIndex uint32 +} + +// NewVRFDelay constructor for RelayVRFDelay +func NewVRFDelay() RelayVRFDelay { + return RelayVRFDelay{} +} + +// VrfSignature represents VRF signature, which itself consists of a VRF pre-output and DLEQ proof +type VrfSignature struct { + // Output VRF output + Output [sr25519.VRFOutputLength]byte `scale:"1"` + // Proof VRF proof + Proof [sr25519.VRFProofLength]byte `scale:"2"` +} + +// AssignmentCert is a certification of assignment +type AssignmentCert struct { + // Kind the criterion which is claimed to be met by this cert. + Kind AssignmentCertKind `scale:"1"` + // Vrf the VRF signature showing the criterion is met. + Vrf VrfSignature `scale:"2"` +} + +// IndirectAssignmentCert is an assignment criterion which refers to the candidate under which the assignment is +// relevant by block hash. +type IndirectAssignmentCert struct { + // BlockHash a block hash where the canidate appears. + BlockHash common.Hash `scale:"1"` + // Validator the validator index. + Validator ValidatorIndex `scale:"2"` + // Cert the cert itself. + Cert AssignmentCert `scale:"3"` +} + +// CandidateIndex represents the index of the candidate in the list of candidates fully included as-of the block. +type CandidateIndex uint32 + +// Assignment holds indirect assignment cert and candidate index +type Assignment struct { + IndirectAssignmentCert IndirectAssignmentCert `scale:"1"` + CandidateIndex CandidateIndex `scale:"2"` +} + +// Assignments for candidates in recent, unfinalized blocks. +type Assignments []Assignment + +// IndirectSignedApprovalVote represents a signed approval vote which references the candidate indirectly via the block. +type IndirectSignedApprovalVote struct { + // BlockHash a block hash where the candidate appears. + BlockHash common.Hash `scale:"1"` + // CandidateIndex the index of the candidate in the list of candidates fully included as-of the block. + CandidateIndex CandidateIndex `scale:"2"` + // ValidatorIndex the validator index. + ValidatorIndex ValidatorIndex `scale:"3"` + // Signature the signature of the validator. + Signature ValidatorSignature `scale:"4"` +} + +// Approvals for candidates in some recent, unfinalized block. +type Approvals []IndirectSignedApprovalVote + +// ApprovalDistributionMessage network messages used by approval distribution subsystem. +type ApprovalDistributionMessageValues interface { + Assignments | Approvals +} + +// ApprovalDistributionMessage network messages used by approval distribution subsystem. +type ApprovalDistributionMessage struct { + inner any +} + +func setApprovalDistributionMessage[Value ApprovalDistributionMessageValues](mvdt *ApprovalDistributionMessage, value Value) { + mvdt.inner = value +} + +func (mvdt *ApprovalDistributionMessage) SetValue(value any) (err error) { + switch value := value.(type) { + case Assignments: + setApprovalDistributionMessage(mvdt, value) + return + + case Approvals: + setApprovalDistributionMessage(mvdt, value) + return + + default: + return fmt.Errorf("unsupported type") + } +} + +func (mvdt ApprovalDistributionMessage) IndexValue() (index uint, value any, err error) { + switch mvdt.inner.(type) { + case Assignments: + return 0, mvdt.inner, nil + + case Approvals: + return 1, mvdt.inner, nil + + } + return 0, nil, scale.ErrUnsupportedVaryingDataTypeValue +} + +func (mvdt ApprovalDistributionMessage) Value() (value any, err error) { + _, value, err = mvdt.IndexValue() + return +} + +func (mvdt ApprovalDistributionMessage) ValueAt(index uint) (value any, err error) { + switch index { + case 0: + return *new(Assignments), nil + + case 1: + return *new(Approvals), nil + + } + return nil, scale.ErrUnknownVaryingDataTypeValue +} + +// NewApprovalDistributionMessageVDT ruturns a new ApprovalDistributionMessage VaryingDataType +func NewApprovalDistributionMessageVDT() ApprovalDistributionMessage { + return ApprovalDistributionMessage{} +} diff --git a/lib/parachain/approval_distribution_message_test.go b/lib/parachain/approval_distribution_message_test.go new file mode 100644 index 0000000000..5d5dbcdd9c --- /dev/null +++ b/lib/parachain/approval_distribution_message_test.go @@ -0,0 +1,202 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +var hash = common.MustHexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + +func TestEncodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + // expected encoding is generated by running rust test code: + // fn try_msg_assignments_encode() { + // let hash = Hash::repeat_byte(0xAA); + // + // let validator_index = ValidatorIndex(1); + // let cert = fake_assignment_cert(hash, validator_index); + // let assignments = vec![(cert.clone(), 4u32)]; + // let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); + // + // let emsg = msg.encode(); + // println!("encode: {:?}", emsg); + //} + expectedEncoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 4, 0, 0, 0} + + approvalDistributionMessage.SetValue(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(1), false), + CandidateIndex: 4, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + + require.Equal(t, expectedEncoding, encodedMessage) + + approvalDistributionMessageDecodedTest := NewApprovalDistributionMessageVDT() + scale.Unmarshal(encodedMessage, &approvalDistributionMessageDecodedTest) + require.Equal(t, approvalDistributionMessage, approvalDistributionMessageDecodedTest) +} + +func TestEncodeApprovalDistributionMessageAssignmentDelay(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + + expectedEncoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 1, 1, 0, 0, 0, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 2, 0, 0, 0} + + approvalDistributionMessage.SetValue(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), true), + CandidateIndex: 2, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + + require.Equal(t, expectedEncoding, encodedMessage) +} + +func TestEncodeAssignmentCertKindModulo(t *testing.T) { + assignmentCertKind := NewAssignmentCertKindVDT() + assignmentCertKind.SetValue(RelayVRFModulo{Sample: 4}) + expectedEncoding := []byte{0, 4, 0, 0, 0} + encodedAssignmentCertKind, err := scale.Marshal(assignmentCertKind) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedAssignmentCertKind) + + assignmentCertTest := NewAssignmentCertKindVDT() + err = scale.Unmarshal(encodedAssignmentCertKind, &assignmentCertTest) + require.NoError(t, err) + require.Equal(t, assignmentCertKind, assignmentCertTest) +} + +func TestEncodeAssignmentCertKindDelay(t *testing.T) { + assignmentCertKind := NewAssignmentCertKindVDT() + assignmentCertKind.SetValue(RelayVRFDelay{CoreIndex: 5}) + expectedEncoding := []byte{1, 5, 0, 0, 0} + encodedAssignmentCertKind, err := scale.Marshal(assignmentCertKind) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedAssignmentCertKind) + + assignmentCertTest := NewAssignmentCertKindVDT() + err = scale.Unmarshal(encodedAssignmentCertKind, &assignmentCertTest) + require.NoError(t, err) + require.Equal(t, assignmentCertKind, assignmentCertTest) +} + +func TestEncodeApprovalDistributionMessageApprovals(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + + expectedEncoding := []byte{1, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 3, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + + approvalDistributionMessage.SetValue(Approvals{ + IndirectSignedApprovalVote{ + BlockHash: hash, + CandidateIndex: CandidateIndex(2), + ValidatorIndex: ValidatorIndex(3), + Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1}, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedMessage) +} + +func TestDecodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { + encoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 0, 2, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 4, 0, 0, 0} + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + err := scale.Unmarshal(encoding, &approvalDistributionMessage) + require.NoError(t, err) + + expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() + expectedApprovalDistributionMessage.SetValue(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), false), + CandidateIndex: 4, + }, + }) + + approvalValue, err := approvalDistributionMessage.Value() + require.NoError(t, err) + expectedValue, err := expectedApprovalDistributionMessage.Value() + require.NoError(t, err) + require.Equal(t, expectedValue, approvalValue) +} + +func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { + encoding := []byte{1, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 3, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() + expectedApprovalDistributionMessage.SetValue(Approvals{ + IndirectSignedApprovalVote{ + BlockHash: hash, + CandidateIndex: CandidateIndex(2), + ValidatorIndex: ValidatorIndex(3), + Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1}, + }, + }) + + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + err := scale.Unmarshal(encoding, &approvalDistributionMessage) + require.NoError(t, err) + require.Equal(t, expectedApprovalDistributionMessage, approvalDistributionMessage) +} + +func fakeAssignmentCert(blockHash common.Hash, validator ValidatorIndex, useDelay bool) IndirectAssignmentCert { + output := [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32} + proof := [64]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64} + assignmentCertKind := NewAssignmentCertKindVDT() + if useDelay { + assignmentCertKind.SetValue(RelayVRFDelay{CoreIndex: 1}) + } else { + assignmentCertKind.SetValue(RelayVRFModulo{Sample: 2}) + } + + return IndirectAssignmentCert{ + BlockHash: blockHash, + Validator: validator, + Cert: AssignmentCert{ + Kind: assignmentCertKind, + Vrf: VrfSignature{ + Output: output, + Proof: proof, + }, + }, + } +}