diff --git a/src/market/market.command.ts b/src/market/market.command.ts index 9bccadd..c0997b9 100644 --- a/src/market/market.command.ts +++ b/src/market/market.command.ts @@ -1,7 +1,7 @@ import { Command, Option } from "commander"; -import { GolemNetwork, MarketOrderSpec, OfferProposal, OfferProposalFilterFactory } from "@golem-sdk/golem-js"; +import { GolemNetwork, ScanOptions, ScannedOffer } from "@golem-sdk/golem-js"; import chalk from "chalk"; -import { switchMap, filter, scan, takeUntil, timer, last } from "rxjs"; +import { filter, takeUntil, timer, toArray } from "rxjs"; export const marketCommand = new Command("market"); @@ -16,16 +16,17 @@ type MarketScanOptions = { paymentNetwork: string; paymentDriver: string; subnetTag: string; - image: string; - minCpuCores: string; - minCpuThreads: string; - minMemGib: string; - minStorageGib: string; - maxStartPrice: string; - maxCpuPerHourPrice: string; - maxEnvPerHourPrice: string; - engine: string; - capabilities: string[]; + engine?: string; + capabilities?: string[]; + minMemGib?: string; + maxMemGib?: string; + minStorageGib?: string; + maxStorageGib?: string; + minCpuThreads?: string; + maxCpuThreads?: string; + minCpuCores?: string; + maxCpuCores?: string; + maxHourPrice?: string; providerId?: string[]; providerName?: string[]; providerWallet?: string[]; @@ -38,18 +39,22 @@ marketCommand .description("Runs a scan of the market with your criteria and presents results") .addOption(new Option("-k, --yagna-appkey ", "Yagna app key to use").env("YAGNA_APPKEY").makeOptionMandatory()) .option("--yagna-url ", "Yagna API URL", "http://127.0.0.1:7465") - .option("-t, --scan-time ", "Number of seconds to scan the market", "30") + .option("-t, --scan-time ", "Number of seconds to scan the market", "10") .option("--payment-network ", "The payment network to use", "polygon") .option("--payment-driver ", "The payment driver to use", "erc20") .option("--subnet-tag ", "The Golem Network's subnet to use", "public") - .option("--image ", "The Golem Registry image to use for the query", "golem/node:20-alpine") - .option("--min-cpu-cores ", "The minimal number of CPU cores to look for", "1") - .option("--min-cpu-threads ", "The minimal number of CPU threads to look for", "1") - .option("--min-mem-gib ", "The minimal memory size to look for", "0.5") - .option("--min-storage-gib ", "The minimal storage size to look for", "0.5") - .option("--max-start-price ", "The max start price you're willing to pay (in GLM)", "10") - .option("--max-cpu-per-hour-price ", "The max ENV price you're willing to pay (in GLM)", "10") - .option("--max-env-per-hour-price ", "The max CPU time price you're willing to pay (in GLM/h)", "10") + .option("--min-cpu-cores ", "The minimal number of CPU cores to look for") + .option("--max-cpu-cores ", "The maximum number of CPU cores to look for") + .option("--min-cpu-threads ", "The minimal number of CPU threads to look for") + .option("--max-cpu-threads ", "The maximum number of CPU threads to look for") + .option("--min-mem-gib ", "The minimal memory size to look for") + .option("--max-mem-gib ", "The maximum memory size to look for") + .option("--min-storage-gib ", "The minimal storage size to look for") + .option("--max-storage-gib ", "The maximum storage size to look for") + .option( + "--max-hour-price ", + "The maximum price per hour of work to look for (start price + CPU/sec * 3600 + env/sec * 3600)", + ) .option("--engine ", "The runtime that you are interested in", "vm") .option("--capabilities [capabilities...]", "List of capabilities listed in the offers", []) .option("--provider-id [id...]", "Filter the results to only include proposals from providers with the given ids") @@ -69,17 +74,17 @@ marketCommand const paymentNetwork = options.paymentNetwork; const paymentDriver = options.paymentDriver; - const maxStartPrice = parseFloat(options.maxStartPrice); - const maxCpuPerHourPrice = parseFloat(options.maxCpuPerHourPrice); - const maxEnvPerHourPrice = parseFloat(options.maxEnvPerHourPrice); - const subnetTag = options.subnetTag; - const imageTag = options.image; - const minCpuCores = parseInt(options.minCpuCores); - const minCpuThreads = parseInt(options.minCpuThreads); - const minMemGib = parseFloat(options.minMemGib); - const minStorageGib = parseFloat(options.minStorageGib); + const minCpuCores = options.minCpuCores !== undefined ? parseInt(options.minCpuCores) : undefined; + const maxCpuCores = options.maxCpuCores !== undefined ? parseInt(options.maxCpuCores) : undefined; + const minCpuThreads = options.minCpuThreads !== undefined ? parseInt(options.minCpuThreads) : undefined; + const maxCpuThreads = options.maxCpuThreads !== undefined ? parseInt(options.maxCpuThreads) : undefined; + const minMemGib = options.minMemGib !== undefined ? parseFloat(options.minMemGib) : undefined; + const maxMemGib = options.maxMemGib !== undefined ? parseFloat(options.maxMemGib) : undefined; + const minStorageGib = options.minStorageGib !== undefined ? parseFloat(options.minStorageGib) : undefined; + const maxStorageGib = options.maxStorageGib !== undefined ? parseFloat(options.maxStorageGib) : undefined; + const maxHourPrice = options.maxHourPrice !== undefined ? parseFloat(options.maxHourPrice) : undefined; const engine = options.engine; const capabilities = options.capabilities; const providerIdFilter = (id: string) => (options.providerId ? options.providerId.includes(id) : true); @@ -95,20 +100,16 @@ marketCommand console.log("Payment network and driver:", paymentNetwork, paymentDriver); console.log("Golem engine:", engine); console.log("Provider capabilities:", capabilities); - console.log( - "Requirements for image '%s', %d cores, %d threads, %dGiB of memory, %dGiB of storage", - imageTag, - minCpuCores, - minCpuThreads, - minMemGib, - minStorageGib, - ); - console.log( - "Price limitations: max start price %d GLM, max CPU price %d GLM/h, max ENV price %d, GLM/h", - maxStartPrice.toFixed(4), - maxCpuPerHourPrice.toFixed(4), - maxEnvPerHourPrice.toFixed(4), - ); + console.log("Requirements:"); + if (minCpuCores) console.log(" - Minimum number of CPU cores:", minCpuCores); + if (maxCpuCores) console.log(" - Maximum number of CPU cores:", maxCpuCores); + if (minCpuThreads) console.log(" - Minimum number of CPU threads:", minCpuThreads); + if (maxCpuThreads) console.log(" - Maximum number of CPU threads:", maxCpuThreads); + if (minMemGib) console.log(" - Minimum memory size:", minMemGib); + if (maxMemGib) console.log(" - Maximum memory size:", maxMemGib); + if (minStorageGib) console.log(" - Minimum storage size:", minStorageGib); + if (maxStorageGib) console.log(" - Maximum storage size:", maxStorageGib); + if (maxHourPrice) console.log(" - Maximum price per hour:", maxHourPrice); } const glm = new GolemNetwork({ @@ -116,10 +117,6 @@ marketCommand key: options.yagnaAppkey, url: options.yagnaUrl, }, - payment: { - driver: paymentDriver, - network: paymentNetwork, - }, }); try { @@ -134,82 +131,69 @@ marketCommand return; } - const priceLimiter = OfferProposalFilterFactory.limitPriceFilter({ - start: maxStartPrice, - cpuPerSec: maxCpuPerHourPrice / 3600, - envPerSec: maxEnvPerHourPrice / 3600, - }); - - const scanningFilter = (p: OfferProposal) => { - const withinPriceRange = priceLimiter(p); - const isValidProviderId = providerIdFilter(p.provider.id); - const isValidProviderName = providerNameFilter(p.provider.name); - const isValidProviderWallet = providerWalletFilter(p.provider.walletAddress); - - return withinPriceRange && isValidProviderId && isValidProviderName && isValidProviderWallet; + const priceFilter = (offer: ScannedOffer) => { + if (!maxHourPrice) { + return true; + } + const hourlyPrice = offer.pricing.start + offer.pricing.cpuSec * 3600 + offer.pricing.envSec * 3600; + return hourlyPrice <= maxHourPrice; }; - const order: MarketOrderSpec = { - demand: { - subnetTag, - workload: { - imageTag, - minCpuCores, - minCpuThreads, - minMemGib, - minStorageGib, - capabilities, - engine, - }, + const scanOptions: ScanOptions = { + workload: { + capabilities, + engine, + maxCpuCores, + maxCpuThreads, + maxMemGib, + maxStorageGib, + minCpuCores, + minCpuThreads, + minMemGib, + minStorageGib, }, - market: { - rentHours: scanTime / 3600, - pricing: { - model: "linear", - maxCpuPerHourPrice, - maxEnvPerHourPrice, - maxStartPrice, - }, - offerProposalFilter: scanningFilter, + payment: { + network: paymentNetwork, + driver: paymentDriver, }, + subnetTag, }; - const allocation = await glm.payment.createAllocation({ - budget: 0, - expirationSec: scanTime, - }); - const demandSpec = await glm.market.buildDemandDetails(order.demand, order.market, allocation); + const scanSpecification = glm.market.buildScanSpecification(scanOptions); + + const paymentToken = ["mainnet", "polygon"].includes(paymentNetwork) ? "glm" : "tglm"; + const paymentPlatform = `${paymentDriver}-${paymentNetwork}-${paymentToken}`; glm.market - .publishAndRefreshDemand(demandSpec) + .scan(scanSpecification) .pipe( - switchMap((demand) => glm.market.collectAllOfferProposals(demand)), - filter(scanningFilter), - scan((acc, proposal) => { - acc.push(proposal); - return acc; - }, [] as OfferProposal[]), + filter((offer) => priceFilter(offer)), + filter((offer) => providerIdFilter(offer.provider.id)), + filter((offer) => providerNameFilter(offer.provider.name)), + filter((offer) => + providerWalletFilter(offer.properties[`golem.com.payment.platform.${paymentPlatform}.address`] as string), + ), takeUntil(timer(scanTime * 1000)), - last(), + toArray(), ) .subscribe({ - next: (proposals) => { + next: (offersFound) => { if (!options.silent) { console.log("Scan finished, here are the results"); - console.log("Your market query was matched with %d proposals", proposals.length); + console.log("Your market query was matched with %d proposals", offersFound.length); } - const displayProposals = proposals.map((p) => { - const memory = p.getDto()["memory"]; - const storage = p.getDto()["storage"]; + const displayProposals = offersFound.map((offer) => { + const memory = offer.memory; + const storage = offer.storage; return { - providerId: p.provider.id, - providerName: p.provider.name, - startPrice: unifyPrice(p.pricing.start), - cpuPerHourPrice: unifyPrice(p.pricing.cpuSec * 3600), - envPerHourPrice: unifyPrice(p.pricing.envSec * 3600), - cpuCores: p.getDto()["cpuCores"], - cpuThreads: p.getDto()["cpuThreads"], + providerId: offer.provider.id, + providerName: offer.provider.name, + startPrice: unifyPrice(offer.pricing.start), + cpuPerHourPrice: unifyPrice(offer.pricing.cpuSec * 3600), + envPerHourPrice: unifyPrice(offer.pricing.envSec * 3600), + cpuCores: offer.cpuCores, + cpuThreads: offer.cpuThreads, memoryGib: memory ? parseFloat(memory.toFixed(1)) : "N/A", storageGib: storage ? parseFloat(storage.toFixed(1)) : "N/A", };