Skip to content

Commit f46fd35

Browse files
authored
Add message signature validation test for partial and non-partial (#1022)
The work here introduces a new testing machinery that can accept a scenario along with custom messages for asserting fine-grained validation rules. The actual test case is being tested, however, is limited to the scope of #1018. Fixes #1018
1 parent b1a4acf commit f46fd35

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

gpbft/validator_api_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package gpbft
2+
3+
import "github.com/filecoin-project/go-f3/internal/caching"
4+
5+
type Validator interface {
6+
MessageValidator
7+
PartialMessageValidator
8+
}
9+
10+
// NewValidator creates a new Validator instance with the provided parameters for
11+
// testing purposes.
12+
func NewValidator(nn NetworkName, verifier Verifier, cp CommitteeProvider, progress Progress, cache *caching.GroupedSet, committeeLookback uint64) Validator {
13+
return newValidator(nn, verifier, cp, progress, cache, committeeLookback)
14+
}

gpbft/validator_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package gpbft_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"testing"
9+
10+
"github.com/filecoin-project/go-f3/emulator"
11+
"github.com/filecoin-project/go-f3/gpbft"
12+
"github.com/filecoin-project/go-f3/internal/caching"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestValidator(t *testing.T) {
17+
var (
18+
ctx = context.Background()
19+
plausibleProposal = &gpbft.ECChain{
20+
TipSets: []*gpbft.TipSet{tipset0, tipSet1, tipSet2},
21+
}
22+
// validVoteSignature is the placeholder to signal that valid signature should be
23+
// populated before test.
24+
validVoteSignature = []byte("valid_vote_signature")
25+
)
26+
type testCase struct {
27+
name string
28+
scenario validatorTestScenario
29+
givenMessage *gpbft.GMessage
30+
givenPartialMessage *gpbft.PartialGMessage
31+
wantError error
32+
}
33+
34+
for _, test := range []testCase{
35+
{
36+
name: "nil message",
37+
wantError: gpbft.ErrValidationInvalid,
38+
},
39+
{
40+
name: "valid partial signature",
41+
scenario: validatorTestScenario{
42+
InstantProgress: gpbft.InstanceProgress{
43+
Instant: gpbft.Instant{
44+
Phase: gpbft.QUALITY_PHASE,
45+
},
46+
Input: plausibleProposal,
47+
},
48+
CommitteeLookback: 10,
49+
Committees: map[uint64]map[gpbft.ActorID]int{
50+
0: {
51+
1: 10,
52+
},
53+
},
54+
CacheMaxGroups: 10,
55+
CacheMaxSetSize: 10,
56+
},
57+
givenMessage: &gpbft.GMessage{
58+
Sender: 1,
59+
Vote: gpbft.Payload{
60+
Phase: gpbft.QUALITY_PHASE,
61+
Value: plausibleProposal,
62+
},
63+
Signature: validVoteSignature,
64+
},
65+
givenPartialMessage: &gpbft.PartialGMessage{
66+
GMessage: &gpbft.GMessage{
67+
Sender: 1,
68+
Vote: gpbft.Payload{
69+
Phase: gpbft.QUALITY_PHASE,
70+
},
71+
Signature: validVoteSignature,
72+
},
73+
VoteValueKey: plausibleProposal.Key(),
74+
},
75+
},
76+
} {
77+
t.Run(test.name, func(t *testing.T) {
78+
environment := newValidatorTestEnvironment(test.scenario)
79+
subject := environment.newTestSubject()
80+
if test.givenMessage != nil {
81+
msg := test.givenMessage
82+
if bytes.Equal(msg.Signature, validVoteSignature) {
83+
signingPayload := msg.Vote.MarshalForSigning(test.scenario.NetworkName)
84+
committee, err := environment.GetCommittee(ctx, msg.Vote.Instance)
85+
require.NoError(t, err)
86+
_, key := committee.PowerTable.Get(msg.Sender)
87+
msg.Signature, err = environment.signing.Sign(ctx, key, signingPayload)
88+
require.NoError(t, err)
89+
}
90+
}
91+
if test.givenPartialMessage != nil {
92+
pmsg := test.givenPartialMessage
93+
if pmsg.GMessage != nil && bytes.Equal(pmsg.GMessage.Signature, validVoteSignature) {
94+
signingPayload := pmsg.GMessage.Vote.MarshalForSigningWithValueKey(test.scenario.NetworkName, pmsg.VoteValueKey)
95+
committee, err := environment.GetCommittee(ctx, pmsg.Vote.Instance)
96+
require.NoError(t, err)
97+
_, key := committee.PowerTable.Get(pmsg.Sender)
98+
pmsg.Signature, err = environment.signing.Sign(ctx, key, signingPayload)
99+
require.NoError(t, err)
100+
}
101+
102+
}
103+
valid, err := subject.ValidateMessage(ctx, test.givenMessage)
104+
if test.wantError != nil {
105+
require.ErrorIs(t, err, test.wantError, "expected error %q, got %v", test.wantError, err)
106+
require.Nil(t, valid)
107+
} else {
108+
require.NoError(t, err, "expected no error, got %v", err)
109+
require.NotNil(t, valid, "expected message to be valid, but it was not")
110+
}
111+
112+
partiallyValid, err := subject.PartiallyValidateMessage(ctx, test.givenPartialMessage)
113+
if test.wantError != nil {
114+
require.ErrorIs(t, err, test.wantError, "expected error %q, got %v", test.wantError, err)
115+
require.Nil(t, partiallyValid)
116+
} else {
117+
require.NoError(t, err, "expected no error, got %v", err)
118+
require.NotNil(t, partiallyValid, "expected partial message to be valid, but it was not")
119+
}
120+
})
121+
}
122+
}
123+
124+
type validatorTestScenario struct {
125+
NetworkName gpbft.NetworkName
126+
InstantProgress gpbft.InstanceProgress
127+
CommitteeLookback uint64
128+
Committees map[uint64]map[gpbft.ActorID]int // Maps instance ID to participant to storage power
129+
CacheMaxGroups int
130+
CacheMaxSetSize int
131+
}
132+
133+
var (
134+
_ gpbft.Verifier = (*validatorTestEnvironment)(nil)
135+
_ gpbft.CommitteeProvider = (*validatorTestEnvironment)(nil)
136+
)
137+
138+
type validatorTestEnvironment struct {
139+
scenario validatorTestScenario
140+
progress gpbft.Progress
141+
cache *caching.GroupedSet
142+
signing emulator.Signing
143+
}
144+
145+
func newValidatorTestEnvironment(scenario validatorTestScenario) *validatorTestEnvironment {
146+
return &validatorTestEnvironment{
147+
scenario: scenario,
148+
progress: func() gpbft.InstanceProgress { return scenario.InstantProgress },
149+
cache: caching.NewGroupedSet(scenario.CacheMaxGroups, scenario.CacheMaxSetSize),
150+
signing: emulator.AdhocSigning(),
151+
}
152+
}
153+
154+
func (v *validatorTestEnvironment) GetCommittee(ctx context.Context, instance uint64) (*gpbft.Committee, error) {
155+
if v.scenario.Committees == nil {
156+
return nil, errors.New("no committees for any instance")
157+
}
158+
committee, ok := v.scenario.Committees[instance]
159+
if !ok {
160+
return nil, fmt.Errorf("no committee for instance %d", instance)
161+
}
162+
powerEntries := make([]gpbft.PowerEntry, 0, len(committee))
163+
publicKeys := make([]gpbft.PubKey, 0, len(committee))
164+
for actor, power := range committee {
165+
pk := []byte(fmt.Sprintf("actor: %d", actor))
166+
powerEntries = append(powerEntries, gpbft.PowerEntry{
167+
ID: actor,
168+
Power: gpbft.NewStoragePower(int64(power)),
169+
PubKey: pk,
170+
})
171+
publicKeys = append(publicKeys, pk)
172+
if ctx.Err() != nil {
173+
return nil, ctx.Err()
174+
}
175+
}
176+
pt := gpbft.NewPowerTable()
177+
if err := pt.Add(powerEntries...); err != nil {
178+
return nil, fmt.Errorf("failed to create power table: %w", err)
179+
}
180+
aggregate, err := v.signing.Aggregate(publicKeys)
181+
if err != nil {
182+
return nil, fmt.Errorf("failed to aggregate public keys: %w", err)
183+
}
184+
return &gpbft.Committee{
185+
PowerTable: pt,
186+
Beacon: []byte(fmt.Sprintf("🥓: %d", instance)),
187+
AggregateVerifier: aggregate,
188+
}, ctx.Err()
189+
}
190+
191+
func (v *validatorTestEnvironment) Verify(pubKey gpbft.PubKey, msg, sig []byte) error {
192+
return v.signing.Verify(pubKey, msg, sig)
193+
}
194+
195+
func (v *validatorTestEnvironment) Aggregate(pubKeys []gpbft.PubKey) (gpbft.Aggregate, error) {
196+
return v.signing.Aggregate(pubKeys)
197+
}
198+
199+
func (v *validatorTestEnvironment) newTestSubject() gpbft.Validator {
200+
return gpbft.NewValidator(v.scenario.NetworkName, v, v, v.progress, v.cache, v.scenario.CommitteeLookback)
201+
}

0 commit comments

Comments
 (0)