[purchasing] Select cheapest offer

This commit is contained in:
Mark Spanbroek 2022-03-28 12:28:22 +02:00 committed by markspanbroek
parent 03140fdf49
commit fe23cb89d7
4 changed files with 145 additions and 8 deletions

View File

@ -8,6 +8,32 @@ export offers
type type
Market* = ref object of RootObj 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.} = method requestStorage*(market: Market, request: StorageRequest) {.base, async.} =
raiseAssert("not implemented") 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")

View File

@ -47,8 +47,27 @@ proc purchase*(purchasing: Purchasing, request: StorageRequest): Purchase =
purchase.start() purchase.start()
purchase 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.} = 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) = proc start(purchase: Purchase) =
purchase.future = purchase.run() purchase.future = purchase.run()

View File

@ -1,8 +1,84 @@
import std/sequtils
import std/heapqueue
import pkg/dagger/market import pkg/dagger/market
type type
MockMarket* = ref object of Market MockMarket* = ref object of Market
requested*: seq[StorageRequest] 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.} = method requestStorage*(market: MockMarket, request: StorageRequest) {.async.} =
market.requested.add(request) 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()

View File

@ -21,8 +21,13 @@ suite "Purchasing":
contentHash: array[32, byte].example 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": test "submits a storage request when asked":
await purchasing.purchase(request).wait() await purchaseAndWait(request)
let submitted = market.requested[0] let submitted = market.requested[0]
check submitted.duration == request.duration check submitted.duration == request.duration
check submitted.size == request.size check submitted.size == request.size
@ -34,12 +39,12 @@ suite "Purchasing":
test "can change default value for proof probability": test "can change default value for proof probability":
purchasing.proofProbability = 42.u256 purchasing.proofProbability = 42.u256
await purchasing.purchase(request).wait() await purchaseAndWait(request)
check market.requested[0].proofProbability == 42.u256 check market.requested[0].proofProbability == 42.u256
test "can override proof probability per request": test "can override proof probability per request":
request.proofProbability = 42.u256 request.proofProbability = 42.u256
await purchasing.purchase(request).wait() await purchaseAndWait(request)
check market.requested[0].proofProbability == 42.u256 check market.requested[0].proofProbability == 42.u256
test "has a default value for request expiration interval": test "has a default value for request expiration interval":
@ -48,16 +53,27 @@ suite "Purchasing":
test "can change default value for request expiration interval": test "can change default value for request expiration interval":
purchasing.requestExpiryInterval = 42.u256 purchasing.requestExpiryInterval = 42.u256
let start = getTime().toUnix() let start = getTime().toUnix()
await purchasing.purchase(request).wait() await purchaseAndWait(request)
check market.requested[0].expiry == (start + 42).u256 check market.requested[0].expiry == (start + 42).u256
test "can override expiry time per request": test "can override expiry time per request":
let expiry = (getTime().toUnix() + 42).u256 let expiry = (getTime().toUnix() + 42).u256
request.expiry = expiry request.expiry = expiry
await purchasing.purchase(request).wait() await purchaseAndWait(request)
check market.requested[0].expiry == expiry check market.requested[0].expiry == expiry
test "includes a random nonce in every storage request": test "includes a random nonce in every storage request":
await purchasing.purchase(request).wait() await purchaseAndWait(request)
await purchasing.purchase(request).wait() await purchaseAndWait(request)
check market.requested[0].nonce != market.requested[1].nonce 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