Skip to content

Commit

Permalink
Make the FakeEC more configurable (#675)
Browse files Browse the repository at this point in the history
1. Use functional options.
2. Make it possible to "forget" power tables after some time.
3. Make it possible to evolve the power-table over time.
  • Loading branch information
Stebalien authored Sep 30, 2024
1 parent 475919a commit c438aa5
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 60 deletions.
6 changes: 5 additions & 1 deletion cmd/f3/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ var runCmd = cli.Command{
id := c.Uint64("id")
signingBackend.Allow(int(id))

ec := consensus.NewFakeEC(ctx, 1, m.BootstrapEpoch, m.EC.Period, initialPowerTable)
ec := consensus.NewFakeEC(ctx,
consensus.WithBootstrapEpoch(m.BootstrapEpoch),
consensus.WithECPeriod(m.EC.Period),
consensus.WithInitialPowerTable(initialPowerTable),
)

module, err := f3.New(ctx, mprovider, ds, h, ps, signingBackend, ec, filepath.Join(tmpdir, "f3"))
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions ec/powerdelta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var powerTableC = gpbft.PowerEntries{
}

func TestReplacePowerTable(t *testing.T) {
backend := consensus.NewFakeEC(context.Background(), 0, 0, 30, powerTableA)
backend := consensus.NewFakeEC(context.Background(), consensus.WithInitialPowerTable(powerTableA))
modifiedBackend := ec.WithModifiedPower(backend, powerTableB, true)

head, err := modifiedBackend.GetHead(context.Background())
Expand All @@ -43,7 +43,7 @@ func TestReplacePowerTable(t *testing.T) {
}

func TestModifyPowerTable(t *testing.T) {
backend := consensus.NewFakeEC(context.Background(), 0, 0, 30, powerTableA)
backend := consensus.NewFakeEC(context.Background(), consensus.WithInitialPowerTable(powerTableA))
modifiedBackend := ec.WithModifiedPower(backend, powerTableB, false)

head, err := modifiedBackend.GetHead(context.Background())
Expand All @@ -55,7 +55,7 @@ func TestModifyPowerTable(t *testing.T) {
}

func TestBypassModifiedPowerTable(t *testing.T) {
backend := consensus.NewFakeEC(context.Background(), 0, 0, 30, powerTableA)
backend := consensus.NewFakeEC(context.Background(), consensus.WithInitialPowerTable(powerTableA))
modifiedBackend := ec.WithModifiedPower(backend, nil, false)
require.Equal(t, backend, modifiedBackend)
}
9 changes: 5 additions & 4 deletions f3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,11 @@ func (e *testEnv) initialize() *testEnv {
})
}

e.ec = consensus.NewFakeEC(e.testCtx, 1,
e.manifest.BootstrapEpoch+e.manifest.EC.Finality,
e.manifest.EC.Period,
initialPowerTable)
e.ec = consensus.NewFakeEC(e.testCtx,
consensus.WithBootstrapEpoch(e.manifest.BootstrapEpoch+e.manifest.EC.Finality),
consensus.WithECPeriod(e.manifest.EC.Period),
consensus.WithInitialPowerTable(initialPowerTable),
)
}

for _, n := range e.nodes {
Expand Down
93 changes: 80 additions & 13 deletions internal/consensus/fake_ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ var (
_ ec.TipSet = (*tipset)(nil)
)

type PowerTableMutator func(epoch int64, pt gpbft.PowerEntries) gpbft.PowerEntries

type FakeEC struct {
clock clock.Clock
seed []byte
initialPowerTable gpbft.PowerEntries
evolvePowerTable PowerTableMutator

ecPeriod time.Duration
ecStart time.Time
bootstrapEpoch int64
ecPeriod time.Duration
ecMaxLookback int64
ecStart time.Time

lk sync.RWMutex
pausedAt *time.Time
Expand Down Expand Up @@ -67,16 +72,59 @@ func (ts *tipset) String() string {
return res
}

func NewFakeEC(ctx context.Context, seed uint64, bootstrapEpoch int64, ecPeriod time.Duration, initialPowerTable gpbft.PowerEntries) *FakeEC {
type fakeECConfig FakeEC

type FakeECOption func(*fakeECConfig)

func WithBootstrapEpoch(epoch int64) FakeECOption {
return func(ec *fakeECConfig) {
ec.bootstrapEpoch = epoch
}
}

func WithSeed(seed uint64) FakeECOption {
return func(ec *fakeECConfig) {
ec.seed = binary.BigEndian.AppendUint64(nil, seed)
}
}

func WithInitialPowerTable(initialPowerTable gpbft.PowerEntries) FakeECOption {
return func(ec *fakeECConfig) {
ec.initialPowerTable = initialPowerTable
}
}

func WithECPeriod(ecPeriod time.Duration) FakeECOption {
return func(ec *fakeECConfig) {
ec.ecPeriod = ecPeriod
}
}

func WithMaxLookback(distance int64) FakeECOption {
return func(ec *fakeECConfig) {
ec.ecMaxLookback = distance
}
}

func WithEvolvingPowerTable(fn PowerTableMutator) FakeECOption {
return func(ec *fakeECConfig) {
ec.evolvePowerTable = fn
}
}

func NewFakeEC(ctx context.Context, options ...FakeECOption) *FakeEC {
clk := clock.GetClock(ctx)
return &FakeEC{
clock: clk,
seed: binary.BigEndian.AppendUint64(nil, seed),
initialPowerTable: initialPowerTable,
fakeEc := &FakeEC{
clock: clk,
ecPeriod: 30,
}

ecPeriod: ecPeriod,
ecStart: clk.Now().Add(-time.Duration(bootstrapEpoch) * ecPeriod),
for _, option := range options {
option((*fakeECConfig)(fakeEc))
}

fakeEc.ecStart = clk.Now().Add(-time.Duration(fakeEc.bootstrapEpoch) * fakeEc.ecPeriod)
return fakeEc
}

var cidPrefixBytes = gpbft.CidPrefix.Bytes()
Expand Down Expand Up @@ -175,13 +223,32 @@ func (ec *FakeEC) GetHead(ctx context.Context) (ec.TipSet, error) {
return ec.GetTipsetByEpoch(ctx, ec.GetCurrentHead())
}

func (ec *FakeEC) GetPowerTable(context.Context, gpbft.TipSetKey) (gpbft.PowerEntries, error) {
return ec.initialPowerTable, nil
func (ec *FakeEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpbft.PowerEntries, error) {
targetEpoch := ec.epochFromTsk(tsk)
headEpoch := ec.GetCurrentHead()

if targetEpoch > headEpoch {
return nil, fmt.Errorf("requested epoch %d beyond head %d", targetEpoch, headEpoch)
}

if ec.ecMaxLookback > 0 && targetEpoch < headEpoch-ec.ecMaxLookback {
return nil, fmt.Errorf("oops, we forgot that power table, head %d, epoch %d", headEpoch, targetEpoch)
}

pt := ec.initialPowerTable
if ec.evolvePowerTable != nil {
pt = ec.evolvePowerTable(targetEpoch, pt)
}

return pt, nil
}

func (ec *FakeEC) epochFromTsk(tsk gpbft.TipSetKey) int64 {
return int64(binary.BigEndian.Uint64(tsk[6+32-8 : 6+32]))
}

func (ec *FakeEC) GetTipset(_ context.Context, tsk gpbft.TipSetKey) (ec.TipSet, error) {
epoch := binary.BigEndian.Uint64(tsk[6+32-8 : 6+32])
return ec.genTipset(int64(epoch)), nil
return ec.genTipset(ec.epochFromTsk(tsk)), nil
}

func (ec *FakeEC) Finalize(context.Context, gpbft.TipSetKey) error { return nil }
51 changes: 12 additions & 39 deletions internal/powerstore/powerstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package powerstore_test

import (
"context"
"fmt"
"slices"
"testing"
"time"
Expand All @@ -23,40 +22,6 @@ import (
"github.com/stretchr/testify/require"
)

type forgetfulEC struct {
*consensus.FakeEC

ecFinality int64
}

// GetPowerTable implements ec.Backend.
func (f *forgetfulEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpbft.PowerEntries, error) {
ts, err := f.GetTipset(ctx, tsk)
if err != nil {
return nil, err
}
head, err := f.GetHead(ctx)
if err != nil {
return nil, err
}
if ts.Epoch() < head.Epoch()-2*f.ecFinality {
return nil, fmt.Errorf("oops, we forgot that power table, head %d, epoch %d", head.Epoch(), ts.Epoch())
}
pt, err := f.FakeEC.GetPowerTable(ctx, tsk)
if err != nil {
return nil, err
}

// make sure power changes over time by adding the current epoch to the first entry.
pt = slices.Clone(pt)
newPower := gpbft.NewStoragePower(ts.Epoch())
pt[0].Power = big.Add(newPower, pt[0].Power)

return pt, nil
}

var _ ec.Backend = (*forgetfulEC)(nil)

var basePowerTable = gpbft.PowerEntries{
{ID: 1, Power: gpbft.NewStoragePower(50), PubKey: gpbft.PubKey("1")},
{ID: 3, Power: gpbft.NewStoragePower(10), PubKey: gpbft.PubKey("2")},
Expand All @@ -68,10 +33,18 @@ func TestPowerStore(t *testing.T) {

m := manifest.LocalDevnetManifest()

ec := &forgetfulEC{
FakeEC: consensus.NewFakeEC(ctx, 1234, m.BootstrapEpoch, m.EC.Period, basePowerTable),
ecFinality: m.EC.Finality,
}
ec := consensus.NewFakeEC(ctx,
consensus.WithMaxLookback(2*m.EC.Finality),
consensus.WithBootstrapEpoch(m.BootstrapEpoch),
consensus.WithECPeriod(m.EC.Period),
consensus.WithInitialPowerTable(basePowerTable),
consensus.WithEvolvingPowerTable(func(epoch int64, pt gpbft.PowerEntries) gpbft.PowerEntries {
pt = slices.Clone(pt)
newPower := gpbft.NewStoragePower(epoch)
pt[0].Power = big.Add(newPower, pt[0].Power)
return pt
}),
)

head, err := ec.GetHead(ctx)
require.NoError(t, err)
Expand Down

0 comments on commit c438aa5

Please sign in to comment.