From 86093564e71cc638485336dee41a99a7c54ef058 Mon Sep 17 00:00:00 2001 From: Seweryn Kras Date: Mon, 20 May 2024 17:03:07 +0200 Subject: [PATCH] feat: fetch payment related properties and constraints from yagna based on allocation --- examples/basic/hello-world.ts | 11 ++-- examples/local-image/serveLocalGvmi.ts | 12 ++-- examples/market/scan.ts | 61 ------------------- examples/pool/hello-world.ts | 3 +- src/deployment/deployment.ts | 4 +- src/golem-network.ts | 5 +- src/market/api.ts | 9 +++ .../directors/payment-demand-director.ts | 22 +++---- src/market/market.module.test.ts | 28 +++++++-- src/market/market.module.ts | 19 +++--- .../yagna/adapters/market-api-adapter.ts | 4 ++ tests/e2e/leaseProcessPool.spec.ts | 4 +- 12 files changed, 68 insertions(+), 114 deletions(-) delete mode 100644 examples/market/scan.ts diff --git a/examples/basic/hello-world.ts b/examples/basic/hello-world.ts index c71984f58..6a751a533 100644 --- a/examples/basic/hello-world.ts +++ b/examples/basic/hello-world.ts @@ -40,8 +40,11 @@ import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; logger, }); - const payerDetails = await glm.payment.getPayerDetails(); - const demandSpecification = await glm.market.buildDemandDetails(demand.demand, payerDetails); + const allocation = await glm.payment.createAllocation({ + budget: glm.market.estimateBudget(demand), + expirationSec: 60 * 60, // 60 minutes + }); + const demandSpecification = await glm.market.buildDemandDetails(demand.demand, allocation); const proposal$ = glm.market.startCollectingProposals({ demandSpecification, bufferSize: 15, @@ -51,10 +54,6 @@ import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; const agreement = await glm.market.proposeAgreement(draftProposal); - const allocation = await glm.payment.createAllocation({ - budget: glm.market.estimateBudget(demand), - expirationSec: 60 * 60, // 60 minutes - }); const lease = await glm.market.createLease(agreement, allocation); const activity = await glm.activity.createActivity(agreement); diff --git a/examples/local-image/serveLocalGvmi.ts b/examples/local-image/serveLocalGvmi.ts index d67fa62c5..950383c21 100644 --- a/examples/local-image/serveLocalGvmi.ts +++ b/examples/local-image/serveLocalGvmi.ts @@ -40,8 +40,11 @@ const getImagePath = (path: string) => fileURLToPath(new URL(path, import.meta.u logger, }); - const payerDetails = await glm.payment.getPayerDetails(); - const demandSpecification = await glm.market.buildDemandDetails(demand.demand, payerDetails); + const allocation = await glm.payment.createAllocation({ + budget: 1, + expirationSec: 30 * 60, // 30 minutes + }); + const demandSpecification = await glm.market.buildDemandDetails(demand.demand, allocation); const proposal$ = glm.market.startCollectingProposals({ demandSpecification, bufferSize: 15, @@ -50,10 +53,7 @@ const getImagePath = (path: string) => fileURLToPath(new URL(path, import.meta.u const draftProposal = await proposalPool.acquire(); const agreement = await glm.market.proposeAgreement(draftProposal); - const allocation = await glm.payment.createAllocation({ - budget: 1, - expirationSec: 30 * 60, // 30 minutes - }); + const lease = await glm.market.createLease(agreement, allocation); const activity = await glm.activity.createActivity(agreement); diff --git a/examples/market/scan.ts b/examples/market/scan.ts deleted file mode 100644 index d63276c8c..000000000 --- a/examples/market/scan.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * This example demonstrates how to scan the market for OfferProposals - * Lets learn what is the average start price - * Notice that we don't need to even allocate any budget for this operation - */ -import { GolemNetwork, OfferProposal } from "@golem-sdk/golem-js"; - -(async () => { - const glm = new GolemNetwork({ - payment: { - payment: { - network: "holesky", - driver: "erc20", - }, - }, - }); - - try { - await glm.connect(); - - const payerDetails = await glm.payment.getPayerDetails(); - const demandSpecification = await glm.market.buildDemandDetails( - { - activity: { imageTag: "golem/alpine:latest" }, - }, - payerDetails, - ); - - const offers = new Set(); - - console.log("Scanning the market..."); - const subscription = glm.market - .startCollectingProposals({ - demandSpecification, - bufferSize: 5, - }) - .subscribe({ - next: (OfferProposals) => { - console.log("Received a batch of ", OfferProposals.length, " offers..."); - OfferProposals.forEach((OfferProposal) => offers.add(OfferProposal)); - }, - error: (e) => { - console.error("Error while collecting OfferProposals", e); - }, - }); - - setTimeout(() => { - subscription.unsubscribe(); - - const offersArray = [...offers.values()]; - const offersCount = offersArray.length; - const averagePrice = offersArray.reduce((total, offer) => (total += offer.pricing.start), 0) / offersCount; - - console.log(`Collected ${offersCount} offers from the market with the start price of ${averagePrice}`); - }, 10_000); - } catch (err) { - console.error("Error while executing the example", err); - } finally { - await glm.disconnect(); - } -})().catch(console.error); diff --git a/examples/pool/hello-world.ts b/examples/pool/hello-world.ts index 71322a0f0..7a9139a3e 100644 --- a/examples/pool/hello-world.ts +++ b/examples/pool/hello-world.ts @@ -48,8 +48,7 @@ import { pinoPrettyLogger } from "@golem-sdk/pino-logger"; }; const proposalPool = new DraftOfferProposalPool({ minCount: 1 }); - const payerDetails = await glm.payment.getPayerDetails(); - const demandSpecification = await glm.market.buildDemandDetails(demandOptions.demand, payerDetails); + const demandSpecification = await glm.market.buildDemandDetails(demandOptions.demand, allocation); const proposals$ = glm.market.startCollectingProposals({ demandSpecification, diff --git a/src/deployment/deployment.ts b/src/deployment/deployment.ts index c50dbc162..d132a813a 100644 --- a/src/deployment/deployment.ts +++ b/src/deployment/deployment.ts @@ -143,8 +143,6 @@ export class Deployment { await this.dataTransferProtocol.init(); - const payerDetails = await this.modules.payment.getPayerDetails(); - for (const network of this.components.networks) { const networkInstance = await Network.create(this.yagnaApi, network.options); this.networks.set(network.name, networkInstance); @@ -161,7 +159,7 @@ export class Deployment { for (const pool of this.components.activityPools) { const { demandBuildOptions, leaseProcessPoolOptions } = this.prepareParams(pool.options); - const demandSpecification = await this.modules.market.buildDemandDetails(demandBuildOptions.demand, payerDetails); + const demandSpecification = await this.modules.market.buildDemandDetails(demandBuildOptions.demand, allocation); const proposalPool = new DraftOfferProposalPool(); const proposalSubscription = this.modules.market diff --git a/src/golem-network.ts b/src/golem-network.ts index ed2902c64..d1783d09a 100644 --- a/src/golem-network.ts +++ b/src/golem-network.ts @@ -238,15 +238,12 @@ export class GolemNetwork { logger: this.logger, }); - const payerDetails = await this.payment.getPayerDetails(); - const demandSpecification = await this.market.buildDemandDetails(demand.demand, payerDetails); - const budget = this.market.estimateBudget(demand); - const allocation = await this.payment.createAllocation({ budget, expirationSec: demand.market.rentHours * 60 * 60, }); + const demandSpecification = await this.market.buildDemandDetails(demand.demand, allocation); const proposalSubscription = this.market .startCollectingProposals({ diff --git a/src/market/api.ts b/src/market/api.ts index eadb17c67..777e5d8e5 100644 --- a/src/market/api.ts +++ b/src/market/api.ts @@ -2,6 +2,7 @@ import { Observable } from "rxjs"; import { Demand, DemandSpecification } from "./demand"; import YaTsClient from "ya-ts-client"; import { OfferProposal } from "./offer-proposal"; +import { DemandBodyPrototype } from "./demand/demand-body-builder"; export type NewProposalEvent = YaTsClient.MarketApi.ProposalEventDTO; export type ProposalRejectedEvent = YaTsClient.MarketApi.ProposalRejectedEventDTO; @@ -41,4 +42,12 @@ export interface MarketApi { * @param reason User readable reason that should be presented to the Provider */ rejectProposal(receivedProposal: OfferProposal, reason: string): Promise; + + /** + * Fetches payment related decorations, based on the given allocation ID. + * + * @param allocationId The ID of the allocation that will be used to pay for computations related to the demand + * + */ + getPaymentRelatedDemandDecorations(allocationId: string): Promise; } diff --git a/src/market/demand/directors/payment-demand-director.ts b/src/market/demand/directors/payment-demand-director.ts index d621af625..b9df41a01 100644 --- a/src/market/demand/directors/payment-demand-director.ts +++ b/src/market/demand/directors/payment-demand-director.ts @@ -1,15 +1,17 @@ -import { PayerDetails } from "../../../payment/PayerDetails"; -import { ComparisonOperator, DemandBodyBuilder } from "../demand-body-builder"; +import { DemandBodyBuilder } from "../demand-body-builder"; import { IDemandDirector } from "../../market.module"; import { PaymentDemandDirectorConfig } from "./payment-demand-director-config"; +import { Allocation } from "../../../payment"; +import { MarketApi } from "../../api"; export class PaymentDemandDirector implements IDemandDirector { constructor( - private payerDetails: PayerDetails, + private allocation: Allocation, + private marketApiAdapter: MarketApi, private config: PaymentDemandDirectorConfig = new PaymentDemandDirectorConfig(), ) {} - apply(builder: DemandBodyBuilder) { + async apply(builder: DemandBodyBuilder) { // Configure mid-agreement payments builder .addProperty("golem.com.scheme.payu.debit-note.interval-sec?", this.config.midAgreementDebitNoteIntervalSec) @@ -17,13 +19,9 @@ export class PaymentDemandDirector implements IDemandDirector { .addProperty("golem.com.payment.debit-notes.accept-timeout?", this.config.debitNotesAcceptanceTimeoutSec); // Configure payment platform - builder - .addProperty( - `golem.com.payment.platform.${this.payerDetails.getPaymentPlatform()}.address`, - this.payerDetails.address, - ) - .addConstraint(`golem.com.payment.platform.${this.payerDetails.getPaymentPlatform()}.address`, "*") - .addProperty("golem.com.payment.protocol.version", "2") - .addConstraint("golem.com.payment.protocol.version", "1", ComparisonOperator.Gt); + const { constraints, properties } = await this.marketApiAdapter.getPaymentRelatedDemandDecorations( + this.allocation.id, + ); + builder.mergePrototype({ constraints, properties }); } } diff --git a/src/market/market.module.test.ts b/src/market/market.module.test.ts index 2b83df464..f7609fa02 100644 --- a/src/market/market.module.test.ts +++ b/src/market/market.module.test.ts @@ -7,11 +7,10 @@ import { from, of, take, takeUntil, timer } from "rxjs"; import { IProposalRepository, OfferProposal, ProposalProperties } from "./offer-proposal"; import { MarketApiAdapter } from "../shared/yagna/"; import { IAgreementApi } from "../agreement/agreement"; -import { PayerDetails } from "../payment/PayerDetails"; import { IActivityApi, IFileServer } from "../activity"; import { StorageProvider } from "../shared/storage"; import { GolemMarketError } from "./error"; -import { IPaymentApi } from "../payment"; +import { Allocation, IPaymentApi } from "../payment"; const mockMarketApiAdapter = mock(MarketApiAdapter); const mockYagna = mock(YagnaApi); @@ -38,7 +37,26 @@ beforeEach(() => { describe("Market module", () => { describe("buildDemand()", () => { it("should build a demand", async () => { - const payerDetails = new PayerDetails("holesky", "erc20", "0x123"); + const allocation = { + id: "allocation-id", + paymentPlatform: "erc20-holesky-tglm", + } as Allocation; + when(mockMarketApiAdapter.getPaymentRelatedDemandDecorations("allocation-id")).thenResolve({ + properties: [ + { + key: "golem.com.payment.platform.erc20-holesky-tglm.address", + value: "0x123", + }, + { + key: "golem.com.payment.protocol.version", + value: "2", + }, + ], + constraints: [ + "(golem.com.payment.platform.erc20-holesky-tglm.address=*)", + "(golem.com.payment.protocol.version>1)", + ], + }); const demandSpecification = await marketModule.buildDemandDetails( { @@ -55,7 +73,7 @@ describe("Market module", () => { midAgreementPaymentTimeoutSec: 42, }, }, - payerDetails, + allocation, ); const expectedConstraints = [ @@ -113,7 +131,7 @@ describe("Market module", () => { }, ]; - expect(demandSpecification.paymentPlatform).toBe(payerDetails.getPaymentPlatform()); + expect(demandSpecification.paymentPlatform).toBe(allocation.paymentPlatform); expect(demandSpecification.expirationSec).toBe(42); expect(demandSpecification.prototype.constraints).toEqual(expect.arrayContaining(expectedConstraints)); expect(demandSpecification.prototype.properties).toEqual(expectedProperties); diff --git a/src/market/market.module.ts b/src/market/market.module.ts index 4af178212..b89555acf 100644 --- a/src/market/market.module.ts +++ b/src/market/market.module.ts @@ -17,7 +17,6 @@ import { DemandBodyBuilder } from "./demand/demand-body-builder"; import { IAgreementApi } from "../agreement/agreement"; import { BuildDemandOptions, DemandSpecification, IDemandRepository } from "./demand"; import { ProposalsBatch } from "./proposals_batch"; -import { PayerDetails } from "../payment/PayerDetails"; import { IActivityApi, IFileServer } from "../activity"; import { StorageProvider } from "../shared/storage"; import { ActivityDemandDirectorConfig } from "./demand/directors/activity-demand-director-config"; @@ -91,12 +90,12 @@ export interface MarketModule { events: EventEmitter; /** - * Build a DemandSpecification based on the given options and payer details. - * You can obtain the payer details from the payment module. + * Build a DemandSpecification based on the given options and allocation. + * You can obtain an allocation using the payment module. * The method returns a DemandSpecification that can be used to publish the demand to the market, * for example using the `publishDemand` method. */ - buildDemandDetails(options: BuildDemandOptions, payerDetails: PayerDetails): Promise; + buildDemandDetails(options: BuildDemandOptions, allocation: Allocation): Promise; /** * Publishes the demand to the market and handles refreshing it when needed. @@ -223,7 +222,7 @@ export class MarketModuleImpl implements MarketModule { this.fileServer = deps.fileServer; } - async buildDemandDetails(options: BuildDemandOptions, payerDetails: PayerDetails): Promise { + async buildDemandDetails(options: BuildDemandOptions, allocation: Allocation): Promise { const builder = new DemandBodyBuilder(); // Instruct the builder what's required @@ -240,14 +239,10 @@ export class MarketModuleImpl implements MarketModule { await workloadDirector.apply(builder); const paymentConfig = new PaymentDemandDirectorConfig(options.payment); - const paymentDirector = new PaymentDemandDirector(payerDetails, paymentConfig); - paymentDirector.apply(builder); + const paymentDirector = new PaymentDemandDirector(allocation, this.deps.marketApi, paymentConfig); + await paymentDirector.apply(builder); - const spec = new DemandSpecification( - builder.getProduct(), - payerDetails.getPaymentPlatform(), - basicConfig.expirationSec, - ); + const spec = new DemandSpecification(builder.getProduct(), allocation.paymentPlatform, basicConfig.expirationSec); return spec; } diff --git a/src/shared/yagna/adapters/market-api-adapter.ts b/src/shared/yagna/adapters/market-api-adapter.ts index 61e2e7efe..c4e059ff6 100644 --- a/src/shared/yagna/adapters/market-api-adapter.ts +++ b/src/shared/yagna/adapters/market-api-adapter.ts @@ -144,4 +144,8 @@ export class MarketApiAdapter implements MarketApi { return { constraints, properties }; } + + public async getPaymentRelatedDemandDecorations(allocationId: string): Promise { + return this.yagnaApi.payment.getDemandDecorations([allocationId]); + } } diff --git a/tests/e2e/leaseProcessPool.spec.ts b/tests/e2e/leaseProcessPool.spec.ts index 521531261..6b7255712 100644 --- a/tests/e2e/leaseProcessPool.spec.ts +++ b/tests/e2e/leaseProcessPool.spec.ts @@ -3,7 +3,6 @@ import { Allocation, DraftOfferProposalPool, GolemNetwork, YagnaApi } from "../. describe("LeaseProcessPool", () => { const glm = new GolemNetwork(); - const yagnaApi = new YagnaApi(); const modules = { market: glm.market, activity: glm.activity, @@ -25,14 +24,13 @@ describe("LeaseProcessPool", () => { beforeEach(async () => { proposalPool = new DraftOfferProposalPool(); - const payerDetails = await modules.payment.getPayerDetails(); const demandSpecification = await modules.market.buildDemandDetails( { activity: { imageTag: "golem/alpine:latest", }, }, - payerDetails, + allocation, ); proposalSubscription = proposalPool.readFrom( modules.market.startCollectingProposals({