2022-03-30 15:28:56 +00:00
|
|
|
import std/times
|
2022-03-30 10:51:28 +00:00
|
|
|
import std/sequtils
|
|
|
|
import pkg/questionable
|
|
|
|
import pkg/upraises
|
|
|
|
import pkg/stint
|
|
|
|
import pkg/nimcrypto
|
2022-04-20 10:39:40 +00:00
|
|
|
import pkg/chronicles
|
2022-03-30 10:51:28 +00:00
|
|
|
import ./market
|
|
|
|
|
|
|
|
export stint
|
|
|
|
|
2022-03-30 15:28:56 +00:00
|
|
|
const DefaultOfferExpiryInterval = (10 * 60).u256
|
|
|
|
|
2022-03-30 10:51:28 +00:00
|
|
|
type
|
|
|
|
Sales* = ref object
|
|
|
|
market: Market
|
|
|
|
subscription: ?Subscription
|
2022-03-31 08:24:06 +00:00
|
|
|
available*: seq[Availability]
|
2022-03-30 15:28:56 +00:00
|
|
|
offerExpiryInterval*: UInt256
|
2022-03-31 09:41:45 +00:00
|
|
|
onSale: ?OnSale
|
2022-03-30 10:51:28 +00:00
|
|
|
Availability* = object
|
|
|
|
id*: array[32, byte]
|
2022-05-09 14:51:08 +00:00
|
|
|
size*: UInt256
|
|
|
|
duration*: UInt256
|
2022-03-30 10:51:28 +00:00
|
|
|
minPrice*: UInt256
|
2022-03-31 12:35:53 +00:00
|
|
|
Negotiation = ref object
|
|
|
|
sales: Sales
|
2022-04-11 18:03:55 +00:00
|
|
|
requestId: array[32, byte]
|
|
|
|
ask: StorageAsk
|
2022-03-31 12:35:53 +00:00
|
|
|
availability: Availability
|
|
|
|
offer: ?StorageOffer
|
|
|
|
subscription: ?Subscription
|
|
|
|
waiting: ?Future[void]
|
2022-03-31 13:08:26 +00:00
|
|
|
finished: bool
|
2022-03-31 08:24:06 +00:00
|
|
|
OnSale = proc(offer: StorageOffer) {.gcsafe, upraises: [].}
|
2022-03-30 10:51:28 +00:00
|
|
|
|
|
|
|
func new*(_: type Sales, market: Market): Sales =
|
2022-03-30 15:28:56 +00:00
|
|
|
Sales(market: market, offerExpiryInterval: DefaultOfferExpiryInterval)
|
2022-03-30 10:51:28 +00:00
|
|
|
|
2022-03-30 15:07:03 +00:00
|
|
|
proc init*(_: type Availability,
|
2022-05-09 14:51:08 +00:00
|
|
|
size: UInt256,
|
|
|
|
duration: UInt256,
|
2022-03-30 10:51:28 +00:00
|
|
|
minPrice: UInt256): Availability =
|
|
|
|
var id: array[32, byte]
|
|
|
|
doAssert randomBytes(id) == 32
|
|
|
|
Availability(id: id, size: size, duration: duration, minPrice: minPrice)
|
|
|
|
|
2022-03-31 09:41:45 +00:00
|
|
|
proc `onSale=`*(sales: Sales, callback: OnSale) =
|
|
|
|
sales.onSale = some callback
|
|
|
|
|
2022-03-30 10:51:28 +00:00
|
|
|
func add*(sales: Sales, availability: Availability) =
|
|
|
|
sales.available.add(availability)
|
|
|
|
|
|
|
|
func remove*(sales: Sales, availability: Availability) =
|
|
|
|
sales.available.keepItIf(it != availability)
|
|
|
|
|
2022-04-11 18:03:55 +00:00
|
|
|
func findAvailability(sales: Sales, ask: StorageAsk): ?Availability =
|
2022-03-30 10:51:28 +00:00
|
|
|
for availability in sales.available:
|
2022-05-09 14:51:08 +00:00
|
|
|
if ask.size <= availability.size and
|
|
|
|
ask.duration <= availability.duration and
|
2022-04-11 18:03:55 +00:00
|
|
|
ask.maxPrice >= availability.minPrice:
|
2022-03-30 10:51:28 +00:00
|
|
|
return some availability
|
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
proc createOffer(negotiation: Negotiation): StorageOffer =
|
2022-03-30 10:51:28 +00:00
|
|
|
StorageOffer(
|
2022-04-11 18:03:55 +00:00
|
|
|
requestId: negotiation.requestId,
|
|
|
|
price: negotiation.ask.maxPrice,
|
2022-03-31 12:35:53 +00:00
|
|
|
expiry: getTime().toUnix().u256 + negotiation.sales.offerExpiryInterval
|
2022-03-30 10:51:28 +00:00
|
|
|
)
|
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
proc sendOffer(negotiation: Negotiation) {.async.} =
|
|
|
|
let offer = negotiation.createOffer()
|
|
|
|
negotiation.offer = some await negotiation.sales.market.offerStorage(offer)
|
|
|
|
|
2022-03-31 13:08:26 +00:00
|
|
|
proc finish(negotiation: Negotiation, success: bool) =
|
|
|
|
if negotiation.finished:
|
|
|
|
return
|
|
|
|
|
|
|
|
negotiation.finished = true
|
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
if subscription =? negotiation.subscription:
|
|
|
|
asyncSpawn subscription.unsubscribe()
|
2022-03-31 13:08:26 +00:00
|
|
|
|
|
|
|
if waiting =? negotiation.waiting:
|
|
|
|
waiting.cancel()
|
|
|
|
|
|
|
|
if success and offer =? negotiation.offer:
|
2022-03-31 12:35:53 +00:00
|
|
|
if onSale =? negotiation.sales.onSale:
|
|
|
|
onSale(offer)
|
|
|
|
else:
|
|
|
|
negotiation.sales.add(negotiation.availability)
|
|
|
|
|
2022-03-31 13:08:26 +00:00
|
|
|
proc onSelect(negotiation: Negotiation, offerId: array[32, byte]) =
|
|
|
|
if offer =? negotiation.offer and offer.id == offerId:
|
|
|
|
negotiation.finish(success = true)
|
|
|
|
else:
|
|
|
|
negotiation.finish(success = false)
|
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
proc subscribeSelect(negotiation: Negotiation) {.async.} =
|
|
|
|
without offer =? negotiation.offer:
|
2022-03-31 08:24:06 +00:00
|
|
|
return
|
2022-03-31 12:35:53 +00:00
|
|
|
proc onSelect(offerId: array[32, byte]) {.gcsafe, upraises:[].} =
|
|
|
|
negotiation.onSelect(offerId)
|
|
|
|
let market = negotiation.sales.market
|
|
|
|
let subscription = await market.subscribeSelection(offer.requestId, onSelect)
|
|
|
|
negotiation.subscription = some subscription
|
2022-03-31 08:24:06 +00:00
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
proc waitForExpiry(negotiation: Negotiation) {.async.} =
|
|
|
|
without offer =? negotiation.offer:
|
|
|
|
return
|
|
|
|
await negotiation.sales.market.waitUntil(offer.expiry)
|
2022-03-31 13:08:26 +00:00
|
|
|
negotiation.finish(success = false)
|
2022-03-31 12:35:53 +00:00
|
|
|
|
|
|
|
proc start(negotiation: Negotiation) {.async.} =
|
2022-04-20 10:39:40 +00:00
|
|
|
try:
|
|
|
|
let sales = negotiation.sales
|
|
|
|
let availability = negotiation.availability
|
|
|
|
sales.remove(availability)
|
|
|
|
await negotiation.sendOffer()
|
|
|
|
await negotiation.subscribeSelect()
|
|
|
|
negotiation.waiting = some negotiation.waitForExpiry()
|
|
|
|
except CatchableError as e:
|
|
|
|
error "Negotiation failed", msg = e.msg
|
|
|
|
|
|
|
|
proc handleRequest(sales: Sales, requestId: array[32, byte], ask: StorageAsk) =
|
2022-04-11 18:03:55 +00:00
|
|
|
without availability =? sales.findAvailability(ask):
|
2022-03-31 12:35:53 +00:00
|
|
|
return
|
2022-03-31 08:24:06 +00:00
|
|
|
|
2022-03-31 12:35:53 +00:00
|
|
|
let negotiation = Negotiation(
|
|
|
|
sales: sales,
|
2022-04-11 18:03:55 +00:00
|
|
|
requestId: requestId,
|
|
|
|
ask: ask,
|
2022-03-31 12:35:53 +00:00
|
|
|
availability: availability
|
|
|
|
)
|
|
|
|
|
|
|
|
asyncSpawn negotiation.start()
|
2022-03-30 10:51:28 +00:00
|
|
|
|
|
|
|
proc start*(sales: Sales) =
|
|
|
|
doAssert sales.subscription.isNone, "Sales already started"
|
|
|
|
|
2022-04-11 18:03:55 +00:00
|
|
|
proc onRequest(requestId: array[32, byte], ask: StorageAsk) {.gcsafe, upraises:[].} =
|
2022-04-20 10:39:40 +00:00
|
|
|
sales.handleRequest(requestId, ask)
|
2022-03-30 10:51:28 +00:00
|
|
|
|
|
|
|
proc subscribe {.async.} =
|
2022-04-20 10:39:40 +00:00
|
|
|
try:
|
|
|
|
sales.subscription = some await sales.market.subscribeRequests(onRequest)
|
|
|
|
except CatchableError as e:
|
|
|
|
error "Unable to start sales", msg = e.msg
|
2022-03-30 10:51:28 +00:00
|
|
|
|
|
|
|
asyncSpawn subscribe()
|
|
|
|
|
|
|
|
proc stop*(sales: Sales) =
|
|
|
|
if subscription =? sales.subscription:
|
|
|
|
sales.subscription = Subscription.none
|
2022-04-20 10:39:40 +00:00
|
|
|
|
|
|
|
proc unsubscribe {.async.} =
|
|
|
|
try:
|
|
|
|
await subscription.unsubscribe()
|
|
|
|
except CatchableError as e:
|
|
|
|
warn "Unsubscribe failed", msg = e.msg
|
|
|
|
|
|
|
|
asyncSpawn unsubscribe()
|