From bd9fde54a94e7ca3c3d3a6ef0a9ebfcdbbd11376 Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Wed, 29 May 2024 13:34:33 +0200 Subject: [PATCH 1/3] feat(market): added config options for setting the ProposalFilter --- src/experimental/deployment/deployment.ts | 1 + src/experimental/reputation/system.ts | 4 ++-- src/golem-network/golem-network.ts | 2 ++ src/market/index.ts | 2 +- src/market/market.module.ts | 17 +++++------------ src/market/offer-proposal.ts | 2 +- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/experimental/deployment/deployment.ts b/src/experimental/deployment/deployment.ts index e49c0fb2e..f999d7818 100644 --- a/src/experimental/deployment/deployment.ts +++ b/src/experimental/deployment/deployment.ts @@ -170,6 +170,7 @@ export class Deployment { const proposalSubscription = this.modules.market .startCollectingProposals({ demandSpecification, + filter: pool.options.market.proposalFilter, bufferSize: 10, }) .subscribe({ diff --git a/src/experimental/reputation/system.ts b/src/experimental/reputation/system.ts index 56c32f1a3..3043d5e0f 100644 --- a/src/experimental/reputation/system.ts +++ b/src/experimental/reputation/system.ts @@ -1,4 +1,4 @@ -import { ProposalFilterNew, OfferProposal } from "../../market"; +import { ProposalFilter, OfferProposal } from "../../market"; import { AgreementCandidate, AgreementSelector } from "../../market/agreement"; import { GolemReputationError } from "./error"; import { @@ -332,7 +332,7 @@ export class ReputationSystem { * Returns a proposal filter that can be used to filter out providers with low reputation scores. * @param opts */ - proposalFilter(opts?: ProposalFilterOptions): ProposalFilterNew { + proposalFilter(opts?: ProposalFilterOptions): ProposalFilter { return (proposal: OfferProposal) => { // Filter out rejected operators. const operatorEntry = this.rejectedOperatorsMap.get(proposal.provider.walletAddress); diff --git a/src/golem-network/golem-network.ts b/src/golem-network/golem-network.ts index 38a3f842f..f1a042767 100644 --- a/src/golem-network/golem-network.ts +++ b/src/golem-network/golem-network.ts @@ -305,6 +305,7 @@ export class GolemNetwork { const proposal$ = this.market.startCollectingProposals({ demandSpecification, + filter: order.market.proposalFilter, }); const proposalSubscription = proposalPool.readFrom(proposal$); @@ -389,6 +390,7 @@ export class GolemNetwork { const proposal$ = this.market.startCollectingProposals({ demandSpecification, + filter: order.market.proposalFilter, }); const subscription = proposalPool.readFrom(proposal$); diff --git a/src/market/index.ts b/src/market/index.ts index 45356b75d..1407aca21 100644 --- a/src/market/index.ts +++ b/src/market/index.ts @@ -1,4 +1,4 @@ -export { ProposalFilterNew } from "./offer-proposal"; +export { ProposalFilter } from "./offer-proposal"; export { Demand, BasicDemandPropertyConfig, DemandSpecification } from "./demand"; export { OfferProposal, ProposalDTO } from "./offer-proposal"; export * as ProposalFilterFactory from "./strategy"; diff --git a/src/market/market.module.ts b/src/market/market.module.ts index e49ec0ae6..0a7c079e3 100644 --- a/src/market/market.module.ts +++ b/src/market/market.module.ts @@ -11,7 +11,7 @@ import { import { defaultLogger, Logger, runOnNextEventLoopIteration, YagnaApi } from "../shared/utils"; import { Allocation, IPaymentApi } from "../payment"; import { bufferTime, catchError, filter, map, mergeMap, Observable, of, OperatorFunction, switchMap, tap } from "rxjs"; -import { IProposalRepository, OfferProposal, ProposalFilterNew } from "./offer-proposal"; +import { IProposalRepository, OfferProposal, ProposalFilter } from "./offer-proposal"; import { DemandBodyBuilder } from "./demand/demand-body-builder"; import { IAgreementApi } from "./agreement/agreement"; import { BuildDemandOptions, DemandSpecification, IDemandRepository } from "./demand"; @@ -54,15 +54,8 @@ export interface MarketOptions { avgGlmPerHour: number; }; - /** - * List of provider Golem Node IDs that should be considered - * - * If not provided, the list will be pulled from: https://provider-health.golem.network/v1/provider-whitelist - */ - withProviders?: string[]; - withoutProviders?: string[]; - withOperators?: string[]; - withoutOperators?: string[]; + /** An optional filter that can filter proposals from the market before signing an agreement */ + proposalFilter?: ProposalFilter; } export interface MarketModule { @@ -149,7 +142,7 @@ export interface MarketModule { */ startCollectingProposals(options: { demandSpecification: DemandSpecification; - filter?: ProposalFilterNew; + filter?: ProposalFilter; bufferSize?: number; }): Observable; @@ -354,7 +347,7 @@ export class MarketModuleImpl implements MarketModule { startCollectingProposals(options: { demandSpecification: DemandSpecification; - filter?: ProposalFilterNew; + filter?: ProposalFilter; bufferSize?: number; bufferTimeout?: number; minProposalsBatchSize?: number; diff --git a/src/market/offer-proposal.ts b/src/market/offer-proposal.ts index 02fda9be2..323cdbd41 100644 --- a/src/market/offer-proposal.ts +++ b/src/market/offer-proposal.ts @@ -3,7 +3,7 @@ import { GolemMarketError, MarketErrorCode } from "./error"; import { ProviderInfo } from "./agreement"; import { Demand } from "./demand"; -export type ProposalFilterNew = (proposal: OfferProposal) => boolean; +export type ProposalFilter = (proposal: OfferProposal) => boolean; export type PricingInfo = { cpuSec: number; From 8dea5a61536ed747b939a4d754f746c4b9a4bd60 Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Wed, 29 May 2024 14:23:32 +0200 Subject: [PATCH 2/3] docs(market): added examples demonstrating the use of proposalFilter --- examples/advanced/proposal-filter.ts | 51 +++++++++++++++++++ .../advanced/proposal-predefined-filter.ts | 48 +++++++++++++++++ examples/package.json | 2 + tests/examples/examples.json | 2 + 4 files changed, 103 insertions(+) create mode 100644 examples/advanced/proposal-filter.ts create mode 100644 examples/advanced/proposal-predefined-filter.ts diff --git a/examples/advanced/proposal-filter.ts b/examples/advanced/proposal-filter.ts new file mode 100644 index 000000000..51ae60368 --- /dev/null +++ b/examples/advanced/proposal-filter.ts @@ -0,0 +1,51 @@ +import { MarketOrderSpec, GolemNetwork, ProposalFilter } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example demonstrating how to write a custom proposal filter. + * In this case the proposal must include VPN access and must not be from "bad-provider" + */ +const myFilter: ProposalFilter = (proposal) => { + return ( + proposal.provider.name !== "bad-provider" || !proposal.properties["golem.runtime.capabilities"]?.includes("vpn") + ); +}; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + maxAgreements: 1, + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + proposalFilter: myFilter, + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const lease = await glm.oneOf(order); + await lease + .getExeUnit() + .then((exe) => exe.run(`echo [provider:${exe.provider.name}] Hello, Golem! 👋`)) + .then((res) => console.log(res.stdout)); + await lease.finalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/advanced/proposal-predefined-filter.ts b/examples/advanced/proposal-predefined-filter.ts new file mode 100644 index 000000000..c228e4c86 --- /dev/null +++ b/examples/advanced/proposal-predefined-filter.ts @@ -0,0 +1,48 @@ +import { MarketOrderSpec, GolemNetwork, ProposalFilterFactory } from "@golem-sdk/golem-js"; +import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; + +/** + * Example showing how to use a proposal filter using the predefined filter `disallowProvidersByName`, + * which blocks any proposal from a provider whose name is in the array of + */ + +const blackListProvidersNames = ["provider-1", "bad-provider", "slow-provider"]; + +const order: MarketOrderSpec = { + demand: { + workload: { imageTag: "golem/alpine:latest" }, + }, + market: { + maxAgreements: 1, + rentHours: 0.5, + pricing: { + model: "linear", + maxStartPrice: 0.5, + maxCpuPerHourPrice: 1.0, + maxEnvPerHourPrice: 0.5, + }, + proposalFilter: ProposalFilterFactory.disallowProvidersByName(blackListProvidersNames), + }, +}; + +(async () => { + const glm = new GolemNetwork({ + logger: pinoPrettyLogger({ + level: "info", + }), + }); + + try { + await glm.connect(); + const lease = await glm.oneOf(order); + await lease + .getExeUnit() + .then((exe) => exe.run(`echo [provider:${exe.provider.name}] Hello, Golem! 👋`)) + .then((res) => console.log(res.stdout)); + await lease.finalize(); + } catch (err) { + console.error("Failed to run the example", err); + } finally { + await glm.disconnect(); + } +})().catch(console.error); diff --git a/examples/package.json b/examples/package.json index 33fa3c881..cbf7252bf 100644 --- a/examples/package.json +++ b/examples/package.json @@ -12,6 +12,8 @@ "advanced-hello-world": "tsx advanced/hello-world.ts", "advanced-manual-pools": "tsx advanced/manual-pools.ts", "advanced-payment-filters": "tsx advanced/payment-filters.ts", + "advanced-proposal-filters": "tsx advanced/proposal-filter.ts", + "advanced-proposal-predefined-filter": "tsx advanced/proposal-predefined-filter.ts", "local-image": "tsx advanced/local-image/serveLocalGvmi.ts", "deployment": "tsx experimental/deployment/new-api.ts", "market-scan": "tsx market/scan.ts", diff --git a/tests/examples/examples.json b/tests/examples/examples.json index f2d9b51c7..5bf5bee99 100644 --- a/tests/examples/examples.json +++ b/tests/examples/examples.json @@ -6,6 +6,8 @@ { "cmd": "tsx", "path": "examples/advanced/hello-world.ts" }, { "cmd": "tsx", "path": "examples/advanced/manual-pools.ts" }, { "cmd": "tsx", "path": "examples/advanced/payment-filters.ts" }, + { "cmd": "tsx", "path": "examples/advanced/proposal-filter.ts" }, + { "cmd": "tsx", "path": "examples/advanced/proposal-predefined-filter.ts" }, { "cmd": "tsx", "path": "examples/experimental/deployment/new-api.ts" }, { "cmd": "tsx", "path": "examples/experimental/job/getJobById.ts" }, { "cmd": "tsx", "path": "examples/experimental/job/waitForResults.ts" }, From aa6acd6e34d6470a62bcf72d0db0acf585ff366c Mon Sep 17 00:00:00 2001 From: Marcin Gordel Date: Wed, 29 May 2024 14:28:15 +0200 Subject: [PATCH 3/3] chore: improved example --- examples/advanced/proposal-filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/proposal-filter.ts b/examples/advanced/proposal-filter.ts index 51ae60368..e5914d7ee 100644 --- a/examples/advanced/proposal-filter.ts +++ b/examples/advanced/proposal-filter.ts @@ -7,7 +7,7 @@ import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; */ const myFilter: ProposalFilter = (proposal) => { return ( - proposal.provider.name !== "bad-provider" || !proposal.properties["golem.runtime.capabilities"]?.includes("vpn") + proposal.provider.name !== "bad-provider" && proposal.properties["golem.runtime.capabilities"]?.includes("vpn") ); };