From 13a7ed279713696605230a6dbf773b04d8cd53fb Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Sat, 4 May 2024 23:04:53 +0530 Subject: [PATCH 01/19] implement a func to get attested candidate --- .../backing/candidate_backing_test.go | 11 +++ .../backing/get_backed_candidates.go | 3 +- .../backing/get_backed_candidates_test.go | 3 + dot/parachain/backing/mocks_test.go | 8 +- .../backing/per_relay_parent_state.go | 2 +- dot/parachain/backing/statement_table.go | 81 +++++++++++++++++-- 6 files changed, 97 insertions(+), 11 deletions(-) diff --git a/dot/parachain/backing/candidate_backing_test.go b/dot/parachain/backing/candidate_backing_test.go index c863646471..6c2dc8459f 100644 --- a/dot/parachain/backing/candidate_backing_test.go +++ b/dot/parachain/backing/candidate_backing_test.go @@ -448,6 +448,7 @@ func rpStateWhenPpmDisabled(t *testing.T) perRelayParentState { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(&attestedToReturn, nil) return perRelayParentState{ @@ -498,6 +499,7 @@ func TestPostImportStatement(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) return perRelayParentState{ @@ -523,6 +525,7 @@ func TestPostImportStatement(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(&AttestedCandidate{ GroupID: 4, Candidate: candidate, @@ -548,6 +551,7 @@ func TestPostImportStatement(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(&AttestedCandidate{ GroupID: 3, Candidate: getDummyCommittedCandidateReceipt(t), @@ -942,6 +946,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) return map[common.Hash]*perRelayParentState{ @@ -977,6 +982,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) return map[common.Hash]*perRelayParentState{ @@ -1014,6 +1020,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) return map[common.Hash]*perRelayParentState{ @@ -1054,6 +1061,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) return map[common.Hash]*perRelayParentState{ @@ -1097,6 +1105,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) return map[common.Hash]*perRelayParentState{ @@ -1147,6 +1156,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) mockTable.EXPECT().getCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), @@ -1190,6 +1200,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) mockTable.EXPECT().getCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), diff --git a/dot/parachain/backing/get_backed_candidates.go b/dot/parachain/backing/get_backed_candidates.go index 90e809cee0..a03bd5163b 100644 --- a/dot/parachain/backing/get_backed_candidates.go +++ b/dot/parachain/backing/get_backed_candidates.go @@ -22,7 +22,8 @@ func (cb *CandidateBacking) handleGetBackedCandidatesMessage(requestedCandidates continue } - attested, err := rpState.table.attestedCandidate(candidate.CandidateHash, &rpState.tableContext) + attested, err := rpState.table.attestedCandidate( + candidate.CandidateHash, &rpState.tableContext, rpState.minBackingVotes) if err != nil { logger.Debugf("getting attested candidate: %w", err) continue diff --git a/dot/parachain/backing/get_backed_candidates_test.go b/dot/parachain/backing/get_backed_candidates_test.go index 394b0f0eb7..24dac3cd2b 100644 --- a/dot/parachain/backing/get_backed_candidates_test.go +++ b/dot/parachain/backing/get_backed_candidates_test.go @@ -42,6 +42,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) return map[common.Hash]*perRelayParentState{ @@ -60,6 +61,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, nil) return map[common.Hash]*perRelayParentState{ @@ -78,6 +80,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(uint32(0)), ).Return(new(AttestedCandidate), nil) return map[common.Hash]*perRelayParentState{ diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index 51497910a6..2e00d3e5ef 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -41,18 +41,18 @@ func (m *MockTable) EXPECT() *MockTableMockRecorder { } // attestedCandidate mocks base method. -func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *TableContext) (*AttestedCandidate, error) { +func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *TableContext, arg2 uint32) (*AttestedCandidate, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "attestedCandidate", arg0, arg1) + ret := m.ctrl.Call(m, "attestedCandidate", arg0, arg1, arg2) ret0, _ := ret[0].(*AttestedCandidate) ret1, _ := ret[1].(error) return ret0, ret1 } // attestedCandidate indicates an expected call of attestedCandidate. -func (mr *MockTableMockRecorder) attestedCandidate(arg0, arg1 any) *gomock.Call { +func (mr *MockTableMockRecorder) attestedCandidate(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "attestedCandidate", reflect.TypeOf((*MockTable)(nil).attestedCandidate), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "attestedCandidate", reflect.TypeOf((*MockTable)(nil).attestedCandidate), arg0, arg1, arg2) } // drainMisbehaviors mocks base method. diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go index 1544ff459b..563989fbec 100644 --- a/dot/parachain/backing/per_relay_parent_state.go +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -120,7 +120,7 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan return } - attested, err := rpState.table.attestedCandidate(summary.Candidate, &rpState.tableContext) + attested, err := rpState.table.attestedCandidate(summary.Candidate, &rpState.tableContext, rpState.minBackingVotes) if err != nil { logger.Error(err.Error()) } diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 69419735b2..7e6dddf83a 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -6,6 +6,7 @@ package backing import ( "errors" "fmt" + "math" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" ) @@ -35,9 +36,54 @@ type candidateData struct { //nolint:unused validityVotes map[parachaintypes.ValidatorIndex]validityVoteWithSign } +// attested yields a full attestation for a candidate. +// If the candidate can be included, it will return attested candidate. +func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, error) { //nolint:unused + numOfValidityVotes := uint(len(data.validityVotes)) + if numOfValidityVotes < validityThreshold { + return nil, fmt.Errorf("not enough validity votes: %d < %d", numOfValidityVotes, validityThreshold) + } + + validityAttestations := make([]validityAttestation, numOfValidityVotes) + for validatorIndex, voteWithSign := range data.validityVotes { + switch voteWithSign.validityVote { + case valid: + attestation := parachaintypes.NewValidityAttestation() + err := attestation.Set(parachaintypes.Explicit(voteWithSign.signature)) + if err != nil { + return nil, fmt.Errorf("failed to set validity attestation: %w", err) + } + + validityAttestations = append(validityAttestations, validityAttestation{ + ValidatorIndex: validatorIndex, + ValidityAttestation: attestation, + }) + case issued: + attestation := parachaintypes.NewValidityAttestation() + err := attestation.Set(parachaintypes.Implicit(voteWithSign.signature)) + if err != nil { + return nil, fmt.Errorf("failed to set validity attestation: %w", err) + } + + validityAttestations = append(validityAttestations, validityAttestation{ + ValidatorIndex: validatorIndex, + ValidityAttestation: attestation, + }) + default: + return nil, fmt.Errorf("unknown validity vote: %d", voteWithSign.validityVote) + } + } + + return &AttestedCandidate{ + GroupID: data.groupID, + Candidate: data.candidate, + ValidityAttestations: validityAttestations, + }, nil +} + type validityVoteWithSign struct { //nolint:unused validityVote validityVote - signature parachaintypes.Signature + signature parachaintypes.ValidatorSignature } type validityVote byte //nolint:unused @@ -67,10 +113,35 @@ func (statementTable) importStatement( //nolint:unused return nil, nil } -func (statementTable) attestedCandidate(candidateHash parachaintypes.CandidateHash, ctx *TableContext, //nolint:unused +// attestedCandidate retrieves the attested candidate for the given candidate hash. +// returns attested candidate if the candidate exists and is includable. +func (table statementTable) attestedCandidate( //nolint:unused + candidateHash parachaintypes.CandidateHash, tableContext *TableContext, minimumBackingVotes uint32, ) (*AttestedCandidate, error) { - // TODO: Implement this method - return nil, nil + // size of the backing group. + var groupLen uint + + data, ok := table.candidateVotes[candidateHash] + if !ok { + return nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, candidateHash) + } + + group, ok := tableContext.groups[data.groupID] + if ok { + groupLen = uint(len(group)) + } else { + groupLen = math.MaxUint + } + + validityThreshold := effectiveMinimumBackingVotes(groupLen, minimumBackingVotes) + return data.attested(validityThreshold) +} + +// effectiveMinimumBackingVotes adjusts the configured needed backing votes with the size of the backing group. +// +// groupLen is the size of the backing group. +func effectiveMinimumBackingVotes(groupLen uint, configuredMinimumBackingVotes uint32) uint { //nolint:unused + return min(groupLen, uint(configuredMinimumBackingVotes)) } func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport { //nolint:unused @@ -81,7 +152,7 @@ func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisb type Table interface { getCandidate(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) importStatement(*TableContext, parachaintypes.SignedFullStatementWithPVD) (*Summary, error) - attestedCandidate(parachaintypes.CandidateHash, *TableContext) (*AttestedCandidate, error) + attestedCandidate(parachaintypes.CandidateHash, *TableContext, uint32) (*AttestedCandidate, error) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } From 6e3460ceceee4e40b481c166e439575f1e379b3e Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 8 May 2024 20:11:50 +0530 Subject: [PATCH 02/19] unit tests --- dot/parachain/backing/statement_table.go | 39 +++-- dot/parachain/backing/statement_table_test.go | 140 ++++++++++++++++++ 2 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 dot/parachain/backing/statement_table_test.go diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 7e6dddf83a..d9c2fb1c58 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -7,17 +7,19 @@ import ( "errors" "fmt" "math" + "sort" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" ) -var errCandidateDataNotFound = errors.New("candidate data not found") //nolint:unused +var errCandidateDataNotFound = errors.New("candidate data not found") +var errNotEnoughValidityVotes = errors.New("not enough validity votes") // statementTable implements the Table interface. -type statementTable struct { //nolint:unused - authorityData map[parachaintypes.ValidatorIndex]authorityData +type statementTable struct { + authorityData map[parachaintypes.ValidatorIndex]authorityData //nolint:unused candidateVotes map[parachaintypes.CandidateHash]candidateData - config tableConfig + config tableConfig //nolint:unused // TODO: Implement this // detected_misbehaviour: HashMap>>, @@ -30,7 +32,7 @@ type proposal struct { //nolint:unused signature parachaintypes.Signature } -type candidateData struct { //nolint:unused +type candidateData struct { groupID parachaintypes.ParaID candidate parachaintypes.CommittedCandidateReceipt validityVotes map[parachaintypes.ValidatorIndex]validityVoteWithSign @@ -38,13 +40,13 @@ type candidateData struct { //nolint:unused // attested yields a full attestation for a candidate. // If the candidate can be included, it will return attested candidate. -func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, error) { //nolint:unused +func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, error) { numOfValidityVotes := uint(len(data.validityVotes)) if numOfValidityVotes < validityThreshold { - return nil, fmt.Errorf("not enough validity votes: %d < %d", numOfValidityVotes, validityThreshold) + return nil, fmt.Errorf("%w: %d < %d", errNotEnoughValidityVotes, numOfValidityVotes, validityThreshold) } - validityAttestations := make([]validityAttestation, numOfValidityVotes) + validityAttestations := make([]validityAttestation, 0, numOfValidityVotes) for validatorIndex, voteWithSign := range data.validityVotes { switch voteWithSign.validityVote { case valid: @@ -74,6 +76,10 @@ func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, } } + sort.Slice(validityAttestations, func(i, j int) bool { + return validityAttestations[i].ValidatorIndex < validityAttestations[j].ValidatorIndex + }) + return &AttestedCandidate{ GroupID: data.groupID, Candidate: data.candidate, @@ -81,18 +87,19 @@ func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, }, nil } -type validityVoteWithSign struct { //nolint:unused +type validityVoteWithSign struct { validityVote validityVote - signature parachaintypes.ValidatorSignature + signature parachaintypes.ValidatorSignature // NOTE: should never be empty } -type validityVote byte //nolint:unused +type validityVote byte +// To make sure the validity vote has a value assigned, we use iota + 1. const ( // Implicit validity vote. - issued validityVote = iota //nolint:unused + issued validityVote = iota + 1 // Direct validity vote. - valid //nolint:unused + valid ) // getCommittedCandidateReceipt returns the committed candidate receipt for the given candidate hash. @@ -115,7 +122,7 @@ func (statementTable) importStatement( //nolint:unused // attestedCandidate retrieves the attested candidate for the given candidate hash. // returns attested candidate if the candidate exists and is includable. -func (table statementTable) attestedCandidate( //nolint:unused +func (table statementTable) attestedCandidate( candidateHash parachaintypes.CandidateHash, tableContext *TableContext, minimumBackingVotes uint32, ) (*AttestedCandidate, error) { // size of the backing group. @@ -140,7 +147,7 @@ func (table statementTable) attestedCandidate( //nolint:unused // effectiveMinimumBackingVotes adjusts the configured needed backing votes with the size of the backing group. // // groupLen is the size of the backing group. -func effectiveMinimumBackingVotes(groupLen uint, configuredMinimumBackingVotes uint32) uint { //nolint:unused +func effectiveMinimumBackingVotes(groupLen uint, configuredMinimumBackingVotes uint32) uint { return min(groupLen, uint(configuredMinimumBackingVotes)) } @@ -172,6 +179,8 @@ type Summary struct { } // AttestedCandidate represents an attested-to candidate. +// +// TODO: test AttestedCandidate scale encode decode type AttestedCandidate struct { // The group ID that the candidate is in. GroupID parachaintypes.ParaID `scale:"1"` diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go new file mode 100644 index 0000000000..c2dfa6ee06 --- /dev/null +++ b/dot/parachain/backing/statement_table_test.go @@ -0,0 +1,140 @@ +package backing + +import ( + "testing" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/stretchr/testify/require" +) + +func TestCandidateData_attested(t *testing.T) { + committedCandidateReceipt := getDummyCommittedCandidateReceipt(t) + validityThreshold := uint(2) + data := candidateData{ + groupID: 1, + candidate: committedCandidateReceipt, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{}, + } + + attestedCandidate, err := data.attested(validityThreshold) + require.ErrorIs(t, err, errNotEnoughValidityVotes) + require.Nil(t, attestedCandidate) + + data.validityVotes = map[parachaintypes.ValidatorIndex]validityVoteWithSign{ + 1: {validityVote: issued, signature: parachaintypes.ValidatorSignature{1}}, + 2: {validityVote: valid, signature: parachaintypes.ValidatorSignature{1, 2}}, + 3: {validityVote: valid, signature: parachaintypes.ValidatorSignature{1, 2, 3}}, + } + + expectedAttestedCandidate := &AttestedCandidate{ + GroupID: 1, + Candidate: committedCandidateReceipt, + ValidityAttestations: func() []validityAttestation { + var attestations []validityAttestation + + // validity vote: issued + vote1 := parachaintypes.NewValidityAttestation() + err := vote1.Set(parachaintypes.Implicit( + parachaintypes.ValidatorSignature{1}, + )) + require.NoError(t, err) + attest1 := validityAttestation{ + ValidatorIndex: 1, + ValidityAttestation: vote1, + } + + // validity vote: valid + vote2 := parachaintypes.NewValidityAttestation() + err = vote2.Set(parachaintypes.Explicit( + parachaintypes.ValidatorSignature{1, 2}, + )) + require.NoError(t, err) + attest2 := validityAttestation{ + ValidatorIndex: 2, + ValidityAttestation: vote2, + } + + // validity vote: valid + vote3 := parachaintypes.NewValidityAttestation() + err = vote3.Set(parachaintypes.Explicit( + parachaintypes.ValidatorSignature{1, 2, 3}, + )) + require.NoError(t, err) + attest3 := validityAttestation{ + ValidatorIndex: 3, + ValidityAttestation: vote3, + } + + return append(attestations, attest1, attest2, attest3) + }(), + } + + attestedCandidate, err = data.attested(validityThreshold) + require.NoError(t, err) + require.Equal(t, expectedAttestedCandidate, attestedCandidate) +} + +func TestStatementTable_attestedCandidate(t *testing.T) { + t.Parallel() + + type args struct { + candidateHash parachaintypes.CandidateHash + tableContext *TableContext + minimumBackingVotes uint32 + } + tests := []struct { + name string + table *statementTable + args args + want *AttestedCandidate + wantErr error + }{ + { + name: "candidate_votes_not_available_for_given_candidate_hash", + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]candidateData{}, + }, + args: args{ + candidateHash: dummyCandidateHash(t), + }, + wantErr: errCandidateDataNotFound, + }, + { + name: "not_enough_validity_votes", + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]candidateData{ + dummyCandidateHash(t): { + groupID: 1, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{}, + }, + }, + }, + args: args{ + candidateHash: dummyCandidateHash(t), + tableContext: &TableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {1, 2, 3}, + 2: {4, 5, 6}, + 3: {7, 8, 9}, + }, + }, + minimumBackingVotes: 2, + }, + wantErr: errNotEnoughValidityVotes, + }, + // Positive test case is not added here because it is already covered in TestCandidateData_attested. + // Context: When there are enough validity votes available, attested method of candidateData is called. + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + attestedCandidate, err := tt.table.attestedCandidate( + tt.args.candidateHash, tt.args.tableContext, tt.args.minimumBackingVotes) + require.ErrorIs(t, err, tt.wantErr) + require.Nil(t, attestedCandidate) + }) + } +} From 276b6e0c39eea0635abf17992b24ef499acd0405 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 15 May 2024 16:34:19 +0530 Subject: [PATCH 03/19] remove scale tags and unexport struct --- .../backing/candidate_backing_test.go | 44 ++++++++-------- .../backing/get_backed_candidates_test.go | 2 +- dot/parachain/backing/mocks_test.go | 4 +- .../backing/per_relay_parent_state.go | 14 ++--- dot/parachain/backing/statement_table.go | 52 +++++++++---------- dot/parachain/backing/statement_table_test.go | 38 +++++++------- 6 files changed, 76 insertions(+), 78 deletions(-) diff --git a/dot/parachain/backing/candidate_backing_test.go b/dot/parachain/backing/candidate_backing_test.go index 6c2dc8459f..ec8c1f428d 100644 --- a/dot/parachain/backing/candidate_backing_test.go +++ b/dot/parachain/backing/candidate_backing_test.go @@ -421,21 +421,21 @@ func dummyTableContext(t *testing.T) TableContext { func rpStateWhenPpmDisabled(t *testing.T) perRelayParentState { t.Helper() - attestedToReturn := AttestedCandidate{ - GroupID: 3, - Candidate: getDummyCommittedCandidateReceipt(t), - ValidityAttestations: []validityAttestation{ + attestedToReturn := attestedCandidate{ + groupID: 3, + committedCandidateReceipt: getDummyCommittedCandidateReceipt(t), + validityAttestations: []validatorIndexWithAttestation{ { - ValidatorIndex: 7, - ValidityAttestation: dummyValidityAttestation(t, "implicit"), + validatorIndex: 7, + validityAttestation: dummyValidityAttestation(t, "implicit"), }, { - ValidatorIndex: 8, - ValidityAttestation: dummyValidityAttestation(t, "explicit"), + validatorIndex: 8, + validityAttestation: dummyValidityAttestation(t, "explicit"), }, { - ValidatorIndex: 9, - ValidityAttestation: dummyValidityAttestation(t, "implicit"), + validatorIndex: 9, + validityAttestation: dummyValidityAttestation(t, "implicit"), }, }, } @@ -526,9 +526,9 @@ func TestPostImportStatement(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(&AttestedCandidate{ - GroupID: 4, - Candidate: candidate, + ).Return(&attestedCandidate{ + groupID: 4, + committedCandidateReceipt: candidate, }, nil) return perRelayParentState{ @@ -552,9 +552,9 @@ func TestPostImportStatement(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(&AttestedCandidate{ - GroupID: 3, - Candidate: getDummyCommittedCandidateReceipt(t), + ).Return(&attestedCandidate{ + groupID: 3, + committedCandidateReceipt: getDummyCommittedCandidateReceipt(t), }, nil) return perRelayParentState{ @@ -983,7 +983,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) return map[common.Hash]*perRelayParentState{ relayParent: { @@ -1021,7 +1021,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) return map[common.Hash]*perRelayParentState{ relayParent: { @@ -1062,7 +1062,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) return map[common.Hash]*perRelayParentState{ relayParent: { @@ -1106,7 +1106,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) return map[common.Hash]*perRelayParentState{ relayParent: { @@ -1157,7 +1157,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) mockTable.EXPECT().getCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), ).Return( @@ -1201,7 +1201,7 @@ func TestHandleStatementMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) mockTable.EXPECT().getCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), ).Return(dummyCCR, nil) diff --git a/dot/parachain/backing/get_backed_candidates_test.go b/dot/parachain/backing/get_backed_candidates_test.go index 24dac3cd2b..fbb00934dd 100644 --- a/dot/parachain/backing/get_backed_candidates_test.go +++ b/dot/parachain/backing/get_backed_candidates_test.go @@ -81,7 +81,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), gomock.AssignableToTypeOf(new(TableContext)), gomock.AssignableToTypeOf(uint32(0)), - ).Return(new(AttestedCandidate), nil) + ).Return(new(attestedCandidate), nil) return map[common.Hash]*perRelayParentState{ getDummyHash(t, 2): { diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index 2e00d3e5ef..fda2ef7a7e 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -41,10 +41,10 @@ func (m *MockTable) EXPECT() *MockTableMockRecorder { } // attestedCandidate mocks base method. -func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *TableContext, arg2 uint32) (*AttestedCandidate, error) { +func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *TableContext, arg2 uint32) (*attestedCandidate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "attestedCandidate", arg0, arg1, arg2) - ret0, _ := ret[0].(*AttestedCandidate) + ret0, _ := ret[0].(*attestedCandidate) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go index 563989fbec..1b08bf31f1 100644 --- a/dot/parachain/backing/per_relay_parent_state.go +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -131,7 +131,7 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan return } - hash, err := attested.Candidate.Hash() + hash, err := attested.committedCandidateReceipt.Hash() if err != nil { logger.Error(err.Error()) return @@ -215,10 +215,10 @@ func issueNewMisbehaviors(subSystemToOverseer chan<- any, relayParent common.Has } func attestedToBackedCandidate( - attested AttestedCandidate, + attested attestedCandidate, tableContext *TableContext, ) *parachaintypes.BackedCandidate { - group := tableContext.groups[attested.GroupID] + group := tableContext.groups[attested.groupID] validatorIndices := make([]bool, len(group)) var validityAttestations []parachaintypes.ValidityAttestation @@ -226,10 +226,10 @@ func attestedToBackedCandidate( // the order of bits set in the bitfield, which is not necessarily // the order of the `validity_votes` we got from the table. for positionInGroup, validatorIndex := range group { - for _, validityVote := range attested.ValidityAttestations { - if validityVote.ValidatorIndex == validatorIndex { + for _, validityVote := range attested.validityAttestations { + if validityVote.validatorIndex == validatorIndex { validatorIndices[positionInGroup] = true - validityAttestations = append(validityAttestations, validityVote.ValidityAttestation) + validityAttestations = append(validityAttestations, validityVote.validityAttestation) } } @@ -240,7 +240,7 @@ func attestedToBackedCandidate( } return ¶chaintypes.BackedCandidate{ - Candidate: attested.Candidate, + Candidate: attested.committedCandidateReceipt, ValidityVotes: validityAttestations, ValidatorIndices: scale.NewBitVec(validatorIndices), } diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index d9c2fb1c58..f42db62b1a 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -40,13 +40,13 @@ type candidateData struct { // attested yields a full attestation for a candidate. // If the candidate can be included, it will return attested candidate. -func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, error) { +func (data candidateData) attested(validityThreshold uint) (*attestedCandidate, error) { numOfValidityVotes := uint(len(data.validityVotes)) if numOfValidityVotes < validityThreshold { return nil, fmt.Errorf("%w: %d < %d", errNotEnoughValidityVotes, numOfValidityVotes, validityThreshold) } - validityAttestations := make([]validityAttestation, 0, numOfValidityVotes) + validityAttestations := make([]validatorIndexWithAttestation, 0, numOfValidityVotes) for validatorIndex, voteWithSign := range data.validityVotes { switch voteWithSign.validityVote { case valid: @@ -56,9 +56,9 @@ func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, return nil, fmt.Errorf("failed to set validity attestation: %w", err) } - validityAttestations = append(validityAttestations, validityAttestation{ - ValidatorIndex: validatorIndex, - ValidityAttestation: attestation, + validityAttestations = append(validityAttestations, validatorIndexWithAttestation{ + validatorIndex: validatorIndex, + validityAttestation: attestation, }) case issued: attestation := parachaintypes.NewValidityAttestation() @@ -67,9 +67,9 @@ func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, return nil, fmt.Errorf("failed to set validity attestation: %w", err) } - validityAttestations = append(validityAttestations, validityAttestation{ - ValidatorIndex: validatorIndex, - ValidityAttestation: attestation, + validityAttestations = append(validityAttestations, validatorIndexWithAttestation{ + validatorIndex: validatorIndex, + validityAttestation: attestation, }) default: return nil, fmt.Errorf("unknown validity vote: %d", voteWithSign.validityVote) @@ -77,13 +77,13 @@ func (data candidateData) attested(validityThreshold uint) (*AttestedCandidate, } sort.Slice(validityAttestations, func(i, j int) bool { - return validityAttestations[i].ValidatorIndex < validityAttestations[j].ValidatorIndex + return validityAttestations[i].validatorIndex < validityAttestations[j].validatorIndex }) - return &AttestedCandidate{ - GroupID: data.groupID, - Candidate: data.candidate, - ValidityAttestations: validityAttestations, + return &attestedCandidate{ + groupID: data.groupID, + committedCandidateReceipt: data.candidate, + validityAttestations: validityAttestations, }, nil } @@ -124,7 +124,7 @@ func (statementTable) importStatement( //nolint:unused // returns attested candidate if the candidate exists and is includable. func (table statementTable) attestedCandidate( candidateHash parachaintypes.CandidateHash, tableContext *TableContext, minimumBackingVotes uint32, -) (*AttestedCandidate, error) { +) (*attestedCandidate, error) { // size of the backing group. var groupLen uint @@ -159,7 +159,7 @@ func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisb type Table interface { getCandidate(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) importStatement(*TableContext, parachaintypes.SignedFullStatementWithPVD) (*Summary, error) - attestedCandidate(parachaintypes.CandidateHash, *TableContext, uint32) (*AttestedCandidate, error) + attestedCandidate(parachaintypes.CandidateHash, *TableContext, uint32) (*attestedCandidate, error) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } @@ -178,22 +178,20 @@ type Summary struct { ValidityVotes uint64 } -// AttestedCandidate represents an attested-to candidate. -// -// TODO: test AttestedCandidate scale encode decode -type AttestedCandidate struct { +// attestedCandidate represents an attested-to candidate. +type attestedCandidate struct { // The group ID that the candidate is in. - GroupID parachaintypes.ParaID `scale:"1"` - // The candidate data. - Candidate parachaintypes.CommittedCandidateReceipt `scale:"2"` + groupID parachaintypes.ParaID + // The committedCandidateReceipt data. + committedCandidateReceipt parachaintypes.CommittedCandidateReceipt // Validity attestations. - ValidityAttestations []validityAttestation `scale:"3"` + validityAttestations []validatorIndexWithAttestation } -// validityAttestation represents a validity attestation for a candidate. -type validityAttestation struct { - ValidatorIndex parachaintypes.ValidatorIndex `scale:"1"` - ValidityAttestation parachaintypes.ValidityAttestation `scale:"2"` +// validatorIndexWithAttestation represents a validity attestation for a candidate. +type validatorIndexWithAttestation struct { + validatorIndex parachaintypes.ValidatorIndex + validityAttestation parachaintypes.ValidityAttestation } // Table configuration. diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index c2dfa6ee06..2e7cda98d2 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -16,9 +16,9 @@ func TestCandidateData_attested(t *testing.T) { validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{}, } - attestedCandidate, err := data.attested(validityThreshold) + attested, err := data.attested(validityThreshold) require.ErrorIs(t, err, errNotEnoughValidityVotes) - require.Nil(t, attestedCandidate) + require.Nil(t, attested) data.validityVotes = map[parachaintypes.ValidatorIndex]validityVoteWithSign{ 1: {validityVote: issued, signature: parachaintypes.ValidatorSignature{1}}, @@ -26,11 +26,11 @@ func TestCandidateData_attested(t *testing.T) { 3: {validityVote: valid, signature: parachaintypes.ValidatorSignature{1, 2, 3}}, } - expectedAttestedCandidate := &AttestedCandidate{ - GroupID: 1, - Candidate: committedCandidateReceipt, - ValidityAttestations: func() []validityAttestation { - var attestations []validityAttestation + expectedAttestedCandidate := &attestedCandidate{ + groupID: 1, + committedCandidateReceipt: committedCandidateReceipt, + validityAttestations: func() []validatorIndexWithAttestation { + var attestations []validatorIndexWithAttestation // validity vote: issued vote1 := parachaintypes.NewValidityAttestation() @@ -38,9 +38,9 @@ func TestCandidateData_attested(t *testing.T) { parachaintypes.ValidatorSignature{1}, )) require.NoError(t, err) - attest1 := validityAttestation{ - ValidatorIndex: 1, - ValidityAttestation: vote1, + attest1 := validatorIndexWithAttestation{ + validatorIndex: 1, + validityAttestation: vote1, } // validity vote: valid @@ -49,9 +49,9 @@ func TestCandidateData_attested(t *testing.T) { parachaintypes.ValidatorSignature{1, 2}, )) require.NoError(t, err) - attest2 := validityAttestation{ - ValidatorIndex: 2, - ValidityAttestation: vote2, + attest2 := validatorIndexWithAttestation{ + validatorIndex: 2, + validityAttestation: vote2, } // validity vote: valid @@ -60,18 +60,18 @@ func TestCandidateData_attested(t *testing.T) { parachaintypes.ValidatorSignature{1, 2, 3}, )) require.NoError(t, err) - attest3 := validityAttestation{ - ValidatorIndex: 3, - ValidityAttestation: vote3, + attest3 := validatorIndexWithAttestation{ + validatorIndex: 3, + validityAttestation: vote3, } return append(attestations, attest1, attest2, attest3) }(), } - attestedCandidate, err = data.attested(validityThreshold) + attested, err = data.attested(validityThreshold) require.NoError(t, err) - require.Equal(t, expectedAttestedCandidate, attestedCandidate) + require.Equal(t, expectedAttestedCandidate, attested) } func TestStatementTable_attestedCandidate(t *testing.T) { @@ -86,7 +86,7 @@ func TestStatementTable_attestedCandidate(t *testing.T) { name string table *statementTable args args - want *AttestedCandidate + want *attestedCandidate wantErr error }{ { From d2855d7ce468f60aaaf0c54cccc574db99d18c6a Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 15 May 2024 21:16:48 +0530 Subject: [PATCH 04/19] use slices.SortFunc to sort validity attestations --- dot/parachain/backing/statement_table.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index f42db62b1a..a0de8fd748 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -4,10 +4,11 @@ package backing import ( + "cmp" "errors" "fmt" "math" - "sort" + "slices" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" ) @@ -76,8 +77,8 @@ func (data candidateData) attested(validityThreshold uint) (*attestedCandidate, } } - sort.Slice(validityAttestations, func(i, j int) bool { - return validityAttestations[i].validatorIndex < validityAttestations[j].validatorIndex + slices.SortFunc(validityAttestations, func(i, j validatorIndexWithAttestation) int { + return cmp.Compare(i.validatorIndex, j.validatorIndex) }) return &attestedCandidate{ From f8f9f6d8b94eee3a5f34e49a0e6bff6edb1970cf Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Tue, 21 May 2024 15:10:15 +0530 Subject: [PATCH 05/19] implement import statement --- dot/parachain/backing/active_leaves_update.go | 2 +- dot/parachain/backing/candidate_backing.go | 9 +- .../backing/candidate_backing_test.go | 50 ++-- .../backing/get_backed_candidates.go | 2 +- .../backing/get_backed_candidates_test.go | 6 +- dot/parachain/backing/mocks_test.go | 4 +- .../backing/per_relay_parent_state.go | 37 +-- dot/parachain/backing/statement_table.go | 283 +++++++++++++++++- dot/parachain/backing/statement_table_test.go | 8 +- dot/parachain/types/misbehavior.go | 70 ++--- dot/parachain/types/types.go | 16 + 11 files changed, 352 insertions(+), 135 deletions(-) diff --git a/dot/parachain/backing/active_leaves_update.go b/dot/parachain/backing/active_leaves_update.go index 281d22c493..429f1e5b40 100644 --- a/dot/parachain/backing/active_leaves_update.go +++ b/dot/parachain/backing/active_leaves_update.go @@ -360,7 +360,7 @@ func constructPerRelayParentState( } } - tableContext := TableContext{ + tableContext := tableContext{ validator: localValidator, groups: groups, validators: validators, diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index b77561b421..68ef80bbda 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -115,14 +115,19 @@ type attestingData struct { backing []parachaintypes.ValidatorIndex } -// TableContext represents the contextual information associated with a validator and groups +// tableContext represents the contextual information associated with a validator and groups // for a table under a relay-parent. -type TableContext struct { +type tableContext struct { validator *validator groups map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex validators []parachaintypes.ValidatorID } +func (tc *tableContext) isMemberOf(validatorIndex parachaintypes.ValidatorIndex, paraID parachaintypes.ParaID) bool { + // TODO: implement this + return false +} + // validator represents local validator information. // It can be created if the local node is a validator in the context of a particular relay chain block. type validator struct { diff --git a/dot/parachain/backing/candidate_backing_test.go b/dot/parachain/backing/candidate_backing_test.go index ec8c1f428d..dab3517570 100644 --- a/dot/parachain/backing/candidate_backing_test.go +++ b/dot/parachain/backing/candidate_backing_test.go @@ -223,7 +223,7 @@ func TestImportStatement(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(new(Summary), nil) @@ -259,7 +259,7 @@ func TestImportStatement(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(new(Summary), nil) @@ -290,7 +290,7 @@ func TestImportStatement(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(new(Summary), nil) @@ -310,7 +310,7 @@ func TestImportStatement(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(new(Summary), nil) @@ -397,10 +397,10 @@ func dummyValidityAttestation(t *testing.T, value string) parachaintypes.Validit return vdt } -func dummyTableContext(t *testing.T) TableContext { +func dummyTableContext(t *testing.T) tableContext { t.Helper() - return TableContext{ + return tableContext{ validator: &validator{ index: 1, }, @@ -447,7 +447,7 @@ func rpStateWhenPpmDisabled(t *testing.T) perRelayParentState { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(&attestedToReturn, nil) @@ -498,7 +498,7 @@ func TestPostImportStatement(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) @@ -524,7 +524,7 @@ func TestPostImportStatement(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(&attestedCandidate{ groupID: 4, @@ -550,7 +550,7 @@ func TestPostImportStatement(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(&attestedCandidate{ groupID: 3, @@ -909,7 +909,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(nil, nil) mockTable.EXPECT().drainMisbehaviors(). @@ -936,7 +936,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ GroupID: 4, @@ -945,7 +945,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) @@ -971,7 +971,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -981,7 +981,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) @@ -1009,7 +1009,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -1019,7 +1019,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) @@ -1050,7 +1050,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -1060,7 +1060,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) @@ -1094,7 +1094,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -1104,7 +1104,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) @@ -1145,7 +1145,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -1155,7 +1155,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) mockTable.EXPECT().getCandidate( @@ -1189,7 +1189,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable := NewMockTable(ctrl) mockTable.EXPECT().importStatement( - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), ).Return(&Summary{ Candidate: candidateHash, @@ -1199,7 +1199,7 @@ func TestHandleStatementMessage(t *testing.T) { Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) mockTable.EXPECT().getCandidate( diff --git a/dot/parachain/backing/get_backed_candidates.go b/dot/parachain/backing/get_backed_candidates.go index a03bd5163b..c56dab70b8 100644 --- a/dot/parachain/backing/get_backed_candidates.go +++ b/dot/parachain/backing/get_backed_candidates.go @@ -34,7 +34,7 @@ func (cb *CandidateBacking) handleGetBackedCandidatesMessage(requestedCandidates continue } - backed := attestedToBackedCandidate(*attested, &rpState.tableContext) + backed := attested.toBackedCandidate(&rpState.tableContext) backedCandidates = append(backedCandidates, backed) } diff --git a/dot/parachain/backing/get_backed_candidates_test.go b/dot/parachain/backing/get_backed_candidates_test.go index fbb00934dd..a9ad97d2ee 100644 --- a/dot/parachain/backing/get_backed_candidates_test.go +++ b/dot/parachain/backing/get_backed_candidates_test.go @@ -41,7 +41,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, errors.New("could not get attested candidate from table")) @@ -60,7 +60,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(nil, nil) @@ -79,7 +79,7 @@ func TestHandleGetBackedCandidatesMessage(t *testing.T) { mockTable.EXPECT().attestedCandidate( gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), - gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(new(tableContext)), gomock.AssignableToTypeOf(uint32(0)), ).Return(new(attestedCandidate), nil) diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index fda2ef7a7e..1e44c000ab 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -41,7 +41,7 @@ func (m *MockTable) EXPECT() *MockTableMockRecorder { } // attestedCandidate mocks base method. -func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *TableContext, arg2 uint32) (*attestedCandidate, error) { +func (m *MockTable) attestedCandidate(arg0 parachaintypes.CandidateHash, arg1 *tableContext, arg2 uint32) (*attestedCandidate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "attestedCandidate", arg0, arg1, arg2) ret0, _ := ret[0].(*attestedCandidate) @@ -85,7 +85,7 @@ func (mr *MockTableMockRecorder) getCandidate(arg0 any) *gomock.Call { } // importStatement mocks base method. -func (m *MockTable) importStatement(arg0 *TableContext, arg1 parachaintypes.SignedFullStatementWithPVD) (*Summary, error) { +func (m *MockTable) importStatement(arg0 *tableContext, arg1 parachaintypes.SignedFullStatementWithPVD) (*Summary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "importStatement", arg0, arg1) ret0, _ := ret[0].(*Summary) diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go index 1b08bf31f1..4f0d1c285c 100644 --- a/dot/parachain/backing/per_relay_parent_state.go +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -14,7 +14,6 @@ import ( wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/pkg/scale" ) // PerRelayParentState represents the state information for a relay-parent in the subsystem. @@ -27,7 +26,7 @@ type perRelayParentState struct { // The table of candidates and statements under this relay-parent. table Table // The table context, including groups. - tableContext TableContext + tableContext tableContext // Data needed for retrying in case of `ValidatedCandidateCommand::AttestNoPoV`. fallbacks map[parachaintypes.CandidateHash]attestingData // These candidates are undergoing validation in the background. @@ -149,7 +148,7 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan rpState.backed[candidateHash] = true // Convert the attested candidate to a backed candidate. - backedCandidate := attestedToBackedCandidate(*attested, &rpState.tableContext) + backedCandidate := attested.toBackedCandidate(&rpState.tableContext) if backedCandidate == nil { issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) return @@ -214,38 +213,6 @@ func issueNewMisbehaviors(subSystemToOverseer chan<- any, relayParent common.Has } } -func attestedToBackedCandidate( - attested attestedCandidate, - tableContext *TableContext, -) *parachaintypes.BackedCandidate { - group := tableContext.groups[attested.groupID] - validatorIndices := make([]bool, len(group)) - var validityAttestations []parachaintypes.ValidityAttestation - - // The order of the validity votes in the backed candidate must match - // the order of bits set in the bitfield, which is not necessarily - // the order of the `validity_votes` we got from the table. - for positionInGroup, validatorIndex := range group { - for _, validityVote := range attested.validityAttestations { - if validityVote.validatorIndex == validatorIndex { - validatorIndices[positionInGroup] = true - validityAttestations = append(validityAttestations, validityVote.validityAttestation) - } - } - - if !validatorIndices[positionInGroup] { - logger.Error("validity vote from unknown validator") - return nil - } - } - - return ¶chaintypes.BackedCandidate{ - Candidate: attested.committedCandidateReceipt, - ValidityVotes: validityAttestations, - ValidatorIndices: scale.NewBitVec(validatorIndices), - } -} - // Kick off validation work and distribute the result as a signed statement. func (rpState *perRelayParentState) kickOffValidationWork( blockState BlockState, diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index a0de8fd748..4014350707 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -11,26 +11,26 @@ import ( "slices" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/pkg/scale" ) var errCandidateDataNotFound = errors.New("candidate data not found") var errNotEnoughValidityVotes = errors.New("not enough validity votes") +var errUnknownValidityVote = errors.New("unknown validity vote") // statementTable implements the Table interface. type statementTable struct { - authorityData map[parachaintypes.ValidatorIndex]authorityData //nolint:unused - candidateVotes map[parachaintypes.CandidateHash]candidateData - config tableConfig //nolint:unused - - // TODO: Implement this - // detected_misbehaviour: HashMap>>, + authorityData map[parachaintypes.ValidatorIndex]authorityData //nolint:unused + detectedMisbehaviour map[parachaintypes.ValidatorIndex][]parachaintypes.Misbehaviour + candidateVotes map[parachaintypes.CandidateHash]*candidateData + config tableConfig //nolint:unused } type authorityData []proposal //nolint:unused type proposal struct { //nolint:unused candidateHash parachaintypes.CandidateHash - signature parachaintypes.Signature + signature parachaintypes.ValidatorSignature } type candidateData struct { @@ -39,6 +39,14 @@ type candidateData struct { validityVotes map[parachaintypes.ValidatorIndex]validityVoteWithSign } +func (data *candidateData) getSummary(candidateHash parachaintypes.CandidateHash) *Summary { + return &Summary{ + GroupID: data.groupID, + Candidate: candidateHash, + ValidityVotes: uint(len(data.validityVotes)), + } +} + // attested yields a full attestation for a candidate. // If the candidate can be included, it will return attested candidate. func (data candidateData) attested(validityThreshold uint) (*attestedCandidate, error) { @@ -114,17 +122,231 @@ func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachai return data.candidate, nil } -func (statementTable) importStatement( //nolint:unused - ctx *TableContext, statement parachaintypes.SignedFullStatementWithPVD, +/* +TODO: + - change interface method arguments after implementing this method +*/ +func (table *statementTable) importStatement( //nolint:unused + tableCtx *tableContext, signedStatement parachaintypes.SignedFullStatement, ) (*Summary, error) { - // TODO: Implement this method + var summary *Summary + var misbehavior parachaintypes.Misbehaviour + + statementVDT, err := signedStatement.Payload.Value() + if err != nil { + return nil, fmt.Errorf("getting value from statement: %w", err) + } + + switch statementVDT := statementVDT.(type) { + case parachaintypes.Seconded: + summary, misbehavior, err = table.importCandidate( + signedStatement.ValidatorIndex, + parachaintypes.CommittedCandidateReceipt(statementVDT), + signedStatement.Signature, + tableCtx, + ) + case parachaintypes.Valid: + summary, misbehavior, err = table.validityVote( + signedStatement.ValidatorIndex, + parachaintypes.CandidateHash(statementVDT), + validityVoteWithSign{validityVote: valid, signature: signedStatement.Signature}, + tableCtx, + ) + } + + if err == nil { + return summary, nil + } + + // If misbehavior is detected, store it. + { + misbehaviors, ok := table.detectedMisbehaviour[signedStatement.ValidatorIndex] + if !ok { + misbehaviors = []parachaintypes.Misbehaviour{misbehavior} + } else { + misbehaviors = append(misbehaviors, misbehavior) + } + + table.detectedMisbehaviour[signedStatement.ValidatorIndex] = misbehaviors + } return nil, nil } +func isCandidateAlreadyProposed(authData authorityData, candidateHash parachaintypes.CandidateHash) bool { + return slices.ContainsFunc(authData, func(p proposal) bool { + return p.candidateHash == candidateHash + }) +} + +func (table *statementTable) importCandidate( + authority parachaintypes.ValidatorIndex, + candidate parachaintypes.CommittedCandidateReceipt, + signature parachaintypes.ValidatorSignature, + tableCtx *tableContext, +) (*Summary, parachaintypes.Misbehaviour, error) { + paraID := parachaintypes.ParaID(candidate.Descriptor.ParaID) + + if !tableCtx.isMemberOf(authority, paraID) { + statementSeconded := parachaintypes.NewStatementVDT() + err := statementSeconded.Set(parachaintypes.Seconded(candidate)) + if err != nil { + return nil, nil, fmt.Errorf("setting seconded statement: %w", err) + } + + misbehavior := parachaintypes.UnauthorizedStatement{ + Payload: statementSeconded, + ValidatorIndex: authority, + Signature: signature, + } + + return nil, misbehavior, nil + } + + candidateHash, err := parachaintypes.GetCandidateHash(candidate) + if err != nil { + return nil, nil, fmt.Errorf("getting candidate hash: %w", err) + } + + var isNewProposal bool + authData, ok := table.authorityData[authority] + if !ok { + table.authorityData[authority] = authorityData{{candidateHash, signature}} + isNewProposal = true + } else { + // if digest is different, fetch candidate and note misbehavior. + if !table.config.allowMultipleSeconded && len(authData) == 1 { + oldCandidateHash := authData[0].candidateHash + oldSignature := authData[0].signature + + if oldCandidateHash != candidateHash { + data, ok := table.candidateVotes[oldCandidateHash] + if !ok { + // when proposal first received from authority, candidate votes entry is created. + // and here authData is not empty, so candidate votes entry should be present. + // So, this error should never happen. + return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, oldCandidateHash) + } + + oldCandidate := data.candidate + + misbehavior := parachaintypes.MultipleCandidates{ + First: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: oldCandidate, + Signature: oldSignature, + }, + Second: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: candidate, + Signature: signature, + }, + } + return nil, misbehavior, nil + } + } else if table.config.allowMultipleSeconded && isCandidateAlreadyProposed(authData, candidateHash) { + // nothing to do + } else { + authData = append(authData, proposal{candidateHash, signature}) + table.authorityData[authority] = authData + isNewProposal = true + } + } + + if isNewProposal { + table.candidateVotes[candidateHash] = &candidateData{ + groupID: paraID, + candidate: candidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + } + } + + return table.validityVote( + authority, + candidateHash, + validityVoteWithSign{validityVote: issued, signature: signature}, + tableCtx, + ) +} + +func (table *statementTable) validityVote( + from parachaintypes.ValidatorIndex, + candidateHash parachaintypes.CandidateHash, + voteWithSign validityVoteWithSign, + tableCtx *tableContext, +) (*Summary, parachaintypes.Misbehaviour, error) { + data, ok := table.candidateVotes[candidateHash] + if !ok { + return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, candidateHash) + } + + // check that this authority actually can vote in this group. + if !tableCtx.isMemberOf(from, data.groupID) { + switch voteWithSign.validityVote { + case valid: + validStatement := parachaintypes.NewStatementVDT() + err := validStatement.Set(parachaintypes.Valid(candidateHash)) + if err != nil { + return nil, nil, fmt.Errorf("setting valid statement: %w", err) + } + + misbehavior := parachaintypes.UnauthorizedStatement{ + Payload: validStatement, + ValidatorIndex: from, + Signature: voteWithSign.signature, + } + + return nil, misbehavior, nil + case issued: + panic("implicit issuance vote must only cast from `importCandidate` after checking group membership of issuer.") + default: + return nil, nil, fmt.Errorf("%w: %d", errUnknownValidityVote, voteWithSign.validityVote) + } + } + + existingVoteWithSign, ok := data.validityVotes[from] + if !ok { + data.validityVotes[from] = voteWithSign + return data.getSummary(candidateHash), nil, nil + } + + // check for double votes. + if existingVoteWithSign != voteWithSign { + var misbehavior parachaintypes.Misbehaviour + + switch { + case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == valid, + existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == issued: + misbehavior = parachaintypes.ValidityDoubleVoteIssuedAndValidity{ + CommittedCandidateReceiptAndSign: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: data.candidate, + Signature: existingVoteWithSign.signature, + }, + CandidateHashAndSign: parachaintypes.CandidateHashAndSign{ + CandidateHash: candidateHash, + Signature: voteWithSign.signature, + }, + } + case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == issued: + misbehavior = parachaintypes.DoubleSignOnSeconded{ + Candidate: data.candidate, + Sign1: existingVoteWithSign.signature, + Sign2: voteWithSign.signature, + } + case existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == valid: + misbehavior = parachaintypes.DoubleSignOnValidity{ + CandidateHash: candidateHash, + Sign1: existingVoteWithSign.signature, + Sign2: voteWithSign.signature, + } + } + return nil, misbehavior, nil + } + + return nil, nil, nil +} + // attestedCandidate retrieves the attested candidate for the given candidate hash. // returns attested candidate if the candidate exists and is includable. -func (table statementTable) attestedCandidate( - candidateHash parachaintypes.CandidateHash, tableContext *TableContext, minimumBackingVotes uint32, +func (table *statementTable) attestedCandidate( + candidateHash parachaintypes.CandidateHash, tableCtx *tableContext, minimumBackingVotes uint32, ) (*attestedCandidate, error) { // size of the backing group. var groupLen uint @@ -134,7 +356,7 @@ func (table statementTable) attestedCandidate( return nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, candidateHash) } - group, ok := tableContext.groups[data.groupID] + group, ok := tableCtx.groups[data.groupID] if ok { groupLen = uint(len(group)) } else { @@ -159,8 +381,8 @@ func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisb type Table interface { getCandidate(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) - importStatement(*TableContext, parachaintypes.SignedFullStatementWithPVD) (*Summary, error) - attestedCandidate(parachaintypes.CandidateHash, *TableContext, uint32) (*attestedCandidate, error) + importStatement(*tableContext, parachaintypes.SignedFullStatementWithPVD) (*Summary, error) + attestedCandidate(parachaintypes.CandidateHash, *tableContext, uint32) (*attestedCandidate, error) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } @@ -176,7 +398,7 @@ type Summary struct { // The group that the candidate is in. GroupID parachaintypes.ParaID // How many validity votes are currently witnessed. - ValidityVotes uint64 + ValidityVotes uint } // attestedCandidate represents an attested-to candidate. @@ -189,6 +411,35 @@ type attestedCandidate struct { validityAttestations []validatorIndexWithAttestation } +func (attested *attestedCandidate) toBackedCandidate(tableCtx *tableContext) *parachaintypes.BackedCandidate { + group := tableCtx.groups[attested.groupID] + validatorIndices := make([]bool, len(group)) + var validityAttestations []parachaintypes.ValidityAttestation + + // The order of the validity votes in the backed candidate must match + // the order of bits set in the bitfield, which is not necessarily + // the order of the `validity_votes` we got from the table. + for positionInGroup, validatorIndex := range group { + for _, validityVote := range attested.validityAttestations { + if validityVote.validatorIndex == validatorIndex { + validatorIndices[positionInGroup] = true + validityAttestations = append(validityAttestations, validityVote.validityAttestation) + } + } + + if !validatorIndices[positionInGroup] { + logger.Error("validity vote from unknown validator") + return nil + } + } + + return ¶chaintypes.BackedCandidate{ + Candidate: attested.committedCandidateReceipt, + ValidityVotes: validityAttestations, + ValidatorIndices: scale.NewBitVec(validatorIndices), + } +} + // validatorIndexWithAttestation represents a validity attestation for a candidate. type validatorIndexWithAttestation struct { validatorIndex parachaintypes.ValidatorIndex diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index 2e7cda98d2..d2e6fb3716 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -79,7 +79,7 @@ func TestStatementTable_attestedCandidate(t *testing.T) { type args struct { candidateHash parachaintypes.CandidateHash - tableContext *TableContext + tableContext *tableContext minimumBackingVotes uint32 } tests := []struct { @@ -92,7 +92,7 @@ func TestStatementTable_attestedCandidate(t *testing.T) { { name: "candidate_votes_not_available_for_given_candidate_hash", table: &statementTable{ - candidateVotes: map[parachaintypes.CandidateHash]candidateData{}, + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{}, }, args: args{ candidateHash: dummyCandidateHash(t), @@ -102,7 +102,7 @@ func TestStatementTable_attestedCandidate(t *testing.T) { { name: "not_enough_validity_votes", table: &statementTable{ - candidateVotes: map[parachaintypes.CandidateHash]candidateData{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ dummyCandidateHash(t): { groupID: 1, validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{}, @@ -111,7 +111,7 @@ func TestStatementTable_attestedCandidate(t *testing.T) { }, args: args{ candidateHash: dummyCandidateHash(t), - tableContext: &TableContext{ + tableContext: &tableContext{ groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ 1: {1, 2, 3}, 2: {4, 5, 6}, diff --git a/dot/parachain/types/misbehavior.go b/dot/parachain/types/misbehavior.go index 35c0ff9d67..2bb83a444d 100644 --- a/dot/parachain/types/misbehavior.go +++ b/dot/parachain/types/misbehavior.go @@ -4,14 +4,11 @@ package parachaintypes var ( - _ Misbehaviour = (*MultipleCandidates)(nil) - _ Misbehaviour = (*UnauthorizedStatement)(nil) - _ Misbehaviour = (*IssuedAndValidity)(nil) - _ Misbehaviour = (*OnSeconded)(nil) - _ Misbehaviour = (*OnValidity)(nil) - _ DoubleSign = (*OnSeconded)(nil) - _ DoubleSign = (*OnValidity)(nil) - _ ValidityDoubleVote = (*IssuedAndValidity)(nil) + _ Misbehaviour = (*MultipleCandidates)(nil) + _ Misbehaviour = (*UnauthorizedStatement)(nil) + _ Misbehaviour = (*ValidityDoubleVoteIssuedAndValidity)(nil) + _ Misbehaviour = (*DoubleSignOnSeconded)(nil) + _ Misbehaviour = (*DoubleSignOnValidity)(nil) ) // Misbehaviour is intended to represent different kinds of misbehaviour along with supporting proofs. @@ -19,25 +16,17 @@ type Misbehaviour interface { IsMisbehaviour() } +// ValidityDoubleVoteIssuedAndValidity misbehaviour: voting implicitly by issuing and explicit voting for validity. +// // ValidityDoubleVote misbehaviour: voting more than one way on candidate validity. // Since there are three possible ways to vote, a double vote is possible in // three possible combinations (unordered) -type ValidityDoubleVote interface { - Misbehaviour - IsValidityDoubleVote() -} - -// IssuedAndValidity represents an implicit vote by issuing and explicit voting for validity. -type IssuedAndValidity struct { +type ValidityDoubleVoteIssuedAndValidity struct { CommittedCandidateReceiptAndSign CommittedCandidateReceiptAndSign - CandidateHashAndSign struct { - CandidateHash CandidateHash - Signature ValidatorSignature - } + CandidateHashAndSign CandidateHashAndSign } -func (IssuedAndValidity) IsMisbehaviour() {} -func (IssuedAndValidity) IsValidityDoubleVote() {} +func (ValidityDoubleVoteIssuedAndValidity) IsMisbehaviour() {} // CommittedCandidateReceiptAndSign combines a committed candidate receipt and its associated signature. type CommittedCandidateReceiptAndSign struct { @@ -45,6 +34,12 @@ type CommittedCandidateReceiptAndSign struct { Signature ValidatorSignature } +// CandidateHashAndSign combines a candidate hash and its associated signature. +type CandidateHashAndSign struct { + CandidateHash CandidateHash + Signature ValidatorSignature +} + // MultipleCandidates misbehaviour: declaring multiple candidates. type MultipleCandidates struct { First CommittedCandidateReceiptAndSign @@ -53,43 +48,26 @@ type MultipleCandidates struct { func (MultipleCandidates) IsMisbehaviour() {} -// SignedStatement represents signed statements about candidates. -type SignedStatement struct { - Statement StatementVDT `scale:"1"` - Signature ValidatorSignature `scale:"2"` - Sender ValidatorIndex `scale:"3"` -} - // UnauthorizedStatement misbehaviour: submitted statement for wrong group. -type UnauthorizedStatement struct { - // A signed statement which was submitted without proper authority. - Statement SignedStatement -} +// A signed statement which was submitted without proper authority. +type UnauthorizedStatement SignedFullStatement func (UnauthorizedStatement) IsMisbehaviour() {} -// DoubleSign misbehaviour: multiple signatures on same statement. -type DoubleSign interface { - Misbehaviour - IsDoubleSign() -} - -// OnSeconded represents a double sign on a candidate. -type OnSeconded struct { +// DoubleSignOnSeconded represents a double sign on a candidate. +type DoubleSignOnSeconded struct { Candidate CommittedCandidateReceipt Sign1 ValidatorSignature Sign2 ValidatorSignature } -func (OnSeconded) IsMisbehaviour() {} -func (OnSeconded) IsDoubleSign() {} +func (DoubleSignOnSeconded) IsMisbehaviour() {} -// OnValidity represents a double sign on validity. -type OnValidity struct { +// DoubleSignOnValidity represents a double sign on validity. +type DoubleSignOnValidity struct { CandidateHash CandidateHash Sign1 ValidatorSignature Sign2 ValidatorSignature } -func (OnValidity) IsMisbehaviour() {} -func (OnValidity) IsDoubleSign() {} +func (DoubleSignOnValidity) IsMisbehaviour() {} diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index 518074d4ff..d2d9de510e 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -301,6 +301,22 @@ func (c CommittedCandidateReceipt) Hash() (common.Hash, error) { return c.ToPlain().Hash() } +type hashable interface { + Hash() (common.Hash, error) +} + +// GetCandidateHash returns the CandidateHash. +// +// candidate would be either CommittedCandidateReceipt or CandidateReceipt. +func GetCandidateHash(candidate hashable) (CandidateHash, error) { + h, err := candidate.Hash() + if err != nil { + return CandidateHash{}, err + } + + return CandidateHash{Value: h}, nil +} + // AssignmentID The public key of a keypair used by a validator for determining assignments // to approve included parachain candidates. type AssignmentID [sr25519.PublicKeyLength]byte From 9bfdfc6fc8f7284c83c4527fae49f262d851b7e0 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Tue, 21 May 2024 17:04:37 +0530 Subject: [PATCH 06/19] change interface method argument --- .../backing/candidate_backing_test.go | 24 +++++++++---------- dot/parachain/backing/mocks_test.go | 2 +- .../backing/per_relay_parent_state.go | 6 ++--- dot/parachain/backing/statement_table.go | 15 +++++------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/dot/parachain/backing/candidate_backing_test.go b/dot/parachain/backing/candidate_backing_test.go index dab3517570..de4195d0d2 100644 --- a/dot/parachain/backing/candidate_backing_test.go +++ b/dot/parachain/backing/candidate_backing_test.go @@ -224,7 +224,7 @@ func TestImportStatement(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(new(Summary), nil) return perRelayParentState{ @@ -260,7 +260,7 @@ func TestImportStatement(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(new(Summary), nil) return perRelayParentState{ @@ -291,7 +291,7 @@ func TestImportStatement(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(new(Summary), nil) return perRelayParentState{ @@ -311,7 +311,7 @@ func TestImportStatement(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(new(Summary), nil) return perRelayParentState{ @@ -910,7 +910,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(nil, nil) mockTable.EXPECT().drainMisbehaviors(). Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) @@ -937,7 +937,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ GroupID: 4, }, nil) @@ -972,7 +972,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, @@ -1010,7 +1010,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, @@ -1051,7 +1051,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, @@ -1095,7 +1095,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, @@ -1146,7 +1146,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, @@ -1190,7 +1190,7 @@ func TestHandleStatementMessage(t *testing.T) { mockTable.EXPECT().importStatement( gomock.AssignableToTypeOf(new(tableContext)), - gomock.AssignableToTypeOf(parachaintypes.SignedFullStatementWithPVD{}), + gomock.AssignableToTypeOf(parachaintypes.SignedFullStatement{}), ).Return(&Summary{ Candidate: candidateHash, GroupID: 4, diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index 1e44c000ab..e3e813510b 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -85,7 +85,7 @@ func (mr *MockTableMockRecorder) getCandidate(arg0 any) *gomock.Call { } // importStatement mocks base method. -func (m *MockTable) importStatement(arg0 *tableContext, arg1 parachaintypes.SignedFullStatementWithPVD) (*Summary, error) { +func (m *MockTable) importStatement(arg0 *tableContext, arg1 parachaintypes.SignedFullStatement) (*Summary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "importStatement", arg0, arg1) ret0, _ := ret[0].(*Summary) diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go index 4f0d1c285c..1b4d25cc3d 100644 --- a/dot/parachain/backing/per_relay_parent_state.go +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -51,7 +51,7 @@ func (rpState *perRelayParentState) importStatement( } if statementVDT.Index() == 2 { // Valid - return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD.SignedFullStatement) } // PersistedValidationData should not be nil if the statementVDT is Seconded. @@ -68,7 +68,7 @@ func (rpState *perRelayParentState) importStatement( candidateHash := parachaintypes.CandidateHash{Value: hash} if _, ok := perCandidate[candidateHash]; ok { - return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD.SignedFullStatement) } if rpState.prospectiveParachainsMode.IsEnabled { @@ -107,7 +107,7 @@ func (rpState *perRelayParentState) importStatement( relayParent: statementVDTSeconded.Descriptor.RelayParent, } - return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD.SignedFullStatement) } // postImportStatement handles a summary received from importStatement func and dispatches `Backed` notifications and diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 4014350707..1051698583 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -122,10 +122,6 @@ func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachai return data.candidate, nil } -/* -TODO: - - change interface method arguments after implementing this method -*/ func (table *statementTable) importStatement( //nolint:unused tableCtx *tableContext, signedStatement parachaintypes.SignedFullStatement, ) (*Summary, error) { @@ -154,12 +150,12 @@ func (table *statementTable) importStatement( //nolint:unused ) } - if err == nil { - return summary, nil + if err != nil { + return nil, err } // If misbehavior is detected, store it. - { + if misbehavior != nil { misbehaviors, ok := table.detectedMisbehaviour[signedStatement.ValidatorIndex] if !ok { misbehaviors = []parachaintypes.Misbehaviour{misbehavior} @@ -169,7 +165,8 @@ func (table *statementTable) importStatement( //nolint:unused table.detectedMisbehaviour[signedStatement.ValidatorIndex] = misbehaviors } - return nil, nil + + return summary, nil } func isCandidateAlreadyProposed(authData authorityData, candidateHash parachaintypes.CandidateHash) bool { @@ -381,7 +378,7 @@ func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisb type Table interface { getCandidate(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) - importStatement(*tableContext, parachaintypes.SignedFullStatementWithPVD) (*Summary, error) + importStatement(*tableContext, parachaintypes.SignedFullStatement) (*Summary, error) attestedCandidate(parachaintypes.CandidateHash, *tableContext, uint32) (*attestedCandidate, error) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } From aae30b4db068f9b811666793598e6203749e17b8 Mon Sep 17 00:00:00 2001 From: edwardmack Date: Tue, 21 May 2024 09:37:06 -0400 Subject: [PATCH 07/19] add nil check in availabilty store test --- dot/parachain/availability-store/availability_store_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go index a61858fc63..33aaf0773d 100644 --- a/dot/parachain/availability-store/availability_store_test.go +++ b/dot/parachain/availability-store/availability_store_test.go @@ -986,8 +986,10 @@ func (h *testHarness) processMessages() { for { select { case msg := <-h.overseer.SubsystemsToOverseer: - h.processes[processIndex](msg) - processIndex++ + if h.processes != nil && processIndex < len(h.processes) { + h.processes[processIndex](msg) + processIndex++ + } case <-h.overseer.ctx.Done(): if err := h.overseer.ctx.Err(); err != nil { logger.Errorf("ctx error: %v\n", err) From c5b14225e53ffed7f39b9143e1b52301ff965e71 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Tue, 21 May 2024 19:18:05 +0530 Subject: [PATCH 08/19] implement isMemberOf method of table context --- dot/parachain/backing/candidate_backing.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 68ef80bbda..d759028e59 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -25,6 +25,7 @@ import ( "context" "errors" "fmt" + "slices" "sync" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -124,8 +125,12 @@ type tableContext struct { } func (tc *tableContext) isMemberOf(validatorIndex parachaintypes.ValidatorIndex, paraID parachaintypes.ParaID) bool { - // TODO: implement this - return false + indexes, ok := tc.groups[paraID] + if !ok { + return false + } + + return slices.Contains(indexes, validatorIndex) } // validator represents local validator information. From 769fa5e6308aaf415bf1c6994dbccf7b47ecb50d Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Tue, 21 May 2024 19:58:40 +0530 Subject: [PATCH 09/19] newTable func + lint --- dot/parachain/backing/candidate_backing.go | 2 +- dot/parachain/backing/mocks_test.go | 6 +- dot/parachain/backing/statement_table.go | 88 +++++++++++----------- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index d759028e59..2c3e29bd7f 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -317,7 +317,7 @@ func (cb *CandidateBacking) handleStatementMessage( var attesting attestingData switch statementVDT := statementVDT.(type) { case parachaintypes.Seconded: - commitedCandidateReceipt, err := rpState.table.getCandidate(summary.Candidate) + commitedCandidateReceipt, err := rpState.table.getCommittedCandidateReceipt(summary.Candidate) if err != nil { return fmt.Errorf("getting candidate: %w", err) } diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index e3e813510b..fb9979f48d 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -69,8 +69,8 @@ func (mr *MockTableMockRecorder) drainMisbehaviors() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "drainMisbehaviors", reflect.TypeOf((*MockTable)(nil).drainMisbehaviors)) } -// getCandidate mocks base method. -func (m *MockTable) getCandidate(arg0 parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) { +// getCommittedCandidateReceipt mocks base method. +func (m *MockTable) getCommittedCandidateReceipt(arg0 parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "getCandidate", arg0) ret0, _ := ret[0].(parachaintypes.CommittedCandidateReceipt) @@ -81,7 +81,7 @@ func (m *MockTable) getCandidate(arg0 parachaintypes.CandidateHash) (parachainty // getCandidate indicates an expected call of getCandidate. func (mr *MockTableMockRecorder) getCandidate(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getCandidate", reflect.TypeOf((*MockTable)(nil).getCandidate), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getCandidate", reflect.TypeOf((*MockTable)(nil).getCommittedCandidateReceipt), arg0) } // importStatement mocks base method. diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 1051698583..c16cc6deb1 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -18,17 +18,17 @@ var errCandidateDataNotFound = errors.New("candidate data not found") var errNotEnoughValidityVotes = errors.New("not enough validity votes") var errUnknownValidityVote = errors.New("unknown validity vote") +var _ Table = (*statementTable)(nil) + // statementTable implements the Table interface. type statementTable struct { - authorityData map[parachaintypes.ValidatorIndex]authorityData //nolint:unused + authorityData map[parachaintypes.ValidatorIndex][]proposal detectedMisbehaviour map[parachaintypes.ValidatorIndex][]parachaintypes.Misbehaviour candidateVotes map[parachaintypes.CandidateHash]*candidateData - config tableConfig //nolint:unused + config tableConfig } -type authorityData []proposal //nolint:unused - -type proposal struct { //nolint:unused +type proposal struct { candidateHash parachaintypes.CandidateHash signature parachaintypes.ValidatorSignature } @@ -81,7 +81,7 @@ func (data candidateData) attested(validityThreshold uint) (*attestedCandidate, validityAttestation: attestation, }) default: - return nil, fmt.Errorf("unknown validity vote: %d", voteWithSign.validityVote) + return nil, fmt.Errorf("%w: %d", errUnknownValidityVote, voteWithSign.validityVote) } } @@ -112,7 +112,7 @@ const ( ) // getCommittedCandidateReceipt returns the committed candidate receipt for the given candidate hash. -func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachaintypes.CandidateHash, //nolint:unused +func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachaintypes.CandidateHash, ) (parachaintypes.CommittedCandidateReceipt, error) { data, ok := table.candidateVotes[candidateHash] if !ok { @@ -122,11 +122,11 @@ func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachai return data.candidate, nil } -func (table *statementTable) importStatement( //nolint:unused +func (table *statementTable) importStatement( tableCtx *tableContext, signedStatement parachaintypes.SignedFullStatement, ) (*Summary, error) { var summary *Summary - var misbehavior parachaintypes.Misbehaviour + var misbehaviour parachaintypes.Misbehaviour statementVDT, err := signedStatement.Payload.Value() if err != nil { @@ -135,14 +135,14 @@ func (table *statementTable) importStatement( //nolint:unused switch statementVDT := statementVDT.(type) { case parachaintypes.Seconded: - summary, misbehavior, err = table.importCandidate( + summary, misbehaviour, err = table.importCandidate( signedStatement.ValidatorIndex, parachaintypes.CommittedCandidateReceipt(statementVDT), signedStatement.Signature, tableCtx, ) case parachaintypes.Valid: - summary, misbehavior, err = table.validityVote( + summary, misbehaviour, err = table.validityVote( signedStatement.ValidatorIndex, parachaintypes.CandidateHash(statementVDT), validityVoteWithSign{validityVote: valid, signature: signedStatement.Signature}, @@ -154,13 +154,13 @@ func (table *statementTable) importStatement( //nolint:unused return nil, err } - // If misbehavior is detected, store it. - if misbehavior != nil { + // If misbehaviour is detected, store it. + if misbehaviour != nil { misbehaviors, ok := table.detectedMisbehaviour[signedStatement.ValidatorIndex] if !ok { - misbehaviors = []parachaintypes.Misbehaviour{misbehavior} + misbehaviors = []parachaintypes.Misbehaviour{misbehaviour} } else { - misbehaviors = append(misbehaviors, misbehavior) + misbehaviors = append(misbehaviors, misbehaviour) } table.detectedMisbehaviour[signedStatement.ValidatorIndex] = misbehaviors @@ -169,8 +169,8 @@ func (table *statementTable) importStatement( //nolint:unused return summary, nil } -func isCandidateAlreadyProposed(authData authorityData, candidateHash parachaintypes.CandidateHash) bool { - return slices.ContainsFunc(authData, func(p proposal) bool { +func isCandidateAlreadyProposed(proposals []proposal, candidateHash parachaintypes.CandidateHash) bool { + return slices.ContainsFunc(proposals, func(p proposal) bool { return p.candidateHash == candidateHash }) } @@ -190,13 +190,13 @@ func (table *statementTable) importCandidate( return nil, nil, fmt.Errorf("setting seconded statement: %w", err) } - misbehavior := parachaintypes.UnauthorizedStatement{ + misbehaviour := parachaintypes.UnauthorizedStatement{ Payload: statementSeconded, ValidatorIndex: authority, Signature: signature, } - return nil, misbehavior, nil + return nil, misbehaviour, nil } candidateHash, err := parachaintypes.GetCandidateHash(candidate) @@ -205,15 +205,15 @@ func (table *statementTable) importCandidate( } var isNewProposal bool - authData, ok := table.authorityData[authority] + proposals, ok := table.authorityData[authority] if !ok { - table.authorityData[authority] = authorityData{{candidateHash, signature}} + table.authorityData[authority] = []proposal{{candidateHash, signature}} isNewProposal = true } else { - // if digest is different, fetch candidate and note misbehavior. - if !table.config.allowMultipleSeconded && len(authData) == 1 { - oldCandidateHash := authData[0].candidateHash - oldSignature := authData[0].signature + // if digest is different, fetch candidate and note misbehaviour. + if !table.config.allowMultipleSeconded && len(proposals) == 1 { + oldCandidateHash := proposals[0].candidateHash + oldSignature := proposals[0].signature if oldCandidateHash != candidateHash { data, ok := table.candidateVotes[oldCandidateHash] @@ -226,7 +226,7 @@ func (table *statementTable) importCandidate( oldCandidate := data.candidate - misbehavior := parachaintypes.MultipleCandidates{ + misbehaviour := parachaintypes.MultipleCandidates{ First: parachaintypes.CommittedCandidateReceiptAndSign{ CommittedCandidateReceipt: oldCandidate, Signature: oldSignature, @@ -236,13 +236,13 @@ func (table *statementTable) importCandidate( Signature: signature, }, } - return nil, misbehavior, nil + return nil, misbehaviour, nil } - } else if table.config.allowMultipleSeconded && isCandidateAlreadyProposed(authData, candidateHash) { + } else if table.config.allowMultipleSeconded && isCandidateAlreadyProposed(proposals, candidateHash) { // nothing to do } else { - authData = append(authData, proposal{candidateHash, signature}) - table.authorityData[authority] = authData + proposals = append(proposals, proposal{candidateHash, signature}) + table.authorityData[authority] = proposals isNewProposal = true } } @@ -284,13 +284,13 @@ func (table *statementTable) validityVote( return nil, nil, fmt.Errorf("setting valid statement: %w", err) } - misbehavior := parachaintypes.UnauthorizedStatement{ + misbehaviour := parachaintypes.UnauthorizedStatement{ Payload: validStatement, ValidatorIndex: from, Signature: voteWithSign.signature, } - return nil, misbehavior, nil + return nil, misbehaviour, nil case issued: panic("implicit issuance vote must only cast from `importCandidate` after checking group membership of issuer.") default: @@ -306,12 +306,12 @@ func (table *statementTable) validityVote( // check for double votes. if existingVoteWithSign != voteWithSign { - var misbehavior parachaintypes.Misbehaviour + var misbehaviour parachaintypes.Misbehaviour switch { case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == valid, existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == issued: - misbehavior = parachaintypes.ValidityDoubleVoteIssuedAndValidity{ + misbehaviour = parachaintypes.ValidityDoubleVoteIssuedAndValidity{ CommittedCandidateReceiptAndSign: parachaintypes.CommittedCandidateReceiptAndSign{ CommittedCandidateReceipt: data.candidate, Signature: existingVoteWithSign.signature, @@ -322,19 +322,19 @@ func (table *statementTable) validityVote( }, } case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == issued: - misbehavior = parachaintypes.DoubleSignOnSeconded{ + misbehaviour = parachaintypes.DoubleSignOnSeconded{ Candidate: data.candidate, Sign1: existingVoteWithSign.signature, Sign2: voteWithSign.signature, } case existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == valid: - misbehavior = parachaintypes.DoubleSignOnValidity{ + misbehaviour = parachaintypes.DoubleSignOnValidity{ CandidateHash: candidateHash, Sign1: existingVoteWithSign.signature, Sign2: voteWithSign.signature, } } - return nil, misbehavior, nil + return nil, misbehaviour, nil } return nil, nil, nil @@ -371,21 +371,25 @@ func effectiveMinimumBackingVotes(groupLen uint, configuredMinimumBackingVotes u return min(groupLen, uint(configuredMinimumBackingVotes)) } -func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport { //nolint:unused +func (statementTable) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport { // TODO: Implement this method return nil } type Table interface { - getCandidate(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) + getCommittedCandidateReceipt(parachaintypes.CandidateHash) (parachaintypes.CommittedCandidateReceipt, error) importStatement(*tableContext, parachaintypes.SignedFullStatement) (*Summary, error) attestedCandidate(parachaintypes.CandidateHash, *tableContext, uint32) (*attestedCandidate, error) drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } -func newTable(tableConfig) Table { - // TODO: Implement this function - return nil +func newTable(config tableConfig) Table { + return &statementTable{ + authorityData: make(map[parachaintypes.ValidatorIndex][]proposal), + detectedMisbehaviour: make(map[parachaintypes.ValidatorIndex][]parachaintypes.Misbehaviour), + candidateVotes: make(map[parachaintypes.CandidateHash]*candidateData), + config: config, + } } // Summary represents summary of import of a statement. From c85c3a357a7e343a63743a237c4f4b10303e9924 Mon Sep 17 00:00:00 2001 From: edwardmack Date: Tue, 21 May 2024 11:20:25 -0400 Subject: [PATCH 10/19] assign process functions before sending request message to avoid empty process queue --- .../availability_store_test.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go index 33aaf0773d..c7d84114b4 100644 --- a/dot/parachain/availability-store/availability_store_test.go +++ b/dot/parachain/availability-store/availability_store_test.go @@ -986,10 +986,10 @@ func (h *testHarness) processMessages() { for { select { case msg := <-h.overseer.SubsystemsToOverseer: - if h.processes != nil && processIndex < len(h.processes) { - h.processes[processIndex](msg) - processIndex++ - } + //if h.processes != nil && processIndex < len(h.processes) { + h.processes[processIndex](msg) + processIndex++ + //} case <-h.overseer.ctx.Done(): if err := h.overseer.ctx.Err(); err != nil { logger.Errorf("ctx error: %v\n", err) @@ -1024,13 +1024,6 @@ func (h *testHarness) importLeaf(t *testing.T, parentHash common.Hash, } activatedLeaf := header.Hash() - h.overseer.broadcast(parachaintypes.ActiveLeavesUpdateSignal{ - Activated: ¶chaintypes.ActivatedLeaf{ - Hash: activatedLeaf, - Number: uint32(1), - }, - }) - h.processes = append(h.processes, func(msg any) { msg2, _ := msg.(chainapi.ChainAPIMessage[chainapi.BlockHeader]) msg2.ResponseChannel <- header @@ -1056,6 +1049,13 @@ func (h *testHarness) importLeaf(t *testing.T, parentHash common.Hash, msg2.Resp <- inst }) + h.overseer.broadcast(parachaintypes.ActiveLeavesUpdateSignal{ + Activated: ¶chaintypes.ActivatedLeaf{ + Hash: activatedLeaf, + Number: uint32(1), + }, + }) + return activatedLeaf } From 125f81af3d948aa549aaeb09c57e4e1b1e018374 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Tue, 21 May 2024 22:43:22 +0530 Subject: [PATCH 11/19] improve import candidate method --- dot/parachain/backing/statement_table.go | 100 ++++++++++++----------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index c16cc6deb1..0230c8c487 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -204,63 +204,67 @@ func (table *statementTable) importCandidate( return nil, nil, fmt.Errorf("getting candidate hash: %w", err) } - var isNewProposal bool proposals, ok := table.authorityData[authority] if !ok { table.authorityData[authority] = []proposal{{candidateHash, signature}} - isNewProposal = true - } else { + table.addCandidateVote(candidateHash, paraID, candidate) + + return table.validityVote(authority, candidateHash, + validityVoteWithSign{validityVote: issued, signature: signature}, tableCtx) + } + + switch { + case !table.config.allowMultipleSeconded && len(proposals) == 1: + oldCandidateHash := proposals[0].candidateHash + oldSignature := proposals[0].signature + // if digest is different, fetch candidate and note misbehaviour. - if !table.config.allowMultipleSeconded && len(proposals) == 1 { - oldCandidateHash := proposals[0].candidateHash - oldSignature := proposals[0].signature - - if oldCandidateHash != candidateHash { - data, ok := table.candidateVotes[oldCandidateHash] - if !ok { - // when proposal first received from authority, candidate votes entry is created. - // and here authData is not empty, so candidate votes entry should be present. - // So, this error should never happen. - return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, oldCandidateHash) - } - - oldCandidate := data.candidate - - misbehaviour := parachaintypes.MultipleCandidates{ - First: parachaintypes.CommittedCandidateReceiptAndSign{ - CommittedCandidateReceipt: oldCandidate, - Signature: oldSignature, - }, - Second: parachaintypes.CommittedCandidateReceiptAndSign{ - CommittedCandidateReceipt: candidate, - Signature: signature, - }, - } - return nil, misbehaviour, nil + if oldCandidateHash != candidateHash { + data, ok := table.candidateVotes[oldCandidateHash] + if !ok { + // when proposal first received from authority, candidate votes entry is created. + // and here authData is not empty, so candidate votes entry should be present. + // So, this error should never happen. + return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, oldCandidateHash) } - } else if table.config.allowMultipleSeconded && isCandidateAlreadyProposed(proposals, candidateHash) { - // nothing to do - } else { - proposals = append(proposals, proposal{candidateHash, signature}) - table.authorityData[authority] = proposals - isNewProposal = true - } - } - if isNewProposal { - table.candidateVotes[candidateHash] = &candidateData{ - groupID: paraID, - candidate: candidate, - validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + oldCandidate := data.candidate + + misbehaviour := parachaintypes.MultipleCandidates{ + First: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: oldCandidate, + Signature: oldSignature, + }, + Second: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: candidate, + Signature: signature, + }, + } + return nil, misbehaviour, nil } + case table.config.allowMultipleSeconded && isCandidateAlreadyProposed(proposals, candidateHash): + // nothing to do here. + default: + proposals = append(proposals, proposal{candidateHash, signature}) + table.authorityData[authority] = proposals + + table.addCandidateVote(candidateHash, paraID, candidate) } - return table.validityVote( - authority, - candidateHash, - validityVoteWithSign{validityVote: issued, signature: signature}, - tableCtx, - ) + return table.validityVote(authority, candidateHash, + validityVoteWithSign{validityVote: issued, signature: signature}, tableCtx) +} + +func (table *statementTable) addCandidateVote( + candidateHash parachaintypes.CandidateHash, + paraID parachaintypes.ParaID, + candidate parachaintypes.CommittedCandidateReceipt, +) { + table.candidateVotes[candidateHash] = &candidateData{ + groupID: paraID, + candidate: candidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + } } func (table *statementTable) validityVote( From 19d2f4d389bcb103dea2d8fced29aca6242e74f4 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 22 May 2024 12:12:13 +0530 Subject: [PATCH 12/19] address reviews --- dot/parachain/backing/statement_table.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index a0de8fd748..24a1b63343 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -7,7 +7,6 @@ import ( "cmp" "errors" "fmt" - "math" "slices" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -126,22 +125,21 @@ func (statementTable) importStatement( //nolint:unused func (table statementTable) attestedCandidate( candidateHash parachaintypes.CandidateHash, tableContext *TableContext, minimumBackingVotes uint32, ) (*attestedCandidate, error) { - // size of the backing group. - var groupLen uint - data, ok := table.candidateVotes[candidateHash] if !ok { return nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, candidateHash) } + var validityThreshold uint group, ok := tableContext.groups[data.groupID] if ok { - groupLen = uint(len(group)) + // size of the backing group. + groupLen := uint(len(group)) + validityThreshold = effectiveMinimumBackingVotes(groupLen, minimumBackingVotes) } else { - groupLen = math.MaxUint + validityThreshold = uint(minimumBackingVotes) } - validityThreshold := effectiveMinimumBackingVotes(groupLen, minimumBackingVotes) return data.attested(validityThreshold) } From b085602b61f39986eb5326ee0ce5343fde4458fa Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 22 May 2024 21:59:42 +0530 Subject: [PATCH 13/19] improve --- dot/parachain/backing/statement_table.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 2c75311d79..9c922d0261 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -149,7 +149,7 @@ func (table *statementTable) importStatement( ) } - if err != nil { + if err != nil && !errors.Is(err, errCandidateDataNotFound) { return nil, err } @@ -222,9 +222,9 @@ func (table *statementTable) importCandidate( data, ok := table.candidateVotes[oldCandidateHash] if !ok { // when proposal first received from authority, candidate votes entry is created. - // and here authData is not empty, so candidate votes entry should be present. - // So, this error should never happen. - return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, oldCandidateHash) + // and here proposals is not empty, so candidate votes entry should be present. + // So, this should never happen. + panic(fmt.Sprintf("%s for candidate-hash: %s", errCandidateDataNotFound, oldCandidateHash)) } oldCandidate := data.candidate @@ -274,7 +274,7 @@ func (table *statementTable) validityVote( ) (*Summary, parachaintypes.Misbehaviour, error) { data, ok := table.candidateVotes[candidateHash] if !ok { - return nil, nil, fmt.Errorf("%w for candidate-hash: %s", errCandidateDataNotFound, candidateHash) + return nil, nil, errCandidateDataNotFound } // check that this authority actually can vote in this group. @@ -385,7 +385,7 @@ type Table interface { drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport } -func newTable(config tableConfig) Table { +func newTable(config tableConfig) *statementTable { return &statementTable{ authorityData: make(map[parachaintypes.ValidatorIndex][]proposal), detectedMisbehaviour: make(map[parachaintypes.ValidatorIndex][]parachaintypes.Misbehaviour), From d565b6747756700a40171349736c3e9a6310ec40 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 22 May 2024 21:59:55 +0530 Subject: [PATCH 14/19] unit test(WIP) --- dot/parachain/backing/statement_table_test.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index d2e6fb3716..70ef87efe4 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -138,3 +138,44 @@ func TestStatementTable_attestedCandidate(t *testing.T) { }) } } + +func TestStatementTable_importStatement(t *testing.T) { + + tableCtx := &tableContext{} + config := tableConfig{} + + committedCandidate := getDummyCommittedCandidateReceipt(t) + + secondedStatement := parachaintypes.NewStatementVDT() + err := secondedStatement.Set(parachaintypes.Seconded(committedCandidate)) + require.NoError(t, err) + + signedStatement := parachaintypes.SignedFullStatement{ + Payload: secondedStatement, + } + + table := newTable(config) + summary, err := table.importStatement(tableCtx, signedStatement) + require.NoError(t, err) + require.Nil(t, summary) + + require.Len(t, table.detectedMisbehaviour, 1) + + // === + + candidateHash, err := parachaintypes.GetCandidateHash(committedCandidate) + require.NoError(t, err) + + validStatement := parachaintypes.NewStatementVDT() + err = validStatement.Set(parachaintypes.Valid(candidateHash)) + require.NoError(t, err) + + signedStatement = parachaintypes.SignedFullStatement{ + Payload: validStatement, + } + + summary, err = table.importStatement(tableCtx, signedStatement) + require.NoError(t, err) + require.Nil(t, summary) + require.Len(t, table.detectedMisbehaviour, 1) +} From ad766c36de4c03f07e85a68aaaac09e9b9397aa3 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Thu, 23 May 2024 15:11:05 +0530 Subject: [PATCH 15/19] unit test for import statement method --- dot/parachain/backing/statement_table_test.go | 74 +++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index 70ef87efe4..e1695e040f 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -140,42 +140,58 @@ func TestStatementTable_attestedCandidate(t *testing.T) { } func TestStatementTable_importStatement(t *testing.T) { - - tableCtx := &tableContext{} - config := tableConfig{} - + t.Parallel() committedCandidate := getDummyCommittedCandidateReceipt(t) - secondedStatement := parachaintypes.NewStatementVDT() - err := secondedStatement.Set(parachaintypes.Seconded(committedCandidate)) - require.NoError(t, err) - - signedStatement := parachaintypes.SignedFullStatement{ - Payload: secondedStatement, + testCases := []struct { + description string + statementVDT parachaintypes.StatementVDT + detectedMisbehaviourLen int + }{ + { + description: "seconded_statement", + statementVDT: func() parachaintypes.StatementVDT { + secondedStatement := parachaintypes.NewStatementVDT() + err := secondedStatement.Set(parachaintypes.Seconded(committedCandidate)) + require.NoError(t, err) + + return secondedStatement + }(), + detectedMisbehaviourLen: 1, + }, + { + description: "valid_statement", + statementVDT: func() parachaintypes.StatementVDT { + candidateHash, err := parachaintypes.GetCandidateHash(committedCandidate) + require.NoError(t, err) + + validStatement := parachaintypes.NewStatementVDT() + err = validStatement.Set(parachaintypes.Valid(candidateHash)) + require.NoError(t, err) + + return validStatement + }(), + detectedMisbehaviourLen: 0, + }, } - table := newTable(config) - summary, err := table.importStatement(tableCtx, signedStatement) - require.NoError(t, err) - require.Nil(t, summary) - - require.Len(t, table.detectedMisbehaviour, 1) + for _, tc := range testCases { + tc := tc + t.Run(tc.description, func(t *testing.T) { + t.Parallel() - // === + tableCtx := &tableContext{} + signedStatement := parachaintypes.SignedFullStatement{ + Payload: tc.statementVDT, + } - candidateHash, err := parachaintypes.GetCandidateHash(committedCandidate) - require.NoError(t, err) + table := newTable(tableConfig{}) - validStatement := parachaintypes.NewStatementVDT() - err = validStatement.Set(parachaintypes.Valid(candidateHash)) - require.NoError(t, err) + summary, err := table.importStatement(tableCtx, signedStatement) + require.NoError(t, err) + require.Nil(t, summary) - signedStatement = parachaintypes.SignedFullStatement{ - Payload: validStatement, + require.Len(t, table.detectedMisbehaviour, tc.detectedMisbehaviourLen) + }) } - - summary, err = table.importStatement(tableCtx, signedStatement) - require.NoError(t, err) - require.Nil(t, summary) - require.Len(t, table.detectedMisbehaviour, 1) } From fae18144c1371cfa63fed0934a4fd4736287e42a Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Fri, 24 May 2024 13:04:22 +0530 Subject: [PATCH 16/19] unit test importCandidate (WIP) --- dot/parachain/backing/statement_table_test.go | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index e1695e040f..c1055cc45a 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -4,6 +4,7 @@ import ( "testing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/stretchr/testify/require" ) @@ -195,3 +196,84 @@ func TestStatementTable_importStatement(t *testing.T) { }) } } + +func TestStatementTable_importCandidate(t *testing.T) { + + authority := parachaintypes.ValidatorIndex(10) + candidate := getDummyCommittedCandidateReceipt(t) + var signature parachaintypes.ValidatorSignature + + var tempSignature = common.MustHexToBytes("0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86") //nolint:lll + copy(signature[:], tempSignature) + + tableCtx := &tableContext{} + + statementSeconded := parachaintypes.NewStatementVDT() + err := statementSeconded.Set(parachaintypes.Seconded(candidate)) + require.NoError(t, err) + + // candidateHash, err := parachaintypes.GetCandidateHash(candidate) + require.NoError(t, err) + + table := newTable(tableConfig{}) + + /* + // validator not present in group of parachain validator + summary, misehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) + require.NoError(t, err) + require.Nil(t, summary) + require.Equal(t, parachaintypes.UnauthorizedStatement{ + Payload: statementSeconded, + ValidatorIndex: authority, + Signature: signature, + }, misehaviour) + */ + + //no proposals available from the validator + tableCtx = &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + } + /* + summary, misbehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) + require.NoError(t, err) + require.Nil(t, misbehaviour) + require.Equal(t, &Summary{ + Candidate: candidateHash, + GroupID: parachaintypes.ParaID(candidate.Descriptor.ParaID), + ValidityVotes: 1, + }, summary) + + */ + + // proposal already exists + oldCandidateHash := parachaintypes.CandidateHash{Value: getDummyHash(t, 4)} + oldCandidate := parachaintypes.CommittedCandidateReceipt{} + oldSign := parachaintypes.ValidatorSignature{1, 2, 3} + table.authorityData[authority] = []proposal{ + { + candidateHash: oldCandidateHash, + signature: oldSign, + }, + } + + table.candidateVotes[oldCandidateHash] = &candidateData{ + candidate: oldCandidate, + } + + summary, misbehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) + require.NoError(t, err) + require.Nil(t, summary) + require.Equal(t, parachaintypes.MultipleCandidates{ + First: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: oldCandidate, + Signature: oldSign, + }, + Second: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: candidate, + Signature: signature, + }, + }, misbehaviour) + +} From 306262094383a3442e8f10d63dc511a786cb0c61 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 29 May 2024 14:10:34 +0530 Subject: [PATCH 17/19] unit tests --- dot/parachain/backing/statement_table.go | 6 + dot/parachain/backing/statement_table_test.go | 431 +++++++++++++++--- 2 files changed, 385 insertions(+), 52 deletions(-) diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 9c922d0261..48d861d8b6 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -312,6 +312,7 @@ func (table *statementTable) validityVote( var misbehaviour parachaintypes.Misbehaviour switch { + // valid vote conflicting with candidate statement case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == valid, existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == issued: misbehaviour = parachaintypes.ValidityDoubleVoteIssuedAndValidity{ @@ -324,12 +325,16 @@ func (table *statementTable) validityVote( Signature: voteWithSign.signature, }, } + + // two signatures on same candidate case existingVoteWithSign.validityVote == issued && voteWithSign.validityVote == issued: misbehaviour = parachaintypes.DoubleSignOnSeconded{ Candidate: data.candidate, Sign1: existingVoteWithSign.signature, Sign2: voteWithSign.signature, } + + // two signatures on same validity vote case existingVoteWithSign.validityVote == valid && voteWithSign.validityVote == valid: misbehaviour = parachaintypes.DoubleSignOnValidity{ CandidateHash: candidateHash, @@ -337,6 +342,7 @@ func (table *statementTable) validityVote( Sign2: voteWithSign.signature, } } + return nil, misbehaviour, nil } diff --git a/dot/parachain/backing/statement_table_test.go b/dot/parachain/backing/statement_table_test.go index c1055cc45a..affff0ff9d 100644 --- a/dot/parachain/backing/statement_table_test.go +++ b/dot/parachain/backing/statement_table_test.go @@ -198,6 +198,7 @@ func TestStatementTable_importStatement(t *testing.T) { } func TestStatementTable_importCandidate(t *testing.T) { + t.Parallel() authority := parachaintypes.ValidatorIndex(10) candidate := getDummyCommittedCandidateReceipt(t) @@ -206,74 +207,400 @@ func TestStatementTable_importCandidate(t *testing.T) { var tempSignature = common.MustHexToBytes("0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86") //nolint:lll copy(signature[:], tempSignature) - tableCtx := &tableContext{} - statementSeconded := parachaintypes.NewStatementVDT() err := statementSeconded.Set(parachaintypes.Seconded(candidate)) require.NoError(t, err) - // candidateHash, err := parachaintypes.GetCandidateHash(candidate) + candidateHash, err := parachaintypes.GetCandidateHash(candidate) require.NoError(t, err) - table := newTable(tableConfig{}) - - /* - // validator not present in group of parachain validator - summary, misehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) - require.NoError(t, err) - require.Nil(t, summary) - require.Equal(t, parachaintypes.UnauthorizedStatement{ - Payload: statementSeconded, - ValidatorIndex: authority, - Signature: signature, - }, misehaviour) - */ - - //no proposals available from the validator - tableCtx = &tableContext{ - groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ - 1: {10}, - }, - } - /* - summary, misbehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) - require.NoError(t, err) - require.Nil(t, misbehaviour) - require.Equal(t, &Summary{ - Candidate: candidateHash, - GroupID: parachaintypes.ParaID(candidate.Descriptor.ParaID), - ValidityVotes: 1, - }, summary) - - */ - - // proposal already exists - oldCandidateHash := parachaintypes.CandidateHash{Value: getDummyHash(t, 4)} oldCandidate := parachaintypes.CommittedCandidateReceipt{} + oldCandidateHash, err := parachaintypes.GetCandidateHash(oldCandidate) + require.NoError(t, err) + oldSign := parachaintypes.ValidatorSignature{1, 2, 3} - table.authorityData[authority] = []proposal{ + + testCases := []struct { + description string + tableCtx *tableContext + table *statementTable + expectedError error + expectedMisehaviour parachaintypes.Misbehaviour + expectedSummary *Summary + }{ + { + description: "validator_not_present_in_group_of_parachain_validators", + tableCtx: &tableContext{}, + table: newTable(tableConfig{}), + expectedError: nil, + expectedMisehaviour: parachaintypes.UnauthorizedStatement{ + Payload: statementSeconded, + ValidatorIndex: authority, + Signature: signature, + }, + expectedSummary: nil, + }, + { + description: "no_proposals_available_from_the_validator", + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: newTable(tableConfig{}), + expectedError: nil, + expectedMisehaviour: nil, + expectedSummary: &Summary{ + Candidate: candidateHash, + GroupID: parachaintypes.ParaID(candidate.Descriptor.ParaID), + ValidityVotes: 1, + }, + }, + { + description: "multiple_seconded_not_allowed_and_a_proposal_already_exists_for_different_candidate", + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + authorityData: map[parachaintypes.ValidatorIndex][]proposal{ + authority: { + { + candidateHash: oldCandidateHash, + signature: oldSign, + }, + }, + }, + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + oldCandidateHash: { + groupID: 1, + candidate: oldCandidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + }, + }, + }, + expectedError: nil, + expectedMisehaviour: parachaintypes.MultipleCandidates{ + First: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: oldCandidate, + Signature: oldSign, + }, + Second: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: candidate, + Signature: signature, + }, + }, + expectedSummary: nil, + }, + { + description: "multiple_seconded_allowed_and_a_proposal_already_exists_for_current_candidate", + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + authorityData: map[parachaintypes.ValidatorIndex][]proposal{ + authority: { + { + candidateHash: candidateHash, + signature: signature, + }, + }, + }, + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: candidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + }, + }, + config: tableConfig{ + allowMultipleSeconded: true, + }, + }, + expectedError: nil, + expectedMisehaviour: nil, + expectedSummary: &Summary{ + Candidate: candidateHash, + GroupID: parachaintypes.ParaID(candidate.Descriptor.ParaID), + ValidityVotes: 1, + }, + }, { - candidateHash: oldCandidateHash, - signature: oldSign, + description: "multiple_seconded_allowed_and_a_proposal_not_exists_for_current_candidate", + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + authorityData: map[parachaintypes.ValidatorIndex][]proposal{ + authority: { + { + candidateHash: oldCandidateHash, + signature: oldSign, + }, + }, + }, + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + oldCandidateHash: { + groupID: 1, + candidate: oldCandidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + }, + }, + config: tableConfig{ + allowMultipleSeconded: true, + }, + }, + expectedError: nil, + expectedMisehaviour: nil, + expectedSummary: &Summary{ + Candidate: candidateHash, + GroupID: parachaintypes.ParaID(candidate.Descriptor.ParaID), + ValidityVotes: 1, + }, }, } - table.candidateVotes[oldCandidateHash] = &candidateData{ - candidate: oldCandidate, + for _, tc := range testCases { + tc := tc + t.Run(tc.description, func(t *testing.T) { + t.Parallel() + + summary, misehaviour, err := tc.table.importCandidate(authority, candidate, signature, tc.tableCtx) + require.Equal(t, tc.expectedError, err) + require.Equal(t, tc.expectedMisehaviour, misehaviour) + require.Equal(t, tc.expectedSummary, summary) + + }) } +} + +func TestStatementTable_validityVote(t *testing.T) { + t.Parallel() + + validatorIndex := parachaintypes.ValidatorIndex(10) + committedCandidate := getDummyCommittedCandidateReceipt(t) - summary, misbehaviour, err := table.importCandidate(authority, candidate, signature, tableCtx) + candidateHash, err := parachaintypes.GetCandidateHash(committedCandidate) require.NoError(t, err) - require.Nil(t, summary) - require.Equal(t, parachaintypes.MultipleCandidates{ - First: parachaintypes.CommittedCandidateReceiptAndSign{ - CommittedCandidateReceipt: oldCandidate, - Signature: oldSign, + + validStatement := parachaintypes.NewStatementVDT() + err = validStatement.Set(parachaintypes.Valid(candidateHash)) + require.NoError(t, err) + + var validatorSign parachaintypes.ValidatorSignature + var tempSignature = common.MustHexToBytes("0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86") //nolint:lll + copy(validatorSign[:], tempSignature) + + oldSign := parachaintypes.ValidatorSignature{} + + testCases := []struct { + description string + vote validityVote + tableCtx *tableContext + table *statementTable + expectedError error + expectedMisehaviour parachaintypes.Misbehaviour + expectedSummary *Summary + }{ + { + description: "no_votes_available_for_the_given_candidate_hash", + vote: valid, + tableCtx: &tableContext{}, + table: newTable(tableConfig{}), + expectedError: errCandidateDataNotFound, + expectedMisehaviour: nil, + expectedSummary: nil, }, - Second: parachaintypes.CommittedCandidateReceiptAndSign{ - CommittedCandidateReceipt: candidate, - Signature: signature, + { + description: "validator_index_not_present_in_group_of_parachain_validators", + vote: valid, + tableCtx: &tableContext{}, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + }, + }, + }, + expectedError: nil, + expectedMisehaviour: parachaintypes.UnauthorizedStatement{ + Payload: validStatement, + ValidatorIndex: validatorIndex, + Signature: validatorSign, + }, + expectedSummary: nil, }, - }, misbehaviour) + { + description: "validity_vote_not_available_from_the_given_validator_index", + vote: valid, + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: make(map[parachaintypes.ValidatorIndex]validityVoteWithSign), + }, + }, + }, + expectedError: nil, + expectedMisehaviour: nil, + expectedSummary: &Summary{ + Candidate: candidateHash, + GroupID: parachaintypes.ParaID(committedCandidate.Descriptor.ParaID), + ValidityVotes: 1, + }, + }, + { + description: "validity_vote_available_from_validator_index_with_same_vote_and_sign", + vote: valid, + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{ + validatorIndex: { + validityVote: valid, + signature: validatorSign, + }, + }, + }, + }, + }, + expectedError: nil, + expectedMisehaviour: nil, + expectedSummary: nil, + }, + // below cases to check double votes + { + description: "vote_confict_with_candidate_statement", + vote: valid, + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{ + validatorIndex: { + validityVote: issued, + signature: oldSign, + }, + }, + }, + }, + }, + expectedError: nil, + expectedMisehaviour: parachaintypes.ValidityDoubleVoteIssuedAndValidity{ + CommittedCandidateReceiptAndSign: parachaintypes.CommittedCandidateReceiptAndSign{ + CommittedCandidateReceipt: committedCandidate, + Signature: oldSign, + }, + CandidateHashAndSign: parachaintypes.CandidateHashAndSign{ + CandidateHash: candidateHash, + Signature: validatorSign, + }, + }, + expectedSummary: nil, + }, + { + description: "two_signatures_on_same_validity_vote", + vote: valid, + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{ + validatorIndex: { + validityVote: valid, + signature: oldSign, + }, + }, + }, + }, + }, + expectedError: nil, + expectedMisehaviour: parachaintypes.DoubleSignOnValidity{ + CandidateHash: candidateHash, + Sign1: oldSign, + Sign2: validatorSign, + }, + expectedSummary: nil, + }, + { + description: "two_signatures_on_same_seconded_candidate", + vote: issued, + tableCtx: &tableContext{ + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {10}, + }, + }, + table: &statementTable{ + candidateVotes: map[parachaintypes.CandidateHash]*candidateData{ + candidateHash: { + groupID: 1, + candidate: committedCandidate, + validityVotes: map[parachaintypes.ValidatorIndex]validityVoteWithSign{ + validatorIndex: { + validityVote: issued, + signature: oldSign, + }, + }, + }, + }, + }, + expectedError: nil, + expectedMisehaviour: parachaintypes.DoubleSignOnSeconded{ + Candidate: committedCandidate, + Sign1: oldSign, + Sign2: validatorSign, + }, + expectedSummary: nil, + }, + } + for _, tc := range testCases { + tc := tc + t.Run("", func(t *testing.T) { + t.Parallel() + + summary, misehaviour, err := tc.table.validityVote( + validatorIndex, + candidateHash, + validityVoteWithSign{tc.vote, validatorSign}, + tc.tableCtx, + ) + + require.Equal(t, tc.expectedError, err) + require.Equal(t, tc.expectedMisehaviour, misehaviour) + require.Equal(t, tc.expectedSummary, summary) + }) + } } From ad9d5339fea1929d1434b7ff83ebce2a8d9fbc82 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Mon, 3 Jun 2024 15:12:32 +0530 Subject: [PATCH 18/19] address reviews --- dot/parachain/backing/candidate_backing.go | 1 + dot/parachain/backing/statement_table.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 2c3e29bd7f..2da1f9fe0b 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -124,6 +124,7 @@ type tableContext struct { validators []parachaintypes.ValidatorID } +// isMemberOf returns true if the validator is a member of the group of validators assigned to the parachain. func (tc *tableContext) isMemberOf(validatorIndex parachaintypes.ValidatorIndex, paraID parachaintypes.ParaID) bool { indexes, ok := tc.groups[paraID] if !ok { diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 48d861d8b6..01fc23ea74 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -48,7 +48,7 @@ func (data *candidateData) getSummary(candidateHash parachaintypes.CandidateHash // attested yields a full attestation for a candidate. // If the candidate can be included, it will return attested candidate. -func (data candidateData) attested(validityThreshold uint) (*attestedCandidate, error) { +func (data *candidateData) attested(validityThreshold uint) (*attestedCandidate, error) { numOfValidityVotes := uint(len(data.validityVotes)) if numOfValidityVotes < validityThreshold { return nil, fmt.Errorf("%w: %d < %d", errNotEnoughValidityVotes, numOfValidityVotes, validityThreshold) From 19b1ec32bb074a211eaebad0d1d9f77c54821002 Mon Sep 17 00:00:00 2001 From: axaysagathiya Date: Wed, 5 Jun 2024 14:15:33 +0530 Subject: [PATCH 19/19] improve --- .../backing/per_relay_parent_state.go | 24 ++++++++----------- dot/parachain/backing/statement_table.go | 1 + 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go index ded31df80a..bfb3ccc5b8 100644 --- a/dot/parachain/backing/per_relay_parent_state.go +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -17,6 +17,8 @@ import ( "github.com/ChainSafe/gossamer/lib/common" ) +var errNilPersistedValidationData = errors.New("persisted validation data is nil") + // PerRelayParentState represents the state information for a relay-parent in the subsystem. type perRelayParentState struct { prospectiveParachainsMode parachaintypes.ProspectiveParachainsMode @@ -51,13 +53,13 @@ func (rpState *perRelayParentState) importStatement( return nil, fmt.Errorf("getting value from statementVDT: %w", err) } - if statementVDT.Index() == 2 { // Valid + if statementVDT.Index() != 1 { // Not Seconded return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD.SignedFullStatement) } // PersistedValidationData should not be nil if the statementVDT is Seconded. if signedStatementWithPVD.PersistedValidationData == nil { - return nil, fmt.Errorf("persisted validation data is nil") + return nil, errNilPersistedValidationData } statementVDTSeconded := statementVDT.(parachaintypes.Seconded) @@ -114,9 +116,10 @@ func (rpState *perRelayParentState) importStatement( // postImportStatement handles a summary received from importStatement func and dispatches `Backed` notifications and // misbehaviors as a result of importing a statement. func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan<- any, summary *Summary) { - // If the summary is nil, issue new misbehaviors and return. + defer issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) + + // Return, If the summary is nil. if summary == nil { - issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) return } @@ -125,23 +128,19 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan logger.Error(err.Error()) } - // If the candidate is not attested, issue new misbehaviors and return. + // Return, If the candidate is not attested. if attested == nil { - issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) return } - hash, err := attested.committedCandidateReceipt.Hash() + candidateHash, err := parachaintypes.GetCandidateHash(attested.committedCandidateReceipt) if err != nil { logger.Error(err.Error()) return } - candidateHash := parachaintypes.CandidateHash{Value: hash} - - // If the candidate is already backed, issue new misbehaviors and return. + // Return, If the candidate is already backed. if rpState.backed[candidateHash] { - issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) return } @@ -151,7 +150,6 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan // Convert the attested candidate to a backed candidate. backedCandidate := attested.toBackedCandidate(&rpState.tableContext) if backedCandidate == nil { - issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) return } @@ -187,8 +185,6 @@ func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan ProvisionableData: parachaintypes.ProvisionableDataBackedCandidate(backedCandidate.Candidate.ToPlain()), } } - - issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) } // issueNewMisbehaviors checks for new misbehaviors and sends necessary messages to the Overseer subsystem. diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go index 01fc23ea74..a1dc97ce95 100644 --- a/dot/parachain/backing/statement_table.go +++ b/dot/parachain/backing/statement_table.go @@ -121,6 +121,7 @@ func (table *statementTable) getCommittedCandidateReceipt(candidateHash parachai return data.candidate, nil } +// importStatement imports a statement into the table. func (table *statementTable) importStatement( tableCtx *tableContext, signedStatement parachaintypes.SignedFullStatement, ) (*Summary, error) {