Skip to content

Commit 7f2068e

Browse files
authored
fix(f3): test API during inactive-F3 modes and make consistent and safe (#12781)
Closes: #12772
1 parent 4dbadfb commit 7f2068e

File tree

3 files changed

+139
-8
lines changed

3 files changed

+139
-8
lines changed

chain/lf3/f3.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -184,20 +184,23 @@ func (fff *F3) GetLatestCert(ctx context.Context) (*certs.FinalityCertificate, e
184184
return fff.inner.GetLatestCert(ctx)
185185
}
186186

187-
func (fff *F3) GetManifest(ctx context.Context) *manifest.Manifest {
187+
func (fff *F3) GetManifest(ctx context.Context) (*manifest.Manifest, error) {
188188
m := fff.inner.Manifest()
189+
if m == nil {
190+
return nil, xerrors.New("no known network manifest")
191+
}
189192
if m.InitialPowerTable.Defined() {
190-
return m
193+
return m, nil
191194
}
192195
cert0, err := fff.inner.GetCert(ctx, 0)
193196
if err != nil {
194-
return m
197+
return m, nil // return manifest without power table
195198
}
196199

197200
var mCopy = *m
198201
m = &mCopy
199202
m.InitialPowerTable = cert0.ECChain.Base().PowerTable
200-
return m
203+
return m, nil
201204
}
202205

203206
func (fff *F3) GetPowerTable(ctx context.Context, tsk types.TipSetKey) (gpbft.PowerEntries, error) {

itests/f3_test.go

+129-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package itests
22

33
import (
44
"context"
5+
"reflect"
56
"sync"
67
"testing"
78
"time"
@@ -14,10 +15,12 @@ import (
1415
"golang.org/x/sync/errgroup"
1516

1617
"github.com/filecoin-project/go-address"
18+
"github.com/filecoin-project/go-f3/certs"
1719
"github.com/filecoin-project/go-f3/gpbft"
1820
"github.com/filecoin-project/go-f3/manifest"
1921
"github.com/filecoin-project/go-state-types/abi"
2022

23+
"github.com/filecoin-project/lotus/api"
2124
lotus_api "github.com/filecoin-project/lotus/api"
2225
"github.com/filecoin-project/lotus/chain/lf3"
2326
"github.com/filecoin-project/lotus/chain/types"
@@ -57,6 +60,129 @@ func TestF3_Enabled(t *testing.T) {
5760
e.requireAllMinersParticipate()
5861
}
5962

63+
// TestF3_Disabled tests the return values and errors of the F3 API when F3 is
64+
// disabled or is not yet running.
65+
func TestF3_InactiveModes(t *testing.T) {
66+
kit.QuietMiningLogs()
67+
68+
testCases := []struct {
69+
mode string
70+
expectedErrors map[string]any
71+
expectedValues map[string]any
72+
customValidateReturn map[string]func(t *testing.T, ret []reflect.Value)
73+
}{
74+
{
75+
mode: "disabled",
76+
expectedErrors: map[string]any{
77+
"F3GetOrRenewParticipationTicket": lotus_api.ErrF3Disabled,
78+
"F3Participate": lotus_api.ErrF3Disabled,
79+
"F3GetCertificate": lotus_api.ErrF3Disabled,
80+
"F3GetLatestCertificate": lotus_api.ErrF3Disabled,
81+
"F3GetManifest": lotus_api.ErrF3Disabled,
82+
"F3GetECPowerTable": lotus_api.ErrF3Disabled,
83+
"F3GetF3PowerTable": lotus_api.ErrF3Disabled,
84+
"F3IsRunning": lotus_api.ErrF3Disabled,
85+
},
86+
expectedValues: map[string]any{
87+
"F3GetOrRenewParticipationTicket": (api.F3ParticipationTicket)(nil),
88+
"F3Participate": api.F3ParticipationLease{},
89+
"F3GetCertificate": (*certs.FinalityCertificate)(nil),
90+
"F3GetLatestCertificate": (*certs.FinalityCertificate)(nil),
91+
"F3GetManifest": (*manifest.Manifest)(nil),
92+
"F3GetECPowerTable": (gpbft.PowerEntries)(nil),
93+
"F3GetF3PowerTable": (gpbft.PowerEntries)(nil),
94+
"F3IsRunning": false,
95+
},
96+
},
97+
{
98+
mode: "not running",
99+
expectedErrors: map[string]any{
100+
"F3GetOrRenewParticipationTicket": api.ErrF3NotReady,
101+
"F3Participate": api.ErrF3NotReady,
102+
"F3GetCertificate": "F3 is not running",
103+
"F3GetLatestCertificate": "F3 is not running",
104+
"F3GetManifest": "no known network manifest",
105+
"F3GetF3PowerTable": "no known network manifest",
106+
},
107+
expectedValues: map[string]any{
108+
"F3GetOrRenewParticipationTicket": (api.F3ParticipationTicket)(nil),
109+
"F3Participate": api.F3ParticipationLease{},
110+
"F3GetCertificate": (*certs.FinalityCertificate)(nil),
111+
"F3GetLatestCertificate": (*certs.FinalityCertificate)(nil),
112+
"F3GetManifest": (*manifest.Manifest)(nil),
113+
"F3GetF3PowerTable": (gpbft.PowerEntries)(nil),
114+
"F3IsRunning": false,
115+
},
116+
customValidateReturn: map[string]func(t *testing.T, ret []reflect.Value){
117+
"F3GetECPowerTable": func(t *testing.T, ret []reflect.Value) {
118+
// special case because it simply returns power table from EC which is not F3 dependent
119+
require.NotNil(t, ret[0].Interface(), "unexpected return value")
120+
},
121+
},
122+
},
123+
}
124+
125+
for _, tc := range testCases {
126+
t.Run(tc.mode, func(t *testing.T) {
127+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
128+
defer cancel()
129+
130+
opts := []any{kit.MockProofs()}
131+
if tc.mode == "disabled" {
132+
opts = append(opts, kit.F3Enabled(nil))
133+
}
134+
135+
client, miner, ens := kit.EnsembleMinimal(t, opts...)
136+
ens.InterconnectAll().BeginMining(2 * time.Millisecond)
137+
ens.Start()
138+
139+
head := client.WaitTillChain(ctx, kit.HeightAtLeast(10))
140+
141+
rctx := reflect.ValueOf(ctx)
142+
rtsk := reflect.ValueOf(head.Key())
143+
rminer := reflect.ValueOf(miner.ActorAddr)
144+
rticket := reflect.ValueOf([]byte("fish"))
145+
rone := reflect.ValueOf(uint64(1))
146+
147+
calls := map[string][]reflect.Value{
148+
"F3GetOrRenewParticipationTicket": {rctx, rminer, rticket, rone},
149+
"F3Participate": {rctx, rticket},
150+
"F3GetCertificate": {rctx, rone},
151+
"F3GetLatestCertificate": {rctx},
152+
"F3GetManifest": {rctx},
153+
"F3GetECPowerTable": {rctx, rtsk},
154+
"F3GetF3PowerTable": {rctx, rtsk},
155+
"F3IsRunning": {rctx},
156+
}
157+
158+
for fn, args := range calls {
159+
t.Run(fn, func(t *testing.T) {
160+
ret := reflect.ValueOf(client).MethodByName(fn).Call(args)
161+
162+
if expectedValue, hasExpectedValue := tc.expectedValues[fn]; hasExpectedValue {
163+
require.Equal(t, expectedValue, ret[0].Interface(), "unexpected return value")
164+
}
165+
166+
if expectedError, hasExpectedError := tc.expectedErrors[fn]; hasExpectedError {
167+
switch err := expectedError.(type) {
168+
case error:
169+
require.ErrorIs(t, ret[1].Interface().(error), err, "unexpected error")
170+
case string:
171+
require.ErrorContains(t, ret[1].Interface().(error), err, "unexpected error")
172+
}
173+
} else {
174+
require.Nil(t, ret[1].Interface(), "unexpected error")
175+
}
176+
177+
if validate, hasValidate := tc.customValidateReturn[fn]; hasValidate {
178+
validate(t, ret)
179+
}
180+
})
181+
}
182+
})
183+
}
184+
}
185+
60186
// TestF3_Rebootstrap tests F3 can be rebootsrapped by changing the manifest
61187
// without disrupting miner participation.
62188
func TestF3_Rebootstrap(t *testing.T) {
@@ -227,7 +353,9 @@ func (e *testEnv) waitTillF3Instance(i uint64, timeout time.Duration) {
227353
func (e *testEnv) waitTillManifestChange(newManifest *manifest.Manifest, timeout time.Duration) {
228354
e.waitFor(func(n *kit.TestFullNode) bool {
229355
m, err := n.F3GetManifest(e.testCtx)
230-
require.NoError(e.t, err)
356+
if err != nil || m == nil {
357+
return false
358+
}
231359
return newManifest.Equal(m)
232360
}, timeout)
233361
}

node/impl/full/f3.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ type F3API struct {
2525
func (f3api *F3API) F3GetOrRenewParticipationTicket(ctx context.Context, miner address.Address, previous api.F3ParticipationTicket, instances uint64) (api.F3ParticipationTicket, error) {
2626
if f3api.F3 == nil {
2727
log.Infof("F3GetParticipationTicket called for %v, F3 is disabled", miner)
28-
return api.F3ParticipationTicket{}, api.ErrF3Disabled
28+
return nil, api.ErrF3Disabled
2929
}
3030
minerID, err := address.IDFromAddress(miner)
3131
if err != nil {
32-
return api.F3ParticipationTicket{}, xerrors.Errorf("miner address is not of ID type: %v: %w", miner, err)
32+
return nil, xerrors.Errorf("miner address is not of ID type: %v: %w", miner, err)
3333
}
3434
return f3api.F3.GetOrRenewParticipationTicket(ctx, minerID, previous, instances)
3535
}
@@ -61,7 +61,7 @@ func (f3api *F3API) F3GetManifest(ctx context.Context) (*manifest.Manifest, erro
6161
if f3api.F3 == nil {
6262
return nil, api.ErrF3Disabled
6363
}
64-
return f3api.F3.GetManifest(ctx), nil
64+
return f3api.F3.GetManifest(ctx)
6565
}
6666

6767
func (f3api *F3API) F3IsRunning(context.Context) (bool, error) {

0 commit comments

Comments
 (0)