lotus/curiosrc/seal/task_submit_commit.go
LexLuthr 7e3846c669
feat: curio: allow multiple pieces per sector (#11935)
* multiple piece per sector, DDO deals

* in memory to DB

* sql parser

* add seal command

* multi piece TreeD

* redo filler pieces

* remove psql exception handling

* fix deal sectors porep

* fix tests

* ddo deals

* lower SDR cpu for test

* devnet cpu 0

* get params for itest

* fix itest sector size

* revert sdr devnet cpu

* improve SectorStatus API

* account for verified constraints
2024-05-22 10:08:32 +02:00

424 lines
14 KiB
Go

package seal
import (
"bytes"
"context"
"encoding/json"
"fmt"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
miner2 "github.com/filecoin-project/go-state-types/builtin/v13/miner"
verifreg13 "github.com/filecoin-project/go-state-types/builtin/v13/verifreg"
verifregtypes9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/curiosrc/message"
"github.com/filecoin-project/lotus/curiosrc/multictladdr"
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
"github.com/filecoin-project/lotus/lib/harmony/harmonytask"
"github.com/filecoin-project/lotus/lib/harmony/resources"
"github.com/filecoin-project/lotus/node/config"
"github.com/filecoin-project/lotus/storage/ctladdr"
)
type SubmitCommitAPI interface {
ChainHead(context.Context) (*types.TipSet, error)
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error)
StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (big.Int, error)
StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error)
StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes9.AllocationId, tsk types.TipSetKey) (*verifregtypes9.Allocation, error)
StateGetAllocationIdForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (verifregtypes9.AllocationId, error)
ctladdr.NodeApi
}
type commitConfig struct {
maxFee types.FIL
RequireActivationSuccess bool
RequireNotificationSuccess bool
}
type SubmitCommitTask struct {
sp *SealPoller
db *harmonydb.DB
api SubmitCommitAPI
sender *message.Sender
as *multictladdr.MultiAddressSelector
cfg commitConfig
}
func NewSubmitCommitTask(sp *SealPoller, db *harmonydb.DB, api SubmitCommitAPI, sender *message.Sender, as *multictladdr.MultiAddressSelector, cfg *config.CurioConfig) *SubmitCommitTask {
cnfg := commitConfig{
maxFee: cfg.Fees.MaxCommitGasFee,
RequireActivationSuccess: cfg.Subsystems.RequireActivationSuccess,
RequireNotificationSuccess: cfg.Subsystems.RequireNotificationSuccess,
}
return &SubmitCommitTask{
sp: sp,
db: db,
api: api,
sender: sender,
as: as,
cfg: cnfg,
}
}
func (s *SubmitCommitTask) Do(taskID harmonytask.TaskID, stillOwned func() bool) (done bool, err error) {
ctx := context.Background()
var sectorParamsArr []struct {
SpID int64 `db:"sp_id"`
SectorNumber int64 `db:"sector_number"`
Proof []byte `db:"porep_proof"`
}
err = s.db.Select(ctx, &sectorParamsArr, `
SELECT sp_id, sector_number, porep_proof
FROM sectors_sdr_pipeline
WHERE task_id_commit_msg = $1`, taskID)
if err != nil {
return false, xerrors.Errorf("getting sector params: %w", err)
}
if len(sectorParamsArr) != 1 {
return false, xerrors.Errorf("expected 1 sector params, got %d", len(sectorParamsArr))
}
sectorParams := sectorParamsArr[0]
var pieces []struct {
PieceIndex int64 `db:"piece_index"`
PieceCID string `db:"piece_cid"`
PieceSize int64 `db:"piece_size"`
Proposal json.RawMessage `db:"f05_deal_proposal"`
Manifest json.RawMessage `db:"direct_piece_activation_manifest"`
DealID abi.DealID `db:"f05_deal_id"`
}
err = s.db.Select(ctx, &pieces, `
SELECT piece_index,
piece_cid,
piece_size,
f05_deal_proposal,
direct_piece_activation_manifest,
COALESCE(f05_deal_id, 0) AS f05_deal_id
FROM sectors_sdr_initial_pieces
WHERE sp_id = $1 AND sector_number = $2 ORDER BY piece_index ASC`, sectorParams.SpID, sectorParams.SectorNumber)
if err != nil {
return false, xerrors.Errorf("getting pieces: %w", err)
}
maddr, err := address.NewIDAddress(uint64(sectorParams.SpID))
if err != nil {
return false, xerrors.Errorf("getting miner address: %w", err)
}
ts, err := s.api.ChainHead(ctx)
if err != nil {
return false, xerrors.Errorf("getting chain head: %w", err)
}
pci, err := s.api.StateSectorPreCommitInfo(ctx, maddr, abi.SectorNumber(sectorParams.SectorNumber), ts.Key())
if err != nil {
return false, xerrors.Errorf("getting precommit info: %w", err)
}
if pci == nil {
return false, xerrors.Errorf("precommit info not found on chain")
}
mi, err := s.api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return false, xerrors.Errorf("getting miner info: %w", err)
}
params := miner.ProveCommitSectors3Params{
RequireActivationSuccess: s.cfg.RequireActivationSuccess,
RequireNotificationSuccess: s.cfg.RequireNotificationSuccess,
}
var pams []miner.PieceActivationManifest
for _, piece := range pieces {
if piece.Proposal != nil {
var prop *market.DealProposal
err = json.Unmarshal(piece.Proposal, &prop)
if err != nil {
return false, xerrors.Errorf("marshalling json to deal proposal: %w", err)
}
alloc, err := s.api.StateGetAllocationIdForPendingDeal(ctx, piece.DealID, types.EmptyTSK)
if err != nil {
return false, xerrors.Errorf("getting allocation for deal %d: %w", piece.DealID, err)
}
clid, err := s.api.StateLookupID(ctx, prop.Client, types.EmptyTSK)
if err != nil {
return false, xerrors.Errorf("getting client address for deal %d: %w", piece.DealID, err)
}
clientId, err := address.IDFromAddress(clid)
if err != nil {
return false, xerrors.Errorf("getting client address for deal %d: %w", piece.DealID, err)
}
var vac *miner2.VerifiedAllocationKey
if alloc != verifregtypes9.NoAllocationID {
vac = &miner2.VerifiedAllocationKey{
Client: abi.ActorID(clientId),
ID: verifreg13.AllocationId(alloc),
}
}
payload, err := cborutil.Dump(piece.DealID)
if err != nil {
return false, xerrors.Errorf("serializing deal id: %w", err)
}
pams = append(pams, miner.PieceActivationManifest{
CID: prop.PieceCID,
Size: prop.PieceSize,
VerifiedAllocationKey: vac,
Notify: []miner2.DataActivationNotification{
{
Address: market.Address,
Payload: payload,
},
},
})
} else {
var pam *miner.PieceActivationManifest
err = json.Unmarshal(piece.Manifest, &pam)
if err != nil {
return false, xerrors.Errorf("marshalling json to PieceManifest: %w", err)
}
err = s.allocationCheck(ctx, pam, pci, abi.ActorID(sectorParams.SpID), ts)
if err != nil {
return false, err
}
pams = append(pams, *pam)
}
}
params.SectorActivations = append(params.SectorActivations, miner.SectorActivationManifest{
SectorNumber: abi.SectorNumber(sectorParams.SectorNumber),
Pieces: pams,
})
params.SectorProofs = append(params.SectorProofs, sectorParams.Proof)
enc := new(bytes.Buffer)
if err := params.MarshalCBOR(enc); err != nil {
return false, xerrors.Errorf("could not serialize commit params: %w", err)
}
collateral, err := s.api.StateMinerInitialPledgeCollateral(ctx, maddr, pci.Info, ts.Key())
if err != nil {
return false, xerrors.Errorf("getting initial pledge collateral: %w", err)
}
collateral = big.Sub(collateral, pci.PreCommitDeposit)
if collateral.LessThan(big.Zero()) {
collateral = big.Zero()
}
a, _, err := s.as.AddressFor(ctx, s.api, maddr, mi, api.CommitAddr, collateral, big.Zero())
if err != nil {
return false, xerrors.Errorf("getting address for precommit: %w", err)
}
msg := &types.Message{
To: maddr,
From: a,
Method: builtin.MethodsMiner.ProveCommitSectors3,
Params: enc.Bytes(),
Value: collateral, // todo config for pulling from miner balance!!
}
mss := &api.MessageSendSpec{
MaxFee: abi.TokenAmount(s.cfg.maxFee),
}
mcid, err := s.sender.Send(ctx, msg, mss, "commit")
if err != nil {
return false, xerrors.Errorf("pushing message to mpool: %w", err)
}
_, err = s.db.Exec(ctx, `UPDATE sectors_sdr_pipeline SET commit_msg_cid = $1, after_commit_msg = TRUE, task_id_commit_msg = NULL WHERE sp_id = $2 AND sector_number = $3`, mcid, sectorParams.SpID, sectorParams.SectorNumber)
if err != nil {
return false, xerrors.Errorf("updating commit_msg_cid: %w", err)
}
_, err = s.db.Exec(ctx, `INSERT INTO message_waits (signed_message_cid) VALUES ($1)`, mcid)
if err != nil {
return false, xerrors.Errorf("inserting into message_waits: %w", err)
}
if err := s.transferFinalizedSectorData(ctx, sectorParams.SpID, sectorParams.SectorNumber); err != nil {
return false, xerrors.Errorf("transferring finalized sector data: %w", err)
}
return true, nil
}
func (s *SubmitCommitTask) transferFinalizedSectorData(ctx context.Context, spID, sectorNum int64) error {
if _, err := s.db.Exec(ctx, `
INSERT INTO sectors_meta (
sp_id,
sector_num,
reg_seal_proof,
ticket_epoch,
ticket_value,
orig_sealed_cid,
orig_unsealed_cid,
cur_sealed_cid,
cur_unsealed_cid,
msg_cid_precommit,
msg_cid_commit,
seed_epoch,
seed_value
)
SELECT
sp_id,
sector_number as sector_num,
reg_seal_proof,
ticket_epoch,
ticket_value,
tree_r_cid as orig_sealed_cid,
tree_d_cid as orig_unsealed_cid,
tree_r_cid as cur_sealed_cid,
tree_d_cid as cur_unsealed_cid,
precommit_msg_cid,
commit_msg_cid,
seed_epoch,
seed_value
FROM
sectors_sdr_pipeline
WHERE
sp_id = $1 AND
sector_number = $2
ON CONFLICT (sp_id, sector_num) DO UPDATE SET
reg_seal_proof = excluded.reg_seal_proof,
ticket_epoch = excluded.ticket_epoch,
ticket_value = excluded.ticket_value,
orig_sealed_cid = excluded.orig_sealed_cid,
cur_sealed_cid = excluded.cur_sealed_cid,
msg_cid_precommit = excluded.msg_cid_precommit,
msg_cid_commit = excluded.msg_cid_commit,
seed_epoch = excluded.seed_epoch,
seed_value = excluded.seed_value;
`, spID, sectorNum); err != nil {
return fmt.Errorf("failed to insert/update sectors_meta: %w", err)
}
// Execute the query for piece metadata
if _, err := s.db.Exec(ctx, `
INSERT INTO sectors_meta_pieces (
sp_id,
sector_num,
piece_num,
piece_cid,
piece_size,
requested_keep_data,
raw_data_size,
start_epoch,
orig_end_epoch,
f05_deal_id,
ddo_pam
)
SELECT
sp_id,
sector_number AS sector_num,
piece_index AS piece_num,
piece_cid,
piece_size,
not data_delete_on_finalize as requested_keep_data,
data_raw_size,
COALESCE(f05_deal_start_epoch, direct_start_epoch) as start_epoch,
COALESCE(f05_deal_end_epoch, direct_end_epoch) as orig_end_epoch,
f05_deal_id,
direct_piece_activation_manifest as ddo_pam
FROM
sectors_sdr_initial_pieces
WHERE
sp_id = $1 AND
sector_number = $2
ON CONFLICT (sp_id, sector_num, piece_num) DO UPDATE SET
piece_cid = excluded.piece_cid,
piece_size = excluded.piece_size,
requested_keep_data = excluded.requested_keep_data,
raw_data_size = excluded.raw_data_size,
start_epoch = excluded.start_epoch,
orig_end_epoch = excluded.orig_end_epoch,
f05_deal_id = excluded.f05_deal_id,
ddo_pam = excluded.ddo_pam;
`, spID, sectorNum); err != nil {
return fmt.Errorf("failed to insert/update sector_meta_pieces: %w", err)
}
return nil
}
func (s *SubmitCommitTask) CanAccept(ids []harmonytask.TaskID, engine *harmonytask.TaskEngine) (*harmonytask.TaskID, error) {
id := ids[0]
return &id, nil
}
func (s *SubmitCommitTask) TypeDetails() harmonytask.TaskTypeDetails {
return harmonytask.TaskTypeDetails{
Max: 128,
Name: "CommitSubmit",
Cost: resources.Resources{
Cpu: 0,
Gpu: 0,
Ram: 1 << 20,
},
MaxFailures: 16,
}
}
func (s *SubmitCommitTask) Adder(taskFunc harmonytask.AddTaskFunc) {
s.sp.pollers[pollerCommitMsg].Set(taskFunc)
}
func (s *SubmitCommitTask) allocationCheck(ctx context.Context, piece *miner.PieceActivationManifest, precomitInfo *miner.SectorPreCommitOnChainInfo, miner abi.ActorID, ts *types.TipSet) error {
// skip pieces not claiming an allocation
if piece.VerifiedAllocationKey == nil {
return nil
}
addr, err := address.NewIDAddress(uint64(piece.VerifiedAllocationKey.Client))
if err != nil {
return err
}
alloc, err := s.api.StateGetAllocation(ctx, addr, verifregtypes9.AllocationId(piece.VerifiedAllocationKey.ID), ts.Key())
if err != nil {
return err
}
if alloc == nil {
return xerrors.Errorf("no allocation found for piece %s with allocation ID %d", piece.CID.String(), piece.VerifiedAllocationKey.ID)
}
if alloc.Provider != miner {
return xerrors.Errorf("provider id mismatch for piece %s: expected %d and found %d", piece.CID.String(), miner, alloc.Provider)
}
if alloc.Size != piece.Size {
return xerrors.Errorf("size mismatch for piece %s: expected %d and found %d", piece.CID.String(), piece.Size, alloc.Size)
}
if precomitInfo.Info.Expiration < ts.Height()+alloc.TermMin {
return xerrors.Errorf("sector expiration %d is before than allocation TermMin %d for piece %s", precomitInfo.Info.Expiration, ts.Height()+alloc.TermMin, piece.CID.String())
}
if precomitInfo.Info.Expiration > ts.Height()+alloc.TermMax {
return xerrors.Errorf("sector expiration %d is later than allocation TermMax %d for piece %s", precomitInfo.Info.Expiration, ts.Height()+alloc.TermMax, piece.CID.String())
}
return nil
}
var _ harmonytask.TaskInterface = &SubmitCommitTask{}