Skip to content

Commit da0f41d

Browse files
authored
fix: improve filter (#421)
* improve filter * fix allocate
1 parent 1f962a1 commit da0f41d

File tree

8 files changed

+216
-117
lines changed

8 files changed

+216
-117
lines changed

cmd/sptool/toolbox_deal_tools.go

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,15 @@ var allocateCmd = &cli.Command{
421421
Usage: "storage provider address[es]",
422422
Aliases: []string{"m", "provider", "p"},
423423
},
424-
&cli.StringSliceFlag{
425-
Name: "piece-info",
426-
Usage: "data piece-info[s] to create the allocation. The format must be --piece-info pieceCid1=pieceSize1 --piece-info pieceCid2=pieceSize2",
427-
Aliases: []string{"pi"},
424+
&cli.StringFlag{
425+
Name: "piece-cid",
426+
Usage: "data piece-cid to create the allocation",
427+
Aliases: []string{"piece"},
428+
},
429+
&cli.Int64Flag{
430+
Name: "piece-size",
431+
Usage: "piece size to create the allocation",
432+
Aliases: []string{"size"},
428433
},
429434
&cli.StringFlag{
430435
Name: "wallet",
@@ -457,7 +462,7 @@ var allocateCmd = &cli.Command{
457462
},
458463
&cli.StringFlag{
459464
Name: "piece-file",
460-
Usage: "file containing piece-info[s] to create the allocation. Each line in the file should be in the format 'pieceCid,pieceSize,miner,tmin,tmax,expiration'",
465+
Usage: "file containing piece information to create the allocation. Each line in the file should be in the format 'pieceCid,pieceSize,miner,tmin,tmax,expiration'",
461466
Aliases: []string{"pf"},
462467
},
463468
&cli.IntFlag{
@@ -490,18 +495,18 @@ var allocateCmd = &cli.Command{
490495

491496
pieceFile := cctx.String("piece-file")
492497
miners := cctx.StringSlice("miner")
493-
pinfos := cctx.StringSlice("piece-info")
498+
pcids := cctx.String("piece-cid")
494499

495-
if pieceFile == "" && len(pinfos) < 1 {
496-
return fmt.Errorf("must provide at least one --piece-info or use --piece-file")
500+
if pieceFile == "" && pcids == "" {
501+
return fmt.Errorf("must provide at least one --piece-cid or use --piece-file")
497502
}
498503

499504
if pieceFile == "" && len(miners) < 1 {
500505
return fmt.Errorf("must provide at least one miner address or use --piece-file")
501506
}
502507

503-
if pieceFile != "" && len(pinfos) > 0 {
504-
return fmt.Errorf("cannot use both --piece-info and --piece-file flags at once")
508+
if pieceFile != "" && pcids != "" {
509+
return fmt.Errorf("cannot use both --piece-cid and --piece-file flags at once")
505510
}
506511

507512
var pieceInfos []PieceInfos
@@ -590,44 +595,30 @@ var allocateCmd = &cli.Command{
590595
if err != nil {
591596
return fmt.Errorf("failed to convert miner address %w", err)
592597
}
593-
for _, p := range cctx.StringSlice("piece-info") {
594-
pieceDetail := strings.Split(p, "=")
595-
if len(pieceDetail) != 2 {
596-
return fmt.Errorf("incorrect pieceInfo format: %s", pieceDetail)
597-
}
598-
599-
size, err := strconv.ParseInt(pieceDetail[1], 10, 64)
600-
if err != nil {
601-
return fmt.Errorf("failed to parse the piece size for %s for pieceCid %s: %w", pieceDetail[0], pieceDetail[1], err)
602-
}
603-
pcid, err := cid.Parse(pieceDetail[0])
604-
if err != nil {
605-
return fmt.Errorf("failed to parse the pieceCid for %s: %w", pieceDetail[0], err)
606-
}
607-
608-
tmin := abi.ChainEpoch(cctx.Int64("term-min"))
609-
610-
tmax := abi.ChainEpoch(cctx.Int64("term-max"))
611-
612-
exp := abi.ChainEpoch(cctx.Int64("expiration"))
613-
if exp == verifreg13.MaximumVerifiedAllocationExpiration {
614-
exp -= 5
615-
}
616-
617-
if tmax < tmin {
618-
return fmt.Errorf("maximum duration %d cannot be smaller than minimum duration %d", tmax, tmin)
619-
}
620-
621-
pieceInfos = append(pieceInfos, PieceInfos{
622-
Cid: pcid,
623-
Size: size,
624-
Miner: abi.ActorID(mid),
625-
MinerAddr: maddr,
626-
Tmin: tmin,
627-
Tmax: tmax,
628-
Exp: exp,
629-
})
598+
pcid, err := cid.Parse(cctx.String("piece-cid"))
599+
if err != nil {
600+
return fmt.Errorf("failed to parse pieceCid %w", err)
601+
}
602+
size := cctx.Int64("piece-size")
603+
604+
tmin := abi.ChainEpoch(cctx.Int64("term-min"))
605+
606+
tmax := abi.ChainEpoch(cctx.Int64("term-max"))
607+
608+
exp := abi.ChainEpoch(cctx.Int64("expiration"))
609+
if exp == verifreg13.MaximumVerifiedAllocationExpiration {
610+
exp -= 5
630611
}
612+
613+
pieceInfos = append(pieceInfos, PieceInfos{
614+
Cid: pcid,
615+
Size: size,
616+
Miner: abi.ActorID(mid),
617+
MinerAddr: maddr,
618+
Tmin: tmin,
619+
Tmax: tmax,
620+
Exp: exp,
621+
})
631622
}
632623
}
633624

docker/piece-server/sample/ddo-deal.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ miner_actor=$(lotus state list-miners | grep -v t01000)
1717

1818
mv /var/lib/curio-client/data/$PAYLOAD_CID.car /var/lib/curio-client/data/$COMMP_CID
1919

20-
sptool --actor t01000 toolbox mk12-client allocate -y -p $miner_actor --pi $COMMP_CID=$PIECE --confidence 0
20+
sptool --actor t01000 toolbox mk12-client allocate -y -p $miner_actor --piece-cid $COMMP_CID --piece-size $PIECE --confidence 0
2121

2222
CLIENT=$(sptool --actor t01000 toolbox mk12-client wallet default)
2323

documentation/en/curio-cli/sptool.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ DESCRIPTION:
641641
642642
OPTIONS:
643643
--miner value, -m value, --provider value, -p value [ --miner value, -m value, --provider value, -p value ] storage provider address[es]
644-
--piece-info value, --pi value [ --piece-info value, --pi value ] data piece-info[s] to create the allocation. The format must be --piece-info pieceCid1=pieceSize1 --piece-info pieceCid2=pieceSize2
644+
--piece-cid value, --piece value data piece-cid to create the allocation
645+
--piece-size value, --size value piece size to create the allocation (default: 0)
645646
--wallet value the wallet address that will used create the allocation
646647
--quiet do not print the allocation list (default: false)
647648
--term-min value, --tmin value The minimum duration which the provider must commit to storing the piece to avoid early-termination penalties (epochs).
@@ -650,7 +651,7 @@ OPTIONS:
650651
Default is 5 years. (default: 5256000)
651652
--expiration value The latest epoch by which a provider must commit data before the allocation expires (epochs).
652653
Default is 60 days. (default: 172800)
653-
--piece-file value, --pf value file containing piece-info[s] to create the allocation. Each line in the file should be in the format 'pieceCid,pieceSize,miner,tmin,tmax,expiration'
654+
--piece-file value, --pf value file containing piece information to create the allocation. Each line in the file should be in the format 'pieceCid,pieceSize,miner,tmin,tmax,expiration'
654655
--batch-size value number of extend requests per batch. If set incorrectly, this will lead to out of gas error (default: 500)
655656
--confidence value number of block confirmations to wait for (default: 5)
656657
--assume-yes, -y, --yes automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively (default: false)

documentation/en/curio-market/storage-market.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,23 @@ curio market seal --actor <actor address> <sector>
239239
* **--actor**: Specifies the actor address.
240240

241241
Consequences: Sealing early can speed up the process, but it may result in inefficiencies if all deals are not batched correctly.
242+
243+
## Offline Verified DDO deals
244+
Curio only supports offline verified DDO deals as of now. The allocation must be created by the client for the piece and handed over to the SP alongside the data.
245+
246+
247+
### How to create allocation
248+
Clients can create allocation using the `sptool toolbox` or other methods.
249+
250+
```shell
251+
sptool --actor t01000 toolbox mk12-client allocate -p <MINER ID> --piece-cid <COMMP> --piece-size <PIECE SIZE>
252+
```
253+
254+
### Start a DDO deal
255+
Storage providers can onboard the DDO deal using the below command.
256+
257+
```shell
258+
curio market ddo --actor <MINER ID> <client-address> <allocation-id>
259+
```
260+
261+
Since this is an offline deal, user must either make the data available via PieceLocator or add a data URL for this offline deal.

documentation/en/installation.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ To build Curio, you need a working installation of [Go](https://golang.org/dl/):
9898
Example of an OLD version's CLI download:
9999
100100
```shell
101-
wget -c https://golang.org/dl/go1.22.3.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local
101+
wget -c https://golang.org/dl/go1.23.6.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local
102102
```
103103
104104
{% hint style="info" %}
@@ -181,9 +181,9 @@ This will put `curio` in `/usr/local/bin`. `curio` will use the `$HOME/.curio` f
181181
Run `curio --version`
182182
183183
```md
184-
curio version 1.23.0+mainnet+git_ae625a5_2024-08-21T15:21:45+04:00
184+
curio version 1.24.5-rc1+mainnet+git_214226e7_2025-02-19T17:02:54+04:00
185185
# or
186-
curio version 1.23.0+calibnet+git_ae625a5_2024-08-21T15:21:45+04:00
186+
curio version 1.24.5-rc1+calibnet+git_214226e7_2025-02-19T17:02:54+04:00
187187
```
188188
189189
1. You should now have Curio installed. You can now [finish setting up the Curio node](https://lotus.filecoin.io/storage-providers/curio/setup/).
@@ -291,9 +291,9 @@ The installation instructions are different depending on which CPU is in your Ma
291291
6. Run `curio --version`
292292
293293
```md
294-
curio version 1.23.0+mainnet+git_ae625a5_2024-08-21T15:21:45+04:00
294+
curio version 1.24.5-rc1+mainnet+git_214226e7_2025-02-19T17:02:54+04:00
295295
# or
296-
curio version 1.23.0+calibnet+git_ae625a5_2024-08-21T15:21:45+04:00
296+
curio version 1.24.5-rc1+calibnet+git_214226e7_2025-02-19T17:02:54+04:00
297297
```
298298
7. You should now have Curio installed. You can now [set up a new Curio cluster or migrating from Lotus-Miner](https://lotus.filecoin.io/storage-providers/curio/setup/).
299299

market/mk12/mk12.go

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package mk12
88
import (
99
"bytes"
1010
"context"
11+
"database/sql"
1112
"encoding/json"
1213
"errors"
1314
"fmt"
@@ -49,6 +50,7 @@ type MK12API interface {
4950
StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error)
5051
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error)
5152
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
53+
StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error)
5254
WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error)
5355
}
5456

@@ -780,14 +782,18 @@ FROM joined
780782
func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *validationError {
781783

782784
var clientRules []struct {
785+
Name string `db:"name"`
783786
Wallets []string `db:"wallets"`
784-
PeerIDs []string `db:"peer_id"`
787+
PeerIDs []string `db:"peer_ids"`
785788
PricingFilters []string `db:"pricing_filters"`
786789
MaxDealsPerHour int64 `db:"max_deals_per_hour"`
787790
MaxDealSizePerHour int64 `db:"max_deal_size_per_hour"`
788791
}
789792

793+
var skipDefaultAsk bool
794+
790795
err := m.db.Select(ctx, &clientRules, `SELECT
796+
name,
791797
wallets,
792798
peer_ids,
793799
pricing_filters,
@@ -799,22 +805,30 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid
799805
return &validationError{error: xerrors.Errorf("failed to query the client rules from DB: %w", err)}
800806
}
801807

808+
log.Debugw("Applicable Deal Client Filters", "Deal", deal.DealUuid.String(), "Client rules", "client_rules", clientRules)
809+
802810
// Check if we have any client rules and match them to client details
803811
for i := range clientRules {
804-
if lo.Contains(clientRules[i].Wallets, deal.ClientDealProposal.Proposal.Client.String()) || lo.Contains(clientRules[i].PeerIDs, deal.ClientPeerID.String()) {
812+
client, err := m.api.StateLookupID(ctx, deal.ClientDealProposal.Proposal.Client, types.EmptyTSK)
813+
if err != nil {
814+
return &validationError{error: xerrors.Errorf("wallet not found: %w", err)}
815+
}
816+
if lo.Contains(clientRules[i].Wallets, deal.ClientDealProposal.Proposal.Client.String()) || lo.Contains(clientRules[i].PeerIDs, deal.ClientPeerID.String()) || lo.Contains(clientRules[i].Wallets, client.String()) {
805817
// Check if Cumulative Storage size has not exceeded the specified limit
806818
if clientRules[i].MaxDealSizePerHour > 0 {
807819
var size int64
808820
err = m.db.QueryRow(ctx, `SELECT COALESCE(SUM(piece_size), 0) AS total_piece_size
809821
FROM market_mk12_deals
810822
WHERE created_at >= NOW() - INTERVAL '1 hour'
811823
AND (
812-
client_peer_id = 'provided_client_peer_id'
813-
OR proposal->>'Client' = 'desired_client_value'
814-
)`).Scan(&size)
824+
client_peer_id = $1
825+
OR proposal->>'Client' = $2
826+
OR proposal->>'Client' = $3
827+
)`, deal.ClientPeerID.String(), deal.ClientDealProposal.Proposal.Client.String(), client.String()).Scan(&size)
815828
if err != nil {
816829
return &validationError{error: xerrors.Errorf("failed to query the cummulative size from DB: %w", err)}
817830
}
831+
log.Debugw("MaxDealSizePerHour Check", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "MaxDealSizePerHour", "size", size, "max", clientRules[i].MaxDealSizePerHour)
818832
if size > clientRules[i].MaxDealSizePerHour {
819833
return &validationError{reason: "deal rejected as cumulative size of deals in past 1 hour has reached the maximum allowed for the client, please retry in some time"}
820834
}
@@ -826,17 +840,21 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid
826840
WHERE created_at >= NOW() - INTERVAL '1 hour'
827841
AND (
828842
client_peer_id = $1
829-
OR proposal->>'Client' = $2)`, deal.ClientPeerID.String(),
830-
deal.ClientDealProposal.Proposal.Client.String()).Scan(&dealCount)
843+
OR proposal->>'Client' = $2
844+
OR proposal->>'Client' = $3)`, deal.ClientPeerID.String(), deal.ClientDealProposal.Proposal.Client.String(),
845+
client.String()).Scan(&dealCount)
831846
if err != nil {
832847
return &validationError{error: xerrors.Errorf("failed to query the deal count from DB: %w", err)}
833848
}
849+
log.Debugw("MaxDealsPerHour Check", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "MaxDealsPerHour", "count", dealCount, "max", clientRules[i].MaxDealsPerHour)
834850
if dealCount >= clientRules[i].MaxDealsPerHour {
835851
return &validationError{reason: "deal rejected as maximum allowed deals per hour limit has been reached for the client, please retry in some time"}
836852
}
837853
}
838854
// Apply pricing filters
839855
if len(clientRules[i].PricingFilters) > 0 {
856+
log.Debugw("Applicable Pricing Filters", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "Pricing Filters", clientRules[i].PricingFilters)
857+
skipDefaultAsk = true
840858
var priceFilters []struct {
841859
MinDur int64 `db:"min_duration_days"`
842860
MaxDur int64 `db:"max_duration_days"`
@@ -859,58 +877,70 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid
859877
}
860878
ret := new(validationError)
861879
for j := range priceFilters {
880+
log.Debugw("Applying Pricing Filter", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name,
881+
"Filter Verified", priceFilters[j].Verified, "Filter MinSize", priceFilters[j].MinSize, "Filter MaxSize", priceFilters[j].MaxSize,
882+
"Filter MinDur", priceFilters[j].MinDur, "Filter MaxDur", priceFilters[j].MaxDur, "Filter Price", priceFilters[j].Price,
883+
"Deal Verified", deal.ClientDealProposal.Proposal.VerifiedDeal, "Deal PieceSize", deal.ClientDealProposal.Proposal.PieceSize,
884+
"Deal Duration", deal.ClientDealProposal.Proposal.Duration, "Deal Price", deal.ClientDealProposal.Proposal.StoragePricePerEpoch)
862885
// Skip filters which are not meant for verified/unverified deals
863-
if !(deal.ClientDealProposal.Proposal.VerifiedDeal && priceFilters[j].Verified) {
886+
if deal.ClientDealProposal.Proposal.VerifiedDeal != priceFilters[j].Verified {
864887
continue
865888
}
866889
if deal.ClientDealProposal.Proposal.PieceSize > abi.PaddedPieceSize(priceFilters[j].MaxSize) {
867890
ret.reason = "deal rejected as piece size is greater than the maximum allowed by the pricing filter"
868-
continue
891+
return ret
869892
}
870893
if deal.ClientDealProposal.Proposal.PieceSize < abi.PaddedPieceSize(priceFilters[j].MinSize) {
871894
ret.reason = "deal rejected as piece size is smaller than the minimum allowed by the pricing filter"
872-
continue
895+
return ret
873896
}
874897
if deal.ClientDealProposal.Proposal.Duration() > abi.ChainEpoch(builtin.EpochsInDay*priceFilters[j].MaxDur) {
875898
ret.reason = "deal rejected as duration is greater than the maximum allowed by the pricing filter"
876-
continue
899+
return ret
877900
}
878901
if deal.ClientDealProposal.Proposal.Duration() < abi.ChainEpoch(builtin.EpochsInDay*priceFilters[j].MinDur) {
879902
ret.reason = "deal rejected as duration is smaller than the minimum allowed by the pricing filter"
880-
continue
903+
return ret
881904
}
882905
if deal.ClientDealProposal.Proposal.StoragePricePerEpoch.LessThan(big.NewInt(priceFilters[j].Price)) {
883906
ret.reason = "deal rejected as storage price per epoch is less than the amount allowed by the pricing filter"
884-
continue
907+
return ret
885908
}
886-
// Accept the deal if any price filter reaches here
887-
return nil
888-
}
889-
// If none of the deal filters matched then we should reject the deal instead if going to default
890-
if ret.reason != "" {
891-
return ret
892909
}
893910
}
894911
}
895912
}
896913

897914
// If no client/pricing rules are found or match the client then apply default Ask validation
898-
if err := m.validateAsk(ctx, deal); err != nil {
899-
return &validationError{error: err}
915+
if !skipDefaultAsk {
916+
if err := m.validateAsk(ctx, deal); err != nil {
917+
return &validationError{error: err}
918+
}
900919
}
920+
901921
return nil
902922
}
903923

904924
// applyAllowList checks if the client making the deal proposal is allowed by the provider
905925
// based on the market_mk12_allow_list table in the database
906926
func (m *MK12) applyAllowList(ctx context.Context, deal *ProviderDealState) (bool, error) {
907-
var allowed bool
908-
err := m.db.QueryRow(ctx, `SELECT status FROM market_allow_list WHERE wallet = $1`, deal.ClientDealProposal.Proposal.Client.String()).Scan(&allowed)
927+
client, err := m.api.StateLookupID(ctx, deal.ClientDealProposal.Proposal.Client, types.EmptyTSK)
928+
if err != nil {
929+
return false, xerrors.Errorf("wallet not found: %w", err)
930+
}
931+
932+
var allowed sql.NullBool
933+
err = m.db.QueryRow(ctx, `SELECT status FROM market_allow_list WHERE wallet = $1 OR wallet = $2`, deal.ClientDealProposal.Proposal.Client.String(), client.String()).Scan(&allowed)
909934
if err != nil {
910935
if !errors.Is(err, pgx.ErrNoRows) {
911936
return false, xerrors.Errorf("failed to query the allow list status from DB: %w", err)
912937
}
913938
return !m.cfg.Market.StorageMarketConfig.MK12.DenyUnknownClients, nil
914939
}
915-
return allowed, nil
940+
941+
if allowed.Valid {
942+
return allowed.Bool, nil
943+
}
944+
945+
return false, nil
916946
}

0 commit comments

Comments
 (0)