feat: curio: sectors UI (#11869)
* cfg edit 1 * jsonschema deps * feat: lp mig - first few steps * lp mig: default tasks * code comments * docs * lp-mig-progress * shared * comments and todos * fix: curio: rename lotus-provider to curio (#11645) * rename provider to curio * install gotext * fix lint errors, mod tidy * fix typo * fix API_INFO and add gotext to circleCI * add back gotext * add gotext after remerge * lp: channels doc * finish easy-migration TODOs * out generate * merging and more renames * avoid make-all * minor doc stuff * cu: make gen * make gen fix * make gen * tryfix * go mod tidy * minor ez migration fixes * ez setup - ui cleanups * better error message * guided setup colors * better path to saveconfigtolayer * loadconfigwithupgrades fix * readMiner oops * guided - homedir * err if miner is running * prompt error should exit * process already running, miner_id sectors in migration * dont prompt for language a second time * check miner stopped * unlock repo * render and sql oops * curio easyMig - some fixes * easyMigration runs successfully * lint * part 2 of last * message * merge addtl * fixing guided setup for myself * warn-on-no-post * EditorLoads * cleanups and styles * create info * fix tests * make gen * sector early bird * sectors v2 * sector termination v1 * terminate2 * mjs * minor things * flag bad sectors * fix errors * add dealweight and deals * change column width * refactor sql, handle sealing sectors * fix estimates --------- Co-authored-by: LexLuthr <88259624+LexLuthr@users.noreply.github.com> Co-authored-by: LexLuthr <lexluthr@protocol.ai> Co-authored-by: LexLuthr <lexluthr@curiostorage.org>
This commit is contained in:
parent
edd9c82bc1
commit
6b3e9b109f
@ -2,6 +2,7 @@ package spcli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
cbor "github.com/ipfs/go-ipld-cbor"
|
cbor "github.com/ipfs/go-ipld-cbor"
|
||||||
|
"github.com/samber/lo"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@ -1353,25 +1355,71 @@ func TerminateSectorCmd(getActorAddress ActorAddressGetter) *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var outerErr error
|
||||||
|
sectorNumbers := lo.Map(cctx.Args().Slice(), func(sn string, _ int) int {
|
||||||
|
sectorNum, err := strconv.Atoi(sn)
|
||||||
|
if err != nil {
|
||||||
|
outerErr = fmt.Errorf("could not parse sector number: %w", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return sectorNum
|
||||||
|
})
|
||||||
|
if outerErr != nil {
|
||||||
|
return outerErr
|
||||||
|
}
|
||||||
|
|
||||||
|
confidence := uint64(cctx.Int("confidence"))
|
||||||
|
|
||||||
|
var fromAddr address.Address
|
||||||
|
if from := cctx.String("from"); from != "" {
|
||||||
|
var err error
|
||||||
|
fromAddr, err = address.NewFromString(from)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing address %s: %w", from, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
mi, err := nodeApi.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
mi, err := nodeApi.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
terminationDeclarationParams := []miner2.TerminationDeclaration{}
|
fromAddr = mi.Worker
|
||||||
|
}
|
||||||
for _, sn := range cctx.Args().Slice() {
|
smsg, err := TerminateSectors(ctx, nodeApi, maddr, sectorNumbers, fromAddr)
|
||||||
sectorNum, err := strconv.ParseUint(sn, 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse sector number: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sectorbit := bitfield.New()
|
wait, err := nodeApi.StateWaitMsg(ctx, smsg.Cid(), confidence)
|
||||||
sectorbit.Set(sectorNum)
|
|
||||||
|
|
||||||
loca, err := nodeApi.StateSectorPartition(ctx, maddr, abi.SectorNumber(sectorNum), types.EmptyTSK)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get state sector partition %s", err)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if wait.Receipt.ExitCode.IsError() {
|
||||||
|
return fmt.Errorf("terminate sectors message returned exit %d", wait.Receipt.ExitCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TerminatorNode interface {
|
||||||
|
StateSectorPartition(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok types.TipSetKey) (*miner.SectorLocation, error)
|
||||||
|
MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TerminateSectors(ctx context.Context, full TerminatorNode, maddr address.Address, sectorNumbers []int, fromAddr address.Address) (*types.SignedMessage, error) {
|
||||||
|
|
||||||
|
terminationDeclarationParams := []miner2.TerminationDeclaration{}
|
||||||
|
|
||||||
|
for _, sectorNum := range sectorNumbers {
|
||||||
|
|
||||||
|
sectorbit := bitfield.New()
|
||||||
|
sectorbit.Set(uint64(sectorNum))
|
||||||
|
|
||||||
|
loca, err := full.StateSectorPartition(ctx, maddr, abi.SectorNumber(sectorNum), types.EmptyTSK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get state sector partition %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
para := miner2.TerminationDeclaration{
|
para := miner2.TerminationDeclaration{
|
||||||
@ -1387,23 +1435,12 @@ func TerminateSectorCmd(getActorAddress ActorAddressGetter) *cli.Command {
|
|||||||
Terminations: terminationDeclarationParams,
|
Terminations: terminationDeclarationParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
sp, err := actors.SerializeParams(terminateSectorParams)
|
sp, errA := actors.SerializeParams(terminateSectorParams)
|
||||||
if err != nil {
|
if errA != nil {
|
||||||
return xerrors.Errorf("serializing params: %w", err)
|
return nil, xerrors.Errorf("serializing params: %w", errA)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromAddr address.Address
|
smsg, err := full.MpoolPushMessage(ctx, &types.Message{
|
||||||
if from := cctx.String("from"); from != "" {
|
|
||||||
var err error
|
|
||||||
fromAddr, err = address.NewFromString(from)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing address %s: %w", from, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fromAddr = mi.Worker
|
|
||||||
}
|
|
||||||
|
|
||||||
smsg, err := nodeApi.MpoolPushMessage(ctx, &types.Message{
|
|
||||||
From: fromAddr,
|
From: fromAddr,
|
||||||
To: maddr,
|
To: maddr,
|
||||||
Method: builtin.MethodsMiner.TerminateSectors,
|
Method: builtin.MethodsMiner.TerminateSectors,
|
||||||
@ -1412,21 +1449,10 @@ func TerminateSectorCmd(getActorAddress ActorAddressGetter) *cli.Command {
|
|||||||
Params: sp,
|
Params: sp,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("mpool push message: %w", err)
|
return nil, xerrors.Errorf("mpool push message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("sent termination message:", smsg.Cid())
|
fmt.Println("sent termination message:", smsg.Cid())
|
||||||
|
|
||||||
wait, err := nodeApi.StateWaitMsg(ctx, smsg.Cid(), uint64(cctx.Int("confidence")))
|
return smsg, nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if wait.Receipt.ExitCode.IsError() {
|
|
||||||
return fmt.Errorf("terminate sectors message returned exit %d", wait.Receipt.ExitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,6 @@ func getSch(w http.ResponseWriter, r *http.Request) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sch := ref.Reflect(config.CurioConfig{})
|
sch := ref.Reflect(config.CurioConfig{})
|
||||||
//sch := jsonschema.Reflect(config.CurioConfig{})
|
|
||||||
// add comments
|
// add comments
|
||||||
for k, doc := range config.Doc {
|
for k, doc := range config.Doc {
|
||||||
item, ok := sch.Definitions[k]
|
item, ok := sch.Definitions[k]
|
||||||
|
@ -7,9 +7,11 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
||||||
"github.com/filecoin-project/lotus/curiosrc/web/api/config"
|
"github.com/filecoin-project/lotus/curiosrc/web/api/config"
|
||||||
"github.com/filecoin-project/lotus/curiosrc/web/api/debug"
|
"github.com/filecoin-project/lotus/curiosrc/web/api/debug"
|
||||||
|
"github.com/filecoin-project/lotus/curiosrc/web/api/sector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Routes(r *mux.Router, deps *deps.Deps) {
|
func Routes(r *mux.Router, deps *deps.Deps) {
|
||||||
debug.Routes(r.PathPrefix("/debug").Subrouter(), deps)
|
debug.Routes(r.PathPrefix("/debug").Subrouter(), deps)
|
||||||
config.Routes(r.PathPrefix("/config").Subrouter(), deps)
|
config.Routes(r.PathPrefix("/config").Subrouter(), deps)
|
||||||
|
sector.Routes(r.PathPrefix("/sector").Subrouter(), deps)
|
||||||
}
|
}
|
||||||
|
375
curiosrc/web/api/sector/sector.go
Normal file
375
curiosrc/web/api/sector/sector.go
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
package sector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/go-bitfield"
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
|
"github.com/filecoin-project/go-state-types/builtin/v9/market"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/cli/spcli"
|
||||||
|
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
||||||
|
"github.com/filecoin-project/lotus/curiosrc/web/api/apihelper"
|
||||||
|
"github.com/filecoin-project/lotus/storage/sealer/storiface"
|
||||||
|
)
|
||||||
|
|
||||||
|
const verifiedPowerGainMul = 9
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
*deps.Deps
|
||||||
|
}
|
||||||
|
|
||||||
|
func Routes(r *mux.Router, deps *deps.Deps) {
|
||||||
|
c := &cfg{deps}
|
||||||
|
// At menu.html:
|
||||||
|
r.Methods("GET").Path("/all").HandlerFunc(c.getSectors)
|
||||||
|
r.Methods("POST").Path("/terminate").HandlerFunc(c.terminateSectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) terminateSectors(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var in []struct {
|
||||||
|
MinerID int
|
||||||
|
Sector int
|
||||||
|
}
|
||||||
|
apihelper.OrHTTPFail(w, json.NewDecoder(r.Body).Decode(&in))
|
||||||
|
toDel := map[int][]int{}
|
||||||
|
for _, s := range in {
|
||||||
|
toDel[s.MinerID] = append(toDel[s.MinerID], s.Sector)
|
||||||
|
}
|
||||||
|
|
||||||
|
for minerInt, sectors := range toDel {
|
||||||
|
maddr, err := address.NewIDAddress(uint64(minerInt))
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
mi, err := c.Full.StateMinerInfo(r.Context(), maddr, types.EmptyTSK)
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
_, err = spcli.TerminateSectors(r.Context(), c.Full, maddr, sectors, mi.Worker)
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
for _, sectorNumber := range sectors {
|
||||||
|
id := abi.SectorID{Miner: abi.ActorID(minerInt), Number: abi.SectorNumber(sectorNumber)}
|
||||||
|
apihelper.OrHTTPFail(w, c.Stor.Remove(r.Context(), id, storiface.FTAll, true, nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) getSectors(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO get sector info from chain and from database, then fold them together
|
||||||
|
// and return the result.
|
||||||
|
type sector struct {
|
||||||
|
MinerID int64 `db:"miner_id"`
|
||||||
|
SectorNum int64 `db:"sector_num"`
|
||||||
|
SectorFiletype int `db:"sector_filetype" json:"-"` // Useless?
|
||||||
|
HasSealed bool
|
||||||
|
HasUnsealed bool
|
||||||
|
HasSnap bool
|
||||||
|
ExpiresAt abi.ChainEpoch // map to Duration
|
||||||
|
IsOnChain bool
|
||||||
|
IsFilPlus bool
|
||||||
|
SealInfo string
|
||||||
|
Proving bool
|
||||||
|
Flag bool
|
||||||
|
DealWeight string
|
||||||
|
Deals string
|
||||||
|
//StorageID string `db:"storage_id"` // map to serverName
|
||||||
|
// Activation abi.ChainEpoch // map to time.Time. advanced view only
|
||||||
|
// DealIDs []abi.DealID // advanced view only
|
||||||
|
//ExpectedDayReward abi.TokenAmount
|
||||||
|
//SealProof abi.RegisteredSealProof
|
||||||
|
}
|
||||||
|
type piece struct {
|
||||||
|
Size int64 `db:"piece_size"`
|
||||||
|
DealID uint64 `db:"f05_deal_id"`
|
||||||
|
Proposal json.RawMessage `db:"f05_deal_proposal"`
|
||||||
|
Manifest json.RawMessage `db:"direct_piece_activation_manifest"`
|
||||||
|
Miner int64 `db:"sp_id"`
|
||||||
|
Sector int64 `db:"sector_number"`
|
||||||
|
}
|
||||||
|
var sectors []sector
|
||||||
|
var pieces []piece
|
||||||
|
apihelper.OrHTTPFail(w, c.DB.Select(r.Context(), §ors, `SELECT
|
||||||
|
miner_id, sector_num, SUM(sector_filetype) as sector_filetype
|
||||||
|
FROM sector_location WHERE sector_filetype != 32
|
||||||
|
GROUP BY miner_id, sector_num
|
||||||
|
ORDER BY miner_id, sector_num`))
|
||||||
|
minerToAddr := map[int64]address.Address{}
|
||||||
|
head, err := c.Full.ChainHead(r.Context())
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
|
||||||
|
type sectorID struct {
|
||||||
|
mID int64
|
||||||
|
sNum uint64
|
||||||
|
}
|
||||||
|
sectorIdx := map[sectorID]int{}
|
||||||
|
for i, s := range sectors {
|
||||||
|
sectors[i].HasSealed = s.SectorFiletype&int(storiface.FTSealed) != 0 || s.SectorFiletype&int(storiface.FTUpdate) != 0
|
||||||
|
sectors[i].HasUnsealed = s.SectorFiletype&int(storiface.FTUnsealed) != 0
|
||||||
|
sectors[i].HasSnap = s.SectorFiletype&int(storiface.FTUpdate) != 0
|
||||||
|
sectorIdx[sectorID{s.MinerID, uint64(s.SectorNum)}] = i
|
||||||
|
if _, ok := minerToAddr[s.MinerID]; !ok {
|
||||||
|
minerToAddr[s.MinerID], err = address.NewIDAddress(uint64(s.MinerID))
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all pieces
|
||||||
|
apihelper.OrHTTPFail(w, c.DB.Select(r.Context(), &pieces, `SELECT
|
||||||
|
sp_id, sector_number, piece_size, f05_deal_id, f05_deal_proposal, direct_piece_activation_manifest
|
||||||
|
FROM sectors_sdr_initial_pieces
|
||||||
|
ORDER BY sp_id, sector_number`))
|
||||||
|
pieceIndex := map[sectorID][]int{}
|
||||||
|
for i, piece := range pieces {
|
||||||
|
piece := piece
|
||||||
|
cur := pieceIndex[sectorID{mID: piece.Miner, sNum: uint64(piece.Sector)}]
|
||||||
|
pieceIndex[sectorID{mID: piece.Miner, sNum: uint64(piece.Sector)}] = append(cur, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for minerID, maddr := range minerToAddr {
|
||||||
|
onChainInfo, err := c.getCachedSectorInfo(w, r, maddr, head.Key())
|
||||||
|
apihelper.OrHTTPFail(w, err)
|
||||||
|
for _, chainy := range onChainInfo {
|
||||||
|
st := chainy.onChain
|
||||||
|
if i, ok := sectorIdx[sectorID{minerID, uint64(st.SectorNumber)}]; ok {
|
||||||
|
sectors[i].IsOnChain = true
|
||||||
|
sectors[i].ExpiresAt = st.Expiration
|
||||||
|
sectors[i].IsFilPlus = st.VerifiedDealWeight.GreaterThan(st.DealWeight)
|
||||||
|
if ss, err := st.SealProof.SectorSize(); err == nil {
|
||||||
|
sectors[i].SealInfo = ss.ShortString()
|
||||||
|
}
|
||||||
|
sectors[i].Proving = chainy.active
|
||||||
|
if st.Expiration < head.Height() {
|
||||||
|
sectors[i].Flag = true // Flag expired sectors
|
||||||
|
}
|
||||||
|
dw, vp := .0, .0
|
||||||
|
f05, ddo := 0, 0
|
||||||
|
var pi []piece
|
||||||
|
if j, ok := pieceIndex[sectorID{sectors[i].MinerID, uint64(sectors[i].SectorNum)}]; ok {
|
||||||
|
for _, k := range j {
|
||||||
|
pi = append(pi, pieces[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
estimate := st.Expiration-st.Activation <= 0 || sectors[i].HasSnap
|
||||||
|
if estimate {
|
||||||
|
for _, p := range pi {
|
||||||
|
if p.Proposal != nil {
|
||||||
|
var prop *market.DealProposal
|
||||||
|
apihelper.OrHTTPFail(w, json.Unmarshal(p.Proposal, &prop))
|
||||||
|
dw += float64(prop.PieceSize)
|
||||||
|
if prop.VerifiedDeal {
|
||||||
|
vp += float64(prop.PieceSize) * verifiedPowerGainMul
|
||||||
|
}
|
||||||
|
f05++
|
||||||
|
}
|
||||||
|
if p.Manifest != nil {
|
||||||
|
var pam *miner.PieceActivationManifest
|
||||||
|
apihelper.OrHTTPFail(w, json.Unmarshal(p.Manifest, &pam))
|
||||||
|
dw += float64(pam.Size)
|
||||||
|
if pam.VerifiedAllocationKey != nil {
|
||||||
|
vp += float64(pam.Size) * verifiedPowerGainMul
|
||||||
|
}
|
||||||
|
ddo++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rdw := big.Add(st.DealWeight, st.VerifiedDealWeight)
|
||||||
|
dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64())
|
||||||
|
vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(verifiedPowerGainMul)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64())
|
||||||
|
for _, deal := range st.DealIDs {
|
||||||
|
if deal > 0 {
|
||||||
|
f05++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DDO info is not on chain
|
||||||
|
for _, piece := range pieces {
|
||||||
|
if piece.Manifest != nil {
|
||||||
|
//var pam *miner.PieceActivationManifest
|
||||||
|
//apihelper.OrHTTPFail(w, json.Unmarshal(piece.Manifest, pam))
|
||||||
|
//dw += float64(pam.Size)
|
||||||
|
//if pam.VerifiedAllocationKey != nil {
|
||||||
|
// vp += float64(pam.Size) * verifiedPowerGainMul
|
||||||
|
//}
|
||||||
|
ddo++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sectors[i].DealWeight = "CC"
|
||||||
|
if dw > 0 {
|
||||||
|
sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(dw))
|
||||||
|
}
|
||||||
|
if vp > 0 {
|
||||||
|
sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(vp))
|
||||||
|
}
|
||||||
|
sectors[i].Deals = fmt.Sprintf("Market: %d, DDO: %d", f05, ddo)
|
||||||
|
} else {
|
||||||
|
// sector is on chain but not in db
|
||||||
|
s := sector{
|
||||||
|
MinerID: minerID,
|
||||||
|
SectorNum: int64(chainy.onChain.SectorNumber),
|
||||||
|
IsOnChain: true,
|
||||||
|
ExpiresAt: chainy.onChain.Expiration,
|
||||||
|
IsFilPlus: chainy.onChain.VerifiedDealWeight.GreaterThan(chainy.onChain.DealWeight),
|
||||||
|
Proving: chainy.active,
|
||||||
|
Flag: true, // All such sectors should be flagged to be terminated
|
||||||
|
}
|
||||||
|
if ss, err := chainy.onChain.SealProof.SectorSize(); err == nil {
|
||||||
|
s.SealInfo = ss.ShortString()
|
||||||
|
}
|
||||||
|
sectors = append(sectors, s)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
info, err := c.Full.StateSectorGetInfo(r.Context(), minerToAddr[s], abi.SectorNumber(uint64(sectors[i].SectorNum)), headKey)
|
||||||
|
if err != nil {
|
||||||
|
sectors[i].IsValid = false
|
||||||
|
continue
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add deal details to sectors which are not on chain
|
||||||
|
for i := range sectors {
|
||||||
|
if !sectors[i].IsOnChain {
|
||||||
|
var pi []piece
|
||||||
|
dw, vp := .0, .0
|
||||||
|
f05, ddo := 0, 0
|
||||||
|
|
||||||
|
// Find if there are any deals for this sector
|
||||||
|
if j, ok := pieceIndex[sectorID{sectors[i].MinerID, uint64(sectors[i].SectorNum)}]; ok {
|
||||||
|
for _, k := range j {
|
||||||
|
pi = append(pi, pieces[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pi) > 0 {
|
||||||
|
for _, piece := range pi {
|
||||||
|
if piece.Proposal != nil {
|
||||||
|
var prop *market.DealProposal
|
||||||
|
apihelper.OrHTTPFail(w, json.Unmarshal(piece.Proposal, &prop))
|
||||||
|
dw += float64(prop.PieceSize)
|
||||||
|
if prop.VerifiedDeal {
|
||||||
|
vp += float64(prop.PieceSize) * verifiedPowerGainMul
|
||||||
|
}
|
||||||
|
f05++
|
||||||
|
}
|
||||||
|
if piece.Manifest != nil {
|
||||||
|
var pam *miner.PieceActivationManifest
|
||||||
|
apihelper.OrHTTPFail(w, json.Unmarshal(piece.Manifest, &pam))
|
||||||
|
dw += float64(pam.Size)
|
||||||
|
if pam.VerifiedAllocationKey != nil {
|
||||||
|
vp += float64(pam.Size) * verifiedPowerGainMul
|
||||||
|
}
|
||||||
|
ddo++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dw > 0 {
|
||||||
|
sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(dw))
|
||||||
|
} else if vp > 0 {
|
||||||
|
sectors[i].DealWeight = fmt.Sprintf("%s", units.BytesSize(vp))
|
||||||
|
} else {
|
||||||
|
sectors[i].DealWeight = "CC"
|
||||||
|
}
|
||||||
|
sectors[i].Deals = fmt.Sprintf("Market: %d, DDO: %d", f05, ddo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(map[string]any{"data": sectors}))
|
||||||
|
}
|
||||||
|
|
||||||
|
type sectorInfo struct {
|
||||||
|
onChain *miner.SectorOnChainInfo
|
||||||
|
active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type sectorCacheEntry struct {
|
||||||
|
sectors []sectorInfo
|
||||||
|
loading chan struct{}
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheTimeout = 30 * time.Minute
|
||||||
|
|
||||||
|
var mx sync.Mutex
|
||||||
|
var sectorInfoCache = map[address.Address]sectorCacheEntry{}
|
||||||
|
|
||||||
|
// getCachedSectorInfo returns the sector info for the given miner address,
|
||||||
|
// either from the cache or by querying the chain.
|
||||||
|
// Cache can be invalidated by setting the "sector_refresh" cookie to "true".
|
||||||
|
// This is thread-safe.
|
||||||
|
// Parallel requests share the chain's first response.
|
||||||
|
func (c *cfg) getCachedSectorInfo(w http.ResponseWriter, r *http.Request, maddr address.Address, headKey types.TipSetKey) ([]sectorInfo, error) {
|
||||||
|
mx.Lock()
|
||||||
|
v, ok := sectorInfoCache[maddr]
|
||||||
|
mx.Unlock()
|
||||||
|
|
||||||
|
if ok && v.loading != nil {
|
||||||
|
<-v.loading
|
||||||
|
mx.Lock()
|
||||||
|
v, ok = sectorInfoCache[maddr]
|
||||||
|
mx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRefreshCookie, found := lo.Find(r.Cookies(), func(item *http.Cookie) bool { return item.Name == "sector_refresh" })
|
||||||
|
shouldRefresh := found && shouldRefreshCookie.Value == "true"
|
||||||
|
w.Header().Set("Set-Cookie", "sector_refresh=; Max-Age=0; Path=/")
|
||||||
|
|
||||||
|
if !ok || time.Since(v.Time) > cacheTimeout || shouldRefresh {
|
||||||
|
v = sectorCacheEntry{nil, make(chan struct{}), time.Now()}
|
||||||
|
mx.Lock()
|
||||||
|
sectorInfoCache[maddr] = v
|
||||||
|
mx.Unlock()
|
||||||
|
|
||||||
|
// Intentionally not using the context from the request, as this is a cache
|
||||||
|
onChainInfo, err := c.Full.StateMinerSectors(context.Background(), maddr, nil, headKey)
|
||||||
|
if err != nil {
|
||||||
|
mx.Lock()
|
||||||
|
delete(sectorInfoCache, maddr)
|
||||||
|
close(v.loading)
|
||||||
|
mx.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
active, err := c.Full.StateMinerActiveSectors(context.Background(), maddr, headKey)
|
||||||
|
if err != nil {
|
||||||
|
mx.Lock()
|
||||||
|
delete(sectorInfoCache, maddr)
|
||||||
|
close(v.loading)
|
||||||
|
mx.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
activebf := bitfield.New()
|
||||||
|
for i := range active {
|
||||||
|
activebf.Set(uint64(active[i].SectorNumber))
|
||||||
|
}
|
||||||
|
infos := make([]sectorInfo, len(onChainInfo))
|
||||||
|
for i, info := range onChainInfo {
|
||||||
|
info := info
|
||||||
|
set, err := activebf.IsSet(uint64(info.SectorNumber))
|
||||||
|
if err != nil {
|
||||||
|
mx.Lock()
|
||||||
|
delete(sectorInfoCache, maddr)
|
||||||
|
close(v.loading)
|
||||||
|
mx.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
infos[i] = sectorInfo{
|
||||||
|
onChain: info,
|
||||||
|
active: set,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mx.Lock()
|
||||||
|
sectorInfoCache[maddr] = sectorCacheEntry{infos, nil, time.Now()}
|
||||||
|
close(v.loading)
|
||||||
|
mx.Unlock()
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
return v.sectors, nil
|
||||||
|
}
|
@ -37,6 +37,8 @@ type minimalActorInfo struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var startedAt = time.Now()
|
||||||
|
|
||||||
func (a *app) updateActor(ctx context.Context) error {
|
func (a *app) updateActor(ctx context.Context) error {
|
||||||
a.rpcInfoLk.Lock()
|
a.rpcInfoLk.Lock()
|
||||||
api := a.workingApi
|
api := a.workingApi
|
||||||
@ -45,7 +47,9 @@ func (a *app) updateActor(ctx context.Context) error {
|
|||||||
stor := store.ActorStore(ctx, blockstore.NewReadCachedBlockstore(blockstore.NewAPIBlockstore(a.workingApi), ChainBlockCache))
|
stor := store.ActorStore(ctx, blockstore.NewReadCachedBlockstore(blockstore.NewAPIBlockstore(a.workingApi), ChainBlockCache))
|
||||||
|
|
||||||
if api == nil {
|
if api == nil {
|
||||||
|
if time.Since(startedAt) > time.Second*10 {
|
||||||
log.Warnw("no working api yet")
|
log.Warnw("no working api yet")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
129
curiosrc/web/static/sector/index.html
Normal file
129
curiosrc/web/static/sector/index.html
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Sector List</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
|
||||||
|
<script type="module" src="/ux/curio-ux.mjs"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/2.0.3/css/dataTables.dataTables.min.css" />
|
||||||
|
<script src="https://cdn.datatables.net/2.0.2/js/dataTables.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/2.0.3/css/dataTables.bootstrap5.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/scroller/2.4.1/css/scroller.dataTables.min.css" />
|
||||||
|
<script src="https://cdn.datatables.net/scroller/2.4.1/js/dataTables.scroller.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/3.0.1/css/responsive.dataTables.min.css" />
|
||||||
|
<script src="https://cdn.datatables.net/responsive/3.0.1/js/dataTables.responsive.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/3.0.1/css/buttons.dataTables.min.css" />
|
||||||
|
<script src="https://cdn.datatables.net/buttons/3.0.1/js/dataTables.buttons.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/select/2.0.0/css/select.dataTables.min.css" />
|
||||||
|
<script src="https://cdn.datatables.net/select/2.0.0/js/dataTables.select.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="visibility:hidden" data-bs-theme="dark">
|
||||||
|
<curio-ux>
|
||||||
|
<section class="section container-fluid">
|
||||||
|
<div class="row justify-content-center content">
|
||||||
|
<div class="col-md-auto" style="max-width: 95%">
|
||||||
|
<table id="sectorTable" class="hover">
|
||||||
|
<tr>
|
||||||
|
<td>Loading...</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</curio-ux>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let dt = new DataTable('#sectorTable', {
|
||||||
|
ajax: '/api/sector/all',
|
||||||
|
columns: [
|
||||||
|
{title: "", data: null},
|
||||||
|
{title: "Miner", data:'MinerID'},
|
||||||
|
{title: "Sector", data:'SectorNum'},
|
||||||
|
{title: "Expiry", data:'ExpiresAt'},
|
||||||
|
{title: "🔗", data:'IsOnChain'},
|
||||||
|
{title: "Proving", data:'Proving'},
|
||||||
|
{title: "Has Sealed", data:'HasSealed'},
|
||||||
|
{title: "Has Unsealed", data:'HasUnsealed'},
|
||||||
|
{title: "DealWeight", data:"DealWeight"},
|
||||||
|
{title: "Deals", data:"Deals"},
|
||||||
|
{title: "Fil+", data:'IsFilPlus'},
|
||||||
|
{title: "Has Snap", data:'HasSnap'},
|
||||||
|
{title: "Size", data:"SealInfo"},
|
||||||
|
{title: "Flag", data:"Flag"}
|
||||||
|
],
|
||||||
|
layout: {
|
||||||
|
topStart: 'buttons',
|
||||||
|
bottomStart: 'info',
|
||||||
|
},
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
extend: 'copy',
|
||||||
|
text: '📋'
|
||||||
|
},
|
||||||
|
'csv',
|
||||||
|
{
|
||||||
|
extend: 'selected',
|
||||||
|
text: 'Terminate & Delete',
|
||||||
|
action: function (e, dt, button, config) {
|
||||||
|
var res = dt.rows({ selected: true }).data().map(function (row) {
|
||||||
|
return {MinerID: row.MinerID, Sector: row.SectorNum};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirm("Terminate & Delete: "+res.join(", "))) {
|
||||||
|
axios.post('/api/sector/terminate', res)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log(response);
|
||||||
|
document.cookie = "sector_refresh=true; path=/";
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Refresh',
|
||||||
|
action: function (e, dt, button, config) {
|
||||||
|
document.cookie = "sector_refresh=true; path=/";
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
responsive: true,
|
||||||
|
columnDefs: [
|
||||||
|
{
|
||||||
|
orderable: false,
|
||||||
|
render: DataTable.render.select(),
|
||||||
|
targets: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: 13,
|
||||||
|
visible: false, // Make the "Flag" column hidden
|
||||||
|
searchable: false,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [[13, 'desc'], [1, 'asc'], [2, 'asc']],
|
||||||
|
select: {
|
||||||
|
style: 'multi',
|
||||||
|
selector: 'td:first-child',
|
||||||
|
items: 'row',
|
||||||
|
rows: '%d rows selected',
|
||||||
|
headerCheckbox: true,
|
||||||
|
},
|
||||||
|
scrollY: window.innerHeight - 150,
|
||||||
|
deferRender: true,
|
||||||
|
scroller: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -33,18 +33,38 @@ class CurioUX extends LitElement {
|
|||||||
document.body.attributes.setNamedItem(cdsText);
|
document.body.attributes.setNamedItem(cdsText);
|
||||||
|
|
||||||
document.body.style.visibility = 'initial';
|
document.body.style.visibility = 'initial';
|
||||||
|
|
||||||
|
// how Bootstrap & DataTables expect dark mode declared.
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
|
||||||
|
this.messsage = this.getCookieMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<!-- wrap the slot -->
|
<!-- wrap the slot -->
|
||||||
<div>
|
<div>
|
||||||
|
${this.message? html`<div>${this.message}</div>`: html``}
|
||||||
<slot class="curio-slot"></slot>
|
<slot class="curio-slot"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCookieMessage() {
|
||||||
|
const name = 'message';
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
|
const cookie = cookies[i].trim();
|
||||||
|
if (cookie.startsWith(name + '=')) {
|
||||||
|
var val = cookie.substring(name.length + 1);
|
||||||
|
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
customElements.define('curio-ux', CurioUX);
|
customElements.define('curio-ux', CurioUX);
|
@ -500,6 +500,7 @@ func (st *Local) Reserve(ctx context.Context, sid storiface.SectorRef, ft storif
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
for _, fileType := range ft.AllSet() {
|
for _, fileType := range ft.AllSet() {
|
||||||
|
fileType := fileType
|
||||||
id := storiface.ID(storiface.PathByType(storageIDs, fileType))
|
id := storiface.ID(storiface.PathByType(storageIDs, fileType))
|
||||||
|
|
||||||
p, ok := st.paths[id]
|
p, ok := st.paths[id]
|
||||||
|
Loading…
Reference in New Issue
Block a user