nim-codex/tests/testIntegration.nim
markspanbroek 4175689745
Load purchase state from chain (#283)
* [purchasing] Simplify test

* [utils] Move StorageRequest.example up one level

* [purchasing] Load purchases from market

* [purchasing] load purchase states

* Implement myRequest() and getState() methods for OnChainMarket

* [proofs] Fix intermittently failing tests

Ensures that examples of proofs in tests are never of length 0;
these are considered invalid proofs by the smart contract logic.

* [contracts] Fix failing test

With the new solidity contracts update, a contract can only
be paid out after it started.

* [market] Add method to get request end time

* [purchasing] wait until purchase is finished

Purchase.wait() would previously wait until purchase
was started, now we wait until it is finished.

* [purchasing] Handle 'finished' and 'failed' states

* [marketplace] move to failed state once request fails

- Add support for subscribing to request failure events.
- Add supporting contract tests for subscribing to request failure events.
- Allow the PurchaseStarted state to move to PurchaseFailure once a request failure event is emitted
- Add supporting tests for moving from PurchaseStarted to PurchaseFailure
- Add state transition tests for PurchaseUnknown.

* [marketplace] Fix test with longer sleepAsync

* [integration] Add function to restart a codex node

* [purchasing] Set client address before requesting storage

To prevent the purchase id (which equals the request id)
from changing once it's been submitted.

* [contracts] Fix: OnChainMarket.getState()

Had the wrong method signature before

* [purchasing] Load purchases on node start

* [purchasing] Rename state 'PurchaseError' to 'PurchaseErrored'

Allows for an exception type called 'PurchaseError'

* [purchasing] Load purchases in background

No longer calls market.getRequest() for every purchase
on node start.

* [contracts] Add `$` for RequestId, SlotId and Nonce

To aid with debugging

* [purchasing] Add Purchasing.stop()

To ensure that all contract interactions have both a
start() and a stop() for

* [tests] Remove sleepAsync where possible

Use `eventually` loop instead, to make sure that we're
not waiting unnecessarily.

* [integration] Fix: handle non-json response in test

* [purchasing] Add purchase state to json

* [integration] Ensure that purchase is submitted before restart

Fixes test failure on slower CI

* [purchasing] re-implement `description` as method

Allows description to be set in the same module where the
state type is defined.

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

* [contracts] fix typo

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

* [market] Use more generic error type

Should we decide to change the provider type later

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>

Co-authored-by: Eric Mastro <eric.mastro@gmail.com>
2022-11-08 08:10:17 +01:00

149 lines
5.0 KiB
Nim

import std/osproc
import std/os
import std/httpclient
import std/json
import std/strutils
import pkg/chronos
import ./ethertest
import ./contracts/time
import ./integration/nodes
import ./integration/tokens
import ./codex/helpers/eventually
ethersuite "Integration tests":
var node1, node2: NodeProcess
var baseurl1, baseurl2: string
var client: HttpClient
let dataDir1 = getTempDir() / "Codex1"
let dataDir2 = getTempDir() / "Codex2"
setup:
await provider.getSigner(accounts[0]).mint()
await provider.getSigner(accounts[1]).mint()
await provider.getSigner(accounts[1]).deposit()
baseurl1 = "http://localhost:8080/api/codex/v1"
baseurl2 = "http://localhost:8081/api/codex/v1"
client = newHttpClient()
node1 = startNode([
"--api-port=8080",
"--data-dir=" & dataDir1,
"--nat=127.0.0.1",
"--disc-ip=127.0.0.1",
"--disc-port=8090",
"--persistence",
"--eth-account=" & $accounts[0]
], debug = false)
node2 = startNode([
"--api-port=8081",
"--data-dir=" & dataDir2,
"--nat=127.0.0.1",
"--disc-ip=127.0.0.1",
"--disc-port=8091",
"--bootstrap-node=" & strip($(parseJson(client.get(baseurl1 & "/info").body)["spr"]), chars = {'"'}),
"--persistence",
"--eth-account=" & $accounts[1]
], debug = false)
teardown:
client.close()
node1.stop()
node2.stop()
dataDir1.removeDir()
dataDir2.removeDir()
test "nodes can print their peer information":
let info1 = client.get(baseurl1 & "/info").body
let info2 = client.get(baseurl2 & "/info").body
check info1 != info2
test "node accepts file uploads":
let url = baseurl1 & "/upload"
let response = client.post(url, "some file contents")
check response.status == "200 OK"
test "node handles new storage availability":
let url = baseurl1 & "/sales/availability"
let json = %*{"size": "0x1", "duration": "0x2", "minPrice": "0x3"}
check client.post(url, $json).status == "200 OK"
test "node lists storage that is for sale":
let url = baseurl1 & "/sales/availability"
let json = %*{"size": "0x1", "duration": "0x2", "minPrice": "0x3"}
let availability = parseJson(client.post(url, $json).body)
let response = client.get(url)
check response.status == "200 OK"
check %*availability in parseJson(response.body)
test "node handles storage request":
let cid = client.post(baseurl1 & "/upload", "some file contents").body
let url = baseurl1 & "/storage/request/" & cid
let json = %*{"duration": "0x1", "reward": "0x2"}
let response = client.post(url, $json)
check response.status == "200 OK"
test "node retrieves purchase status":
let cid = client.post(baseurl1 & "/upload", "some file contents").body
let request = %*{"duration": "0x1", "reward": "0x2"}
let id = client.post(baseurl1 & "/storage/request/" & cid, $request).body
let response = client.get(baseurl1 & "/storage/purchases/" & id)
check response.status == "200 OK"
let json = parseJson(response.body)
check json["request"]["ask"]["duration"].getStr == "0x1"
check json["request"]["ask"]["reward"].getStr == "0x2"
test "node remembers purchase status after restart":
let cid = client.post(baseurl1 & "/upload", "some file contents").body
let request = %*{"duration": "0x1", "reward": "0x2"}
let id = client.post(baseurl1 & "/storage/request/" & cid, $request).body
proc getPurchase(id: string): JsonNode =
let response = client.get(baseurl1 & "/storage/purchases/" & id)
return parseJson(response.body).catch |? nil
check eventually getPurchase(id){"state"}.getStr == "submitted"
node1.restart()
client.close()
client = newHttpClient()
check eventually (not isNil getPurchase(id){"request"}{"ask"})
check getPurchase(id){"request"}{"ask"}{"duration"}.getStr == "0x1"
check getPurchase(id){"request"}{"ask"}{"reward"}.getStr == "0x2"
test "nodes negotiate contracts on the marketplace":
proc sell =
let json = %*{"size": "0xFFFFF", "duration": "0x200", "minPrice": "0x300"}
discard client.post(baseurl2 & "/sales/availability", $json)
proc available: JsonNode =
client.get(baseurl2 & "/sales/availability").body.parseJson
proc upload: string =
client.post(baseurl1 & "/upload", "some file contents").body
proc buy(cid: string): string =
let expiry = ((waitFor provider.currentTime()) + 30).toHex
let json = %*{"duration": "0x1", "reward": "0x400", "expiry": expiry}
client.post(baseurl1 & "/storage/request/" & cid, $json).body
proc finish(purchase: string): Future[JsonNode] {.async.} =
while true:
let response = client.get(baseurl1 & "/storage/purchases/" & purchase)
let json = parseJson(response.body)
if json["state"].getStr == "finished": return json
await sleepAsync(1.seconds)
sell()
let purchase = waitFor upload().buy().finish()
check purchase["error"].getStr == ""
check available().len == 0