feat: CLI: add claim-extend cli (#11711)
* add claim-extend cli * fix arg usage * add missing question * fix client addr, datacap prompt * replace waitGrp with errGrp * use promptUI * replace fmt.ErrorF with xerror * apply var name suggestion * GST rc3, update types * add itest * make gen * add changelog
This commit is contained in:
+435
@@ -4,23 +4,30 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
actorstypes "github.com/filecoin-project/go-state-types/actors"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
"github.com/filecoin-project/go-state-types/builtin"
|
||||
verifregtypes13 "github.com/filecoin-project/go-state-types/builtin/v13/verifreg"
|
||||
verifregtypes8 "github.com/filecoin-project/go-state-types/builtin/v8/verifreg"
|
||||
datacap2 "github.com/filecoin-project/go-state-types/builtin/v9/datacap"
|
||||
verifregtypes9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/v0api"
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
@@ -47,6 +54,7 @@ var filplusCmd = &cli.Command{
|
||||
filplusListClaimsCmd,
|
||||
filplusRemoveExpiredAllocationsCmd,
|
||||
filplusRemoveExpiredClaimsCmd,
|
||||
filplusExtendClaimCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -923,3 +931,430 @@ var filplusSignRemoveDataCapProposal = &cli.Command{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var filplusExtendClaimCmd = &cli.Command{
|
||||
Name: "extend-claim",
|
||||
Usage: "extend claim expiration (TermMax)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Int64Flag{
|
||||
Name: "term-max",
|
||||
Usage: "The maximum period for which a provider can earn quality-adjusted power for the piece (epochs). Default is 5 years.",
|
||||
Aliases: []string{"tmax"},
|
||||
Value: verifregtypes13.MaximumVerifiedAllocationTerm,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "client",
|
||||
Usage: "the client address that will used to send the message",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Usage: "automatically extend TermMax of all claims for specified miner[s] to --term-max (default: 5 years from claim start epoch)",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "miner",
|
||||
Usage: "storage provider address[es]",
|
||||
Aliases: []string{"m", "provider", "p"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "assume-yes",
|
||||
Usage: "automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively",
|
||||
Aliases: []string{"y", "yes"},
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "confidence",
|
||||
Usage: "number of block confirmations to wait for",
|
||||
Value: int(build.MessageConfidence),
|
||||
},
|
||||
},
|
||||
ArgsUsage: "<claim1> <claim2> ... or <miner1=claim1> <miner2=claims2> ...",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
|
||||
miners := cctx.StringSlice("miner")
|
||||
all := cctx.Bool("all")
|
||||
client := cctx.String("client")
|
||||
tmax := cctx.Int64("term-max")
|
||||
|
||||
// No miner IDs and no arguments
|
||||
if len(miners) == 0 && cctx.Args().Len() == 0 {
|
||||
return xerrors.Errorf("must specify at least one miner ID or argument[s]")
|
||||
}
|
||||
|
||||
// Single Miner with no claimID and no --all flag
|
||||
if len(miners) == 1 && cctx.Args().Len() == 0 && !all {
|
||||
return xerrors.Errorf("must specify either --all flag or claim IDs to extend in argument")
|
||||
}
|
||||
|
||||
// Multiple Miner with claimIDs
|
||||
if len(miners) > 1 && cctx.Args().Len() > 0 {
|
||||
return xerrors.Errorf("either specify multiple miner IDs or multiple arguments")
|
||||
}
|
||||
|
||||
// Multiple Miner with no claimID and no --all flag
|
||||
if len(miners) > 1 && cctx.Args().Len() == 0 && !all {
|
||||
return xerrors.Errorf("must specify --all flag with multiple miner IDs")
|
||||
}
|
||||
|
||||
// Tmax can't be more than policy max
|
||||
if tmax > verifregtypes13.MaximumVerifiedAllocationTerm {
|
||||
return xerrors.Errorf("specified term-max %d is larger than %d maximum allowed by verified regirty actor policy", tmax, verifregtypes13.MaximumVerifiedAllocationTerm)
|
||||
}
|
||||
|
||||
api, closer, err := GetFullNodeAPIV1(cctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get full node api: %s", err)
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
clientAddr, err := address.NewFromString(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
claimMap := make(map[verifregtypes13.ClaimId]ProvInfo)
|
||||
|
||||
// If no miners and arguments are present
|
||||
if len(miners) == 0 && cctx.Args().Len() > 0 {
|
||||
for _, arg := range cctx.Args().Slice() {
|
||||
detail := strings.Split(arg, "=")
|
||||
if len(detail) > 2 {
|
||||
return xerrors.Errorf("incorrect argument format: %s", detail)
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(detail[1], 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to parse the claim ID for %s for argument %s: %s", detail[0], detail, err)
|
||||
}
|
||||
|
||||
maddr, err := address.NewFromString(detail[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify that minerID exists
|
||||
_, err = api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mid, err := address.IDFromAddress(maddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pi := ProvInfo{
|
||||
Addr: maddr,
|
||||
ID: abi.ActorID(mid),
|
||||
}
|
||||
|
||||
claimMap[verifregtypes13.ClaimId(n)] = pi
|
||||
}
|
||||
}
|
||||
|
||||
// If 1 miner ID and multiple arguments
|
||||
if len(miners) == 1 && cctx.Args().Len() > 0 && !all {
|
||||
for _, arg := range cctx.Args().Slice() {
|
||||
detail := strings.Split(arg, "=")
|
||||
if len(detail) > 1 {
|
||||
return xerrors.Errorf("incorrect argument format %s. Must provide only claim IDs with single miner ID", detail)
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(detail[0], 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to parse the claim ID for %s for argument %s: %s", detail[0], detail, err)
|
||||
}
|
||||
|
||||
claimMap[verifregtypes13.ClaimId(n)] = ProvInfo{}
|
||||
}
|
||||
}
|
||||
|
||||
msgs, err := CreateExtendClaimMsg(ctx, api, claimMap, miners, clientAddr, abi.ChainEpoch(tmax), all, cctx.Bool("assume-yes"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If not msgs are found then no claims can be extended
|
||||
if msgs == nil {
|
||||
fmt.Println("No eligible claims to extend")
|
||||
return nil
|
||||
}
|
||||
|
||||
// MpoolBatchPushMessage method will take care of gas estimation and funds check
|
||||
smsgs, err := api.MpoolBatchPushMessage(ctx, msgs, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for msgs to get mined into a block
|
||||
eg := errgroup.Group{}
|
||||
eg.SetLimit(10)
|
||||
for _, msg := range smsgs {
|
||||
msg := msg
|
||||
eg.Go(func() error {
|
||||
wait, err := api.StateWaitMsg(ctx, msg.Cid(), uint64(cctx.Int("confidence")), 2000, true)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("timeout waiting for message to land on chain %s", wait.Message)
|
||||
|
||||
}
|
||||
|
||||
if wait.Receipt.ExitCode.IsError() {
|
||||
return xerrors.Errorf("failed to execute message %s: %s", wait.Message, wait.Receipt.ExitCode)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
},
|
||||
}
|
||||
|
||||
type ProvInfo struct {
|
||||
Addr address.Address
|
||||
ID abi.ActorID
|
||||
}
|
||||
|
||||
// CreateExtendClaimMsg creates extend message[s] based on the following conditions
|
||||
// 1. Extend all claims for a miner ID
|
||||
// 2. Extend all claims for multiple miner IDs
|
||||
// 3. Extend specified claims for a miner ID
|
||||
// 4. Extend specific claims for specific miner ID
|
||||
// 5. Extend all claims for a miner ID with different client address (2 messages)
|
||||
// 6. Extend all claims for multiple miner IDs with different client address (2 messages)
|
||||
// 7. Extend specified claims for a miner ID with different client address (2 messages)
|
||||
// 8. Extend specific claims for specific miner ID with different client address (2 messages)
|
||||
func CreateExtendClaimMsg(ctx context.Context, api api.FullNode, pcm map[verifregtypes13.ClaimId]ProvInfo, miners []string, wallet address.Address, tmax abi.ChainEpoch, all, assumeYes bool) ([]*types.Message, error) {
|
||||
|
||||
ac, err := api.StateLookupID(ctx, wallet, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := address.IDFromAddress(ac)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("converting wallet address to ID: %w", err)
|
||||
}
|
||||
|
||||
wid := abi.ActorID(w)
|
||||
|
||||
head, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var terms []verifregtypes13.ClaimTerm
|
||||
var newClaims []verifregtypes13.ClaimExtensionRequest
|
||||
rDataCap := big.NewInt(0)
|
||||
|
||||
// If --all is set
|
||||
if all {
|
||||
for _, id := range miners {
|
||||
maddr, err := address.NewFromString(id)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parsing miner %s: %s", id, err)
|
||||
}
|
||||
mid, err := address.IDFromAddress(maddr)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("converting miner address to miner ID: %s", err)
|
||||
}
|
||||
claims, err := api.StateGetClaims(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting claims for miner %s: %s", maddr, err)
|
||||
}
|
||||
for claimID, claim := range claims {
|
||||
claimID := claimID
|
||||
claim := claim
|
||||
if claim.TermMax < tmax && claim.TermStart+claim.TermMax > head.Height() {
|
||||
// If client is not same - needs to burn datacap
|
||||
if claim.Client != wid {
|
||||
newClaims = append(newClaims, verifregtypes13.ClaimExtensionRequest{
|
||||
Claim: verifregtypes13.ClaimId(claimID),
|
||||
Provider: abi.ActorID(mid),
|
||||
TermMax: tmax,
|
||||
})
|
||||
rDataCap.Add(big.NewInt(int64(claim.Size)).Int, rDataCap.Int)
|
||||
continue
|
||||
}
|
||||
terms = append(terms, verifregtypes13.ClaimTerm{
|
||||
ClaimId: verifregtypes13.ClaimId(claimID),
|
||||
TermMax: tmax,
|
||||
Provider: abi.ActorID(mid),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single miner and specific claims
|
||||
if len(miners) == 1 && len(pcm) > 0 {
|
||||
maddr, err := address.NewFromString(miners[0])
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parsing miner %s: %s", miners[0], err)
|
||||
}
|
||||
mid, err := address.IDFromAddress(maddr)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("converting miner address to miner ID: %s", err)
|
||||
}
|
||||
claims, err := api.StateGetClaims(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting claims for miner %s: %s", maddr, err)
|
||||
}
|
||||
|
||||
for claimID := range pcm {
|
||||
claimID := claimID
|
||||
claim, ok := claims[verifregtypes9.ClaimId(claimID)]
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("claim %d not found for provider %s", claimID, miners[0])
|
||||
}
|
||||
if claim.TermMax < tmax && claim.TermStart+claim.TermMax > head.Height() {
|
||||
// If client is not same - needs to burn datacap
|
||||
if claim.Client != wid {
|
||||
newClaims = append(newClaims, verifregtypes13.ClaimExtensionRequest{
|
||||
Claim: claimID,
|
||||
Provider: abi.ActorID(mid),
|
||||
TermMax: tmax,
|
||||
})
|
||||
rDataCap.Add(big.NewInt(int64(claim.Size)).Int, rDataCap.Int)
|
||||
continue
|
||||
}
|
||||
terms = append(terms, verifregtypes13.ClaimTerm{
|
||||
ClaimId: claimID,
|
||||
TermMax: tmax,
|
||||
Provider: abi.ActorID(mid),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(miners) == 0 && len(pcm) > 0 {
|
||||
for claimID, prov := range pcm {
|
||||
prov := prov
|
||||
claimID := claimID
|
||||
claim, err := api.StateGetClaim(ctx, prov.Addr, verifregtypes9.ClaimId(claimID), types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not load the claim %d: %s", claimID, err)
|
||||
}
|
||||
if claim == nil {
|
||||
return nil, xerrors.Errorf("claim %d not found in the actor state", claimID)
|
||||
}
|
||||
if claim.TermMax < tmax && claim.TermStart+claim.TermMax > head.Height() {
|
||||
// If client is not same - needs to burn datacap
|
||||
if claim.Client != wid {
|
||||
newClaims = append(newClaims, verifregtypes13.ClaimExtensionRequest{
|
||||
Claim: claimID,
|
||||
Provider: prov.ID,
|
||||
TermMax: tmax,
|
||||
})
|
||||
rDataCap.Add(big.NewInt(int64(claim.Size)).Int, rDataCap.Int)
|
||||
continue
|
||||
}
|
||||
terms = append(terms, verifregtypes13.ClaimTerm{
|
||||
ClaimId: claimID,
|
||||
TermMax: tmax,
|
||||
Provider: prov.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var msgs []*types.Message
|
||||
|
||||
if len(terms) > 0 {
|
||||
params, err := actors.SerializeParams(&verifregtypes13.ExtendClaimTermsParams{
|
||||
Terms: terms,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to searialise the parameters: %s", err)
|
||||
}
|
||||
|
||||
oclaimMsg := &types.Message{
|
||||
To: verifreg.Address,
|
||||
From: wallet,
|
||||
Method: verifreg.Methods.ExtendClaimTerms,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
msgs = append(msgs, oclaimMsg)
|
||||
}
|
||||
|
||||
if len(newClaims) > 0 {
|
||||
// Get datacap balance
|
||||
aDataCap, err := api.StateVerifiedClientStatus(ctx, wallet, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if aDataCap == nil {
|
||||
return nil, xerrors.Errorf("wallet %s does not have any datacap", wallet)
|
||||
}
|
||||
|
||||
// Check that we have enough data cap to make the allocation
|
||||
if rDataCap.GreaterThan(big.NewInt(aDataCap.Int64())) {
|
||||
return nil, xerrors.Errorf("requested datacap %s is greater then the available datacap %s", rDataCap, aDataCap)
|
||||
}
|
||||
|
||||
ncparams, err := actors.SerializeParams(&verifregtypes13.AllocationRequests{
|
||||
Extensions: newClaims,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to searialise the parameters: %s", err)
|
||||
}
|
||||
|
||||
transferParams, err := actors.SerializeParams(&datacap2.TransferParams{
|
||||
To: builtin.VerifiedRegistryActorAddr,
|
||||
Amount: big.Mul(rDataCap, builtin.TokenPrecision),
|
||||
OperatorData: ncparams,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to serialize transfer parameters: %s", err)
|
||||
}
|
||||
|
||||
nclaimMsg := &types.Message{
|
||||
To: builtin.DatacapActorAddr,
|
||||
From: wallet,
|
||||
Method: datacap.Methods.TransferExported,
|
||||
Params: transferParams,
|
||||
Value: big.Zero(),
|
||||
}
|
||||
|
||||
if !assumeYes {
|
||||
out := fmt.Sprintf("Some of the specified allocation have a different client address and will require %d Datacap to extend. Proceed? Yes [Y/y] / No [N/n], Ctrl+C (^C) to exit", rDataCap.Int)
|
||||
validate := func(input string) error {
|
||||
if strings.EqualFold(input, "y") || strings.EqualFold(input, "yes") {
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(input, "n") || strings.EqualFold(input, "no") {
|
||||
return nil
|
||||
}
|
||||
return errors.New("incorrect input")
|
||||
}
|
||||
|
||||
templates := &promptui.PromptTemplates{
|
||||
Prompt: "{{ . }} ",
|
||||
Valid: "{{ . | green }} ",
|
||||
Invalid: "{{ . | red }} ",
|
||||
Success: "{{ . | cyan | bold }} ",
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: out,
|
||||
Templates: templates,
|
||||
Validate: validate,
|
||||
}
|
||||
|
||||
input, err := prompt.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(strings.ToLower(input), "n") {
|
||||
fmt.Println("Dropping the extension for claims that require Datacap")
|
||||
return msgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
msgs = append(msgs, nclaimMsg)
|
||||
}
|
||||
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user