Skip to content

Commit

Permalink
Checkpoint tipsets that are finalized by F3
Browse files Browse the repository at this point in the history
Once a decision is received from F3, checkpoint it in chain-store.

As part of checkpointing, refactor the F3 tipset wrapper to reduce type
casting.

Note that go-f3 module now uses Go 1.22, which requires Lotus go module
to be updated accordingly.

Part of filecoin-project/go-f3#603
  • Loading branch information
masih committed Sep 17, 2024
1 parent 6c63df6 commit 4e802b2
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#####################################
FROM golang:1.21.7-bullseye AS lotus-builder
FROM golang:1.22.7-bullseye AS lotus-builder
MAINTAINER Lotus Development Team

RUN apt-get update && apt-get install -y ca-certificates build-essential clang ocl-icd-opencl-dev ocl-icd-libopencl1 jq libhwloc-dev
Expand Down
16 changes: 4 additions & 12 deletions build/openrpc/full.json
Original file line number Diff line number Diff line change
Expand Up @@ -6485,9 +6485,7 @@
"type": "string"
},
"PowerTable": {
"media": {
"binaryEncoding": "base64"
},
"title": "Content Identifier",
"type": "string"
}
},
Expand Down Expand Up @@ -6548,9 +6546,7 @@
"type": "array"
},
"PowerTable": {
"media": {
"binaryEncoding": "base64"
},
"title": "Content Identifier",
"type": "string"
}
},
Expand Down Expand Up @@ -6822,9 +6818,7 @@
"type": "string"
},
"PowerTable": {
"media": {
"binaryEncoding": "base64"
},
"title": "Content Identifier",
"type": "string"
}
},
Expand Down Expand Up @@ -6885,9 +6879,7 @@
"type": "array"
},
"PowerTable": {
"media": {
"binaryEncoding": "base64"
},
"title": "Content Identifier",
"type": "string"
}
},
Expand Down
151 changes: 89 additions & 62 deletions chain/lf3/ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/filecoin-project/go-f3/gpbft"
"github.com/filecoin-project/go-state-types/abi"

"github.com/filecoin-project/lotus/chain"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
"github.com/filecoin-project/lotus/chain/stmgr"
Expand All @@ -20,52 +21,56 @@ import (
"github.com/filecoin-project/lotus/chain/vm"
)

var (
_ ec.Backend = (*ecWrapper)(nil)
_ ec.TipSet = (*f3TipSet)(nil)

emptyBeacon = make([]byte, 32)
)

type ecWrapper struct {
ChainStore *store.ChainStore
Syncer *chain.Syncer
StateManager *stmgr.StateManager
}

var _ ec.TipSet = (*f3TipSet)(nil)

type f3TipSet types.TipSet

