From fe23cb89d70aaee520ce3f7459b79216928c8f80 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Mon, 28 Mar 2022 12:28:22 +0200 Subject: [PATCH] [purchasing] Select cheapest offer --- dagger/market.nim | 26 ++++++++++ dagger/purchasing.nim | 21 +++++++- tests/dagger/helpers/mockmarket.nim | 76 +++++++++++++++++++++++++++++ tests/dagger/testpurchasing.nim | 30 +++++++++--- 4 files changed, 145 insertions(+), 8 deletions(-) diff --git a/dagger/market.nim b/dagger/market.nim index 0bd715c4..80ee9cd6 100644 --- a/dagger/market.nim +++ b/dagger/market.nim @@ -8,6 +8,32 @@ export offers type Market* = ref object of RootObj + Subscription* = ref object of RootObj + OnRequest* = proc(request: StorageRequest) {.gcsafe.} + OnOffer* = proc(offer: StorageOffer) {.gcsafe.} method requestStorage*(market: Market, request: StorageRequest) {.base, async.} = raiseAssert("not implemented") + +method offerStorage*(market: Market, offer: StorageOffer) {.base, async.} = + raiseAssert("not implemented") + +method selectOffer*(market: Market, id: array[32, byte]) {.base, async.} = + raiseAssert("not implemented") + +method waitUntil*(market: Market, expiry: UInt256) {.base, async.} = + raiseAssert("not implemented") + +method subscribeRequests*(market: Market, + callback: OnRequest): + Future[Subscription] {.base, async.} = + raiseAssert("not implemented") + +method subscribeOffers*(market: Market, + requestId: array[32, byte], + callback: OnOffer): + Future[Subscription] {.base, async.} = + raiseAssert("not implemented") + +method unsubscribe*(subscription: Subscription) {.base, async.} = + raiseAssert("not implemented") diff --git a/dagger/purchasing.nim b/dagger/purchasing.nim index 6ecfbe02..9149172a 100644 --- a/dagger/purchasing.nim +++ b/dagger/purchasing.nim @@ -47,8 +47,27 @@ proc purchase*(purchasing: Purchasing, request: StorageRequest): Purchase = purchase.start() purchase +proc selectOffer(purchase: Purchase) {.async.} = + var cheapest: ?StorageOffer + for offer in purchase.offers: + if current =? cheapest: + if current.price > offer.price: + cheapest = some offer + else: + cheapest = some offer + if cheapest =? cheapest: + await purchase.market.selectOffer(cheapest.id) + proc run(purchase: Purchase) {.async.} = - await purchase.market.requestStorage(purchase.request) + proc onOffer(offer: StorageOffer) = + purchase.offers.add(offer) + let market = purchase.market + let request = purchase.request + let subscription = await market.subscribeOffers(request.id, onOffer) + await market.requestStorage(request) + await market.waitUntil(request.expiry) + await purchase.selectOffer() + await subscription.unsubscribe() proc start(purchase: Purchase) = purchase.future = purchase.run() diff --git a/tests/dagger/helpers/mockmarket.nim b/tests/dagger/helpers/mockmarket.nim index 3e644b1b..7dd7a039 100644 --- a/tests/dagger/helpers/mockmarket.nim +++ b/tests/dagger/helpers/mockmarket.nim @@ -1,8 +1,84 @@ +import std/sequtils +import std/heapqueue import pkg/dagger/market type MockMarket* = ref object of Market requested*: seq[StorageRequest] + offered*: seq[StorageOffer] + selected*: seq[array[32, byte]] + subscriptions: Subscriptions + time: UInt256 + waiting: HeapQueue[Expiry] + Subscriptions = object + onRequest: seq[RequestSubscription] + onOffer: seq[OfferSubscription] + RequestSubscription* = ref object of Subscription + market: MockMarket + callback: OnRequest + OfferSubscription* = ref object of Subscription + market: MockMarket + requestId: array[32, byte] + callback: OnOffer + Expiry = object + future: Future[void] + expiry: UInt256 method requestStorage*(market: MockMarket, request: StorageRequest) {.async.} = market.requested.add(request) + for subscription in market.subscriptions.onRequest: + subscription.callback(request) + +method offerStorage*(market: MockMarket, offer: StorageOffer) {.async.} = + market.offered.add(offer) + for subscription in market.subscriptions.onOffer: + if subscription.requestId == offer.requestId: + subscription.callback(offer) + +method selectOffer*(market: MockMarket, id: array[32, byte]) {.async.} = + market.selected.add(id) + +method subscribeRequests*(market: MockMarket, + callback: OnRequest): + Future[Subscription] {.async.} = + let subscription = RequestSubscription( + market: market, + callback: callback + ) + market.subscriptions.onRequest.add(subscription) + return subscription + +method subscribeOffers*(market: MockMarket, + requestId: array[32, byte], + callback: OnOffer): + Future[Subscription] {.async.} = + let subscription = OfferSubscription( + market: market, + requestId: requestId, + callback: callback + ) + market.subscriptions.onOffer.add(subscription) + return subscription + +method unsubscribe*(subscription: RequestSubscription) {.async.} = + subscription.market.subscriptions.onRequest.keepItIf(it != subscription) + +method unsubscribe*(subscription: OfferSubscription) {.async.} = + subscription.market.subscriptions.onOffer.keepItIf(it != subscription) + +func `<`(a, b: Expiry): bool = + a.expiry < b.expiry + +method waitUntil*(market: MockMarket, expiry: UInt256): Future[void] = + let future = Future[void]() + if expiry > market.time: + market.waiting.push(Expiry(future: future, expiry: expiry)) + else: + future.complete() + future + +proc advanceTimeTo*(market: MockMarket, time: UInt256) = + doAssert(time >= market.time) + market.time = time + while market.waiting.len > 0 and market.waiting[0].expiry <= time: + market.waiting.pop().future.complete() diff --git a/tests/dagger/testpurchasing.nim b/tests/dagger/testpurchasing.nim index cddfa862..9d80a210 100644 --- a/tests/dagger/testpurchasing.nim +++ b/tests/dagger/testpurchasing.nim @@ -21,8 +21,13 @@ suite "Purchasing": contentHash: array[32, byte].example ) + proc purchaseAndWait(request: StorageRequest) {.async.} = + let purchase = purchasing.purchase(request) + market.advanceTimeTo(market.requested[^1].expiry) + await purchase.wait() + test "submits a storage request when asked": - await purchasing.purchase(request).wait() + await purchaseAndWait(request) let submitted = market.requested[0] check submitted.duration == request.duration check submitted.size == request.size @@ -34,12 +39,12 @@ suite "Purchasing": test "can change default value for proof probability": purchasing.proofProbability = 42.u256 - await purchasing.purchase(request).wait() + await purchaseAndWait(request) check market.requested[0].proofProbability == 42.u256 test "can override proof probability per request": request.proofProbability = 42.u256 - await purchasing.purchase(request).wait() + await purchaseAndWait(request) check market.requested[0].proofProbability == 42.u256 test "has a default value for request expiration interval": @@ -48,16 +53,27 @@ suite "Purchasing": test "can change default value for request expiration interval": purchasing.requestExpiryInterval = 42.u256 let start = getTime().toUnix() - await purchasing.purchase(request).wait() + await purchaseAndWait(request) check market.requested[0].expiry == (start + 42).u256 test "can override expiry time per request": let expiry = (getTime().toUnix() + 42).u256 request.expiry = expiry - await purchasing.purchase(request).wait() + await purchaseAndWait(request) check market.requested[0].expiry == expiry test "includes a random nonce in every storage request": - await purchasing.purchase(request).wait() - await purchasing.purchase(request).wait() + await purchaseAndWait(request) + await purchaseAndWait(request) check market.requested[0].nonce != market.requested[1].nonce + + test "selects the cheapest offer": + let purchase = purchasing.purchase(request) + let request = market.requested[0] + let offer1 = StorageOffer(requestId: request.id, price: 20.u256) + let offer2 = StorageOffer(requestId: request.id, price: 10.u256) + await market.offerStorage(offer1) + await market.offerStorage(offer2) + market.advanceTimeTo(request.expiry) + await purchase.wait() + check market.selected[0] == offer2.id