diff --git a/codex/conf.nim b/codex/conf.nim index 2e6d0edb..8cbfe828 100644 --- a/codex/conf.nim +++ b/codex/conf.nim @@ -38,7 +38,7 @@ import ./stores import ./units import ./utils -export units, net, codextypes +export units, net, codextypes, logutils export DefaultQuotaBytes, diff --git a/tests/integration/clioption.nim b/tests/integration/clioption.nim index 379cebb8..5f756d80 100644 --- a/tests/integration/clioption.nim +++ b/tests/integration/clioption.nim @@ -1,10 +1,7 @@ -import pkg/questionable - type - CliOption* = object of RootObj - nodeIdx*: ?int - key*: string - value*: string + CliOption* = object + key*: string # option key, including `--` + value*: string # option value proc `$`*(option: CliOption): string = var res = option.key diff --git a/tests/integration/codexconfig.nim b/tests/integration/codexconfig.nim index eda9c81a..9c1d3064 100644 --- a/tests/integration/codexconfig.nim +++ b/tests/integration/codexconfig.nim @@ -1,61 +1,294 @@ import std/options +import std/os import std/sequtils +import std/strutils +import std/sugar +import std/tables +from pkg/chronicles import LogLevel +import pkg/codex/conf import pkg/codex/units +import pkg/confutils +import pkg/confutils/defs +import libp2p except setup +import pkg/questionable import ./clioption -import ./nodeconfig -export nodeconfig export clioption +export confutils type - CodexConfig* = ref object of NodeConfig - numNodes*: int - cliOptions*: seq[CliOption] - logTopics*: seq[string] + CodexConfigs* = object + configs*: seq[CodexConfig] + CodexConfig* = object + cliOptions: Table[StartUpCmd, Table[string, CliOption]] + cliPersistenceOptions: Table[PersistenceCmd, Table[string, CliOption]] + parsedCli: CodexConf + debugEnabled*: bool + CodexConfigError* = object of CatchableError -proc nodes*(config: CodexConfig, numNodes: int): CodexConfig = - if numNodes < 0: - raise newException(ValueError, "numNodes must be >= 0") +proc cliArgs*(config: CodexConfig): seq[string] {.gcsafe, raises: [CodexConfigError].} - var startConfig = config - startConfig.numNodes = numNodes +proc raiseCodexConfigError(msg: string) {.raises: [CodexConfigError].} = + raise newException(CodexConfigError, msg) + +template convertError(body) = + try: + body + except CatchableError as e: + raiseCodexConfigError e.msg + +proc init*(_: type CodexConfigs, nodes = 1): CodexConfigs {.raises: [].} = + CodexConfigs(configs: newSeq[CodexConfig](nodes)) + +func nodes*(self: CodexConfigs): int = + self.configs.len + +proc checkBounds(self: CodexConfigs, idx: int) {.raises: [CodexConfigError].} = + if idx notin 0.. 0: + ": " & msg + else: "" + + try: + config.parsedCli = CodexConf.load(cmdLine = config.cliArgs, + quitOnFailure = false) + except ConfigurationError as e: + raiseCodexConfigError msg & e.msg.postFix + except Exception as e: + ## TODO: remove once proper exception handling added to nim-confutils + raiseCodexConfigError msg & e.msg.postFix + +proc addCliOption*( + config: var CodexConfig, + group = PersistenceCmd.noCmd, + cliOption: CliOption) {.gcsafe, raises: [CodexConfigError].} = + + var options = config.cliPersistenceOptions.getOrDefault(group) + options[cliOption.key] = cliOption # overwrite if already exists + config.cliPersistenceOptions[group] = options + config.validateCliArgs("Invalid cli arg " & $cliOption) + +proc addCliOption*( + config: var CodexConfig, + group = PersistenceCmd.noCmd, + key: string, value = "") {.gcsafe, raises: [CodexConfigError].} = + + config.addCliOption(group, CliOption(key: key, value: value)) + +proc addCliOption*( + config: var CodexConfig, + group = StartUpCmd.noCmd, + cliOption: CliOption) {.gcsafe, raises: [CodexConfigError].} = + + var options = config.cliOptions.getOrDefault(group) + options[cliOption.key] = cliOption # overwrite if already exists + config.cliOptions[group] = options + config.validateCliArgs("Invalid cli arg " & $cliOption) + +proc addCliOption*( + config: var CodexConfig, + group = StartUpCmd.noCmd, + key: string, value = "") {.gcsafe, raises: [CodexConfigError].} = + + config.addCliOption(group, CliOption(key: key, value: value)) + +proc addCliOption*( + config: var CodexConfig, + cliOption: CliOption) {.gcsafe, raises: [CodexConfigError].} = + + config.addCliOption(StartUpCmd.noCmd, cliOption) + +proc addCliOption*( + config: var CodexConfig, + key: string, value = "") {.gcsafe, raises: [CodexConfigError].} = + + config.addCliOption(StartUpCmd.noCmd, CliOption(key: key, value: value)) + +proc cliArgs*( + config: CodexConfig): seq[string] {.gcsafe, raises: [CodexConfigError].} = + ## converts CodexConfig cli options and command groups in a sequence of args + ## and filters out cli options by node index if provided in the CliOption + var args: seq[string] = @[] + + convertError: + for cmd in StartUpCmd: + if config.cliOptions.hasKey(cmd): + if cmd != StartUpCmd.noCmd: + args.add $cmd + var opts = config.cliOptions[cmd].values.toSeq + args = args.concat( opts.map(o => $o) ) + + for cmd in PersistenceCmd: + if config.cliPersistenceOptions.hasKey(cmd): + if cmd != PersistenceCmd.noCmd: + args.add $cmd + var opts = config.cliPersistenceOptions[cmd].values.toSeq + args = args.concat( opts.map(o => $o) ) + + return args + +proc logFile*(config: CodexConfig): ?string {.raises: [CodexConfigError].} = + config.parsedCli.logFile + +proc logLevel*(config: CodexConfig): LogLevel {.raises: [CodexConfigError].} = + convertError: + return parseEnum[LogLevel](config.parsedCli.logLevel.toUpperAscii) + +proc debug*( + self: CodexConfigs, + idx: int, + enabled = true): CodexConfigs {.raises: [CodexConfigError].} = + ## output log in stdout for a specific node in the group + + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].debugEnabled = enabled return startConfig -proc simulateProofFailuresFor*( - config: CodexConfig, - providerIdx: int, +proc debug*(self: CodexConfigs, enabled = true): CodexConfigs {.raises: [].} = + ## output log in stdout for all nodes in group + var startConfig = self + for config in startConfig.configs.mitems: + config.debugEnabled = enabled + return startConfig + +proc withLogFile*( + self: CodexConfigs, + idx: int): CodexConfigs {.raises: [CodexConfigError].} = + + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].addCliOption("--log-file", "") + return startConfig + +proc withLogFile*( + self: CodexConfigs): CodexConfigs {.raises: [CodexConfigError].} = + ## typically called from test, sets config such that a log file should be + ## created + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption("--log-file", "") + return startConfig + +proc withLogFile*( + self: var CodexConfig, + logFile: string) {.raises: [CodexConfigError].} = #: CodexConfigs = + ## typically called internally from the test suite, sets a log file path to + ## be created during the test run, for a specified node in the group + # var config = self + self.addCliOption("--log-file", logFile) + # return startConfig + +proc withLogLevel*( + self: CodexConfig, + level: LogLevel | string): CodexConfig {.raises: [CodexConfigError].} = + + var config = self + config.addCliOption("--log-level", $level) + return config + +proc withLogLevel*( + self: CodexConfigs, + idx: int, + level: LogLevel | string): CodexConfigs {.raises: [CodexConfigError].} = + + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].addCliOption("--log-level", $level) + return startConfig + +proc withLogLevel*( + self: CodexConfigs, + level: LogLevel | string): CodexConfigs {.raises: [CodexConfigError].} = + + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption("--log-level", $level) + return startConfig + +proc withSimulateProofFailures*( + self: CodexConfigs, + idx: int, failEveryNProofs: int -): CodexConfig = +): CodexConfigs {.raises: [CodexConfigError].} = - if providerIdx > config.numNodes - 1: - raise newException(ValueError, "provider index out of bounds") + self.checkBounds idx - var startConfig = config - startConfig.cliOptions.add( - CliOption( - nodeIdx: some providerIdx, - key: "--simulate-proof-failures", - value: $failEveryNProofs - ) - ) + var startConfig = self + startConfig.configs[idx].addCliOption( + StartUpCmd.persistence, "--simulate-proof-failures", $failEveryNProofs) return startConfig +proc withSimulateProofFailures*( + self: CodexConfigs, + failEveryNProofs: int): CodexConfigs {.raises: [CodexConfigError].} = + + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption( + StartUpCmd.persistence, "--simulate-proof-failures", $failEveryNProofs) + return startConfig + +proc logLevelWithTopics( + config: CodexConfig, + topics: varargs[string]): string {.raises: [CodexConfigError].} = + + convertError: + var logLevel = LogLevel.INFO + logLevel = parseEnum[LogLevel](config.parsedCli.logLevel.toUpperAscii) + let level = $logLevel & ";TRACE: " & topics.join(",") + return level + proc withLogTopics*( - config: CodexConfig, - topics: varargs[string] -): CodexConfig = + self: CodexConfigs, + idx: int, + topics: varargs[string]): CodexConfigs {.raises: [CodexConfigError].} = - var startConfig = config - startConfig.logTopics = startConfig.logTopics.concat(@topics) + self.checkBounds idx + + convertError: + let config = self.configs[idx] + let level = config.logLevelWithTopics(topics) + var startConfig = self + return startConfig.withLogLevel(idx, level) + +proc withLogTopics*( + self: CodexConfigs, + topics: varargs[string] +): CodexConfigs {.raises: [CodexConfigError].} = + + var startConfig = self + for config in startConfig.configs.mitems: + let level = config.logLevelWithTopics(topics) + config = config.withLogLevel(level) return startConfig proc withStorageQuota*( - config: CodexConfig, - quota: NBytes -): CodexConfig = + self: CodexConfigs, + idx: int, + quota: NBytes): CodexConfigs {.raises: [CodexConfigError].} = - var startConfig = config - startConfig.cliOptions.add( - CliOption(key: "--storage-quota", value: $quota) - ) + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].addCliOption("--storage-quota", $quota) + return startConfig + +proc withStorageQuota*( + self: CodexConfigs, + quota: NBytes): CodexConfigs {.raises: [CodexConfigError].} = + + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption("--storage-quota", $quota) return startConfig diff --git a/tests/integration/codexprocess.nim b/tests/integration/codexprocess.nim index c7c0c6f1..07760b43 100644 --- a/tests/integration/codexprocess.nim +++ b/tests/integration/codexprocess.nim @@ -40,17 +40,17 @@ method onOutputLineCaptured(node: CodexProcess, line: string) = discard proc dataDir(node: CodexProcess): string = - let config = CodexConf.load(cmdLine = node.arguments) + let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) return config.dataDir.string proc ethAccount*(node: CodexProcess): Address = - let config = CodexConf.load(cmdLine = node.arguments) + let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) without ethAccount =? config.ethAccount: raiseAssert "eth account not set" return Address(ethAccount) proc apiUrl*(node: CodexProcess): string = - let config = CodexConf.load(cmdLine = node.arguments) + let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) return "http://" & config.apiBindAddress & ":" & $config.apiPort & "/api/codex/v1" proc client*(node: CodexProcess): CodexClient = diff --git a/tests/integration/hardhatconfig.nim b/tests/integration/hardhatconfig.nim index 637b40c0..fbd04fe8 100644 --- a/tests/integration/hardhatconfig.nim +++ b/tests/integration/hardhatconfig.nim @@ -1,6 +1,15 @@ -import ./nodeconfig - -export nodeconfig - type - HardhatConfig* = ref object of NodeConfig + HardhatConfig* = object + logFile*: bool + debugEnabled*: bool + +proc debug*(self: HardhatConfig, enabled = true): HardhatConfig = + ## output log in stdout + var config = self + config.debugEnabled = enabled + return config + +proc withLogFile*(self: HardhatConfig, logToFile: bool = true): HardhatConfig = + var config = self + config.logFile = logToFile + return config diff --git a/tests/integration/multinodes.nim b/tests/integration/multinodes.nim index 727ff833..355ba014 100644 --- a/tests/integration/multinodes.nim +++ b/tests/integration/multinodes.nim @@ -3,13 +3,16 @@ import std/sequtils import std/strutils import std/sugar import std/times +import pkg/codex/conf import pkg/codex/logutils import pkg/chronos/transports/stream import pkg/ethers -import ./hardhatprocess +import pkg/questionable +import ./codexconfig import ./codexprocess import ./hardhatconfig -import ./codexconfig +import ./hardhatprocess +import ./nodeconfigs import ../asynctest import ../checktest @@ -24,11 +27,6 @@ type RunningNode* = ref object role*: Role node*: NodeProcess - NodeConfigs* = object - clients*: CodexConfig - providers*: CodexConfig - validators*: CodexConfig - hardhat*: HardhatConfig Role* {.pure.} = enum Client, Provider, @@ -69,6 +67,8 @@ template multinodesuite*(name: string, body: untyped) = var accounts {.inject, used.}: seq[Address] var snapshot: JsonNode + proc teardownImpl(): Future[void] {.gcsafe.} + template test(tname, startNodeConfigs, tbody) = currentTestName = tname nodeConfigs = startNodeConfigs @@ -79,6 +79,7 @@ template multinodesuite*(name: string, body: untyped) = var sanitized = pathSegment for invalid in invalidFilenameChars.items: sanitized = sanitized.replace(invalid, '_') + .replace(' ', '_') sanitized proc getLogFile(role: Role, index: ?int): string = @@ -87,7 +88,7 @@ template multinodesuite*(name: string, body: untyped) = var logDir = currentSourcePath.parentDir() / "logs" / - sanitize($starttime & " " & name) / + sanitize($starttime & "__" & name) / sanitize($currentTestName) createDir(logDir) @@ -116,14 +117,15 @@ template multinodesuite*(name: string, body: untyped) = return node proc newCodexProcess(roleIdx: int, - config: CodexConfig, + conf: CodexConfig, role: Role ): Future[NodeProcess] {.async.} = let nodeIdx = running.len - var conf = config + var config = conf if nodeIdx > accounts.len - 1: + await teardownImpl() raiseAssert("Cannot start node at nodeIdx " & $nodeIdx & ", not enough eth accounts.") @@ -131,30 +133,30 @@ template multinodesuite*(name: string, body: untyped) = sanitize($starttime) / sanitize($role & "_" & $roleIdx) - if conf.logFile: - let updatedLogFile = getLogFile(role, some roleIdx) - conf.cliOptions.add CliOption(key: "--log-file", value: updatedLogFile) + try: + if config.logFile.isSome: + let updatedLogFile = getLogFile(role, some roleIdx) + config.withLogFile(updatedLogFile) - let logLevel = conf.logLevel |? LogLevel.INFO - if conf.logTopics.len > 0: - conf.cliOptions.add CliOption( - key: "--log-level", - value: $logLevel & ";TRACE: " & conf.logTopics.join(",") - ) - else: - conf.cliOptions.add CliOption(key: "--log-level", value: $logLevel) + config.addCliOption("--api-port", $ await nextFreePort(8080 + nodeIdx)) + config.addCliOption("--data-dir", datadir) + config.addCliOption("--nat", "127.0.0.1") + config.addCliOption("--listen-addrs", "/ip4/127.0.0.1/tcp/0") + config.addCliOption("--disc-ip", "127.0.0.1") + config.addCliOption("--disc-port", $ await nextFreePort(8090 + nodeIdx)) - var args = conf.cliOptions.map(o => $o) - .concat(@[ - "--api-port=" & $ await nextFreePort(8080 + nodeIdx), - "--data-dir=" & datadir, - "--nat=127.0.0.1", - "--listen-addrs=/ip4/127.0.0.1/tcp/0", - "--disc-ip=127.0.0.1", - "--disc-port=" & $ await nextFreePort(8090 + nodeIdx), - "--eth-account=" & $accounts[nodeIdx]]) + except CodexConfigError as e: + fatal "invalid cli option", error = e.msg + echo "[FATAL] invalid cli option ", e.msg + await teardownImpl() + fail() + return - let node = await CodexProcess.startNode(args, conf.debugEnabled, $role & $roleIdx) + let node = await CodexProcess.startNode( + config.cliArgs, + config.debugEnabled, + $role & $roleIdx + ) await node.waitUntilStarted() trace "node started", nodeName = $role & $roleIdx @@ -184,80 +186,36 @@ template multinodesuite*(name: string, body: untyped) = if r.role == Role.Validator: CodexProcess(r.node) - proc startHardhatNode(): Future[NodeProcess] {.async.} = - var config = nodeConfigs.hardhat + proc startHardhatNode(config: HardhatConfig): Future[NodeProcess] {.async.} = return await newHardhatProcess(config, Role.Hardhat) - proc startClientNode(): Future[NodeProcess] {.async.} = + proc startClientNode(conf: CodexConfig): Future[NodeProcess] {.async.} = let clientIdx = clients().len - var config = nodeConfigs.clients - config.cliOptions.add CliOption(key: "persistence") + var config = conf + config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len]) return await newCodexProcess(clientIdx, config, Role.Client) - proc startProviderNode(): Future[NodeProcess] {.async.} = + proc startProviderNode(conf: CodexConfig): Future[NodeProcess] {.async.} = let providerIdx = providers().len - var config = nodeConfigs.providers - config.cliOptions.add CliOption(key: "--bootstrap-node", value: bootstrap) - config.cliOptions.add CliOption(key: "persistence") - - # filter out provider options by provided index - config.cliOptions = config.cliOptions.filter( - o => (let idx = o.nodeIdx |? providerIdx; idx == providerIdx) - ) + var config = conf + config.addCliOption("--bootstrap-node", bootstrap) + config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len]) + config.addCliOption(PersistenceCmd.prover, "--circom-r1cs", "tests/circuits/fixtures/proof_main.r1cs") + config.addCliOption(PersistenceCmd.prover, "--circom-wasm", "tests/circuits/fixtures/proof_main.wasm") + config.addCliOption(PersistenceCmd.prover, "--circom-zkey", "tests/circuits/fixtures/proof_main.zkey") return await newCodexProcess(providerIdx, config, Role.Provider) - proc startValidatorNode(): Future[NodeProcess] {.async.} = + proc startValidatorNode(conf: CodexConfig): Future[NodeProcess] {.async.} = let validatorIdx = validators().len - var config = nodeConfigs.validators - config.cliOptions.add CliOption(key: "--bootstrap-node", value: bootstrap) - config.cliOptions.add CliOption(key: "--validator") + var config = conf + config.addCliOption("--bootstrap-node", bootstrap) + config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len]) + config.addCliOption(StartUpCmd.persistence, "--validator") return await newCodexProcess(validatorIdx, config, Role.Validator) - setup: - if not nodeConfigs.hardhat.isNil: - let node = await startHardhatNode() - running.add RunningNode(role: Role.Hardhat, node: node) - - try: - ethProvider = JsonRpcProvider.new("ws://localhost:8545") - # if hardhat was NOT started by the test, take a snapshot so it can be - # reverted in the test teardown - if nodeConfigs.hardhat.isNil: - snapshot = await send(ethProvider, "evm_snapshot") - accounts = await ethProvider.listAccounts() - except CatchableError as e: - fatal "failed to connect to hardhat", error = e.msg - raiseAssert "Hardhat not running. Run hardhat manually before executing tests, or include a HardhatConfig in the test setup." - - if not nodeConfigs.clients.isNil: - for i in 0.. //_.log - .withLogTopics("node"), + .withLogTopics("node") + .some, providers: - CodexConfig() - .nodes(1) + CodexConfigs.init(nodes=5) # .debug() # uncomment to enable console log output .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - .withLogTopics("marketplace", "sales", "reservations", "node"), + .withLogTopics("marketplace", "sales", "reservations", "node") + .some, ): let client0 = clients()[0].client let totalPeriods = 50 - let datasetSizeInBlocks = 2 + let datasetSizeInBlocks = 8 - let data = await RandomChunker.example(blocks=1) - createAvailabilities(data.len, totalPeriods.periods) + let data = await RandomChunker.example(blocks=datasetSizeInBlocks) + # dataset size = 8 block, with 5 nodes, the slot size = 4 blocks, give each + # node enough availability to fill one slot only + createAvailabilities((DefaultBlockSize * 4.NBytes).Natural, totalPeriods.periods) let cid = client0.upload(data).get let purchaseId = await client0.requestStorage( cid, duration=totalPeriods.periods, + expiry=30.periods, + nodes=5, + tolerance=1, origDatasetSizeInBlocks = datasetSizeInBlocks) check eventually client0.purchaseStateIs(purchaseId, "started") @@ -61,240 +67,245 @@ marketplacesuite "Hosts submit regular proofs": await subscription.unsubscribe() -# marketplacesuite "Simulate invalid proofs": +marketplacesuite "Simulate invalid proofs": -# # TODO: these are very loose tests in that they are not testing EXACTLY how -# # proofs were marked as missed by the validator. These tests should be -# # tightened so that they are showing, as an integration test, that specific -# # proofs are being marked as missed by the validator. + # TODO: these are very loose tests in that they are not testing EXACTLY how + # proofs were marked as missed by the validator. These tests should be + # tightened so that they are showing, as an integration test, that specific + # proofs are being marked as missed by the validator. -# test "slot is freed after too many invalid proofs submitted", NodeConfigs( -# # Uncomment to start Hardhat automatically, typically so logs can be inspected locally -# # hardhat: HardhatConfig().withLogFile(), + test "slot is freed after too many invalid proofs submitted", NodeConfigs( + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + hardhat: + HardhatConfig.none, -# clients: -# CodexConfig() -# .nodes(1) -# # .debug() # uncomment to enable console log output -# .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log -# .withLogTopics("node"), + clients: + CodexConfigs.init(nodes=1) + # .debug() # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("node") + .some, -# providers: -# CodexConfig() -# .nodes(1) -# .simulateProofFailuresFor(providerIdx=0, failEveryNProofs=1) -# # .debug() # uncomment to enable console log output -# .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log -# .withLogTopics("marketplace", "sales", "reservations", "node"), + providers: + CodexConfigs.init(nodes=5) + .withSimulateProofFailures(idx=0, failEveryNProofs=1) + # .debug() # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("marketplace", "sales", "reservations", "node") + .some, -# validators: -# CodexConfig() -# .nodes(1) -# .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log -# # .debug() # uncomment to enable console log output -# .withLogTopics("validator", "onchain", "ethers") -# ): -# let client0 = clients()[0].client -# let totalPeriods = 50 + validators: + CodexConfigs.init(nodes=1) + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + # .debug() # uncomment to enable console log output + .withLogTopics("validator", "onchain", "ethers") + .some + ): + let client0 = clients()[0].client + let totalPeriods = 50 -# let datasetSizeInBlocks = 2 -# let data = await RandomChunker.example(blocks=datasetSizeInBlocks) -# createAvailabilities(data.len, totalPeriods.periods) + let datasetSizeInBlocks = 8 + let data = await RandomChunker.example(blocks=datasetSizeInBlocks) + # dataset size = 8 block, with 5 nodes, the slot size = 4 blocks, give each + # node enough availability to fill one slot only + createAvailabilities((DefaultBlockSize * 4.NBytes).Natural, totalPeriods.periods) -# let cid = client0.upload(data).get + let cid = client0.upload(data).get -# let purchaseId = await client0.requestStorage( -# cid, -# duration=totalPeriods.periods, -# origDatasetSizeInBlocks=datasetSizeInBlocks) -# let requestId = client0.requestId(purchaseId).get + let purchaseId = await client0.requestStorage( + cid, + duration=totalPeriods.periods, + expiry=30.periods, + nodes=5, + tolerance=1, + origDatasetSizeInBlocks=datasetSizeInBlocks) + let requestId = client0.requestId(purchaseId).get -# check eventually client0.purchaseStateIs(purchaseId, "started") + check eventually client0.purchaseStateIs(purchaseId, "started") -# var slotWasFreed = false -# proc onSlotFreed(event: SlotFreed) = -# if event.requestId == requestId and -# event.slotIndex == 0.u256: # assume only one slot, so index 0 -# slotWasFreed = true + var slotWasFreed = false + proc onSlotFreed(event: SlotFreed) = + if event.requestId == requestId and + event.slotIndex == 0.u256: # assume only one slot, so index 0 + slotWasFreed = true -# let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) + let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) -# let currentPeriod = await getCurrentPeriod() -# check eventuallyP(slotWasFreed, currentPeriod + totalPeriods.u256 + 1) + let currentPeriod = await getCurrentPeriod() + check eventuallyP(slotWasFreed, currentPeriod + totalPeriods.u256 + 1) -# await subscription.unsubscribe() + await subscription.unsubscribe() - # test "slot is not freed when not enough invalid proofs submitted", NodeConfigs( - # # Uncomment to start Hardhat automatically, typically so logs can be inspected locally - # # hardhat: HardhatConfig().withLogFile(), + test "slot is not freed when not enough invalid proofs submitted", NodeConfigs( + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + hardhat: HardhatConfig.none, - # clients: - # CodexConfig() - # .nodes(1) - # .debug() # uncomment to enable console log output - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("node"), + clients: + CodexConfigs.init(nodes=1) + # .debug() # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("node") + .some, - # providers: - # CodexConfig() - # .nodes(1) - # .simulateProofFailuresFor(providerIdx=0, failEveryNProofs=3) - # .debug() # uncomment to enable console log output - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("marketplace", "sales", "reservations", "node"), + providers: + CodexConfigs.init(nodes=5) + .withSimulateProofFailures(idx=0, failEveryNProofs=3) + # .debug() # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("marketplace", "sales", "reservations", "node") + .some, - # validators: - # CodexConfig() - # .nodes(1) - # .debug() - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("validator", "onchain", "ethers") - # ): - # let client0 = clients()[0].client - # let totalPeriods = 25 + validators: + CodexConfigs.init(nodes=1) + # .debug() + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("validator", "onchain", "ethers") + .some + ): + let client0 = clients()[0].client + let totalPeriods = 25 - # let datasetSizeInBlocks = 2 - # let data = await RandomChunker.example(blocks=datasetSizeInBlocks) - # createAvailabilities(data.len, totalPeriods.periods) + let datasetSizeInBlocks = 8 + let data = await RandomChunker.example(blocks=datasetSizeInBlocks) + # dataset size = 8 block, with 5 nodes, the slot size = 4 blocks, give each + # node enough availability to fill one slot only + createAvailabilities((DefaultBlockSize * 4.NBytes).Natural, totalPeriods.periods) - # let cid = client0.upload(data).get + let cid = client0.upload(data).get - # let purchaseId = await client0.requestStorage( - # cid, - # duration=totalPeriods.periods, - # origDatasetSizeInBlocks=datasetSizeInBlocks) - # let requestId = client0.requestId(purchaseId).get + let purchaseId = await client0.requestStorage( + cid, + duration=totalPeriods.periods, + expiry=30.periods, + nodes=5, + tolerance=1, + origDatasetSizeInBlocks=datasetSizeInBlocks) + let requestId = client0.requestId(purchaseId).get - # check eventually client0.purchaseStateIs(purchaseId, "started") + check eventually client0.purchaseStateIs(purchaseId, "started") - # var slotWasFreed = false - # proc onSlotFreed(event: SlotFreed) = - # if event.requestId == requestId and - # event.slotIndex == 0.u256: - # slotWasFreed = true + var slotWasFreed = false + proc onSlotFreed(event: SlotFreed) = + if event.requestId == requestId and + event.slotIndex == 0.u256: + slotWasFreed = true - # let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) + let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) - # # check not freed - # let currentPeriod = await getCurrentPeriod() - # check not eventuallyP(slotWasFreed, currentPeriod + totalPeriods.u256 + 1) + # check not freed + let currentPeriod = await getCurrentPeriod() + check not eventuallyP(slotWasFreed, currentPeriod + totalPeriods.u256 + 1) - # await subscription.unsubscribe() + await subscription.unsubscribe() - # TODO: uncomment once fixed - # test "host that submits invalid proofs is paid out less", NodeConfigs( - # # Uncomment to start Hardhat automatically, typically so logs can be inspected locally - # # hardhat: HardhatConfig().withLogFile(), + test "host that submits invalid proofs is paid out less", NodeConfigs( + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + # hardhat: HardhatConfig().withLogFile(), + 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", "clock", "purchases", "slotsbuilder") + .some, - # clients: - # CodexConfig() - # .nodes(1) - # # .debug() # uncomment to enable console log output.debug() - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("node", "erasure", "clock", "purchases"), + providers: + CodexConfigs.init(nodes=5) + .withSimulateProofFailures(idx=0, failEveryNProofs=2) + .debug(idx=0) # uncomment to enable console log output + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("marketplace", "sales", "reservations", "node", "slotsbuilder") + .some, - # providers: - # CodexConfig() - # .nodes(3) - # .simulateProofFailuresFor(providerIdx=0, failEveryNProofs=2) - # # .debug() # uncomment to enable console log output - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("marketplace", "sales", "reservations", "node"), + validators: + CodexConfigs.init(nodes=1) + # .debug() + .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + .withLogTopics("validator") + .some + ): + let client0 = clients()[0].client + let providers = providers() + let totalPeriods = 25 - # validators: - # CodexConfig() - # .nodes(1) - # # .debug() - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("validator") - # ): - # let client0 = clients()[0].client - # let provider0 = providers()[0] - # let provider1 = providers()[1] - # let provider2 = providers()[2] - # let totalPeriods = 25 + let datasetSizeInBlocks = 8 + let data = await RandomChunker.example(blocks=datasetSizeInBlocks) + # original data = 3 blocks so slot size will be 4 blocks + let slotSize = (DefaultBlockSize * 4.NBytes).Natural.u256 - # let datasetSizeInBlocks = 3 - # let data = await RandomChunker.example(blocks=datasetSizeInBlocks) - # # original data = 3 blocks so slot size will be 4 blocks - # let slotSize = (DefaultBlockSize * 4.NBytes).Natural.u256 + discard providers[0].client.postAvailability( + size=slotSize, # should match 1 slot only + duration=totalPeriods.periods.u256, + minPrice=300.u256, + maxCollateral=200.u256 + ) - # discard provider0.client.postAvailability( - # size=slotSize, # should match 1 slot only - # duration=totalPeriods.periods.u256, - # minPrice=300.u256, - # maxCollateral=200.u256 - # ) + let cid = client0.upload(data).get - # let cid = client0.upload(data).get + let purchaseId = await client0.requestStorage( + cid, + duration=totalPeriods.periods, + expiry=10.periods, + nodes=5, + tolerance=1, + origDatasetSizeInBlocks=datasetSizeInBlocks + ) - # let purchaseId = await client0.requestStorage( - # cid, - # duration=totalPeriods.periods, - # expiry=10.periods, - # nodes=3, - # tolerance=1, - # origDatasetSizeInBlocks=datasetSizeInBlocks - # ) + without requestId =? client0.requestId(purchaseId): + fail() - # without requestId =? client0.requestId(purchaseId): - # fail() + var filledSlotIds: seq[SlotId] = @[] + proc onSlotFilled(event: SlotFilled) = + let slotId = slotId(event.requestId, event.slotIndex) + filledSlotIds.add slotId - # var filledSlotIds: seq[SlotId] = @[] - # proc onSlotFilled(event: SlotFilled) = - # let slotId = slotId(event.requestId, event.slotIndex) - # filledSlotIds.add slotId + let subscription = await marketplace.subscribe(SlotFilled, onSlotFilled) - # let subscription = await marketplace.subscribe(SlotFilled, onSlotFilled) + # wait til first slot is filled + check eventually filledSlotIds.len > 0 - # # wait til first slot is filled - # check eventually filledSlotIds.len > 0 + template waitForSlotFilled(provider: CodexProcess, idx: int) = + discard provider.client.postAvailability( + size=slotSize, # should match 1 slot only + duration=totalPeriods.periods.u256, + minPrice=300.u256, + maxCollateral=200.u256 + ) - # # now add availability for providers 1 and 2, which should allow them to to - # # put the remaining slots in their queues - # discard provider1.client.postAvailability( - # size=slotSize, # should match 1 slot only - # duration=totalPeriods.periods.u256, - # minPrice=300.u256, - # maxCollateral=200.u256 - # ) + check eventually filledSlotIds.len > idx - # check eventually filledSlotIds.len > 1 + # TODO: becausee we now have 5+ slots to fill plus proof generation, this + # may take way too long. Another idea is to update the SlotFilled contract + # event to include the host that filled the slot. With that, we can use + # `onSlotFilled` to build a provider > slotIdx table in memory and use that + # to check sale states + for i in 1.. 2 + # Wait til remaining providers are in the Proving state. + for i in 1.. + (await token.balanceOf(providers[0].ethAccount)) + ) - # check eventuallyP( - # # SaleFinished happens too quickly, check SalePayout instead - # provider1.client.saleStateIs(filledSlotIds[1], "SalePayout"), - # currentPeriod + totalPeriods.u256 + 1) - - # check eventuallyP( - # # SaleFinished happens too quickly, check SalePayout instead - # provider2.client.saleStateIs(filledSlotIds[2], "SalePayout"), - # currentPeriod + totalPeriods.u256 + 1) - - # check eventually( - # (await token.balanceOf(provider1.ethAccount)) > - # (await token.balanceOf(provider0.ethAccount)) - # ) - - # await subscription.unsubscribe() + await subscription.unsubscribe()