diff --git a/codex/contracts/market.nim b/codex/contracts/market.nim index 6dd7e2b3c..b8d1da686 100644 --- a/codex/contracts/market.nim +++ b/codex/contracts/market.nim @@ -56,7 +56,7 @@ proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} = discard await token.increaseAllowance(market.contract.address(), amount).confirm(0) method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} = - let config = await market.contract.config() + let config = await market.contract.configuration() return some config.proofs.zkeyHash method getSigner*(market: OnChainMarket): Future[Address] {.async.} = @@ -65,18 +65,18 @@ method getSigner*(market: OnChainMarket): Future[Address] {.async.} = method periodicity*(market: OnChainMarket): Future[Periodicity] {.async.} = convertEthersError: - let config = await market.contract.config() + let config = await market.contract.configuration() let period = config.proofs.period return Periodicity(seconds: period) method proofTimeout*(market: OnChainMarket): Future[UInt256] {.async.} = convertEthersError: - let config = await market.contract.config() + let config = await market.contract.configuration() return config.proofs.timeout method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} = convertEthersError: - let config = await market.contract.config() + let config = await market.contract.configuration() return config.proofs.downtime method getPointer*(market: OnChainMarket, slotId: SlotId): Future[uint8] {.async.} = @@ -176,7 +176,7 @@ method fillSlot(market: OnChainMarket, method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} = convertEthersError: - var freeSlot: Future[?TransactionResponse] + var freeSlot: Future[Confirmable] if rewardRecipient =? market.rewardRecipient: # If --reward-recipient specified, use it as the reward recipient, and use # the SP's address as the collateral recipient diff --git a/codex/contracts/marketplace.nim b/codex/contracts/marketplace.nim index 6425bfa0c..020f501eb 100644 --- a/codex/contracts/marketplace.nim +++ b/codex/contracts/marketplace.nim @@ -17,18 +17,18 @@ export requests type Marketplace* = ref object of Contract -proc config*(marketplace: Marketplace): MarketplaceConfig {.contract, view.} +proc configuration*(marketplace: Marketplace): MarketplaceConfig {.contract, view.} proc token*(marketplace: Marketplace): Address {.contract, view.} proc slashMisses*(marketplace: Marketplace): UInt256 {.contract, view.} proc slashPercentage*(marketplace: Marketplace): UInt256 {.contract, view.} proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.} -proc requestStorage*(marketplace: Marketplace, request: StorageRequest): ?TransactionResponse {.contract.} -proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: Groth16Proof): ?TransactionResponse {.contract.} -proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId): ?TransactionResponse {.contract.} -proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId, withdrawAddress: Address): ?TransactionResponse {.contract.} -proc freeSlot*(marketplace: Marketplace, id: SlotId): ?TransactionResponse {.contract.} -proc freeSlot*(marketplace: Marketplace, id: SlotId, rewardRecipient: Address, collateralRecipient: Address): ?TransactionResponse {.contract.} +proc requestStorage*(marketplace: Marketplace, request: StorageRequest): Confirmable {.contract.} +proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: Groth16Proof): Confirmable {.contract.} +proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId): Confirmable {.contract.} +proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId, withdrawAddress: Address): Confirmable {.contract.} +proc freeSlot*(marketplace: Marketplace, id: SlotId): Confirmable {.contract.} +proc freeSlot*(marketplace: Marketplace, id: SlotId, rewardRecipient: Address, collateralRecipient: Address): Confirmable {.contract.} proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.} proc getHost*(marketplace: Marketplace, id: SlotId): Address {.contract, view.} proc getActiveSlot*(marketplace: Marketplace, id: SlotId): Slot {.contract, view.} @@ -49,8 +49,8 @@ proc willProofBeRequired*(marketplace: Marketplace, id: SlotId): bool {.contract proc getChallenge*(marketplace: Marketplace, id: SlotId): array[32, byte] {.contract, view.} proc getPointer*(marketplace: Marketplace, id: SlotId): uint8 {.contract, view.} -proc submitProof*(marketplace: Marketplace, id: SlotId, proof: Groth16Proof): ?TransactionResponse {.contract.} -proc markProofAsMissing*(marketplace: Marketplace, id: SlotId, period: UInt256): ?TransactionResponse {.contract.} +proc submitProof*(marketplace: Marketplace, id: SlotId, proof: Groth16Proof): Confirmable {.contract.} +proc markProofAsMissing*(marketplace: Marketplace, id: SlotId, period: UInt256): Confirmable {.contract.} -proc reserveSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256): ?TransactionResponse {.contract.} +proc reserveSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256): Confirmable {.contract.} proc canReserveSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256): bool {.contract, view.} diff --git a/codex/node.nim b/codex/node.nim index 956acd49a..f180fd629 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -757,15 +757,15 @@ proc stop*(self: CodexNodeRef) {.async.} = if not self.discovery.isNil: await self.discovery.stop() - if not self.clock.isNil: - await self.clock.stop() - if clientContracts =? self.contracts.client: await clientContracts.stop() if hostContracts =? self.contracts.host: await hostContracts.stop() + if not self.clock.isNil: + await self.clock.stop() + if validatorContracts =? self.contracts.validator: await validatorContracts.stop() diff --git a/codex/utils/asyncstatemachine.nim b/codex/utils/asyncstatemachine.nim index 3f10cfbed..3f15af319 100644 --- a/codex/utils/asyncstatemachine.nim +++ b/codex/utils/asyncstatemachine.nim @@ -59,31 +59,28 @@ proc onError(machine: Machine, error: ref CatchableError): Event = state.onError(error) proc run(machine: Machine, state: State) {.async.} = - try: - if next =? await state.run(machine): - machine.schedule(Event.transition(state, next)) - except CancelledError: - discard + if next =? await state.run(machine): + machine.schedule(Event.transition(state, next)) proc scheduler(machine: Machine) {.async.} = var running: Future[void] - try: - while machine.started: - let event = await machine.scheduled.get().track(machine) - if next =? event(machine.state): - if not running.isNil and not running.finished: - await running.cancelAndWait() - let fromState = if machine.state.isNil: "" else: $machine.state - machine.state = next - debug "enter state", state = fromState & " => " & $machine.state - running = machine.run(machine.state) - running - .track(machine) - .catch((err: ref CatchableError) => - machine.schedule(machine.onError(err)) - ) - except CancelledError: - discard + while machine.started: + let event = await machine.scheduled.get().track(machine) + if next =? event(machine.state): + if not running.isNil and not running.finished: + trace "cancelling current state", state = $machine.state + await running.cancelAndWait() + let fromState = if machine.state.isNil: "" else: $machine.state + machine.state = next + debug "enter state", state = fromState & " => " & $machine.state + running = machine.run(machine.state) + running + .track(machine) + .cancelled(proc() = trace "state.run cancelled, swallowing", state = $machine.state) + .catch(proc(err: ref CatchableError) = + trace "error caught in state.run, calling state.onError", state = $machine.state + machine.schedule(machine.onError(err)) + ) proc start*(machine: Machine, initialState: State) = if machine.started: @@ -93,12 +90,13 @@ proc start*(machine: Machine, initialState: State) = machine.scheduled = newAsyncQueue[Event]() machine.started = true - machine.scheduler() - .track(machine) - .catch((err: ref CatchableError) => - error("Error in scheduler", error = err.msg) - ) - machine.schedule(Event.transition(machine.state, initialState)) + try: + discard machine.scheduler().track(machine) + machine.schedule(Event.transition(machine.state, initialState)) + except CancelledError as e: + discard + except CatchableError as e: + error("Error in scheduler", error = e.msg) proc stop*(machine: Machine) {.async.} = if not machine.started: diff --git a/tests/contracts/testContracts.nim b/tests/contracts/testContracts.nim index b098b80f4..bbbf41aaf 100644 --- a/tests/contracts/testContracts.nim +++ b/tests/contracts/testContracts.nim @@ -38,7 +38,7 @@ ethersuite "Marketplace contracts": let tokenAddress = await marketplace.token() token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) - let config = await marketplace.config() + let config = await marketplace.configuration() periodicity = Periodicity(seconds: config.proofs.period) request = StorageRequest.example diff --git a/tests/contracts/testDeployment.nim b/tests/contracts/testDeployment.nim index 4101b71a6..f89e28a8c 100644 --- a/tests/contracts/testDeployment.nim +++ b/tests/contracts/testDeployment.nim @@ -9,7 +9,7 @@ import ../checktest type MockProvider = ref object of Provider chainId*: UInt256 -method getChainId*(provider: MockProvider): Future[UInt256] {.async.} = +method getChainId*(provider: MockProvider): Future[UInt256] {.async: (raises:[ProviderError]).} = return provider.chainId proc configFactory(): CodexConf = diff --git a/tests/contracts/testMarket.nim b/tests/contracts/testMarket.nim index 66088b714..a836628c3 100644 --- a/tests/contracts/testMarket.nim +++ b/tests/contracts/testMarket.nim @@ -34,7 +34,7 @@ ethersuite "On-Chain Market": setup: let address = Marketplace.address(dummyVerifier = true) marketplace = Marketplace.new(address, ethProvider.getSigner()) - let config = await marketplace.config() + let config = await marketplace.configuration() hostRewardRecipient = accounts[2] market = OnChainMarket.new(marketplace) @@ -76,13 +76,13 @@ ethersuite "On-Chain Market": test "can retrieve proof periodicity": let periodicity = await market.periodicity() - let config = await marketplace.config() + 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 config = await marketplace.config() + let config = await marketplace.configuration() check proofTimeout == config.proofs.timeout test "supports marketplace requests": diff --git a/tests/contracts/time.nim b/tests/contracts/time.nim index cd6aac1b8..ae4487895 100644 --- a/tests/contracts/time.nim +++ b/tests/contracts/time.nim @@ -1,4 +1,5 @@ import pkg/ethers +import pkg/serde/json proc currentTime*(provider: Provider): Future[UInt256] {.async.} = return (!await provider.getBlock(BlockTag.pending)).timestamp diff --git a/tests/integration/codexprocess.nim b/tests/integration/codexprocess.nim index ce6334340..e8f404ccf 100644 --- a/tests/integration/codexprocess.nim +++ b/tests/integration/codexprocess.nim @@ -5,6 +5,7 @@ import pkg/chronicles import pkg/chronos/asyncproc import pkg/ethers import pkg/libp2p +import pkg/stew/io2 import std/os import std/strutils import codex/conf @@ -40,6 +41,20 @@ method outputLineEndings(node: CodexProcess): string = method onOutputLineCaptured(node: CodexProcess, line: string) = discard +method logFileContains*(node: CodexProcess, text: string): bool = + let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) + without logFile =? config.logFile.?string: + raiseAssert "codex node does have a --log-file option set (use .withLogFile())" + + let resLogContents = logFile.readAllChars + if resLogContents.isErr: + # without logContents =? logFile.readAllChars: + raiseAssert "failed to open codex log file, aborting (log path: " & logFile & ")" + + let logContents = resLogContents.value + + return logContents.contains(text) + proc dataDir(node: CodexProcess): string = let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) return config.dataDir.string @@ -73,4 +88,4 @@ method stop*(node: CodexProcess) {.async.} = node.client = none CodexClient method removeDataDir*(node: CodexProcess) = - removeDir(node.dataDir) + os.removeDir(node.dataDir) diff --git a/tests/integration/hardhatprocess.nim b/tests/integration/hardhatprocess.nim index 0e88aa786..84cf4132b 100644 --- a/tests/integration/hardhatprocess.nim +++ b/tests/integration/hardhatprocess.nim @@ -40,6 +40,25 @@ method processOptions(node: HardhatProcess): set[AsyncProcessOption] = method outputLineEndings(node: HardhatProcess): string = return "\n" +method logFileContains*(hardhat: HardhatProcess, text: string): bool = + without fileHandle =? hardhat.logFile: + raiseAssert "failed to open hardhat log file, aborting" + + without fileSize =? fileHandle.getFileSize: + raiseAssert "failed to get current hardhat log file size, aborting" + + if checkFileSize(fileSize).isErr: + raiseAssert "file size too big for nim indexing" + + var data = "" + data.setLen(fileSize) + + without bytesRead =? readFile(fileHandle, + data.toOpenArray(0, len(data) - 1)): + raiseAssert "unable to read hardhat log, aborting" + + return data.contains(text) + proc openLogFile(node: HardhatProcess, logFilePath: string): IoHandle = let logFileHandle = openFile( logFilePath, diff --git a/tests/integration/marketplacesuite.nim b/tests/integration/marketplacesuite.nim index 2b81bdd8f..d3b1ef577 100644 --- a/tests/integration/marketplacesuite.nim +++ b/tests/integration/marketplacesuite.nim @@ -85,7 +85,7 @@ template marketplacesuite*(name: string, body: untyped) = marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) let tokenAddress = await marketplace.token() token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) - let config = await mp.config(marketplace) + let config = await marketplace.configuration() period = config.proofs.period.truncate(uint64) periodicity = Periodicity(seconds: period.u256) diff --git a/tests/integration/multinodes.nim b/tests/integration/multinodes.nim index 1ad16a384..97277beb4 100644 --- a/tests/integration/multinodes.nim +++ b/tests/integration/multinodes.nim @@ -222,6 +222,26 @@ template multinodesuite*(name: string, body: untyped) = return await newCodexProcess(validatorIdx, config, Role.Validator) + + proc searchLogs(role: Role, text: string): seq[bool] = + var hits: seq[bool] = @[] + if role == Role.Hardhat: + return @[hardhat().logFileContains(text)] + elif role == Role.Client: + for client in clients(): + hits.add client.logFileContains(text) + else: + for provider in providers(): + hits.add provider.logFileContains(text) + + return hits + + proc logsContain(role: Role, text: string): bool = + return searchLogs(role, text).allIt(it) + + proc logsDoNotContain(role: Role, text: string): bool = + return searchLogs(role, text).allIt(not it) + proc teardownImpl() {.async.} = for nodes in @[validators(), clients(), providers()]: for node in nodes: diff --git a/tests/integration/nodeconfig.nim b/tests/integration/nodeconfig.nim deleted file mode 100644 index d6adb80fa..000000000 --- a/tests/integration/nodeconfig.nim +++ /dev/null @@ -1,34 +0,0 @@ -import pkg/chronicles -import pkg/questionable - -export chronicles - -type - NodeConfig* = ref object of RootObj - logFile*: bool - logLevel*: ?LogLevel - debugEnabled*: bool - -proc debug*[T: NodeConfig](config: T, enabled = true): T = - ## output log in stdout - var startConfig = config - startConfig.debugEnabled = enabled - return startConfig - -proc withLogFile*[T: NodeConfig]( - config: T, - logToFile: bool = true -): T = - - var startConfig = config - startConfig.logFile = logToFile - return startConfig - -proc withLogLevel*[T: NodeConfig]( - config: NodeConfig, - level: LogLevel -): T = - - var startConfig = config - startConfig.logLevel = some level - return startConfig diff --git a/tests/integration/nodeprocess.nim b/tests/integration/nodeprocess.nim index 61947c20c..d8367c14f 100644 --- a/tests/integration/nodeprocess.nim +++ b/tests/integration/nodeprocess.nim @@ -44,6 +44,9 @@ method outputLineEndings(node: NodeProcess): string {.base.} = method onOutputLineCaptured(node: NodeProcess, line: string) {.base.} = raiseAssert "not implemented" +method logFileContains*(hardhat: NodeProcess): bool {.base.} = + raiseAssert "not implemented" + method start*(node: NodeProcess) {.base, async.} = logScope: nodeName = node.name diff --git a/tests/integration/testslotreservations.nim b/tests/integration/testslotreservations.nim new file mode 100644 index 000000000..f8c29beef --- /dev/null +++ b/tests/integration/testslotreservations.nim @@ -0,0 +1,74 @@ +import pkg/stew/byteutils +import pkg/codex/units +import ../examples +import ../contracts/time +import ../contracts/deployment +import ./marketplacesuite +import ./nodeconfigs +import ./hardhatconfig + +marketplacesuite "Slot reservations": + + test "nonce does not go too high when reserving slots", + NodeConfigs( + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + hardhat: HardhatConfig.none, + + clients: + CodexConfigs.init(nodes=1) + # .debug() # uncomment to enable console log output.debug() + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("node", "erasure", "marketplace") + .some, + + providers: + CodexConfigs.init(nodes=6) + # .debug() # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("node", "marketplace", "sales", "reservations", "proving", "ethers", "statemachine") + .some, + ): + let reward = 400.u256 + let duration = 50.periods + let collateral = 200.u256 + let expiry = 30.periods + let data = await RandomChunker.example(blocks=8) + let client = clients()[0] + let clientApi = client.client + + # provider makes storage available + for i in 0..