[marketplace] split ContractInteractions

Split ContractInteractions into:
 - ClientInteractions (with purchasing)
 - HostInteractions (with sales and proving)
This commit is contained in:
Eric Mastro 2023-02-02 18:41:50 +11:00
parent 9f02f90f68
commit cff2ca93d8
No known key found for this signature in database
GPG Key ID: 141E3048D95A4E63
8 changed files with 238 additions and 97 deletions

View File

@ -94,7 +94,10 @@ proc stop*(s: CodexServer) {.async.} =
s.runHandle.complete()
proc new(_: type ContractInteractions, config: CodexConf): ?ContractInteractions =
proc new(_: type ContractInteractions,
config: CodexConf,
repo: RepoStore): Contracts =
if not config.persistence:
if config.ethAccount.isSome:
warn "Ethereum account was set, but persistence is not enabled"
@ -104,10 +107,16 @@ proc new(_: type ContractInteractions, config: CodexConf): ?ContractInteractions
error "Persistence enabled, but no Ethereum account was set"
quit QuitFailure
var client: ?ClientInteractions
var host: ?HostInteractions
if deployment =? config.ethDeployment:
ContractInteractions.new(config.ethProvider, account, deployment)
client = ClientInteractions.new(config.ethProvider, account, deployment)
host = HostInteractions.new(config.ethProvider, account, repo, deployment)
else:
ContractInteractions.new(config.ethProvider, account)
client = ClientInteractions.new(config.ethProvider, account)
host = HostInteractions.new(config.ethProvider, account, repo)
(client, host)
proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey): T =

View File

@ -1,78 +1,5 @@
import pkg/ethers
import pkg/chronicles
import ../purchasing
import ../sales
import ../proving
import ./deployment
import ./marketplace
import ./market
import ./proofs
import ./clock
import ./interactions/interactions
import ./interactions/hostinteractions
import ./interactions/clientinteractions
export purchasing
export sales
export proving
export chronicles
type
ContractInteractions* = ref object
purchasing*: Purchasing
sales*: Sales
proving*: Proving
clock: OnChainClock
proc new*(_: type ContractInteractions,
signer: Signer,
deployment: Deployment): ?ContractInteractions =
without address =? deployment.address(Marketplace):
error "Unable to determine address of the Marketplace smart contract"
return none ContractInteractions
let contract = Marketplace.new(address, signer)
let market = OnChainMarket.new(contract)
let proofs = OnChainProofs.new(contract)
let clock = OnChainClock.new(signer.provider)
let proving = Proving.new(proofs, clock)
some ContractInteractions(
purchasing: Purchasing.new(market, clock),
sales: Sales.new(market, clock, proving),
proving: proving,
clock: clock
)
proc new*(_: type ContractInteractions,
providerUrl: string,
account: Address,
deploymentFile: string = string.default): ?ContractInteractions =
let provider = JsonRpcProvider.new(providerUrl)
let signer = provider.getSigner(account)
var deploy: Deployment
try:
if deploymentFile == string.default:
deploy = deployment()
else:
deploy = deployment(deploymentFile)
except IOError as e:
error "Unable to read deployment json", msg = e.msg
return none ContractInteractions
ContractInteractions.new(signer, deploy)
proc new*(_: type ContractInteractions,
account: Address): ?ContractInteractions =
ContractInteractions.new("ws://localhost:8545", account)
proc start*(interactions: ContractInteractions) {.async.} =
await interactions.clock.start()
await interactions.sales.start()
await interactions.proving.start()
await interactions.purchasing.start()
proc stop*(interactions: ContractInteractions) {.async.} =
await interactions.purchasing.stop()
await interactions.sales.stop()
await interactions.proving.stop()
await interactions.clock.stop()
export interactions, hostinteractions, clientinteractions

View File

@ -0,0 +1,55 @@
import pkg/ethers
import pkg/chronicles
import ../../purchasing
import ../deployment
import ../marketplace
import ../market
import ../proofs
import ../clock
import ./interactions
export purchasing
export chronicles
type
ClientInteractions* = ref object of ContractInteractions
purchasing*: Purchasing
proc new*(_: type ClientInteractions,
signer: Signer,
deployment: Deployment): ?ClientInteractions =
without address =? deployment.address(Marketplace):
error "Unable to determine address of the Marketplace smart contract"
return none ClientInteractions
let contract = Marketplace.new(address, signer)
let market = OnChainMarket.new(contract)
let clock = OnChainClock.new(signer.provider)
let c = ClientInteractions.new(clock)
c.purchasing = Purchasing.new(market, clock)
some c
proc new*(_: type ClientInteractions,
providerUrl: string,
account: Address,
deploymentFile: string = string.default): ?ClientInteractions =
without prepared =? prepare(providerUrl, account, deploymentFile):
return none ClientInteractions
ClientInteractions.new(prepared.signer, prepared.deploy)
proc new*(_: type ClientInteractions,
account: Address): ?ClientInteractions =
ClientInteractions.new("ws://localhost:8545", account)
proc start*(self: ClientInteractions) {.async.} =
await self.purchasing.start()
await procCall ContractInteractions(self).start()
proc stop*(self: ClientInteractions) {.async.} =
await self.purchasing.stop()
await procCall ContractInteractions(self).stop()

