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:
LexLuthr 2024-03-20 13:34:52 +04:00 committed by GitHub
parent 2e75f3b796
commit 02a8848b54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 640 additions and 38 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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:

View File

@ -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
expiringAllocationHeight := head.Height() + 100 if setupBorkAlloc {
allocationRequestBork := verifregtypes13.AllocationRequest{ head, err := node.ChainHead(ctx)
Provider: abi.ActorID(minerId), require.NoError(t, err)
Data: cid.MustParse("baga6ea4seaaqa"), expiringAllocationHeight := head.Height() + 100
Size: dc.Size, allocationRequestBork := verifregtypes13.AllocationRequest{
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm, Provider: abi.ActorID(minerId),
TermMax: verifregtypes13.MaximumVerifiedAllocationTerm, Data: cid.MustParse("baga6ea4seaaqa"),
Expiration: expiringAllocationHeight, Size: dc.Size,
TermMin: verifregtypes13.MinimumVerifiedAllocationTerm,
TermMax: tmax,
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)
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 { 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 {
require.NoError(t, err) verifiedClientAddr, err := client.WalletImport(ctx, &k.KeyInfo)
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,28 +665,30 @@ 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
initialDatacap := big.NewInt(10000) for _, ad := range ret {
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{
From: verifierAddr, From: verifierAddr,
To: verifreg.Address, To: verifreg.Address,
Method: verifreg.Methods.AddVerifiedClient, Method: verifreg.Methods.AddVerifiedClient,
Params: params, Params: params,
Value: big.Zero(), 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) return
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
} }
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)
}