diff --git a/api/api_storage.go b/api/api_storage.go index 04ff8311c94..90de01fb9ad 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -50,7 +50,8 @@ type StorageMiner interface { MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) - MarketSetPrice(context.Context, types.BigInt) error + MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error + MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 02d9e515553..a1fe69f065c 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -192,10 +192,11 @@ type StorageMinerStruct struct { MiningBase func(context.Context) (*types.TipSet, error) `perm:"read"` - MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` - MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` - MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` - MarketSetPrice func(context.Context, types.BigInt) error `perm:"admin"` + MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` + MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` + MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` + MarketSetAsk func(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"` + MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"` PledgeSector func(context.Context) error `perm:"write"` @@ -841,8 +842,12 @@ func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]s return c.Internal.MarketListIncompleteDeals(ctx) } -func (c *StorageMinerStruct) MarketSetPrice(ctx context.Context, p types.BigInt) error { - return c.Internal.MarketSetPrice(ctx, p) +func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error { + return c.Internal.MarketSetAsk(ctx, price, duration, minPieceSize, maxPieceSize) +} + +func (c *StorageMinerStruct) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) { + return c.Internal.MarketGetAsk(ctx) } func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error { diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index ba7722e4e9e..dc6de70292e 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -30,7 +30,6 @@ func main() { stopCmd, sectorsCmd, storageCmd, - setPriceCmd, workersCmd, provingCmd, } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index c2f2555fa37..4dac236e46c 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -3,11 +3,21 @@ package main import ( "encoding/json" "fmt" + "os" + "text/tabwriter" + "time" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" + "github.com/docker/go-units" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/specs-actors/actors/abi" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" ) var enableCmd = &cli.Command{ @@ -40,29 +50,140 @@ var disableCmd = &cli.Command{ }, } -var setPriceCmd = &cli.Command{ - Name: "set-price", - Usage: "Set price that miner will accept storage deals at (FIL / GiB / Epoch)", - Flags: []cli.Flag{}, +var setAskCmd = &cli.Command{ + Name: "set-ask", + Usage: "Configure the miner's ask", + Flags: []cli.Flag{ + &cli.Uint64Flag{ + Name: "price", + Usage: "Set the price of the ask (specified as FIL / GiB / Epoch) to `PRICE`", + Required: true, + }, + &cli.StringFlag{ + Name: "duration", + Usage: "Set duration of ask (a quantity of time after which the ask expires) `DURATION`", + DefaultText: "720h0m0s", + Value: "720h0m0s", + }, + &cli.StringFlag{ + Name: "min-piece-size", + Usage: "Set minimum piece size (w/bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "256B", + Value: "256B", + }, + &cli.StringFlag{ + Name: "max-piece-size", + Usage: "Set maximum piece size (w/bit-padding, in bytes) in ask to `SIZE`", + DefaultText: "miner sector size", + }, + }, Action: func(cctx *cli.Context) error { + ctx := lcli.DaemonContext(cctx) + api, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() + pri := types.NewInt(cctx.Uint64("price")) + + dur, err := time.ParseDuration(cctx.String("duration")) + if err != nil { + return xerrors.Errorf("cannot parse duration: %w", err) + } + + qty := dur.Seconds() / build.BlockDelay + + min, err := units.RAMInBytes(cctx.String("min-piece-size")) + if err != nil { + return xerrors.Errorf("cannot parse min-piece-size to quantity of bytes: %w", err) + } + + if min < 256 { + return xerrors.New("minimum piece size (w/bit-padding) is 256B") + } + + max, err := units.RAMInBytes(cctx.String("max-piece-size")) + if err != nil { + return xerrors.Errorf("cannot parse max-piece-size to quantity of bytes: %w", err) + } + + maddr, err := api.ActorAddress(ctx) + if err != nil { + return err + } + + ssize, err := api.ActorSectorSize(ctx, maddr) + if err != nil { + return err + } + + smax := int64(ssize) + + if max == 0 { + max = smax + } + + if max > smax { + return xerrors.Errorf("max piece size (w/bit-padding) %s cannot exceed miner sector size %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax)))) + } + + return api.MarketSetAsk(ctx, pri, abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max)) + }, +} + +var getAskCmd = &cli.Command{ + Name: "get-ask", + Usage: "Print the miner's ask", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { ctx := lcli.DaemonContext(cctx) - if !cctx.Args().Present() { - return fmt.Errorf("must specify price to set") + fnapi, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + smapi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err } + defer closer() - fp, err := types.ParseFIL(cctx.Args().First()) + sask, err := smapi.MarketGetAsk(ctx) if err != nil { return err } - return api.MarketSetPrice(ctx, types.BigInt(fp)) + var ask *storagemarket.StorageAsk + if sask != nil && sask.Ask != nil { + ask = sask.Ask + } + + w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) + fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (w/bit-padding)\tMax. Piece Size (w/bit-padding)\tExpiry (Epoch)\tExpiry (Appx. Rem. Time)\tSeq. No.\n") + if ask == nil { + fmt.Fprintf(w, "\n") + + return w.Flush() + } + + head, err := fnapi.ChainHead(ctx) + if err != nil { + return err + } + + dlt := ask.Expiry - head.Height() + rem := "" + if dlt > 0 { + rem = (time.Second * time.Duration(dlt*build.BlockDelay)).String() + } + + fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo) + + return w.Flush() }, } @@ -74,6 +195,8 @@ var dealsCmd = &cli.Command{ dealsListCmd, enableCmd, disableCmd, + setAskCmd, + getAskCmd, }, } diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 0cc13177eb7..ed94e173d7e 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -201,8 +201,17 @@ func (sm *StorageMinerAPI) MarketListIncompleteDeals(ctx context.Context) ([]sto return sm.StorageProvider.ListLocalDeals() } -func (sm *StorageMinerAPI) MarketSetPrice(ctx context.Context, p types.BigInt) error { - return sm.StorageProvider.SetAsk(p, 60*60*24*100) // lasts for 100 days? +func (sm *StorageMinerAPI) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error { + options := []storagemarket.StorageAskOption{ + storagemarket.MinPieceSize(minPieceSize), + storagemarket.MaxPieceSize(maxPieceSize), + } + + return sm.StorageProvider.SetAsk(price, duration, options...) +} + +func (sm *StorageMinerAPI) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) { + return sm.StorageProvider.GetAsk(), nil } func (sm *StorageMinerAPI) DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) {