Skip to content

Commit d8d699a

Browse files
authored
feat(f3): checkpoint tipsets that are finalized by F3 (#12460)
* Checkpoint tipsets that are finalized by F3 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 * Make checkpointing chainstore optional via build constraints
1 parent 9b86cc5 commit d8d699a

14 files changed

+142
-87
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
## New features
1010

1111
* Add `EthSendRawTransactionUntrusted` RPC method to be used for the gateway when accepting `EthSendRawTransaction` and `eth_sendRawTransaction`. Applies a tighter limit on the number of messages in the queue from a single sender and applies additional restrictions on nonce increments. ([filecoin-project/lotus#12431](https://github.com/filecoin-project/lotus/pull/12431))
12+
* [Checkpoint TipSets finalized by F3](https://github.com/filecoin-project/lotus/pull/12460): Once a decision is made by F3, the TipSet is check-pointed in `ChainStore`. As part of this change, any missing TipSets are asynchronously synced as required by the `ChainStore` checkpointing mechanism.
1213

1314
## Improvements
1415

build/buildconstants/params_2k.go

+4
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,7 @@ var F3Enabled = true
200200
const ManifestServerID = "12D3KooWHcNBkqXEBrsjoveQvj6zDF3vK5S9tAfqyYaQF1LGSJwG"
201201

202202
var F3BootstrapEpoch abi.ChainEpoch = 1000
203+
204+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
205+
// flag has no effect if F3 is not enabled.
206+
const F3Consensus = true

build/buildconstants/params_butterfly.go

+4
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,7 @@ var WhitelistedBlock = cid.Undef
101101
const F3Enabled = true
102102
const ManifestServerID = "12D3KooWJr9jy4ngtJNR7JC1xgLFra3DjEtyxskRYWvBK9TC3Yn6"
103103
const F3BootstrapEpoch abi.ChainEpoch = 1000
104+
105+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
106+
// flag has no effect if F3 is not enabled.
107+
const F3Consensus = true

build/buildconstants/params_calibnet.go

+4
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,7 @@ var WhitelistedBlock = cid.Undef
148148
const F3Enabled = true
149149
const ManifestServerID = "12D3KooWS9vD9uwm8u2uPyJV32QBAhKAmPYwmziAgr3Xzk2FU1Mr"
150150
const F3BootstrapEpoch abi.ChainEpoch = UpgradeWaffleHeight + 100
151+
152+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
153+
// flag has no effect if F3 is not enabled.
154+
const F3Consensus = true

build/buildconstants/params_interop.go

+4
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,7 @@ var WhitelistedBlock = cid.Undef
139139
const F3Enabled = true
140140
const ManifestServerID = "12D3KooWQJ2rdVnG4okDUB6yHQhAjNutGNemcM7XzqC9Eo4z9Jce"
141141
const F3BootstrapEpoch abi.ChainEpoch = 1000
142+
143+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
144+
// flag has no effect if F3 is not enabled.
145+
const F3Consensus = true

build/buildconstants/params_mainnet.go

+4
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,7 @@ var WhitelistedBlock = cid.MustParse("bafy2bzaceapyg2uyzk7vueh3xccxkuwbz3nxewjyg
173173
const F3Enabled = true
174174
const ManifestServerID = "12D3KooWENMwUF9YxvQxar7uBWJtZkA6amvK4xWmKXfSiHUo2Qq7"
175175
const F3BootstrapEpoch abi.ChainEpoch = -1
176+
177+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
178+
// flag has no effect if F3 is not enabled.
179+
const F3Consensus = false

build/buildconstants/params_testground.go

+4
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ var (
126126
F3Enabled = false
127127
ManifestServerID = ""
128128
F3BootstrapEpoch abi.ChainEpoch = -1
129+
130+
// F3Consensus set whether F3 should checkpoint tipsets finalized by F3. This
131+
// flag has no effect if F3 is not enabled.
132+
F3Consensus = true
129133
)
130134

131135
func init() {

build/openrpc/full.json

+4-12
Original file line numberDiff line numberDiff line change
@@ -6485,9 +6485,7 @@
64856485
"type": "string"
64866486
},
64876487
"PowerTable": {
6488-
"media": {
6489-
"binaryEncoding": "base64"
6490-
},
6488+
"title": "Content Identifier",
64916489
"type": "string"
64926490
}
64936491
},
@@ -6548,9 +6546,7 @@
65486546
"type": "array"
65496547
},
65506548
"PowerTable": {
6551-
"media": {
6552-
"binaryEncoding": "base64"
6553-
},
6549+
"title": "Content Identifier",
65546550
"type": "string"
65556551
}
65566552
},
@@ -6822,9 +6818,7 @@
68226818
"type": "string"
68236819
},
68246820
"PowerTable": {
6825-
"media": {
6826-
"binaryEncoding": "base64"
6827-
},
6821+
"title": "Content Identifier",
68286822
"type": "string"
68296823
}
68306824
},
@@ -6885,9 +6879,7 @@
68856879
"type": "array"
68866880
},
68876881
"PowerTable": {
6888-
"media": {
6889-
"binaryEncoding": "base64"
6890-
},
6882+
"title": "Content Identifier",
68916883
"type": "string"
68926884
}
68936885
},

