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:
parent
2e75f3b796
commit
02a8848b54
@ -3,6 +3,7 @@
|
|||||||
# UNRELEASED
|
# UNRELEASED
|
||||||
|
|
||||||
## New features
|
## New features
|
||||||
|
- feat: CLI: add claim-extend cli (#11711) ([filecoin-project/lotus#11711](https://github.com/filecoin-project/lotus/pull/11711))
|
||||||
|
|
||||||
## Improvements
|
## Improvements
|
||||||
|
|
||||||
|
435
cli/filplus.go
435
cli/filplus.go
@ -4,23 +4,30 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cbor "github.com/ipfs/go-ipld-cbor"
|
cbor "github.com/ipfs/go-ipld-cbor"
|
||||||
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
actorstypes "github.com/filecoin-project/go-state-types/actors"
|
actorstypes "github.com/filecoin-project/go-state-types/actors"
|
||||||
"github.com/filecoin-project/go-state-types/big"
|
"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"
|
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"
|
verifregtypes9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
||||||
"github.com/filecoin-project/go-state-types/network"
|
"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/api/v0api"
|
||||||
"github.com/filecoin-project/lotus/blockstore"
|
"github.com/filecoin-project/lotus/blockstore"
|
||||||
"github.com/filecoin-project/lotus/build"
|
"github.com/filecoin-project/lotus/build"
|
||||||
@ -47,6 +54,7 @@ var filplusCmd = &cli.Command{
|
|||||||
filplusListClaimsCmd,
|
filplusListClaimsCmd,
|
||||||
filplusRemoveExpiredAllocationsCmd,
|
filplusRemoveExpiredAllocationsCmd,
|
||||||
filplusRemoveExpiredClaimsCmd,
|
filplusRemoveExpiredClaimsCmd,
|
||||||
|
filplusExtendClaimCmd,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -923,3 +931,430 @@ var filplusSignRemoveDataCapProposal = &cli.Command{
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -1192,6 +1192,7 @@ COMMANDS:
|
|||||||
list-claims List claims available in verified registry actor or made by provider if specified
|
list-claims List claims available in verified registry actor or made by provider if specified
|
||||||
remove-expired-allocations remove expired allocations (if no allocations are specified all eligible allocations are removed)
|
remove-expired-allocations remove expired allocations (if no allocations are specified all eligible allocations are removed)
|
||||||
remove-expired-claims remove expired claims (if no claims are specified all eligible claims are removed)
|
remove-expired-claims remove expired claims (if no claims are specified all eligible claims are removed)
|
||||||
|
extend-claim extend claim expiration (TermMax)
|
||||||
help, h Shows a list of commands or help for one command
|
help, h Shows a list of commands or help for one command
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
@ -1325,6 +1326,24 @@ OPTIONS:
|
|||||||
--help, -h show help
|
--help, -h show help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### lotus filplus extend-claim
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
lotus filplus extend-claim - extend claim expiration (TermMax)
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
lotus filplus extend-claim [command options] <claim1> <claim2> ... or <miner1=claim1> <miner2=claims2> ...
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--term-max value, --tmax value The maximum period for which a provider can earn quality-adjusted power for the piece (epochs). Default is 5 years. (default: 5256000)
|
||||||
|
--client value the client address that will used to send the message
|
||||||
|
--all automatically extend TermMax of all claims for specified miner[s] to --term-max (default: 5 years from claim start epoch) (default: false)
|
||||||
|
--miner value, -m value, --provider value, -p value [ --miner value, -m value, --provider value, -p value ] storage provider address[es]
|
||||||
|
--assume-yes, -y, --yes automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively (default: false)
|
||||||
|
--confidence value number of block confirmations to wait for (default: 5)
|
||||||
|
--help, -h show help
|
||||||
|
```
|
||||||
|
|
||||||
## lotus paych
|
## lotus paych
|
||||||
```
|
```
|
||||||
NAME:
|
NAME:
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/chain/actors/builtin/verifreg"
|
"github.com/filecoin-project/lotus/chain/actors/builtin/verifreg"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/chain/wallet/key"
|
"github.com/filecoin-project/lotus/chain/wallet/key"
|
||||||
|
"github.com/filecoin-project/lotus/cli"
|
||||||
"github.com/filecoin-project/lotus/itests/kit"
|
"github.com/filecoin-project/lotus/itests/kit"
|
||||||
"github.com/filecoin-project/lotus/lib/must"
|
"github.com/filecoin-project/lotus/lib/must"
|
||||||
"github.com/filecoin-project/lotus/storage/pipeline/piece"
|
"github.com/filecoin-project/lotus/storage/pipeline/piece"
|
||||||
@ -108,7 +109,8 @@ func TestOnboardRawPieceVerified_WithActorEvents(t *testing.T) {
|
|||||||
|
|
||||||
/* --- Setup verified registry and client and allocate datacap to client */
|
/* --- Setup verified registry and client and allocate datacap to client */
|
||||||
|
|
||||||
verifierAddr, verifiedClientAddr := ddoVerifiedSetupVerifiedClient(ctx, t, client, rootKey, verifierKey, verifiedClientKey)
|
verifierAddr, verifiedClientAddrses := ddoVerifiedSetupVerifiedClient(ctx, t, client, rootKey, verifierKey, []*key.Key{verifiedClientKey})
|
||||||
|
verifiedClientAddr := verifiedClientAddrses[0]
|
||||||
|
|
||||||
/* --- Prepare piece for onboarding --- */
|
/* --- Prepare piece for onboarding --- */
|
||||||
|
|
||||||
@ -121,7 +123,7 @@ func TestOnboardRawPieceVerified_WithActorEvents(t *testing.T) {
|
|||||||
|
|
||||||
/* --- Allocate datacap for the piece by the verified client --- */
|
/* --- Allocate datacap for the piece by the verified client --- */
|
||||||
|
|
||||||
clientId, allocationId := ddoVerifiedSetupAllocations(ctx, t, client, minerId, dc, verifiedClientAddr)
|
clientId, allocationId := ddoVerifiedSetupAllocations(ctx, t, client, minerId, dc, verifiedClientAddr, true, 0)
|
||||||
|
|
||||||
head, err := client.ChainHead(ctx)
|
head, err := client.ChainHead(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -396,40 +398,57 @@ func ddoVerifiedSetupAllocations(
|
|||||||
minerId uint64,
|
minerId uint64,
|
||||||
dc abi.PieceInfo,
|
dc abi.PieceInfo,
|
||||||
verifiedClientAddr address.Address,
|
verifiedClientAddr address.Address,
|
||||||
|
setupBorkAlloc bool,
|
||||||
|
tmax abi.ChainEpoch,
|
||||||
) (clientID abi.ActorID, allocationID verifregtypes13.AllocationId) {
|
) (clientID abi.ActorID, allocationID verifregtypes13.AllocationId) {
|
||||||
|
if tmax == 0 {
|
||||||
|
tmax = verifregtypes13.MaximumVerifiedAllocationTerm
|
||||||
|
}
|
||||||
|
|
||||||
head, err := node.ChainHead(ctx)
|
var requests []verifregtypes13.AllocationRequest
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// design this one to expire so we can observe allocation-removed
|
// design this one to expire so we can observe allocation-removed
|
||||||
|
if setupBorkAlloc {
|
||||||
|
head, err := node.ChainHead(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
expiringAllocationHeight := head.Height() + 100
|
expiringAllocationHeight := head.Height() + 100
|
||||||
allocationRequestBork := verifregtypes13.AllocationRequest{
|
allocationRequestBork := verifregtypes13.AllocationRequest{
|
||||||
Provider: abi.ActorID(minerId),
|
Provider: abi.ActorID(minerId),
|
||||||
Data: cid.MustParse("baga6ea4seaaqa"),
|
Data: cid.MustParse("baga6ea4seaaqa"),
|
||||||
Size: dc.Size,
|
Size: dc.Size,
|
||||||
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm,
|
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm,
|
||||||
TermMax: verifregtypes13.MaximumVerifiedAllocationTerm,
|
TermMax: tmax,
|
||||||
Expiration: expiringAllocationHeight,
|
Expiration: expiringAllocationHeight,
|
||||||
}
|
}
|
||||||
|
requests = append(requests, allocationRequestBork)
|
||||||
|
}
|
||||||
|
|
||||||
allocationRequest := verifregtypes13.AllocationRequest{
|
allocationRequest := verifregtypes13.AllocationRequest{
|
||||||
Provider: abi.ActorID(minerId),
|
Provider: abi.ActorID(minerId),
|
||||||
Data: dc.PieceCID,
|
Data: dc.PieceCID,
|
||||||
Size: dc.Size,
|
Size: dc.Size,
|
||||||
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm,
|
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm,
|
||||||
TermMax: verifregtypes13.MaximumVerifiedAllocationTerm,
|
TermMax: tmax,
|
||||||
Expiration: verifregtypes13.MaximumVerifiedAllocationExpiration,
|
Expiration: verifregtypes13.MaximumVerifiedAllocationExpiration,
|
||||||
}
|
}
|
||||||
|
requests = append(requests, allocationRequest)
|
||||||
|
|
||||||
allocationRequests := verifregtypes13.AllocationRequests{
|
allocationRequests := verifregtypes13.AllocationRequests{
|
||||||
Allocations: []verifregtypes13.AllocationRequest{allocationRequestBork, allocationRequest},
|
Allocations: requests,
|
||||||
}
|
}
|
||||||
|
|
||||||
receiverParams, aerr := actors.SerializeParams(&allocationRequests)
|
receiverParams, aerr := actors.SerializeParams(&allocationRequests)
|
||||||
require.NoError(t, aerr)
|
require.NoError(t, aerr)
|
||||||
|
|
||||||
|
var amt abi.TokenAmount
|
||||||
|
amt = big.Mul(big.NewInt(int64(dc.Size)), builtin.TokenPrecision)
|
||||||
|
if setupBorkAlloc {
|
||||||
|
amt = big.Mul(big.NewInt(int64(dc.Size*2)), builtin.TokenPrecision)
|
||||||
|
}
|
||||||
|
|
||||||
transferParams, aerr := actors.SerializeParams(&datacap2.TransferParams{
|
transferParams, aerr := actors.SerializeParams(&datacap2.TransferParams{
|
||||||
To: builtin.VerifiedRegistryActorAddr,
|
To: builtin.VerifiedRegistryActorAddr,
|
||||||
Amount: big.Mul(big.NewInt(int64(dc.Size*2)), builtin.TokenPrecision),
|
Amount: amt,
|
||||||
OperatorData: receiverParams,
|
OperatorData: receiverParams,
|
||||||
})
|
})
|
||||||
require.NoError(t, aerr)
|
require.NoError(t, aerr)
|
||||||
@ -452,7 +471,11 @@ func ddoVerifiedSetupAllocations(
|
|||||||
// check that we have an allocation
|
// check that we have an allocation
|
||||||
allocations, err := node.StateGetAllocations(ctx, verifiedClientAddr, types.EmptyTSK)
|
allocations, err := node.StateGetAllocations(ctx, verifiedClientAddr, types.EmptyTSK)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
if setupBorkAlloc {
|
||||||
require.Len(t, allocations, 2) // allocation waiting to be claimed
|
require.Len(t, allocations, 2) // allocation waiting to be claimed
|
||||||
|
} else {
|
||||||
|
require.Len(t, allocations, 1) // allocation waiting to be claimed
|
||||||
|
}
|
||||||
|
|
||||||
for key, value := range allocations {
|
for key, value := range allocations {
|
||||||
if value.Data == dc.PieceCID {
|
if value.Data == dc.PieceCID {
|
||||||
@ -603,18 +626,21 @@ func ddoVerifiedBuildActorEventsFromMessages(ctx context.Context, t *testing.T,
|
|||||||
return actorEvents
|
return actorEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
func ddoVerifiedSetupVerifiedClient(ctx context.Context, t *testing.T, client *kit.TestFullNode, rootKey *key.Key, verifierKey *key.Key, verifiedClientKey *key.Key) (address.Address, address.Address) {
|
func ddoVerifiedSetupVerifiedClient(ctx context.Context, t *testing.T, client *kit.TestFullNode, rootKey *key.Key, verifierKey *key.Key, verifiedClientKeys []*key.Key) (verifierAddr address.Address, ret []address.Address) {
|
||||||
// import the root key.
|
// import the root key.
|
||||||
rootAddr, err := client.WalletImport(ctx, &rootKey.KeyInfo)
|
rootAddr, err := client.WalletImport(ctx, &rootKey.KeyInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// import the verifiers' keys.
|
// import the verifiers' keys.
|
||||||
verifierAddr, err := client.WalletImport(ctx, &verifierKey.KeyInfo)
|
verifierAddr, err = client.WalletImport(ctx, &verifierKey.KeyInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// import the verified client's key.
|
// import the verified client's key.
|
||||||
verifiedClientAddr, err := client.WalletImport(ctx, &verifiedClientKey.KeyInfo)
|
for _, k := range verifiedClientKeys {
|
||||||
|
verifiedClientAddr, err := client.WalletImport(ctx, &k.KeyInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
ret = append(ret, verifiedClientAddr)
|
||||||
|
}
|
||||||
|
|
||||||
allowance := big.NewInt(100000000000)
|
allowance := big.NewInt(100000000000)
|
||||||
params, aerr := actors.SerializeParams(&verifregtypes13.AddVerifierParams{Address: verifierAddr, Allowance: allowance})
|
params, aerr := actors.SerializeParams(&verifregtypes13.AddVerifierParams{Address: verifierAddr, Allowance: allowance})
|
||||||
@ -639,10 +665,11 @@ func ddoVerifiedSetupVerifiedClient(ctx context.Context, t *testing.T, client *k
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, allowance, *verifierAllowance)
|
require.Equal(t, allowance, *verifierAllowance)
|
||||||
|
|
||||||
// assign datacap to a client
|
// assign datacap to clients
|
||||||
|
for _, ad := range ret {
|
||||||
initialDatacap := big.NewInt(10000)
|
initialDatacap := big.NewInt(10000)
|
||||||
|
|
||||||
params, aerr = actors.SerializeParams(&verifregtypes13.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: initialDatacap})
|
params, aerr = actors.SerializeParams(&verifregtypes13.AddVerifiedClientParams{Address: ad, Allowance: initialDatacap})
|
||||||
require.NoError(t, aerr)
|
require.NoError(t, aerr)
|
||||||
|
|
||||||
msg = &types.Message{
|
msg = &types.Message{
|
||||||
@ -659,8 +686,9 @@ func ddoVerifiedSetupVerifiedClient(ctx context.Context, t *testing.T, client *k
|
|||||||
res, err = client.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true)
|
res, err = client.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, 0, res.Receipt.ExitCode)
|
require.EqualValues(t, 0, res.Receipt.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
return verifierAddr, verifiedClientAddr
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterEvents(events []*types.ActorEvent, key string) []*types.ActorEvent {
|
func filterEvents(events []*types.ActorEvent, key string) []*types.ActorEvent {
|
||||||
@ -711,3 +739,122 @@ func epochPtr(ei int64) *abi.ChainEpoch {
|
|||||||
ep := abi.ChainEpoch(ei)
|
ep := abi.ChainEpoch(ei)
|
||||||
return &ep
|
return &ep
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifiedDDOExtendClaim(t *testing.T) {
|
||||||
|
kit.QuietMiningLogs()
|
||||||
|
|
||||||
|
var (
|
||||||
|
blocktime = 2 * time.Millisecond
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
rootKey, err := key.GenerateKey(types.KTSecp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verifierKey, err := key.GenerateKey(types.KTSecp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verifiedClientKey1, err := key.GenerateKey(types.KTBLS)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
verifiedClientKey2, err := key.GenerateKey(types.KTBLS)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
unverifiedClient, err := key.GenerateKey(types.KTBLS)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bal, err := types.ParseFIL("100fil")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC(),
|
||||||
|
kit.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())),
|
||||||
|
kit.Account(verifierKey, abi.NewTokenAmount(bal.Int64())),
|
||||||
|
kit.Account(verifiedClientKey1, abi.NewTokenAmount(bal.Int64())),
|
||||||
|
kit.Account(verifiedClientKey2, abi.NewTokenAmount(bal.Int64())),
|
||||||
|
kit.Account(unverifiedClient, abi.NewTokenAmount(bal.Int64())),
|
||||||
|
)
|
||||||
|
|
||||||
|
/* --- Start mining --- */
|
||||||
|
|
||||||
|
ens.InterconnectAll().BeginMiningMustPost(blocktime)
|
||||||
|
|
||||||
|
minerId, err := address.IDFromAddress(miner.ActorAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
/* --- Setup verified registry and clients and allocate datacap to client */
|
||||||
|
|
||||||
|
_, verifiedClientAddrses := ddoVerifiedSetupVerifiedClient(ctx, t, client, rootKey, verifierKey, []*key.Key{verifiedClientKey1, verifiedClientKey2})
|
||||||
|
verifiedClientAddr1 := verifiedClientAddrses[0]
|
||||||
|
verifiedClientAddr2 := verifiedClientAddrses[1]
|
||||||
|
|
||||||
|
/* --- Prepare piece for onboarding --- */
|
||||||
|
|
||||||
|
pieceSize := abi.PaddedPieceSize(2048).Unpadded()
|
||||||
|
pieceData := make([]byte, pieceSize)
|
||||||
|
_, _ = rand.Read(pieceData)
|
||||||
|
|
||||||
|
dc, err := miner.ComputeDataCid(ctx, pieceSize, bytes.NewReader(pieceData))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
/* --- Allocate datacap for the piece by the verified client --- */
|
||||||
|
clientId, allocationId := ddoVerifiedSetupAllocations(ctx, t, client, minerId, dc, verifiedClientAddr1, false, builtin.EpochsInYear*3)
|
||||||
|
|
||||||
|
/* --- Onboard the piece --- */
|
||||||
|
|
||||||
|
_, _ = ddoVerifiedOnboardPiece(ctx, t, miner, clientId, allocationId, dc, pieceData)
|
||||||
|
|
||||||
|
oldclaim, err := client.StateGetClaim(ctx, miner.ActorAddr, verifreg.ClaimId(allocationId), types.EmptyTSK)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, oldclaim)
|
||||||
|
|
||||||
|
prov := cli.ProvInfo{
|
||||||
|
Addr: miner.ActorAddr,
|
||||||
|
ID: abi.ActorID(minerId),
|
||||||
|
}
|
||||||
|
|
||||||
|
pcm := make(map[verifregtypes13.ClaimId]cli.ProvInfo)
|
||||||
|
pcm[verifregtypes13.ClaimId(allocationId)] = prov
|
||||||
|
|
||||||
|
// Extend claim with same client
|
||||||
|
msgs, err := cli.CreateExtendClaimMsg(ctx, client.FullNode, pcm, []string{}, verifiedClientAddr1, (builtin.EpochsInYear*3)+3000, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, msgs)
|
||||||
|
require.Len(t, msgs, 1)
|
||||||
|
|
||||||
|
// MpoolBatchPushMessage method will take care of gas estimation and funds check
|
||||||
|
smsg, err := client.MpoolPushMessage(ctx, msgs[0], nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 1, 2000, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, wait.Receipt.ExitCode.IsSuccess())
|
||||||
|
|
||||||
|
newclaim, err := client.StateGetClaim(ctx, miner.ActorAddr, verifreg.ClaimId(allocationId), types.EmptyTSK)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, newclaim)
|
||||||
|
require.EqualValues(t, newclaim.TermMax-oldclaim.TermMax, 3000)
|
||||||
|
|
||||||
|
// Extend claim with non-verified client | should fail
|
||||||
|
_, err = cli.CreateExtendClaimMsg(ctx, client.FullNode, pcm, []string{}, unverifiedClient.Address, verifregtypes13.MaximumVerifiedAllocationTerm, false, true)
|
||||||
|
require.ErrorContains(t, err, "does not have any datacap")
|
||||||
|
|
||||||
|
// Extend all claim with verified client
|
||||||
|
msgs, err = cli.CreateExtendClaimMsg(ctx, client.FullNode, nil, []string{miner.ActorAddr.String()}, verifiedClientAddr2, verifregtypes13.MaximumVerifiedAllocationTerm, true, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, msgs, 1)
|
||||||
|
smsg, err = client.MpoolPushMessage(ctx, msgs[0], nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
wait, err = client.StateWaitMsg(ctx, smsg.Cid(), 1, 2000, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, wait.Receipt.ExitCode.IsSuccess())
|
||||||
|
|
||||||
|
// Extend all claims with lower TermMax
|
||||||
|
msgs, err = cli.CreateExtendClaimMsg(ctx, client.FullNode, pcm, []string{}, verifiedClientAddr2, builtin.EpochsInYear*4, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, msgs)
|
||||||
|
|
||||||
|
newclaim, err = client.StateGetClaim(ctx, miner.ActorAddr, verifreg.ClaimId(allocationId), types.EmptyTSK)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, newclaim)
|
||||||
|
require.EqualValues(t, newclaim.TermMax, verifregtypes13.MaximumVerifiedAllocationTerm)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user