lotus/chain/lf3/ec.go
Jakub Sztandera ac87fce514 feat: ec: Integrate Fast Finality for Filecoin (#12119)
Integrate F3 into Lotus

Signed-off-by: Jakub Sztandera <oss@kubuxu.com>
Co-authored-by: Jennifer Wang <jiayingw703@gmail.com>
Co-authored-by: Masih H. Derkani <m@derkani.org>
Co-authored-by: Steven Allen <steven@stebalien.com>
2024-07-01 10:47:59 +04:00

214 lines
6.0 KiB
Go

package lf3
import (
"context"
"sort"
"time"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-f3"
"github.com/filecoin-project/go-f3/gpbft"
"github.com/filecoin-project/go-state-types/abi"
"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"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
)
type ecWrapper struct {
ChainStore *store.ChainStore
StateManager *stmgr.StateManager
Manifest f3.Manifest
}
type f3TipSet types.TipSet
func (ts *f3TipSet) cast() *types.TipSet {
return (*types.TipSet)(ts)
}
func (ts *f3TipSet) Key() gpbft.TipSetKey {
return ts.cast().Key().Bytes()
}
func (ts *f3TipSet) Beacon() []byte {
entries := ts.cast().Blocks()[0].BeaconEntries
if len(entries) == 0 {
// Set beacon to a non-nil 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: Rework the F3 message builder APIs to include ticket when needed instead
// of relying on the nil check of beacon.
return []byte{}
}
return entries[len(entries)-1].Data
}
func (ts *f3TipSet) Epoch() int64 {
return int64(ts.cast().Height())
}
func (ts *f3TipSet) Timestamp() time.Time {
return time.Unix(int64(ts.cast().Blocks()[0].Timestamp), 0)
}
func wrapTS(ts *types.TipSet) f3.TipSet {
if ts == nil {
return nil
}
return (*f3TipSet)(ts)
}
// GetTipsetByEpoch should return a tipset before the one requested if the requested
// tipset does not exist due to null epochs
func (ec *ecWrapper) GetTipsetByEpoch(ctx context.Context, epoch int64) (f3.TipSet, error) {
ts, err := ec.ChainStore.GetTipsetByHeight(ctx, abi.ChainEpoch(epoch), nil, true)
if err != nil {
return nil, xerrors.Errorf("getting tipset by height: %w", err)
}
return wrapTS(ts), nil
}
func (ec *ecWrapper) GetTipset(ctx context.Context, tsk gpbft.TipSetKey) (f3.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)
if err != nil {
return nil, xerrors.Errorf("getting tipset by key: %w", err)
}
return wrapTS(ts), nil
}
func (ec *ecWrapper) GetHead(_ context.Context) (f3.TipSet, error) {
return wrapTS(ec.ChainStore.GetHeaviestTipSet()), nil
}
func (ec *ecWrapper) GetParent(ctx context.Context, tsF3 f3.TipSet) (f3.TipSet, error) {
var ts *types.TipSet
if tsW, ok := tsF3.(*f3TipSet); ok {
ts = tsW.cast()
} else {
// There are only two implementations of F3.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)
}
}
parentTs, err := ec.ChainStore.GetTipSetFromKey(ctx, ts.Parents())
if err != nil {
return nil, xerrors.Errorf("getting parent tipset: %w", err)
}
return wrapTS(parentTs), nil
}
func (ec *ecWrapper) GetPowerTable(ctx context.Context, tskF3 gpbft.TipSetKey) (gpbft.PowerEntries, error) {
tskLotus, err := types.TipSetKeyFromBytes(tskF3)
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)
}
state, err := ec.StateManager.ParentState(ts)
if err != nil {
return nil, xerrors.Errorf("loading the state tree: %w", err)
}
powerAct, err := state.GetActor(power.Address)
if err != nil {
return nil, xerrors.Errorf("getting the power actor: %w", err)
}
powerState, err := power.Load(ec.ChainStore.ActorStore(ctx), powerAct)
if err != nil {
return nil, xerrors.Errorf("loading power actor state: %w", err)
}
var powerEntries gpbft.PowerEntries
err = powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error {
if claim.QualityAdjPower.Sign() <= 0 {
return nil
}
// TODO: optimize
ok, err := powerState.MinerNominalPowerMeetsConsensusMinimum(minerAddr)
if err != nil {
return xerrors.Errorf("checking consensus minimums: %w", err)
}
if !ok {
return nil
}
id, err := address.IDFromAddress(minerAddr)
if err != nil {
return xerrors.Errorf("transforming address to ID: %w", err)
}
pe := gpbft.PowerEntry{
ID: gpbft.ActorID(id),
Power: claim.QualityAdjPower.Int,
}
act, err := state.GetActor(minerAddr)
if err != nil {
return xerrors.Errorf("(get sset) failed to load miner actor: %w", err)
}
mstate, err := miner.Load(ec.ChainStore.ActorStore(ctx), act)
if err != nil {
return xerrors.Errorf("(get sset) failed to load miner actor state: %w", err)
}
info, err := mstate.Info()
if err != nil {
return xerrors.Errorf("failed to load actor info: %w", err)
}
// check fee debt
if debt, err := mstate.FeeDebt(); err != nil {
return err
} else if !debt.IsZero() {
// fee debt don't add the miner to power table
return nil
}
// check consensus faults
if ts.Height() <= info.ConsensusFaultElapsed {
return nil
}
waddr, err := vm.ResolveToDeterministicAddr(state, ec.ChainStore.ActorStore(ctx), info.Worker)
if err != nil {
return xerrors.Errorf("resolve miner worker address: %w", err)
}
if waddr.Protocol() != address.BLS {
return xerrors.Errorf("wrong type of worker address")
}
pe.PubKey = gpbft.PubKey(waddr.Payload())
powerEntries = append(powerEntries, pe)
return nil
})
if err != nil {
return nil, xerrors.Errorf("collecting the power table: %w", err)
}
sort.Sort(powerEntries)
return powerEntries, nil
}