chain/lf3/ec.go

+91-60
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/filecoin-project/go-f3/gpbft"
1313
"github.com/filecoin-project/go-state-types/abi"
1414

15+
"github.com/filecoin-project/lotus/chain"
1516
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
1617
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
1718
"github.com/filecoin-project/lotus/chain/stmgr"
@@ -20,52 +21,57 @@ import (
2021
"github.com/filecoin-project/lotus/chain/vm"
2122
)
2223

24+
var (
25+
_ ec.Backend = (*ecWrapper)(nil)
26+
_ ec.TipSet = (*f3TipSet)(nil)
27+
)
28+
2329
type ecWrapper struct {
2430
ChainStore *store.ChainStore
31+
Syncer *chain.Syncer
2532
StateManager *stmgr.StateManager
26-
}
27-
28-
var _ ec.TipSet = (*f3TipSet)(nil)
29-
30-
type f3TipSet types.TipSet
3133

32-
func (ts *f3TipSet) cast() *types.TipSet {
33-
return (*types.TipSet)(ts)
34+
// Checkpoint sets whether to checkpoint tipsets finalized by F3 in ChainStore.
35+
Checkpoint bool
3436
}
3537

36-
func (ts *f3TipSet) String() string {
37-
return ts.cast().String()
38+
type f3TipSet struct {
39+
*types.TipSet
3840
}
3941

40-
func (ts *f3TipSet) Key() gpbft.TipSetKey {
41-
return ts.cast().Key().Bytes()
42+
func (ts *f3TipSet) String() string { return ts.TipSet.String() }
43+
func (ts *f3TipSet) Key() gpbft.TipSetKey { return ts.TipSet.Key().Bytes() }
44+
func (ts *f3TipSet) Epoch() int64 { return int64(ts.TipSet.Height()) }
45+
46+
func (ts *f3TipSet) FirstBlockHeader() *types.BlockHeader {
47+
if ts.TipSet == nil || len(ts.TipSet.Blocks()) == 0 {
48+
return nil
49+
}
50+
return ts.TipSet.Blocks()[0]
4251
}
4352

4453
func (ts *f3TipSet) Beacon() []byte {
45-
entries := ts.cast().Blocks()[0].BeaconEntries
46-
if len(entries) == 0 {
47-
// This should never happen in practice, but set beacon to a non-nil
48-
// 32byte slice to force the message builder to generate a
49-
// ticket. Otherwise, messages that require ticket, i.e. CONVERGE will fail
50-
// validation due to the absence of ticket. This is a convoluted way of doing it.
54+
switch header := ts.FirstBlockHeader(); {
55+
case header == nil, len(header.BeaconEntries) == 0:
56+
// This should never happen in practice, but set beacon to a non-nil 32byte slice
57+
// to force the message builder to generate a ticket. Otherwise, messages that
58+
// require ticket, i.e. CONVERGE will fail validation due to the absence of
59+
// ticket. This is a convoluted way of doing it.
60+
61+
// TODO: investigate if this is still necessary, or how message builder can be
62+
// adapted to behave correctly regardless of beacon value, e.g. fail fast
63+
// instead of building CONVERGE with empty beacon.
5164
return make([]byte, 32)
65+
default:
66+
return header.BeaconEntries[len(header.BeaconEntries)-1].Data
5267
}
53-
return entries[len(entries)-1].Data
54-
}
55-
56-
func (ts *f3TipSet) Epoch() int64 {
57-
return int64(ts.cast().Height())
5868
}
5969

6070
func (ts *f3TipSet) Timestamp() time.Time {
61-
return time.Unix(int64(ts.cast().Blocks()[0].Timestamp), 0)
62-
}
63-
64-
func wrapTS(ts *types.TipSet) ec.TipSet {
65-
if ts == nil {
66-
return nil
71+
if header := ts.FirstBlockHeader(); header != nil {
72+
return time.Unix(int64(header.Timestamp), 0)
6773
}
68-
return (*f3TipSet)(ts)
74+
return time.Time{}
6975
}
7076

7177
// GetTipsetByEpoch should return a tipset before the one requested if the requested
@@ -75,57 +81,42 @@ func (ec *ecWrapper) GetTipsetByEpoch(ctx context.Context, epoch int64) (ec.TipS
7581
if err != nil {
7682
return nil, xerrors.Errorf("getting tipset by height: %w", err)
7783
}
78-
return wrapTS(ts), nil
84+
return &f3TipSet{TipSet: ts}, nil
7985
}
8086

8187
func (ec *ecWrapper) GetTipset(ctx context.Context, tsk gpbft.TipSetKey) (ec.TipSet, error) {
82-
tskLotus, err := types.TipSetKeyFromBytes(tsk)
83-
if err != nil {
84-
return nil, xerrors.Errorf("decoding tsk: %w", err)
85-
}
86-
87-
ts, err := ec.ChainStore.GetTipSetFromKey(ctx, tskLotus)
88+
ts, err := ec.getTipSetFromF3TSK(ctx, tsk)
8889
if err != nil {
8990
return nil, xerrors.Errorf("getting tipset by key: %w", err)
9091
}
9192

92-
return wrapTS(ts), nil
93+
return &f3TipSet{TipSet: ts}, nil
9394
}
9495

95-
func (ec *ecWrapper) GetHead(_ context.Context) (ec.TipSet, error) {
96-
return wrapTS(ec.ChainStore.GetHeaviestTipSet()), nil
96+
func (ec *ecWrapper) GetHead(context.Context) (ec.TipSet, error) {
97+
head := ec.ChainStore.GetHeaviestTipSet()
98+
if head == nil {
99+
return nil, xerrors.New("no heaviest tipset")
100+
}
101+
return &f3TipSet{TipSet: head}, nil
97102
}
98103

99104
func (ec *ecWrapper) GetParent(ctx context.Context, tsF3 ec.TipSet) (ec.TipSet, error) {
100-
var ts *types.TipSet
101-
if tsW, ok := tsF3.(*f3TipSet); ok {
102-
ts = tsW.cast()
103-
} else {
104-
// There are only two implementations of ec.TipSet: f3TipSet, and one in fake EC
105-
// backend.
106-
//
107-
// TODO: Revisit the type check here and remove as needed once testing
108-
// is over and fake EC backend goes away.
109-
tskLotus, err := types.TipSetKeyFromBytes(tsF3.Key())
110-
if err != nil {
111-
return nil, xerrors.Errorf("decoding tsk: %w", err)
112-
}
113-
ts, err = ec.ChainStore.GetTipSetFromKey(ctx, tskLotus)
114-
if err != nil {
115-
return nil, xerrors.Errorf("getting tipset by key for get parent: %w", err)
116-
}
105+
ts, err := ec.toLotusTipSet(ctx, tsF3)
106+
if err != nil {
107+
return nil, err
117108
}
118109
parentTs, err := ec.ChainStore.GetTipSetFromKey(ctx, ts.Parents())
119110
if err != nil {
120111
return nil, xerrors.Errorf("getting parent tipset: %w", err)
121112
}
122-
return wrapTS(parentTs), nil
113+
return &f3TipSet{TipSet: parentTs}, nil
123114
}
124115

125116
func (ec *ecWrapper) GetPowerTable(ctx context.Context, tskF3 gpbft.TipSetKey) (gpbft.PowerEntries, error) {
126-
tsk, err := types.TipSetKeyFromBytes(tskF3)
117+
tsk, err := toLotusTipSetKey(tskF3)
127118
if err != nil {
128-
return nil, xerrors.Errorf("decoding tsk: %w", err)
119+
return nil, err
129120
}
130121
return ec.getPowerTableLotusTSK(ctx, tsk)
131122
}
@@ -208,7 +199,7 @@ func (ec *ecWrapper) getPowerTableLotusTSK(ctx context.Context, tsk types.TipSet
208199
if waddr.Protocol() != address.BLS {
209200
return xerrors.Errorf("wrong type of worker address")
210201
}
211-
pe.PubKey = gpbft.PubKey(waddr.Payload())
202+
pe.PubKey = waddr.Payload()
212203
powerEntries = append(powerEntries, pe)
213204
return nil
214205
})
@@ -219,3 +210,43 @@ func (ec *ecWrapper) getPowerTableLotusTSK(ctx context.Context, tsk types.TipSet
219210
sort.Sort(powerEntries)
220211
return powerEntries, nil
221212
}
213+
214+
func (ec *ecWrapper) Finalize(ctx context.Context, key gpbft.TipSetKey) error {
215+
if !ec.Checkpoint {
216+
return nil // Nothing to do; checkpointing is not enabled.
217+
}
218+
tsk, err := toLotusTipSetKey(key)
219+
if err != nil {
220+
return err
221+
}
222+
if err = ec.Syncer.SyncCheckpoint(ctx, tsk); err != nil {
223+
return xerrors.Errorf("checkpointing finalized tipset: %w", err)
224+
}
225+
return nil
226+
}
227+
228+
func (ec *ecWrapper) toLotusTipSet(ctx context.Context, ts ec.TipSet) (*types.TipSet, error) {
229+
switch tst := ts.(type) {
230+
case *f3TipSet:
231+
return tst.TipSet, nil
232+
default:
233+
// Fall back on getting the tipset by key. This path is executed only in testing.
234+
return ec.getTipSetFromF3TSK(ctx, ts.Key())
235+
}
236+
}
237+
238+
func (ec *ecWrapper) getTipSetFromF3TSK(ctx context.Context, key gpbft.TipSetKey) (*types.TipSet, error) {
239+
tsk, err := toLotusTipSetKey(key)
240+
if err != nil {
241+
return nil, err
242+
}
243+
ts, err := ec.ChainStore.GetTipSetFromKey(ctx, tsk)
244+
if err != nil {
245+
return nil, xerrors.Errorf("getting tipset from key: %w", err)
246+
}
247+
return ts, nil
248+
}
249+
250+
func toLotusTipSetKey(key gpbft.TipSetKey) (types.TipSetKey, error) {
251+
return types.TipSetKeyFromBytes(key)
252+
}

chain/lf3/f3.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/filecoin-project/go-f3/manifest"
2121

2222
"github.com/filecoin-project/lotus/api"
23+
"github.com/filecoin-project/lotus/chain"
2324
"github.com/filecoin-project/lotus/chain/stmgr"
2425
"github.com/filecoin-project/lotus/chain/store"
2526
"github.com/filecoin-project/lotus/chain/types"
@@ -35,17 +36,21 @@ type F3 struct {
3536
newLeases chan leaseRequest
3637
}
3738

39+
type F3ConsensusEnabled bool
40+
3841
type F3Params struct {
3942
fx.In
4043

41-
NetworkName dtypes.NetworkName
42-
ManifestProvider manifest.ManifestProvider
43-
PubSub *pubsub.PubSub
44-
Host host.Host
45-
ChainStore *store.ChainStore
46-
StateManager *stmgr.StateManager
47-
Datastore dtypes.MetadataDS
48-
Wallet api.Wallet
44+
NetworkName dtypes.NetworkName
45+
ManifestProvider manifest.ManifestProvider
46+
PubSub *pubsub.PubSub
47+
Host host.Host
48+
ChainStore *store.ChainStore
49+
Syncer *chain.Syncer
50+
StateManager *stmgr.StateManager
51+
Datastore dtypes.MetadataDS
52+
Wallet api.Wallet
53+
F3ConsensusEnabled F3ConsensusEnabled
4954
}
5055

5156
var log = logging.Logger("f3")
@@ -56,6 +61,8 @@ func New(mctx helpers.MetricsCtx, lc fx.Lifecycle, params F3Params) (*F3, error)
5661
ec := &ecWrapper{
5762
ChainStore: params.ChainStore,
5863
StateManager: params.StateManager,
64+
Syncer: params.Syncer,
65+
Checkpoint: bool(params.F3ConsensusEnabled),
5966
}
6067
verif := blssig.VerifierWithKeyOnG1()
6168

chain/types/tipset_key.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
block "github.com/ipfs/go-block-format"
1111
"github.com/ipfs/go-cid"
1212
typegen "github.com/whyrusleeping/cbor-gen"
13+
"golang.org/x/xerrors"
1314

1415
"github.com/filecoin-project/go-state-types/abi"
1516
)
@@ -52,7 +53,7 @@ func NewTipSetKey(cids ...cid.Cid) TipSetKey {
5253
func TipSetKeyFromBytes(encoded []byte) (TipSetKey, error) {
5354
_, err := decodeKey(encoded)
5455
if err != nil {
55-
return EmptyTSK, err
56+
return EmptyTSK, xerrors.Errorf("decoding tpiset key: %w", err)
5657
}
5758
return TipSetKey{string(encoded)}, nil
5859
}

0 commit comments

Comments
 (0)