View File

@ -0,0 +1,67 @@
import pkg/ethers
import pkg/chronicles
import ../../sales
import ../../proving
import ../../stores
import ../deployment
import ../marketplace
import ../market
import ../proofs
import ../clock
import ./interactions
export sales
export proving
export chronicles
type
HostInteractions* = ref object of ContractInteractions
sales*: Sales
proving*: Proving
proc new*(_: type HostInteractions,
signer: Signer,
deployment: Deployment,
repoStore: RepoStore): ?HostInteractions =
without address =? deployment.address(Marketplace):
error "Unable to determine address of the Marketplace smart contract"
return none HostInteractions
let contract = Marketplace.new(address, signer)
let market = OnChainMarket.new(contract)
let proofs = OnChainProofs.new(contract)
let clock = OnChainClock.new(signer.provider)
let proving = Proving.new(proofs, clock)
let h = HostInteractions.new(clock)
h.sales = Sales.new(market, clock, proving, repoStore)
h.proving = proving
some h
proc new*(_: type HostInteractions,
providerUrl: string,
account: Address,
repo: RepoStore,
deploymentFile: string = string.default): ?HostInteractions =
without prepared =? prepare(providerUrl, account, deploymentFile):
return none HostInteractions
HostInteractions.new(prepared.signer, prepared.deploy, repo)
proc new*(_: type HostInteractions,
account: Address,
repo: RepoStore): ?HostInteractions =
HostInteractions.new("ws://localhost:8545", account, repo)
method start*(self: HostInteractions) {.async.} =
await self.sales.start()
await self.proving.start()
await procCall ContractInteractions(self).start()
method stop*(self: HostInteractions) {.async.} =
await self.sales.stop()
await self.proving.stop()
await procCall ContractInteractions(self).start()

View File

@ -0,0 +1,45 @@
import pkg/ethers
import pkg/chronicles
import pkg/questionable
import pkg/questionable/results
import ../../errors
import ../deployment
import ../clock
type
ContractInteractions* = ref object of RootObj
clock: OnChainClock
ContractInteractionsError* = object of CodexError
ReadDeploymentFileFailureError* = object of ContractInteractionsError
method new*[T: ContractInteractions](_: type T, clock: OnChainClock): T {.base.} =
T(clock: clock)
proc prepare*(
providerUrl: string = "ws://localhost:8545",
account: Address,
deploymentFile: string = string.default):
?!tuple[signer: JsonRpcSigner, deploy: Deployment] =
let provider = JsonRpcProvider.new(providerUrl)
let signer = provider.getSigner(account)
var deploy: Deployment
try:
if deploymentFile == string.default:
deploy = deployment()
else:
deploy = deployment(deploymentFile)
except IOError as e:
let err = newException(ReadDeploymentFileFailureError,
"Unable to read deployment json")
err.parent = e
return failure(err)
return success((signer, deploy))
method start*(interactions: ContractInteractions) {.async, base.} =
await interactions.clock.start()
method stop*(interactions: ContractInteractions) {.async, base.} =
await interactions.clock.stop()

View File

