|
1 | 1 | package certstore |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "context" |
| 6 | + "math/rand" |
| 7 | + "testing" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/filecoin-project/go-f3/certchain" |
| 11 | + "github.com/filecoin-project/go-f3/gpbft" |
| 12 | + "github.com/filecoin-project/go-f3/internal/clock" |
| 13 | + "github.com/filecoin-project/go-f3/internal/consensus" |
| 14 | + "github.com/filecoin-project/go-f3/manifest" |
| 15 | + "github.com/filecoin-project/go-f3/sim/signing" |
| 16 | + "github.com/ipfs/go-datastore" |
| 17 | + "github.com/stretchr/testify/require" |
| 18 | + "go.uber.org/zap/buffer" |
| 19 | +) |
| 20 | + |
| 21 | +func Test_SnapshotExportImportRoundTrip(t *testing.T) { |
| 22 | + const ( |
| 23 | + seed = 1427 |
| 24 | + certChainLength = 150 |
| 25 | + testingPowerTableFreqency = uint64(23) |
| 26 | + ) |
| 27 | + |
| 28 | + ctx, clk := clock.WithMockClock(context.Background()) |
| 29 | + m := manifest.LocalDevnetManifest() |
| 30 | + m.InitialInstance = 100 |
| 31 | + signVerifier := signing.NewFakeBackend() |
| 32 | + rng := rand.New(rand.NewSource(seed * 23)) |
| 33 | + generatePublicKey := func(id gpbft.ActorID) gpbft.PubKey { |
| 34 | + //TODO: add the ability to evolve public key across instances. Fake signing |
| 35 | + // backed does not support this. |
| 36 | + |
| 37 | + // Use allow instead of GenerateKey for a reproducible key generation. |
| 38 | + return signVerifier.Allow(int(id)) |
| 39 | + } |
| 40 | + initialPowerTable := generatePowerTable(t, rng, generatePublicKey, nil) |
| 41 | + |
| 42 | + ec := consensus.NewFakeEC( |
| 43 | + consensus.WithClock(clk), |
| 44 | + consensus.WithSeed(seed*13), |
| 45 | + consensus.WithBootstrapEpoch(m.BootstrapEpoch), |
| 46 | + consensus.WithECPeriod(m.EC.Period), |
| 47 | + consensus.WithInitialPowerTable(initialPowerTable), |
| 48 | + consensus.WithEvolvingPowerTable( |
| 49 | + func(epoch int64, entries gpbft.PowerEntries) gpbft.PowerEntries { |
| 50 | + if epoch == m.BootstrapEpoch-m.EC.Finality { |
| 51 | + return initialPowerTable |
| 52 | + } |
| 53 | + rng := rand.New(rand.NewSource(epoch * seed)) |
| 54 | + next := generatePowerTable(t, rng, generatePublicKey, entries) |
| 55 | + return next |
| 56 | + }, |
| 57 | + ), |
| 58 | + ) |
| 59 | + |
| 60 | + subject, err := certchain.New( |
| 61 | + certchain.WithSeed(seed), |
| 62 | + certchain.WithSignVerifier(signVerifier), |
| 63 | + certchain.WithManifest(m), |
| 64 | + certchain.WithEC(ec), |
| 65 | + ) |
| 66 | + require.NoError(t, err) |
| 67 | + |
| 68 | + // The mock clock is buried into context passed to fake EC. The face EC will |
| 69 | + // refuse to generate a chain if the clock is not advanced. Advance it |
| 70 | + // sufficiently to never be bothered by it again. |
| 71 | + // |
| 72 | + // The fake EC and its relationship with clock needs to be reworked: Clock should |
| 73 | + // ideally be passed as an option, and its absence should mean "advance the clock |
| 74 | + // as needed". Because, we do not always care about controlling the progress of |
| 75 | + // chain generated by fake EC. |
| 76 | + clk.Add(200 * time.Hour) |
| 77 | + |
| 78 | + generatedChain, err := subject.Generate(ctx, certChainLength) |
| 79 | + require.NoError(t, err) |
| 80 | + |
| 81 | + ds1 := datastore.NewMapDatastore() |
| 82 | + cs, err := OpenOrCreateStore(ctx, ds1, generatedChain[0].GPBFTInstance, initialPowerTable) |
| 83 | + cs.powerTableFrequency = testingPowerTableFreqency |
| 84 | + require.NoError(t, err) |
| 85 | + |
| 86 | + for _, cert := range generatedChain { |
| 87 | + cs.Put(ctx, cert) |
| 88 | + } |
| 89 | + |
| 90 | + snapshot := buffer.Buffer{} |
| 91 | + err = cs.ExportLatestSnapshot(ctx, &snapshot) |
| 92 | + require.NoError(t, err) |
| 93 | + |
| 94 | + ds2 := datastore.NewMapDatastore() |
| 95 | + err = importSnapshotToDatastoreWithTestingPowerTableFrequency(ctx, bytes.NewReader(snapshot.Bytes()), ds2, testingPowerTableFreqency) |
| 96 | + require.NoError(t, err) |
| 97 | + |
| 98 | + require.Equal(t, ds1, ds2) |
| 99 | + |
| 100 | + ds3 := datastore.NewMapDatastore() |
| 101 | + err = ImportSnapshotToDatastore(ctx, bytes.NewReader(snapshot.Bytes()), ds3) |
| 102 | + require.NoError(t, err) |
| 103 | + |
| 104 | + require.NotEqual(t, ds1, ds3) |
| 105 | +} |
| 106 | + |
| 107 | +func generatePowerTable(t *testing.T, rng *rand.Rand, generatePublicKey func(id gpbft.ActorID) gpbft.PubKey, previousEntries gpbft.PowerEntries) gpbft.PowerEntries { |
| 108 | + const ( |
| 109 | + maxEntries = 100 |
| 110 | + maxPower = 1 << 20 |
| 111 | + minPower = 0 // Pick a sufficiently low power to facilitate entries with zero scaled power. |
| 112 | + actorIDOffset = 1413 |
| 113 | + powerChangeProbability = 0.2 |
| 114 | + ) |
| 115 | + |
| 116 | + size := rng.Intn(maxEntries) |
| 117 | + entries := make(gpbft.PowerEntries, 0, size) |
| 118 | + for i := range size { |
| 119 | + var entry gpbft.PowerEntry |
| 120 | + if i < previousEntries.Len() { |
| 121 | + entry = previousEntries[i] |
| 122 | + changedPower := rng.Float64() > powerChangeProbability |
| 123 | + if changedPower { |
| 124 | + entry.Power = gpbft.NewStoragePower(int64(rng.Intn(maxPower) + minPower)) |
| 125 | + } |
| 126 | + } else { |
| 127 | + id := gpbft.ActorID(uint64(actorIDOffset + i)) |
| 128 | + entry = gpbft.PowerEntry{ |
| 129 | + ID: id, |
| 130 | + Power: gpbft.NewStoragePower(int64(rng.Intn(maxPower) + minPower)), |
| 131 | + PubKey: generatePublicKey(id), |
| 132 | + } |
| 133 | + } |
| 134 | + entries = append(entries, entry) |
| 135 | + } |
| 136 | + next := gpbft.NewPowerTable() |
| 137 | + require.NoError(t, next.Add(entries...)) |
| 138 | + return next.Entries |
| 139 | +} |
0 commit comments