market: make all configuration getters synchronous

This commit is contained in:
Mark Spanbroek 2025-03-13 10:36:21 +01:00
parent d077883577
commit a57dd9ddb1
No known key found for this signature in database
GPG Key ID: FBE3E9548D427C00
14 changed files with 83 additions and 165 deletions

View File

@ -109,9 +109,12 @@ proc bootstrapInteractions(s: CodexServer): Future[void] {.async.} =
quit QuitFailure
let marketplace = Marketplace.new(marketplaceAddress, signer)
let market = OnChainMarket.new(
without market =? await OnChainMarket.load(
marketplace, config.rewardRecipient, config.marketplaceRequestCacheSize
)
), error:
fatal "Cannot load market", error = error.msg
quit QuitFailure
let clock = OnChainClock.new(provider)
var client: ?ClientInteractions
@ -134,10 +137,6 @@ proc bootstrapInteractions(s: CodexServer): Future[void] {.async.} =
if config.simulateProofFailures > 0:
warn "Proof failure simulation is not enabled for this build! Configuration ignored"
if error =? (await market.loadConfig()).errorOption:
fatal "Cannot load market configuration", error = error.msg
quit QuitFailure
let purchasing = Purchasing.new(market, clock)
let sales = Sales.new(market, clock, repo, proofFailures)
client = some ClientInteractions.new(clock, purchasing)

View File

