nim-dagger/codex/sales.nim

187 lines
5.4 KiB
Nim

import std/sequtils
import pkg/questionable
import pkg/upraises
import pkg/stint
import pkg/nimcrypto
import pkg/chronicles
import ./market
import ./clock
export stint
const DefaultOfferExpiryInterval = (10 * 60).u256
type
Sales* = ref object
market: Market
clock: Clock
subscription: ?Subscription
available*: seq[Availability]
offerExpiryInterval*: UInt256
retrieve: ?Retrieve
prove: ?Prove
onSale: ?OnSale
Availability* = object
id*: array[32, byte]
size*: UInt256
duration*: UInt256
minPrice*: UInt256
Negotiation = ref object
sales: Sales
requestId: array[32, byte]
ask: StorageAsk
availability: Availability
request: ?StorageRequest
offer: ?StorageOffer
subscription: ?Subscription
running: ?Future[void]
waiting: ?Future[void]
finished: bool
Retrieve = proc(cid: string): Future[void] {.gcsafe, upraises: [].}
Prove = proc(cid: string): Future[seq[byte]] {.gcsafe, upraises: [].}
OnSale = proc(availability: Availability, request: StorageRequest) {.gcsafe, upraises: [].}
func new*(_: type Sales, market: Market, clock: Clock): Sales =
Sales(
market: market,
clock: clock,
offerExpiryInterval: DefaultOfferExpiryInterval
)
proc init*(_: type Availability,
size: UInt256,
duration: UInt256,
minPrice: UInt256): Availability =
var id: array[32, byte]
doAssert randomBytes(id) == 32
Availability(id: id, size: size, duration: duration, minPrice: minPrice)
proc `retrieve=`*(sales: Sales, retrieve: Retrieve) =
sales.retrieve = some retrieve
proc `prove=`*(sales: Sales, prove: Prove) =
sales.prove = some prove
proc `onSale=`*(sales: Sales, callback: OnSale) =
sales.onSale = some callback
func add*(sales: Sales, availability: Availability) =
sales.available.add(availability)
func remove*(sales: Sales, availability: Availability) =
sales.available.keepItIf(it != availability)
func findAvailability(sales: Sales, ask: StorageAsk): ?Availability =
for availability in sales.available:
if ask.size <= availability.size and
ask.duration <= availability.duration and
ask.maxPrice >= availability.minPrice:
return some availability
proc finish(negotiation: Negotiation, success: bool) =
if negotiation.finished:
return
negotiation.finished = true
if subscription =? negotiation.subscription:
asyncSpawn subscription.unsubscribe()
if running =? negotiation.running:
running.cancel()
if waiting =? negotiation.waiting:
waiting.cancel()
if success and request =? negotiation.request:
if onSale =? negotiation.sales.onSale:
onSale(negotiation.availability, request)
else:
negotiation.sales.add(negotiation.availability)
proc onFulfill(negotiation: Negotiation, requestId: array[32, byte]) {.async.} =
try:
let market = negotiation.sales.market
let host = await market.getHost(requestId)
let me = await market.getSigner()
negotiation.finish(success = (host == me.some))
except CatchableError:
negotiation.finish(success = false)
proc subscribeFulfill(negotiation: Negotiation) {.async.} =
proc onFulfill(requestId: array[32, byte]) {.gcsafe, upraises:[].} =
asyncSpawn negotiation.onFulfill(requestId)
let market = negotiation.sales.market
let subscription = await market.subscribeFulfillment(negotiation.requestId, onFulfill)
negotiation.subscription = some subscription
proc waitForExpiry(negotiation: Negotiation) {.async.} =
without offer =? negotiation.offer:
return
await negotiation.sales.clock.waitUntil(offer.expiry.truncate(int64))
negotiation.finish(success = false)
proc start(negotiation: Negotiation) {.async.} =
try:
let sales = negotiation.sales
let market = sales.market
let availability = negotiation.availability
without retrieve =? sales.retrieve:
raiseAssert "retrieve proc not set"
without prove =? sales.prove:
raiseAssert "prove proc not set"
sales.remove(availability)
await negotiation.subscribeFulfill()
negotiation.request = await market.getRequest(negotiation.requestId)
without request =? negotiation.request:
negotiation.finish(success = false)
return
await retrieve(request.content.cid)
let proof = await prove(request.content.cid)
await market.fulfillRequest(request.id, proof)
negotiation.waiting = some negotiation.waitForExpiry()
except CancelledError:
raise
except CatchableError as e:
error "Negotiation failed", msg = e.msg
negotiation.finish(success = false)
proc handleRequest(sales: Sales, requestId: array[32, byte], ask: StorageAsk) =
without availability =? sales.findAvailability(ask):
return
let negotiation = Negotiation(
sales: sales,
requestId: requestId,
ask: ask,
availability: availability
)
negotiation.running = some negotiation.start()
proc start*(sales: Sales) {.async.} =
doAssert sales.subscription.isNone, "Sales already started"
proc onRequest(requestId: array[32, byte], ask: StorageAsk) {.gcsafe, upraises:[].} =
sales.handleRequest(requestId, ask)
try:
sales.subscription = some await sales.market.subscribeRequests(onRequest)
except CatchableError as e:
error "Unable to start sales", msg = e.msg
proc stop*(sales: Sales) {.async.} =
if subscription =? sales.subscription:
sales.subscription = Subscription.none
try:
await subscription.unsubscribe()
except CatchableError as e:
warn "Unsubscribe failed", msg = e.msg