@ -43,6 +43,10 @@ type
CodexError = object of CatchableError
Contracts* = tuple
client: ?ClientInteractions
host: ?HostInteractions
CodexNodeRef* = ref object
switch*: Switch
networkId*: PeerID
@ -50,7 +54,7 @@ type
engine*: BlockExcEngine
erasure*: Erasure
discovery*: Discovery
contracts*: ?ContractInteractions
contracts*: Contracts
proc findPeer*(
node: CodexNodeRef,
@ -250,7 +254,7 @@ proc requestStorage*(self: CodexNodeRef,
##
trace "Received a request for storage!", cid, duration, nodes, tolerance, reward
without contracts =? self.contracts:
without contracts =? self.contracts.client:
trace "Purchasing not available"
return failure "Purchasing not available"
@ -307,7 +311,7 @@ proc new*(
engine: BlockExcEngine,
erasure: Erasure,
discovery: Discovery,
contracts = ContractInteractions.none): T =
contracts: Contracts = (ClientInteractions.none, HostInteractions.none)): T =
T(
switch: switch,
blockStore: store,
@ -329,7 +333,7 @@ proc start*(node: CodexNodeRef) {.async.} =
if not node.discovery.isNil:
await node.discovery.start()
if contracts =? node.contracts:
if contracts =? node.contracts.host:
# TODO: remove Sales callbacks, pass BlockStore and StorageProofs instead
contracts.sales.onStore = proc(request: StorageRequest,
slot: UInt256,
@ -369,7 +373,7 @@ proc start*(node: CodexNodeRef) {.async.} =
await contracts.start()
except CatchableError as error:
error "Unable to start contract interactions: ", error=error.msg
node.contracts = ContractInteractions.none
node.contracts.host = HostInteractions.none
node.networkId = node.switch.peerInfo.peerId
notice "Started codex node", id = $node.networkId, addrs = node.switch.peerInfo.addrs
@ -389,8 +393,11 @@ proc stop*(node: CodexNodeRef) {.async.} =
if not node.discovery.isNil:
await node.discovery.stop()
if contracts =? node.contracts:
await contracts.stop()
if clientContracts =? node.contracts.client:
await clientContracts.stop()
if hostContracts =? node.contracts.host:
await hostContracts.stop()
if not node.blockStore.isNil:
await node.blockStore.close

View File

@ -244,7 +244,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
"/api/codex/v1/sales/availability") do () -> RestApiResponse:
## Returns storage that is for sale
without contracts =? node.contracts:
without contracts =? node.contracts.host:
return RestApiResponse.error(Http503, "Sales unavailable")
let json = %contracts.sales.available
@ -259,7 +259,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
## duration - maximum time the storage should be sold for (in seconds)
## minPrice - minimum price to be paid (in amount of tokens)
without contracts =? node.contracts:
without contracts =? node.contracts.host:
return RestApiResponse.error(Http503, "Sales unavailable")
let body = await request.getBody()
@ -281,7 +281,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter =
"/api/codex/v1/storage/purchases/{id}") do (
id: PurchaseId) -> RestApiResponse:
without contracts =? node.contracts:
without contracts =? node.contracts.client:
return RestApiResponse.error(Http503, "Purchasing unavailable")
without id =? id.tryGet.catch, error:

View File

@ -1,32 +1,63 @@
import std/os
import codex/contracts
import pkg/datastore
import pkg/codex/contracts
import pkg/codex/stores
import ../ethertest
import ./examples
ethersuite "Marketplace Contract Interactions":
ethersuite "Marketplace Contract Client Interactions":
let account = Address.example
var contracts: ContractInteractions
var contracts: ClientInteractions
setup:
contracts = !ContractInteractions.new(account)
contracts = !ClientInteractions.new(account)
test "can be instantiated with a signer and deployment info":
let signer = provider.getSigner()
let deployment = deployment()
check ContractInteractions.new(signer, deployment).isSome
check ClientInteractions.new(signer, deployment).isSome
test "can be instantiated with a provider url":
let url = "http://localhost:8545"
let account = Address.example
let deployment = "vendor" / "codex-contracts-eth" / "deployment-localhost.json"
check ContractInteractions.new(url, account).isSome
check ContractInteractions.new(url, account, deployment).isSome
check ClientInteractions.new(url, account).isSome
check ClientInteractions.new(url, account, deployment).isSome
test "provides purchasing":
check contracts.purchasing != nil
ethersuite "Marketplace Contract Host Interactions":
let account = Address.example
var
contracts: HostInteractions
repo: RepoStore
setup:
let repoDs = SQLiteDatastore.new(Memory).tryGet()
let metaDs = SQLiteDatastore.new(Memory).tryGet()
repo = RepoStore.new(repoDs, metaDs)
contracts = !HostInteractions.new(account, repo)
test "can be instantiated with a signer and deployment info":
let signer = provider.getSigner()
let deployment = deployment()
check HostInteractions.new(signer, deployment, repo).isSome
test "can be instantiated with a provider url":
let url = "http://localhost:8545"
let account = Address.example
let deployment = "vendor" / "codex-contracts-eth" / "deployment-localhost.json"
check HostInteractions.new(url, account, repo).isSome
check HostInteractions.new(url, account, repo, deployment).isSome
test "provides sales":
check contracts.sales != nil