@ -21,7 +21,7 @@ type
contract: Marketplace
signer: Signer
rewardRecipient: ?Address
configuration: ?MarketplaceConfig
configuration: MarketplaceConfig
requestCache: LruCache[string, StorageRequest]
MarketSubscription = market.Subscription
@ -29,24 +29,40 @@ type
OnChainMarketSubscription = ref object of MarketSubscription
eventSubscription: EventSubscription
func new*(
proc loadConfig(
market: OnChainMarket
): Future[?!MarketplaceConfig] {.async: (raises: [CancelledError]).} =
try:
return success await market.contract.configuration()
except EthersError:
let err = getCurrentException()
return failure newException(
MarketError,
"Failed to fetch the config from the Marketplace contract: " & err.msg,
)
proc load*(
_: type OnChainMarket,
contract: Marketplace,
rewardRecipient = Address.none,
requestCacheSize: uint16 = DefaultRequestCacheSize,
): OnChainMarket =
): Future[?!OnChainMarket] {.async: (raises: [CancelledError]).} =
without signer =? contract.signer:
raiseAssert("Marketplace contract should have a signer")
var requestCache = newLruCache[string, StorageRequest](int(requestCacheSize))
OnChainMarket(
let market = OnChainMarket(
contract: contract,
signer: signer,
rewardRecipient: rewardRecipient,
requestCache: requestCache,
)
market.configuration = ? await market.loadConfig()
return success market
proc raiseMarketError(message: string) {.raises: [MarketError].} =
raise newException(MarketError, message)
@ -62,20 +78,6 @@ template convertEthersError(msg: string = "", body) =
except EthersError as error:
raiseMarketError(error.msgDetail.prefixWith(msg))
proc config(
market: OnChainMarket
): Future[MarketplaceConfig] {.async: (raises: [CancelledError, MarketError]).} =
without resolvedConfig =? market.configuration:
if err =? (await market.loadConfig()).errorOption:
raiseMarketError(err.msg)
without config =? market.configuration:
raiseMarketError("Failed to access to config from the Marketplace contract")
return config
return resolvedConfig
proc approveFunds(
market: OnChainMarket, amount: UInt256
) {.async: (raises: [CancelledError, MarketError]).} =
@ -85,67 +87,30 @@ proc approveFunds(
let token = Erc20Token.new(tokenAddress, market.signer)
discard await token.increaseAllowance(market.contract.address(), amount).confirm(1)
method loadConfig*(
market: OnChainMarket
): Future[?!void] {.async: (raises: [CancelledError]).} =
try:
without config =? market.configuration:
let fetchedConfig = await market.contract.configuration()
market.configuration = some fetchedConfig
return success()
except EthersError as err:
return failure newException(
MarketError,
"Failed to fetch the config from the Marketplace contract: " & err.msg,
)
method getZkeyHash*(
market: OnChainMarket
): Future[?string] {.async: (raises: [CancelledError, MarketError]).} =
let config = await market.config()
return some config.proofs.zkeyHash
method getSigner*(
market: OnChainMarket
): Future[Address] {.async: (raises: [CancelledError, MarketError]).} =
convertEthersError("Failed to get signer address"):
return await market.signer.getAddress()
method periodicity*(
market: OnChainMarket
): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} =
convertEthersError("Failed to get Marketplace config"):
let config = await market.config()
let period = config.proofs.period
return Periodicity(seconds: period)
method zkeyHash*(market: OnChainMarket): string =
return market.configuration.proofs.zkeyHash
method proofTimeout*(
market: OnChainMarket
): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} =
convertEthersError("Failed to get Marketplace config"):
let config = await market.config()
return config.proofs.timeout
method periodicity*(market: OnChainMarket): Periodicity =
let period = market.configuration.proofs.period
return Periodicity(seconds: period)
method repairRewardPercentage*(
market: OnChainMarket
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
convertEthersError("Failed to get Marketplace config"):
let config = await market.config()
return config.collateral.repairRewardPercentage
method proofTimeout*(market: OnChainMarket): uint64 =
return market.configuration.proofs.timeout
method requestDurationLimit*(market: OnChainMarket): Future[uint64] {.async.} =
convertEthersError("Failed to get Marketplace config"):
let config = await market.config()
return config.requestDurationLimit
method repairRewardPercentage*(market: OnChainMarket): uint8 =
return market.configuration.collateral.repairRewardPercentage
method proofDowntime*(
market: OnChainMarket
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
convertEthersError("Failed to get Marketplace config"):
let config = await market.config()
return config.proofs.downtime
method requestDurationLimit*(market: OnChainMarket): uint64 =
return market.configuration.requestDurationLimit
method proofDowntime*(market: OnChainMarket): uint8 =
return market.configuration.proofs.downtime
method getPointer*(market: OnChainMarket, slotId: SlotId): Future[uint8] {.async.} =
convertEthersError("Failed to get slot pointer"):

View File

@ -64,51 +64,32 @@ type
ProofSubmitted* = object of MarketplaceEvent
id*: SlotId
method loadConfig*(
market: Market
): Future[?!void] {.base, async: (raises: [CancelledError]).} =
raiseAssert("not implemented")
method getZkeyHash*(
market: Market
): Future[?string] {.base, async: (raises: [CancelledError, MarketError]).} =
raiseAssert("not implemented")
method getSigner*(
market: Market
): Future[Address] {.base, async: (raises: [CancelledError, MarketError]).} =
raiseAssert("not implemented")
method periodicity*(
market: Market
): Future[Periodicity] {.base, async: (raises: [CancelledError, MarketError]).} =
method periodicity*(market: Market): Periodicity {.base, gcsafe, raises:[].} =
raiseAssert("not implemented")
method proofTimeout*(
market: Market
): Future[uint64] {.base, async: (raises: [CancelledError, MarketError]).} =
method proofTimeout*(market: Market): uint64 {.base, gcsafe, raises:[].} =
raiseAssert("not implemented")
method repairRewardPercentage*(
market: Market
): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} =
method repairRewardPercentage*(market: Market): uint8 {.base, gcsafe, raises:[].} =
raiseAssert("not implemented")
method requestDurationLimit*(market: Market): Future[uint64] {.base, async.} =
method requestDurationLimit*(market: Market): uint64 {.base, gcsafe, raises:[].} =
raiseAssert("not implemented")
method proofDowntime*(
market: Market
): Future[uint8] {.base, async: (raises: [CancelledError, MarketError]).} =
method proofDowntime*(market: Market): uint8 {.base, gcsafe, raises: [].} =
raiseAssert("not implemented")
method getPointer*(market: Market, slotId: SlotId): Future[uint8] {.base, async.} =
raiseAssert("not implemented")
proc inDowntime*(market: Market, slotId: SlotId): Future[bool] {.async.} =
let downtime = await market.proofDowntime
let pntr = await market.getPointer(slotId)
return pntr < downtime
return pntr < market.proofDowntime
method requestStorage*(
market: Market, request: StorageRequest

View File

@ -705,7 +705,8 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) =
headers = headers,
)
let requestDurationLimit = await contracts.purchasing.market.requestDurationLimit
let requestDurationLimit = contracts.purchasing.market.requestDurationLimit
if params.duration > requestDurationLimit:
return RestApiResponse.error(
Http422,

View File

@ -30,8 +30,8 @@ proc waitUntilNextPeriod(clock: Clock, periodicity: Periodicity) {.async.} =
await clock.waitUntil((periodEnd + 1).toSecondsSince1970)
proc waitForStableChallenge(market: Market, clock: Clock, slotId: SlotId) {.async.} =
let periodicity = await market.periodicity()
let downtime = await market.proofDowntime()
let periodicity = market.periodicity
let downtime = market.proofDowntime
await clock.waitUntilNextPeriod(periodicity)
while (await market.getPointer(slotId)) > (256 - downtime):
await clock.waitUntilNextPeriod(periodicity)

View File

@ -58,17 +58,17 @@ proc proveLoop(
slotIndex
slotId = slot.id
proc getCurrentPeriod(): Future[Period] {.async.} =
let periodicity = await market.periodicity()
proc getCurrentPeriod(): Period =
let periodicity = market.periodicity
return periodicity.periodOf(clock.now().Timestamp)
proc waitUntilPeriod(period: Period) {.async.} =
let periodicity = await market.periodicity()
let periodicity = market.periodicity
# Ensure that we're past the period boundary by waiting an additional second
await clock.waitUntil((periodicity.periodStart(period) + 1).toSecondsSince1970)
while true:
let currentPeriod = await getCurrentPeriod()
let currentPeriod = getCurrentPeriod()
let slotState = await market.slotState(slot.id)
case slotState

View File

@ -120,7 +120,7 @@ proc findEpoch(validation: Validation, secondsAgo: uint64): SecondsSince1970 =
proc restoreHistoricalState(validation: Validation) {.async.} =
trace "Restoring historical state..."
let requestDurationLimit = await validation.market.requestDurationLimit
let requestDurationLimit = validation.market.requestDurationLimit
let startTimeEpoch = validation.findEpoch(secondsAgo = requestDurationLimit)
let slotFilledEvents =
await validation.market.queryPastSlotFilledEvents(fromTime = startTimeEpoch)
@ -137,8 +137,8 @@ proc restoreHistoricalState(validation: Validation) {.async.} =
proc start*(validation: Validation) {.async.} =
trace "Starting validator",
groups = validation.config.groups, groupIndex = validation.config.groupIndex
validation.periodicity = await validation.market.periodicity()
validation.proofTimeout = await validation.market.proofTimeout()
validation.periodicity = validation.market.periodicity
validation.proofTimeout = validation.market.proofTimeout
await validation.subscribeSlotFilled()
await validation.restoreHistoricalState()
validation.running = validation.run()

View File

@ -142,37 +142,24 @@ proc new*(_: type MockMarket, clock: ?Clock = Clock.none): MockMarket =
signer: Address.example, config: config, canReserveSlot: true, clock: clock
)
method loadConfig*(
market: MockMarket
): Future[?!void] {.async: (raises: [CancelledError]).} =
discard
method getSigner*(
market: MockMarket
): Future[Address] {.async: (raises: [CancelledError, MarketError]).} =
return market.signer
method periodicity*(
mock: MockMarket
): Future[Periodicity] {.async: (raises: [CancelledError, MarketError]).} =
method periodicity*(mock: MockMarket): Periodicity =
return Periodicity(seconds: mock.config.proofs.period)
method proofTimeout*(
market: MockMarket
): Future[uint64] {.async: (raises: [CancelledError, MarketError]).} =
method proofTimeout*(market: MockMarket): uint64 =
return market.config.proofs.timeout
method requestDurationLimit*(market: MockMarket): Future[uint64] {.async.} =
method requestDurationLimit*(market: MockMarket): uint64 =
return market.config.requestDurationLimit
method proofDowntime*(
market: MockMarket
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
method proofDowntime*(market: MockMarket): uint8 =
return market.config.proofs.downtime
method repairRewardPercentage*(
market: MockMarket
): Future[uint8] {.async: (raises: [CancelledError, MarketError]).} =
method repairRewardPercentage*(market: MockMarket): uint8 =
return market.config.collateral.repairRewardPercentage
method getPointer*(market: MockMarket, slotId: SlotId): Future[uint8] {.async.} =

View File

@ -1,8 +1,8 @@
import pkg/codex/market
import ../../helpers/mockclock
proc advanceToNextPeriod*(clock: MockClock, market: Market) {.async.} =
let periodicity = await market.periodicity()
proc advanceToNextPeriod*(clock: MockClock, market: Market) =
let periodicity = market.periodicity()
let period = periodicity.periodOf(clock.now().Timestamp)
let periodEnd = periodicity.periodEnd(period)
clock.set(periodEnd.toSecondsSince1970 + 1)

View File

@ -40,7 +40,7 @@ asyncchecksuite "sales state 'initialproving'":
proc allowProofToStart() {.async.} =
# it won't start proving until the next period
await clock.advanceToNextPeriod(market)
clock.advanceToNextPeriod(market)
test "switches to cancelled state when request expires":
let next = state.onCancelled(request)

View File

@ -14,6 +14,7 @@ import ../../examples
import ../../helpers
import ../../helpers/mockmarket
import ../../helpers/mockclock
import ../helpers/periods
asyncchecksuite "sales state 'proving'":
let slot = Slot.example
@ -38,12 +39,6 @@ asyncchecksuite "sales state 'proving'":
agent = newSalesAgent(context, request.id, slot.slotIndex, request.some)
state = SaleProving.new()
proc advanceToNextPeriod(market: Market) {.async.} =
let periodicity = await market.periodicity()
let current = periodicity.periodOf(clock.now().Timestamp)
let periodEnd = periodicity.periodEnd(current)
clock.set(periodEnd.toSecondsSince1970 + 1)
test "switches to cancelled state when request expires":
let next = state.onCancelled(request)
check !next of SaleCancelled
@ -64,7 +59,7 @@ asyncchecksuite "sales state 'proving'":
let future = state.run(agent)
market.setProofRequired(slot.id, true)
await market.advanceToNextPeriod()
clock.advanceToNextPeriod(market)
check eventually receivedIds.contains(slot.id)
@ -77,7 +72,7 @@ asyncchecksuite "sales state 'proving'":
let future = state.run(agent)
market.slotState[slot.id] = SlotState.Finished
await market.advanceToNextPeriod()
clock.advanceToNextPeriod(market)
check eventually future.finished
check !(future.read()) of SalePayout
@ -88,7 +83,7 @@ asyncchecksuite "sales state 'proving'":
let future = state.run(agent)
market.slotState[slot.id] = SlotState.Free
await market.advanceToNextPeriod()
clock.advanceToNextPeriod(market)
check eventually future.finished
check !(future.read()) of SaleErrored

View File

@ -14,6 +14,7 @@ import ../../examples
import ../../helpers
import ../../helpers/mockmarket
import ../../helpers/mockclock
import ../helpers/periods
asyncchecksuite "sales state 'simulated-proving'":
let slot = Slot.example
@ -54,16 +55,10 @@ asyncchecksuite "sales state 'simulated-proving'":
teardown:
await subscription.unsubscribe()
proc advanceToNextPeriod(market: Market) {.async.} =
let periodicity = await market.periodicity()
let current = periodicity.periodOf(clock.now().Timestamp)
let periodEnd = periodicity.periodEnd(current)
clock.set(periodEnd.toSecondsSince1970 + 1)
proc waitForProvingRounds(market: Market, rounds: int) {.async.} =
var rnds = rounds - 1 # proof round runs prior to advancing
while rnds > 0:
await market.advanceToNextPeriod()
clock.advanceToNextPeriod(market)
await proofSubmitted
rnds -= 1
@ -90,7 +85,7 @@ asyncchecksuite "sales state 'simulated-proving'":
let future = state.run(agent)
market.slotState[slot.id] = SlotState.Finished
await market.advanceToNextPeriod()
clock.advanceToNextPeriod(market)
check eventually future.finished
check !(future.read()) of SalePayout

View File

@ -218,7 +218,7 @@ asyncchecksuite "Sales":
proc allowRequestToStart() {.async.} =
check eventually isInState(0, "SaleInitialProving")
# it won't start proving until the next period
await clock.advanceToNextPeriod(market)
clock.advanceToNextPeriod(market)
proc getAvailability(): Availability =
let key = availability.id.key.get

View File

@ -36,10 +36,10 @@ ethersuite "On-Chain Market":
): UInt256 =
return (endTimestamp - startTimestamp) * r.ask.pricePerSlotPerSecond
proc switchAccount(account: Signer) =
proc switchAccount(account: Signer) {.async.} =
marketplace = marketplace.connect(account)
token = token.connect(account)
market = OnChainMarket.new(marketplace, market.rewardRecipient)
market = ! await OnChainMarket.load(marketplace, market.rewardRecipient)
setup:
let address = Marketplace.address(dummyVerifier = true)
@ -47,7 +47,7 @@ ethersuite "On-Chain Market":
let config = await marketplace.configuration()
hostRewardRecipient = accounts[2]
market = OnChainMarket.new(marketplace)
market = ! await OnChainMarket.load(marketplace)
let tokenAddress = await marketplace.token()
token = Erc20Token.new(tokenAddress, ethProvider.getSigner())
@ -78,27 +78,22 @@ ethersuite "On-Chain Market":
:
await advanceToNextPeriod()
test "caches marketplace configuration":
check isNone market.configuration
discard await market.periodicity()
check isSome market.configuration
test "fails to instantiate when contract does not have a signer":
let storageWithoutSigner = marketplace.connect(ethProvider)
expect AssertionDefect:
discard OnChainMarket.new(storageWithoutSigner)
discard await OnChainMarket.load(storageWithoutSigner)
test "knows signer address":
check (await market.getSigner()) == (await ethProvider.getSigner().getAddress())
test "can retrieve proof periodicity":
let periodicity = await market.periodicity()
let periodicity = market.periodicity
let config = await marketplace.configuration()
let periodLength = config.proofs.period
check periodicity.seconds == periodLength
test "can retrieve proof timeout":
let proofTimeout = await market.proofTimeout()
let proofTimeout = market.proofTimeout
let config = await marketplace.configuration()
check proofTimeout == config.proofs.timeout
@ -256,9 +251,9 @@ ethersuite "On-Chain Market":
await market.subscribeSlotReservationsFull(onSlotReservationsFull)
await market.reserveSlot(request.id, slotIndex)
switchAccount(account2)
await switchAccount(account2)
await market.reserveSlot(request.id, slotIndex)
switchAccount(account3)
await switchAccount(account3)
await market.reserveSlot(request.id, slotIndex)
check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[
@ -545,7 +540,7 @@ ethersuite "On-Chain Market":
await market.requestStorage(request)
let address = await host.getAddress()
switchAccount(host)
await switchAccount(host)
await market.reserveSlot(request.id, 0.uint64)
await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot)
let filledAt = await ethProvider.blockTime(BlockTag.latest)
@ -567,12 +562,12 @@ ethersuite "On-Chain Market":
check endBalance == (startBalance + expectedPayout + request.ask.collateralPerSlot)
test "pays rewards to reward recipient, collateral to host":
market = OnChainMarket.new(marketplace, hostRewardRecipient.some)
market = ! await OnChainMarket.load(marketplace, hostRewardRecipient.some)
let hostAddress = await host.getAddress()
await market.requestStorage(request)
switchAccount(host)
await switchAccount(host)
await market.reserveSlot(request.id, 0.uint64)
await market.fillSlot(request.id, 0.uint64, proof, request.ask.collateralPerSlot)
let filledAt = (await ethProvider.currentTime())