func (ts *f3TipSet) cast() *types.TipSet {
return (*types.TipSet)(ts)
type f3TipSet struct {
*types.TipSet
}

func (ts *f3TipSet) String() string {
return ts.cast().String()
}
func (ts *f3TipSet) String() string { return ts.TipSet.String() }
func (ts *f3TipSet) Key() gpbft.TipSetKey { return ts.TipSet.Key().Bytes() }
func (ts *f3TipSet) Epoch() int64 { return int64(ts.TipSet.Height()) }

func (ts *f3TipSet) Key() gpbft.TipSetKey {
return ts.cast().Key().Bytes()
func (ts *f3TipSet) FirstBlockHeader() *types.BlockHeader {
if ts.TipSet == nil || len(ts.TipSet.Blocks()) == 0 {
return nil
}
return ts.TipSet.Blocks()[0]
}

func (ts *f3TipSet) Beacon() []byte {
entries := ts.cast().Blocks()[0].BeaconEntries
if len(entries) == 0 {
// This should never happen in practice, but set beacon to a non-nil
// 32byte slice to force the message builder to generate a
// ticket. Otherwise, messages that require ticket, i.e. CONVERGE will fail
// validation due to the absence of ticket. This is a convoluted way of doing it.
return make([]byte, 32)
}
return entries[len(entries)-1].Data
}

func (ts *f3TipSet) Epoch() int64 {
return int64(ts.cast().Height())
switch header := ts.FirstBlockHeader(); {
case header == nil, len(header.BeaconEntries) == 0:
// This should never happen in practice, but set beacon to a non-nil 32byte slice
// to force the message builder to generate a ticket. Otherwise, messages that
// require ticket, i.e. CONVERGE will fail validation due to the absence of
// ticket. This is a convoluted way of doing it.

// TODO: investigate if this is still necessary, or how message builder can be
// adapted to behave correctly regardless of beacon value, e.g. fail fast
// instead of building CONVERGE with empty beacon.
return emptyBeacon
default:
return header.BeaconEntries[len(header.BeaconEntries)-1].Data
}
}

func (ts *f3TipSet) Timestamp() time.Time {
return time.Unix(int64(ts.cast().Blocks()[0].Timestamp), 0)
}

func wrapTS(ts *types.TipSet) ec.TipSet {
if ts == nil {
return nil
if header := ts.FirstBlockHeader(); header != nil {
return time.Unix(int64(header.Timestamp), 0)
}
return (*f3TipSet)(ts)
return time.Time{}
}

// GetTipsetByEpoch should return a tipset before the one requested if the requested
Expand All @@ -75,57 +80,38 @@ func (ec *ecWrapper) GetTipsetByEpoch(ctx context.Context, epoch int64) (ec.TipS
if err != nil {
return nil, xerrors.Errorf("getting tipset by height: %w", err)
}
return wrapTS(ts), nil
return &f3TipSet{TipSet: ts}, nil
}

func (ec *ecWrapper) GetTipset(ctx context.Context, tsk gpbft.TipSetKey) (ec.TipSet, error) {
tskLotus, err := types.TipSetKeyFromBytes(tsk)
if err != nil {
return nil, xerrors.Errorf("decoding tsk: %w", err)
}

ts, err := ec.ChainStore.GetTipSetFromKey(ctx, tskLotus)
ts, err := ec.getTipSetFromF3TSK(ctx, tsk)
if err != nil {
return nil, xerrors.Errorf("getting tipset by key: %w", err)
}

return wrapTS(ts), nil
return &f3TipSet{TipSet: ts}, nil
}

func (ec *ecWrapper) GetHead(_ context.Context) (ec.TipSet, error) {
return wrapTS(ec.ChainStore.GetHeaviestTipSet()), nil
func (ec *ecWrapper) GetHead(context.Context) (ec.TipSet, error) {
return &f3TipSet{TipSet: ec.ChainStore.GetHeaviestTipSet()}, nil
}

func (ec *ecWrapper) GetParent(ctx context.Context, tsF3 ec.TipSet) (ec.TipSet, error) {
var ts *types.TipSet
if tsW, ok := tsF3.(*f3TipSet); ok {
ts = tsW.cast()
} else {
// There are only two implementations of ec.TipSet: f3TipSet, and one in fake EC
// backend.
//
// TODO: Revisit the type check here and remove as needed once testing
// is over and fake EC backend goes away.
tskLotus, err := types.TipSetKeyFromBytes(tsF3.Key())
if err != nil {
return nil, xerrors.Errorf("decoding tsk: %w", err)
}
ts, err = ec.ChainStore.GetTipSetFromKey(ctx, tskLotus)
if err != nil {
return nil, xerrors.Errorf("getting tipset by key for get parent: %w", err)
}
ts, err := ec.toLotusTipSet(ctx, tsF3)
if err != nil {
return nil, err
}
parentTs, err := ec.ChainStore.GetTipSetFromKey(ctx, ts.Parents())
if err != nil {
return nil, xerrors.Errorf("getting parent tipset: %w", err)
}
return wrapTS(parentTs), nil
return &f3TipSet{TipSet: parentTs}, nil
}

func (ec *ecWrapper) GetPowerTable(ctx context.Context, tskF3 gpbft.TipSetKey) (gpbft.PowerEntries, error) {
tsk, err := types.TipSetKeyFromBytes(tskF3)
tsk, err := toLotusTipSetKey(tskF3)
if err != nil {
return nil, xerrors.Errorf("decoding tsk: %w", err)
return nil, err
}
return ec.getPowerTableLotusTSK(ctx, tsk)
}
Expand Down Expand Up @@ -208,7 +194,7 @@ func (ec *ecWrapper) getPowerTableLotusTSK(ctx context.Context, tsk types.TipSet
if waddr.Protocol() != address.BLS {
return xerrors.Errorf("wrong type of worker address")
}
pe.PubKey = gpbft.PubKey(waddr.Payload())
pe.PubKey = waddr.Payload()
powerEntries = append(powerEntries, pe)
return nil
})
Expand All @@ -219,3 +205,44 @@ func (ec *ecWrapper) getPowerTableLotusTSK(ctx context.Context, tsk types.TipSet
sort.Sort(powerEntries)
return powerEntries, nil
}

func (ec *ecWrapper) Finalize(ctx context.Context, key gpbft.TipSetKey) error {
tsk, err := toLotusTipSetKey(key)
if err != nil {
return err
}
if err = ec.Syncer.SyncCheckpoint(ctx, tsk); err != nil {
return xerrors.Errorf("checkpointing finalized tipset: %w", err)
}
return nil
}

func (ec *ecWrapper) toLotusTipSet(ctx context.Context, ts ec.TipSet) (*types.TipSet, error) {
switch tst := ts.(type) {
case *f3TipSet:
return tst.TipSet, nil
default:
// Fall back on getting the tipset by key. This path is executed only in testing.
return ec.getTipSetFromF3TSK(ctx, ts.Key())
}
}

func (ec *ecWrapper) getTipSetFromF3TSK(ctx context.Context, key gpbft.TipSetKey) (*types.TipSet, error) {
tsk, err := toLotusTipSetKey(key)
if err != nil {
return nil, err
}
ts, err := ec.ChainStore.GetTipSetFromKey(ctx, tsk)
if err != nil {
return nil, xerrors.Errorf("getting tipset from key: %w", err)
}
return ts, nil
}

func toLotusTipSetKey(key gpbft.TipSetKey) (types.TipSetKey, error) {
tsk, err := types.TipSetKeyFromBytes(key)
if err != nil {
return types.TipSetKey{}, xerrors.Errorf("decoding tpiset key: %w", err)
}
return tsk, nil
}
3 changes: 3 additions & 0 deletions chain/lf3/f3.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/filecoin-project/go-f3/manifest"

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
Expand All @@ -43,6 +44,7 @@ type F3Params struct {
PubSub *pubsub.PubSub
Host host.Host
ChainStore *store.ChainStore
Syncer *chain.Syncer
StateManager *stmgr.StateManager
Datastore dtypes.MetadataDS
Wallet api.Wallet
Expand All @@ -56,6 +58,7 @@ func New(mctx helpers.MetricsCtx, lc fx.Lifecycle, params F3Params) (*F3, error)
ec := &ecWrapper{
ChainStore: params.ChainStore,
StateManager: params.StateManager,
Syncer: params.Syncer,
}
verif := blssig.VerifierWithKeyOnG1()

Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/filecoin-project/lotus

go 1.21
go 1.22

toolchain go1.22.6

retract v1.14.0 // Accidentally force-pushed tag, use v1.14.1+ instead.

Expand Down Expand Up @@ -42,7 +44,7 @@ require (
github.com/filecoin-project/go-cbor-util v0.0.1
github.com/filecoin-project/go-commp-utils/v2 v2.1.0
github.com/filecoin-project/go-crypto v0.1.0
github.com/filecoin-project/go-f3 v0.2.0
github.com/filecoin-project/go-f3 v0.2.1-0.20240913140337-3f4036020113
github.com/filecoin-project/go-fil-commcid v0.2.0
github.com/filecoin-project/go-hamt-ipld/v3 v3.4.0
github.com/filecoin-project/go-jsonrpc v0.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ github.com/filecoin-project/go-commp-utils/v2 v2.1.0/go.mod h1:NbxJYlhxtWaNhlVCj
github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ=
github.com/filecoin-project/go-crypto v0.1.0 h1:Pob2MphoipMbe/ksxZOMcQvmBHAd3sI/WEqcbpIsGI0=
github.com/filecoin-project/go-crypto v0.1.0/go.mod h1:K9UFXvvoyAVvB+0Le7oGlKiT9mgA5FHOJdYQXEE8IhI=
github.com/filecoin-project/go-f3 v0.2.0 h1:Gis44+hOrDjSUEw3IDmU7CudNILi5e+bb1pgZgp680k=
github.com/filecoin-project/go-f3 v0.2.0/go.mod h1:43fBLX0iX0+Nnw4Z91wSrdfDYAd6YEDexy7GcLnIJtk=
github.com/filecoin-project/go-f3 v0.2.1-0.20240913140337-3f4036020113 h1:Qbb6IhbkjEqf5LbZU3p8z2FbFFQE3TDjamu+xGYMLRw=
github.com/filecoin-project/go-f3 v0.2.1-0.20240913140337-3f4036020113/go.mod h1:5Gg9h133OU0GMp+oJwOOnJ84p9MO9RJfMKt4Xrgt070=
github.com/filecoin-project/go-fil-commcid v0.2.0 h1:B+5UX8XGgdg/XsdUpST4pEBviKkFOw+Fvl2bLhSKGpI=
github.com/filecoin-project/go-fil-commcid v0.2.0/go.mod h1:8yigf3JDIil+/WpqR5zoKyP0jBPCOGtEqq/K1CcMy9Q=
github.com/filecoin-project/go-fil-commp-hashhash v0.2.0 h1:HYIUugzjq78YvV3vC6rL95+SfC/aSTVSnZSZiDV5pCk=
Expand Down

0 comments on commit 4e802b2

Please sign in to comment.