diff --git a/CHANGELOG.md b/CHANGELOG.md index 4485fe8dc..89aad3563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # UNRELEASED ## New features +- feat: CLI: add claim-extend cli (#11711) ([filecoin-project/lotus#11711](https://github.com/filecoin-project/lotus/pull/11711)) ## Improvements diff --git a/cli/filplus.go b/cli/filplus.go index a4982cecb..fbb922a24 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -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: " ... or ...", + 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 +} diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 65dd92f01..ad04b68ec 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -1192,6 +1192,7 @@ COMMANDS: 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-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 OPTIONS: @@ -1325,6 +1326,24 @@ OPTIONS: --help, -h show help ``` +### lotus filplus extend-claim +``` +NAME: + lotus filplus extend-claim - extend claim expiration (TermMax) + +USAGE: + lotus filplus extend-claim [command options] ... or ... + +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 ``` NAME: diff --git a/itests/direct_data_onboard_verified_test.go b/itests/direct_data_onboard_verified_test.go index da1cac261..2ac6142c0 100644 --- a/itests/direct_data_onboard_verified_test.go +++ b/itests/direct_data_onboard_verified_test.go @@ -37,6 +37,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/types" "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/lib/must" "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 */ - 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 --- */ @@ -121,7 +123,7 @@ func TestOnboardRawPieceVerified_WithActorEvents(t *testing.T) { /* --- 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) require.NoError(t, err) @@ -396,40 +398,57 @@ func ddoVerifiedSetupAllocations( minerId uint64, dc abi.PieceInfo, verifiedClientAddr address.Address, + setupBorkAlloc bool, + tmax abi.ChainEpoch, ) (clientID abi.ActorID, allocationID verifregtypes13.AllocationId) { + if tmax == 0 { + tmax = verifregtypes13.MaximumVerifiedAllocationTerm + } - head, err := node.ChainHead(ctx) - require.NoError(t, err) + var requests []verifregtypes13.AllocationRequest // design this one to expire so we can observe allocation-removed - expiringAllocationHeight := head.Height() + 100 - allocationRequestBork := verifregtypes13.AllocationRequest{ - Provider: abi.ActorID(minerId), - Data: cid.MustParse("baga6ea4seaaqa"), - Size: dc.Size, - TermMin: verifregtypes13.MinimumVerifiedAllocationTerm, - TermMax: verifregtypes13.MaximumVerifiedAllocationTerm, - Expiration: expiringAllocationHeight, + if setupBorkAlloc { + head, err := node.ChainHead(ctx) + require.NoError(t, err) + expiringAllocationHeight := head.Height() + 100 + allocationRequestBork := verifregtypes13.AllocationRequest{ + Provider: abi.ActorID(minerId), + Data: cid.MustParse("baga6ea4seaaqa"), + Size: dc.Size, + TermMin: verifregtypes13.MinimumVerifiedAllocationTerm, + TermMax: tmax, + Expiration: expiringAllocationHeight, + } + requests = append(requests, allocationRequestBork) } + allocationRequest := verifregtypes13.AllocationRequest{ Provider: abi.ActorID(minerId), Data: dc.PieceCID, Size: dc.Size, TermMin: verifregtypes13.MinimumVerifiedAllocationTerm, - TermMax: verifregtypes13.MaximumVerifiedAllocationTerm, + TermMax: tmax, Expiration: verifregtypes13.MaximumVerifiedAllocationExpiration, } + requests = append(requests, allocationRequest) allocationRequests := verifregtypes13.AllocationRequests{ - Allocations: []verifregtypes13.AllocationRequest{allocationRequestBork, allocationRequest}, + Allocations: requests, } receiverParams, aerr := actors.SerializeParams(&allocationRequests) 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{ To: builtin.VerifiedRegistryActorAddr, - Amount: big.Mul(big.NewInt(int64(dc.Size*2)), builtin.TokenPrecision), + Amount: amt, OperatorData: receiverParams, }) require.NoError(t, aerr) @@ -452,7 +471,11 @@ func ddoVerifiedSetupAllocations( // check that we have an allocation allocations, err := node.StateGetAllocations(ctx, verifiedClientAddr, types.EmptyTSK) require.NoError(t, err) - require.Len(t, allocations, 2) // allocation waiting to be claimed + if setupBorkAlloc { + 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 { if value.Data == dc.PieceCID { @@ -603,18 +626,21 @@ func ddoVerifiedBuildActorEventsFromMessages(ctx context.Context, t *testing.T, 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. rootAddr, err := client.WalletImport(ctx, &rootKey.KeyInfo) require.NoError(t, err) // import the verifiers' keys. - verifierAddr, err := client.WalletImport(ctx, &verifierKey.KeyInfo) + verifierAddr, err = client.WalletImport(ctx, &verifierKey.KeyInfo) require.NoError(t, err) // import the verified client's key. - verifiedClientAddr, err := client.WalletImport(ctx, &verifiedClientKey.KeyInfo) - require.NoError(t, err) + for _, k := range verifiedClientKeys { + verifiedClientAddr, err := client.WalletImport(ctx, &k.KeyInfo) + require.NoError(t, err) + ret = append(ret, verifiedClientAddr) + } allowance := big.NewInt(100000000000) params, aerr := actors.SerializeParams(&verifregtypes13.AddVerifierParams{Address: verifierAddr, Allowance: allowance}) @@ -639,28 +665,30 @@ func ddoVerifiedSetupVerifiedClient(ctx context.Context, t *testing.T, client *k require.NoError(t, err) require.Equal(t, allowance, *verifierAllowance) - // assign datacap to a client - initialDatacap := big.NewInt(10000) + // assign datacap to clients + for _, ad := range ret { + initialDatacap := big.NewInt(10000) - params, aerr = actors.SerializeParams(&verifregtypes13.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: initialDatacap}) - require.NoError(t, aerr) + params, aerr = actors.SerializeParams(&verifregtypes13.AddVerifiedClientParams{Address: ad, Allowance: initialDatacap}) + require.NoError(t, aerr) - msg = &types.Message{ - From: verifierAddr, - To: verifreg.Address, - Method: verifreg.Methods.AddVerifiedClient, - Params: params, - Value: big.Zero(), + msg = &types.Message{ + From: verifierAddr, + To: verifreg.Address, + Method: verifreg.Methods.AddVerifiedClient, + Params: params, + Value: big.Zero(), + } + + sm, err = client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + res, err = client.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode) } - sm, err = client.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - res, err = client.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - require.NoError(t, err) - require.EqualValues(t, 0, res.Receipt.ExitCode) - - return verifierAddr, verifiedClientAddr + return } func filterEvents(events []*types.ActorEvent, key string) []*types.ActorEvent { @@ -711,3 +739,122 @@ func epochPtr(ei int64) *abi.ChainEpoch { ep := abi.ChainEpoch(ei) 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) +}