Skip to content

Commit fababb5

Browse files
authored
fix(f3): validate F3 snapshot against manifest before importing (#1059)
* fix(f3): validate F3 snapshot against manifest before importing * fix bootstrap epoch check logic * unit test * Fix bootstrap epoch calculation * no bootstrap epoch check
1 parent 3cd0eac commit fababb5

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

certstore/snapshot.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/filecoin-project/go-f3/certs"
1313
"github.com/filecoin-project/go-f3/gpbft"
14+
"github.com/filecoin-project/go-f3/manifest"
1415
"github.com/filecoin-project/go-state-types/cbor"
1516
cid "github.com/ipfs/go-cid"
1617
"github.com/ipfs/go-datastore"
@@ -87,14 +88,14 @@ type SnapshotReader interface {
8788
io.ByteReader
8889
}
8990

90-
// ImportSnapshotToDatastore imports an F3 snapshot into the specified Datastore
91-
//
91+
// ImportSnapshotToDatastore imports an F3 snapshot into the specified Datastore.
92+
// This function optionally validates the F3 snapshot against the manifest if provided.
9293
// Checkout the snapshot format specification at <https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0108.md>
93-
func ImportSnapshotToDatastore(ctx context.Context, snapshot SnapshotReader, ds datastore.Batching) error {
94-
return importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx, snapshot, ds, 0)
94+
func ImportSnapshotToDatastore(ctx context.Context, snapshot SnapshotReader, ds datastore.Batching, m *manifest.Manifest) error {
95+
return importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx, snapshot, ds, m, 0)
9596
}
9697

97-
func importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx context.Context, snapshot SnapshotReader, ds datastore.Batching, testingPowerTableFrequency uint64) error {
98+
func importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx context.Context, snapshot SnapshotReader, ds datastore.Batching, m *manifest.Manifest, testingPowerTableFrequency uint64) error {
9899
headerBytes, err := readSnapshotBlockBytes(snapshot)
99100
if err != nil {
100101
return err
@@ -104,6 +105,20 @@ func importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx context.Context
104105
if err != nil {
105106
return fmt.Errorf("failed to decode snapshot header: %w", err)
106107
}
108+
// validate the header against the manifest if provided
109+
if m != nil {
110+
if m.InitialInstance != header.FirstInstance {
111+
return fmt.Errorf("F3 initial instance in the snapshot(%d) does not match that in the manifest(%d)", header.FirstInstance, m.InitialInstance)
112+
}
113+
if m.InitialPowerTable.Defined() {
114+
ptCid, err := certs.MakePowerTableCID(header.InitialPowerTable)
115+
if err != nil {
116+
return fmt.Errorf("failed to make initial power table CID: %w", err)
117+
} else if m.InitialPowerTable != ptCid {
118+
return fmt.Errorf("F3 initial power table CID in the snapshot(%s) does not match that in the manifest(%s)", ptCid, m.InitialPowerTable)
119+
}
120+
}
121+
}
107122
dsb := autobatch.NewAutoBatching(ds, 1000)
108123
defer dsb.Flush(ctx)
109124
cs, err := OpenOrCreateStore(ctx, dsb, header.FirstInstance, header.InitialPowerTable)

certstore/snapshot_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/filecoin-project/go-f3/certchain"
11+
"github.com/filecoin-project/go-f3/certs"
1112
"github.com/filecoin-project/go-f3/gpbft"
1213
"github.com/filecoin-project/go-f3/internal/clock"
1314
"github.com/filecoin-project/go-f3/internal/consensus"
@@ -41,6 +42,9 @@ func Test_SnapshotExportImportRoundTrip(t *testing.T) {
4142
return signVerifier.Allow(int(id))
4243
}
4344
initialPowerTable := generatePowerTable(t, rng, generatePublicKey, nil)
45+
ptCid, err := certs.MakePowerTableCID(initialPowerTable)
46+
require.NoError(t, err)
47+
m.InitialPowerTable = ptCid
4448

4549
ec := consensus.NewFakeEC(
4650
consensus.WithClock(clk),
@@ -103,16 +107,31 @@ func Test_SnapshotExportImportRoundTrip(t *testing.T) {
103107
require.Equal(t, c.Hash(), multihash.Multihash(mh))
104108

105109
ds2 := datastore.NewMapDatastore()
106-
err = importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx, bytes.NewReader(snapshot.Bytes()), ds2, testingPowerTableFreqency)
110+
err = importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx, bytes.NewReader(snapshot.Bytes()), ds2, &m, testingPowerTableFreqency)
107111
require.NoError(t, err)
108112

109113
require.Equal(t, ds1, ds2)
110114

111115
ds3 := datastore.NewMapDatastore()
112-
err = ImportSnapshotToDatastore(ctx, bytes.NewReader(snapshot.Bytes()), ds3)
116+
err = ImportSnapshotToDatastore(ctx, bytes.NewReader(snapshot.Bytes()), ds3, &m)
113117
require.NoError(t, err)
114118

115119
require.NotEqual(t, ds1, ds3)
120+
121+
// Test manifest validation logic
122+
ds4 := datastore.NewMapDatastore()
123+
m2 := manifest.LocalDevnetManifest()
124+
125+
// bad initial instance
126+
m2.InitialInstance = m.InitialInstance + 1
127+
err = ImportSnapshotToDatastore(ctx, bytes.NewReader(snapshot.Bytes()), ds4, &m2)
128+
require.ErrorContains(t, err, "initial instance")
129+
130+
// bad InitialPowerTable
131+
m2.InitialInstance = m.InitialInstance
132+
m2.InitialPowerTable = generatedChain[1].ECChain.Head().PowerTable
133+
err = ImportSnapshotToDatastore(ctx, bytes.NewReader(snapshot.Bytes()), ds4, &m2)
134+
require.ErrorContains(t, err, "initial power table CID")
116135
}
117136

118137
func generatePowerTable(t *testing.T, rng *rand.Rand, generatePublicKey func(id gpbft.ActorID) gpbft.PubKey, previousEntries gpbft.PowerEntries) gpbft.PowerEntries {

0 commit comments

Comments
 (0)