From da0f41d6a5f36237c781438e4f4a8a0501053ba8 Mon Sep 17 00:00:00 2001 From: LexLuthr <88259624+LexLuthr@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:03:31 +0400 Subject: [PATCH] fix: improve filter (#421) * improve filter * fix allocate --- cmd/sptool/toolbox_deal_tools.go | 85 ++++++------- docker/piece-server/sample/ddo-deal.sh | 2 +- documentation/en/curio-cli/sptool.md | 5 +- .../en/curio-market/storage-market.md | 20 +++ documentation/en/installation.md | 10 +- market/mk12/mk12.go | 78 ++++++++---- web/api/webrpc/market_filters.go | 119 +++++++++++++----- .../pages/market-settings/client-filters.mjs | 14 ++- 8 files changed, 216 insertions(+), 117 deletions(-) diff --git a/cmd/sptool/toolbox_deal_tools.go b/cmd/sptool/toolbox_deal_tools.go index db27db971..535bb0534 100644 --- a/cmd/sptool/toolbox_deal_tools.go +++ b/cmd/sptool/toolbox_deal_tools.go @@ -421,10 +421,15 @@ var allocateCmd = &cli.Command{ Usage: "storage provider address[es]", Aliases: []string{"m", "provider", "p"}, }, - &cli.StringSliceFlag{ - Name: "piece-info", - Usage: "data piece-info[s] to create the allocation. The format must be --piece-info pieceCid1=pieceSize1 --piece-info pieceCid2=pieceSize2", - Aliases: []string{"pi"}, + &cli.StringFlag{ + Name: "piece-cid", + Usage: "data piece-cid to create the allocation", + Aliases: []string{"piece"}, + }, + &cli.Int64Flag{ + Name: "piece-size", + Usage: "piece size to create the allocation", + Aliases: []string{"size"}, }, &cli.StringFlag{ Name: "wallet", @@ -457,7 +462,7 @@ var allocateCmd = &cli.Command{ }, &cli.StringFlag{ Name: "piece-file", - 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'", + 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'", Aliases: []string{"pf"}, }, &cli.IntFlag{ @@ -490,18 +495,18 @@ var allocateCmd = &cli.Command{ pieceFile := cctx.String("piece-file") miners := cctx.StringSlice("miner") - pinfos := cctx.StringSlice("piece-info") + pcids := cctx.String("piece-cid") - if pieceFile == "" && len(pinfos) < 1 { - return fmt.Errorf("must provide at least one --piece-info or use --piece-file") + if pieceFile == "" && pcids == "" { + return fmt.Errorf("must provide at least one --piece-cid or use --piece-file") } if pieceFile == "" && len(miners) < 1 { return fmt.Errorf("must provide at least one miner address or use --piece-file") } - if pieceFile != "" && len(pinfos) > 0 { - return fmt.Errorf("cannot use both --piece-info and --piece-file flags at once") + if pieceFile != "" && pcids != "" { + return fmt.Errorf("cannot use both --piece-cid and --piece-file flags at once") } var pieceInfos []PieceInfos @@ -590,44 +595,30 @@ var allocateCmd = &cli.Command{ if err != nil { return fmt.Errorf("failed to convert miner address %w", err) } - for _, p := range cctx.StringSlice("piece-info") { - pieceDetail := strings.Split(p, "=") - if len(pieceDetail) != 2 { - return fmt.Errorf("incorrect pieceInfo format: %s", pieceDetail) - } - - size, err := strconv.ParseInt(pieceDetail[1], 10, 64) - if err != nil { - return fmt.Errorf("failed to parse the piece size for %s for pieceCid %s: %w", pieceDetail[0], pieceDetail[1], err) - } - pcid, err := cid.Parse(pieceDetail[0]) - if err != nil { - return fmt.Errorf("failed to parse the pieceCid for %s: %w", pieceDetail[0], err) - } - - tmin := abi.ChainEpoch(cctx.Int64("term-min")) - - tmax := abi.ChainEpoch(cctx.Int64("term-max")) - - exp := abi.ChainEpoch(cctx.Int64("expiration")) - if exp == verifreg13.MaximumVerifiedAllocationExpiration { - exp -= 5 - } - - if tmax < tmin { - return fmt.Errorf("maximum duration %d cannot be smaller than minimum duration %d", tmax, tmin) - } - - pieceInfos = append(pieceInfos, PieceInfos{ - Cid: pcid, - Size: size, - Miner: abi.ActorID(mid), - MinerAddr: maddr, - Tmin: tmin, - Tmax: tmax, - Exp: exp, - }) + pcid, err := cid.Parse(cctx.String("piece-cid")) + if err != nil { + return fmt.Errorf("failed to parse pieceCid %w", err) + } + size := cctx.Int64("piece-size") + + tmin := abi.ChainEpoch(cctx.Int64("term-min")) + + tmax := abi.ChainEpoch(cctx.Int64("term-max")) + + exp := abi.ChainEpoch(cctx.Int64("expiration")) + if exp == verifreg13.MaximumVerifiedAllocationExpiration { + exp -= 5 } + + pieceInfos = append(pieceInfos, PieceInfos{ + Cid: pcid, + Size: size, + Miner: abi.ActorID(mid), + MinerAddr: maddr, + Tmin: tmin, + Tmax: tmax, + Exp: exp, + }) } } diff --git a/docker/piece-server/sample/ddo-deal.sh b/docker/piece-server/sample/ddo-deal.sh index 1aae9120c..394125f7e 100755 --- a/docker/piece-server/sample/ddo-deal.sh +++ b/docker/piece-server/sample/ddo-deal.sh @@ -17,7 +17,7 @@ miner_actor=$(lotus state list-miners | grep -v t01000) mv /var/lib/curio-client/data/$PAYLOAD_CID.car /var/lib/curio-client/data/$COMMP_CID -sptool --actor t01000 toolbox mk12-client allocate -y -p $miner_actor --pi $COMMP_CID=$PIECE --confidence 0 +sptool --actor t01000 toolbox mk12-client allocate -y -p $miner_actor --piece-cid $COMMP_CID --piece-size $PIECE --confidence 0 CLIENT=$(sptool --actor t01000 toolbox mk12-client wallet default) diff --git a/documentation/en/curio-cli/sptool.md b/documentation/en/curio-cli/sptool.md index 3ac12d43f..38a1205ba 100644 --- a/documentation/en/curio-cli/sptool.md +++ b/documentation/en/curio-cli/sptool.md @@ -641,7 +641,8 @@ DESCRIPTION: OPTIONS: --miner value, -m value, --provider value, -p value [ --miner value, -m value, --provider value, -p value ] storage provider address[es] - --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 + --piece-cid value, --piece value data piece-cid to create the allocation + --piece-size value, --size value piece size to create the allocation (default: 0) --wallet value the wallet address that will used create the allocation --quiet do not print the allocation list (default: false) --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: Default is 5 years. (default: 5256000) --expiration value The latest epoch by which a provider must commit data before the allocation expires (epochs). Default is 60 days. (default: 172800) - --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' + --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' --batch-size value number of extend requests per batch. If set incorrectly, this will lead to out of gas error (default: 500) --confidence value number of block confirmations to wait for (default: 5) --assume-yes, -y, --yes automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively (default: false) diff --git a/documentation/en/curio-market/storage-market.md b/documentation/en/curio-market/storage-market.md index 2ae3d3f49..fd679adaa 100644 --- a/documentation/en/curio-market/storage-market.md +++ b/documentation/en/curio-market/storage-market.md @@ -239,3 +239,23 @@ curio market seal --actor * **--actor**: Specifies the actor address. Consequences: Sealing early can speed up the process, but it may result in inefficiencies if all deals are not batched correctly. + +## Offline Verified DDO deals +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. + + +### How to create allocation +Clients can create allocation using the `sptool toolbox` or other methods. + +```shell +sptool --actor t01000 toolbox mk12-client allocate -p --piece-cid --piece-size +``` + +### Start a DDO deal +Storage providers can onboard the DDO deal using the below command. + +```shell +curio market ddo --actor +``` + +Since this is an offline deal, user must either make the data available via PieceLocator or add a data URL for this offline deal. \ No newline at end of file diff --git a/documentation/en/installation.md b/documentation/en/installation.md index dc96ee398..2df3fac22 100644 --- a/documentation/en/installation.md +++ b/documentation/en/installation.md @@ -98,7 +98,7 @@ To build Curio, you need a working installation of [Go](https://golang.org/dl/): Example of an OLD version's CLI download: ```shell -wget -c https://golang.org/dl/go1.22.3.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local +wget -c https://golang.org/dl/go1.23.6.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local ``` {% hint style="info" %} @@ -181,9 +181,9 @@ This will put `curio` in `/usr/local/bin`. `curio` will use the `$HOME/.curio` f Run `curio --version` ```md -curio version 1.23.0+mainnet+git_ae625a5_2024-08-21T15:21:45+04:00 +curio version 1.24.5-rc1+mainnet+git_214226e7_2025-02-19T17:02:54+04:00 # or -curio version 1.23.0+calibnet+git_ae625a5_2024-08-21T15:21:45+04:00 +curio version 1.24.5-rc1+calibnet+git_214226e7_2025-02-19T17:02:54+04:00 ``` 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 6. Run `curio --version` ```md - curio version 1.23.0+mainnet+git_ae625a5_2024-08-21T15:21:45+04:00 + curio version 1.24.5-rc1+mainnet+git_214226e7_2025-02-19T17:02:54+04:00 # or - curio version 1.23.0+calibnet+git_ae625a5_2024-08-21T15:21:45+04:00 + curio version 1.24.5-rc1+calibnet+git_214226e7_2025-02-19T17:02:54+04:00 ``` 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/). diff --git a/market/mk12/mk12.go b/market/mk12/mk12.go index 68a56d319..d394c4164 100644 --- a/market/mk12/mk12.go +++ b/market/mk12/mk12.go @@ -8,6 +8,7 @@ package mk12 import ( "bytes" "context" + "database/sql" "encoding/json" "errors" "fmt" @@ -49,6 +50,7 @@ type MK12API interface { StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) + StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error) } @@ -780,14 +782,18 @@ FROM joined func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *validationError { var clientRules []struct { + Name string `db:"name"` Wallets []string `db:"wallets"` - PeerIDs []string `db:"peer_id"` + PeerIDs []string `db:"peer_ids"` PricingFilters []string `db:"pricing_filters"` MaxDealsPerHour int64 `db:"max_deals_per_hour"` MaxDealSizePerHour int64 `db:"max_deal_size_per_hour"` } + var skipDefaultAsk bool + err := m.db.Select(ctx, &clientRules, `SELECT + name, wallets, peer_ids, pricing_filters, @@ -799,9 +805,15 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid return &validationError{error: xerrors.Errorf("failed to query the client rules from DB: %w", err)} } + log.Debugw("Applicable Deal Client Filters", "Deal", deal.DealUuid.String(), "Client rules", "client_rules", clientRules) + // Check if we have any client rules and match them to client details for i := range clientRules { - if lo.Contains(clientRules[i].Wallets, deal.ClientDealProposal.Proposal.Client.String()) || lo.Contains(clientRules[i].PeerIDs, deal.ClientPeerID.String()) { + client, err := m.api.StateLookupID(ctx, deal.ClientDealProposal.Proposal.Client, types.EmptyTSK) + if err != nil { + return &validationError{error: xerrors.Errorf("wallet not found: %w", err)} + } + 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()) { // Check if Cumulative Storage size has not exceeded the specified limit if clientRules[i].MaxDealSizePerHour > 0 { var size int64 @@ -809,12 +821,14 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid FROM market_mk12_deals WHERE created_at >= NOW() - INTERVAL '1 hour' AND ( - client_peer_id = 'provided_client_peer_id' - OR proposal->>'Client' = 'desired_client_value' - )`).Scan(&size) + client_peer_id = $1 + OR proposal->>'Client' = $2 + OR proposal->>'Client' = $3 + )`, deal.ClientPeerID.String(), deal.ClientDealProposal.Proposal.Client.String(), client.String()).Scan(&size) if err != nil { return &validationError{error: xerrors.Errorf("failed to query the cummulative size from DB: %w", err)} } + log.Debugw("MaxDealSizePerHour Check", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "MaxDealSizePerHour", "size", size, "max", clientRules[i].MaxDealSizePerHour) if size > clientRules[i].MaxDealSizePerHour { 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"} } @@ -826,17 +840,21 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid WHERE created_at >= NOW() - INTERVAL '1 hour' AND ( client_peer_id = $1 - OR proposal->>'Client' = $2)`, deal.ClientPeerID.String(), - deal.ClientDealProposal.Proposal.Client.String()).Scan(&dealCount) + OR proposal->>'Client' = $2 + OR proposal->>'Client' = $3)`, deal.ClientPeerID.String(), deal.ClientDealProposal.Proposal.Client.String(), + client.String()).Scan(&dealCount) if err != nil { return &validationError{error: xerrors.Errorf("failed to query the deal count from DB: %w", err)} } + log.Debugw("MaxDealsPerHour Check", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "MaxDealsPerHour", "count", dealCount, "max", clientRules[i].MaxDealsPerHour) if dealCount >= clientRules[i].MaxDealsPerHour { return &validationError{reason: "deal rejected as maximum allowed deals per hour limit has been reached for the client, please retry in some time"} } } // Apply pricing filters if len(clientRules[i].PricingFilters) > 0 { + log.Debugw("Applicable Pricing Filters", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, "Pricing Filters", clientRules[i].PricingFilters) + skipDefaultAsk = true var priceFilters []struct { MinDur int64 `db:"min_duration_days"` MaxDur int64 `db:"max_duration_days"` @@ -859,58 +877,70 @@ func (m *MK12) applyFilters(ctx context.Context, deal *ProviderDealState) *valid } ret := new(validationError) for j := range priceFilters { + log.Debugw("Applying Pricing Filter", "Deal", deal.DealUuid.String(), "Client Rule", clientRules[i].Name, + "Filter Verified", priceFilters[j].Verified, "Filter MinSize", priceFilters[j].MinSize, "Filter MaxSize", priceFilters[j].MaxSize, + "Filter MinDur", priceFilters[j].MinDur, "Filter MaxDur", priceFilters[j].MaxDur, "Filter Price", priceFilters[j].Price, + "Deal Verified", deal.ClientDealProposal.Proposal.VerifiedDeal, "Deal PieceSize", deal.ClientDealProposal.Proposal.PieceSize, + "Deal Duration", deal.ClientDealProposal.Proposal.Duration, "Deal Price", deal.ClientDealProposal.Proposal.StoragePricePerEpoch) // Skip filters which are not meant for verified/unverified deals - if !(deal.ClientDealProposal.Proposal.VerifiedDeal && priceFilters[j].Verified) { + if deal.ClientDealProposal.Proposal.VerifiedDeal != priceFilters[j].Verified { continue } if deal.ClientDealProposal.Proposal.PieceSize > abi.PaddedPieceSize(priceFilters[j].MaxSize) { ret.reason = "deal rejected as piece size is greater than the maximum allowed by the pricing filter" - continue + return ret } if deal.ClientDealProposal.Proposal.PieceSize < abi.PaddedPieceSize(priceFilters[j].MinSize) { ret.reason = "deal rejected as piece size is smaller than the minimum allowed by the pricing filter" - continue + return ret } if deal.ClientDealProposal.Proposal.Duration() > abi.ChainEpoch(builtin.EpochsInDay*priceFilters[j].MaxDur) { ret.reason = "deal rejected as duration is greater than the maximum allowed by the pricing filter" - continue + return ret } if deal.ClientDealProposal.Proposal.Duration() < abi.ChainEpoch(builtin.EpochsInDay*priceFilters[j].MinDur) { ret.reason = "deal rejected as duration is smaller than the minimum allowed by the pricing filter" - continue + return ret } if deal.ClientDealProposal.Proposal.StoragePricePerEpoch.LessThan(big.NewInt(priceFilters[j].Price)) { ret.reason = "deal rejected as storage price per epoch is less than the amount allowed by the pricing filter" - continue + return ret } - // Accept the deal if any price filter reaches here - return nil - } - // If none of the deal filters matched then we should reject the deal instead if going to default - if ret.reason != "" { - return ret } } } } // If no client/pricing rules are found or match the client then apply default Ask validation - if err := m.validateAsk(ctx, deal); err != nil { - return &validationError{error: err} + if !skipDefaultAsk { + if err := m.validateAsk(ctx, deal); err != nil { + return &validationError{error: err} + } } + return nil } // applyAllowList checks if the client making the deal proposal is allowed by the provider // based on the market_mk12_allow_list table in the database func (m *MK12) applyAllowList(ctx context.Context, deal *ProviderDealState) (bool, error) { - var allowed bool - err := m.db.QueryRow(ctx, `SELECT status FROM market_allow_list WHERE wallet = $1`, deal.ClientDealProposal.Proposal.Client.String()).Scan(&allowed) + client, err := m.api.StateLookupID(ctx, deal.ClientDealProposal.Proposal.Client, types.EmptyTSK) + if err != nil { + return false, xerrors.Errorf("wallet not found: %w", err) + } + + var allowed sql.NullBool + 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) if err != nil { if !errors.Is(err, pgx.ErrNoRows) { return false, xerrors.Errorf("failed to query the allow list status from DB: %w", err) } return !m.cfg.Market.StorageMarketConfig.MK12.DenyUnknownClients, nil } - return allowed, nil + + if allowed.Valid { + return allowed.Bool, nil + } + + return false, nil } diff --git a/web/api/webrpc/market_filters.go b/web/api/webrpc/market_filters.go index 4d51058b1..d3ab9ab9c 100644 --- a/web/api/webrpc/market_filters.go +++ b/web/api/webrpc/market_filters.go @@ -10,6 +10,8 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/builtin/v15/market" + + "github.com/filecoin-project/lotus/chain/types" ) type PriceFilter struct { @@ -93,23 +95,39 @@ func (a *WebRPC) GetAllowDenyList(ctx context.Context) ([]AllowDeny, error) { } func (a *WebRPC) SetClientFilters(ctx context.Context, name string, active bool, wallets, peers []string, filters []string, maxDealPerHour, maxDealSizePerHour int64, info string) error { - for i := range wallets { - if wallets[i] == "" { - return xerrors.Errorf("wallet address cannot be empty") - } - _, err := address.NewFromString(wallets[i]) - if err != nil { - return xerrors.Errorf("invalid wallet address: %w", err) - } + if len(wallets) == 0 && len(peers) == 0 { + return xerrors.Errorf("either wallets or peers must be provided") } - for i := range peers { - if peers[i] == "" { - return xerrors.Errorf("peer ID cannot be empty") + var clients []string + if len(wallets) > 0 { + for i := range wallets { + if wallets[i] == "" { + continue + } + w, err := address.NewFromString(wallets[i]) + if err != nil { + return xerrors.Errorf("invalid wallet address: %w", err) + } + client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("wallet not found: %w", err) + } + clients = append(clients, client.String()) } - _, err := peer.Decode(peers[i]) - if err != nil { - return xerrors.Errorf("invalid peer ID: %w", err) + } + + var peerIds []string + if len(peers) > 0 { + for i := range peers { + if peers[i] == "" { + continue + } + _, err := peer.Decode(peers[i]) + if err != nil { + return xerrors.Errorf("invalid peer ID: %w", err) + } + peerIds = append(peerIds, peers[i]) } } @@ -133,6 +151,10 @@ func (a *WebRPC) SetClientFilters(ctx context.Context, name string, active bool, return xerrors.Errorf("name length exceeds maximum limit of 64 characters") } + if len(name) == 0 { + return xerrors.Errorf("name cannot be empty") + } + if len(info) > 256 { return xerrors.Errorf("info length exceeds maximum limit of 256 characters") } @@ -147,7 +169,7 @@ func (a *WebRPC) SetClientFilters(ctx context.Context, name string, active bool, } n, err := a.deps.DB.Exec(ctx, `UPDATE market_mk12_client_filters SET active = $2, wallets = $3, peer_ids = $4, pricing_filters = $5, max_deals_per_hour = $6, max_deal_size_per_hour = $7, additional_info = $8 WHERE name = $1`, name, - active, wallets, peers, filters, maxDealPerHour, maxDealSizePerHour, info) + active, clients, peerIds, filters, maxDealPerHour, maxDealSizePerHour, info) if err != nil { return xerrors.Errorf("updating client filter: %w", err) } @@ -186,6 +208,10 @@ func (a *WebRPC) SetPriceFilters(ctx context.Context, name string, minDur, maxDu return xerrors.Errorf("name length exceeds maximum limit of 64 characters") } + if len(name) == 0 { + return xerrors.Errorf("name cannot be empty") + } + n, err := a.deps.DB.Exec(ctx, `UPDATE market_mk12_pricing_filters SET min_duration_days = $2, max_duration_days = $3, min_size = $4, max_size = $5, price= $6, verified = $7 WHERE name = $1`, name, minDur, maxDur, minSize, maxSize, price, verified) @@ -215,23 +241,39 @@ func (a *WebRPC) SetAllowDenyList(ctx context.Context, wallet string, status boo } func (a *WebRPC) AddClientFilters(ctx context.Context, name string, active bool, wallets, peers []string, filters []string, maxDealPerHour, maxDealSizePerHour int64, info string) error { - for i := range wallets { - if wallets[i] == "" { - return xerrors.Errorf("wallet address cannot be empty") - } - _, err := address.NewFromString(wallets[i]) - if err != nil { - return xerrors.Errorf("invalid wallet address: %w", err) - } + if len(wallets) == 0 && len(peers) == 0 { + return xerrors.Errorf("either wallets or peers must be provided") } - for i := range peers { - if peers[i] == "" { - return xerrors.Errorf("peer ID cannot be empty") + var clients []string + if len(wallets) > 0 { + for i := range wallets { + if wallets[i] == "" { + continue + } + w, err := address.NewFromString(wallets[i]) + if err != nil { + return xerrors.Errorf("invalid wallet address: %w", err) + } + client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("wallet not found: %w", err) + } + clients = append(clients, client.String()) } - _, err := peer.Decode(peers[i]) - if err != nil { - return xerrors.Errorf("invalid peer ID: %w", err) + } + + var peerIds []string + if len(peers) > 0 { + for i := range peers { + if peers[i] == "" { + continue + } + _, err := peer.Decode(peers[i]) + if err != nil { + return xerrors.Errorf("invalid peer ID: %w", err) + } + peerIds = append(peerIds, peers[i]) } } @@ -255,6 +297,10 @@ func (a *WebRPC) AddClientFilters(ctx context.Context, name string, active bool, return xerrors.Errorf("name length exceeds maximum limit of 64 characters") } + if len(name) == 0 { + return xerrors.Errorf("name cannot be empty") + } + if len(info) > 256 { return xerrors.Errorf("info length exceeds maximum limit of 256 characters") } @@ -271,7 +317,7 @@ func (a *WebRPC) AddClientFilters(ctx context.Context, name string, active bool, n, err := a.deps.DB.Exec(ctx, `INSERT INTO market_mk12_client_filters (name, active, wallets, peer_ids, pricing_filters, max_deals_per_hour, max_deal_size_per_hour, additional_info) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, - name, active, wallets, peers, filters, maxDealPerHour, maxDealSizePerHour, info) + name, active, clients, peerIds, filters, maxDealPerHour, maxDealSizePerHour, info) if err != nil { return xerrors.Errorf("failed to add client filters: %w", err) } @@ -310,6 +356,10 @@ func (a *WebRPC) AddPriceFilters(ctx context.Context, name string, minDur, maxDu return xerrors.Errorf("name length exceeds maximum limit of 64 characters") } + if len(name) == 0 { + return xerrors.Errorf("name cannot be empty") + } + n, err := a.deps.DB.Exec(ctx, `INSERT INTO market_mk12_pricing_filters (name, min_duration_days, max_duration_days, min_size, max_size, price, verified) VALUES ($1, $2, $3, $4, $5, $6, $7)`, @@ -324,12 +374,17 @@ func (a *WebRPC) AddPriceFilters(ctx context.Context, name string, minDur, maxDu } func (a *WebRPC) AddAllowDenyList(ctx context.Context, wallet string, status bool) error { - _, err := address.NewFromString(wallet) + w, err := address.NewFromString(wallet) if err != nil { return xerrors.Errorf("invalid wallet address: %w", err) } - n, err := a.deps.DB.Exec(ctx, "INSERT INTO market_allow_list (wallet, status) VALUES ($1, $2)", wallet, status) + client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("wallet not found: %w", err) + } + + n, err := a.deps.DB.Exec(ctx, "INSERT INTO market_allow_list (wallet, status) VALUES ($1, $2)", client.String(), status) if err != nil { return err } diff --git a/web/static/pages/market-settings/client-filters.mjs b/web/static/pages/market-settings/client-filters.mjs index d461746d4..afd6316f4 100644 --- a/web/static/pages/market-settings/client-filters.mjs +++ b/web/static/pages/market-settings/client-filters.mjs @@ -227,9 +227,10 @@ class ClientFilters extends LitElement { class="form-control" .value="${(this.editingClientFilter.wallets || []).join(', ')}" @input="${(e) => - (this.editingClientFilter.wallets = e.target.value - .split(',') - .map((w) => w.trim()))}" + (this.editingClientFilter.wallets = e.target.value + .split(',') + .map((w) => w.trim()) + .filter((w) => w.length > 0))}" />
@@ -239,9 +240,10 @@ class ClientFilters extends LitElement { class="form-control" .value="${(this.editingClientFilter.peers || []).join(', ')}" @input="${(e) => - (this.editingClientFilter.peers = e.target.value - .split(',') - .map((p) => p.trim()))}" + (this.editingClientFilter.peers = e.target.value + .split(',') + .map((p) => p.trim()) + .filter((p) => p.length > 0))}" />