diff --git a/codexcrawler.nimble b/codexcrawler.nimble index a38d166..3af92dc 100644 --- a/codexcrawler.nimble +++ b/codexcrawler.nimble @@ -25,6 +25,7 @@ requires "questionable >= 0.10.15 & < 0.11.0" requires "https://github.com/codex-storage/nim-codex-dht#f6eef1ac95c70053b2518f1e3909c909ed8701a6" requires "docopt >= 0.7.1 & < 1.0.0" requires "nph >= 0.6.1 & < 1.0.0" +requires "ethers >= 1.0.0 & < 2.0.0" task format, "Formatting...": exec "nph ./" diff --git a/codexcrawler/services/marketplace.nim b/codexcrawler/services/marketplace.nim new file mode 100644 index 0000000..80d6a36 --- /dev/null +++ b/codexcrawler/services/marketplace.nim @@ -0,0 +1,6 @@ +import ./marketplace/marketplace + +# todo + +proc aaa*() = + echo "aaa" diff --git a/codexcrawler/services/marketplace/config.nim b/codexcrawler/services/marketplace/config.nim new file mode 100644 index 0000000..3c31c8b --- /dev/null +++ b/codexcrawler/services/marketplace/config.nim @@ -0,0 +1,103 @@ +import pkg/contractabi +import pkg/ethers/fields +import pkg/questionable/results + +export contractabi + +const DefaultRequestCacheSize* = 128.uint16 + +type + MarketplaceConfig* = object + collateral*: CollateralConfig + proofs*: ProofConfig + reservations*: SlotReservationsConfig + requestDurationLimit*: uint64 + + CollateralConfig* = object + repairRewardPercentage*: uint8 + # percentage of remaining collateral slot has after it has been freed + maxNumberOfSlashes*: uint8 # frees slot when the number of slashes reaches this value + slashPercentage*: uint8 # percentage of the collateral that is slashed + validatorRewardPercentage*: uint8 + # percentage of the slashed amount going to the validators + + ProofConfig* = object + period*: uint64 # proofs requirements are calculated per period (in seconds) + timeout*: uint64 # mark proofs as missing before the timeout (in seconds) + downtime*: uint8 # ignore this much recent blocks for proof requirements + downtimeProduct*: uint8 + zkeyHash*: string # hash of the zkey file which is linked to the verifier + # Ensures the pointer does not remain in downtime for many consecutive + # periods. For each period increase, move the pointer `pointerProduct` + # blocks. Should be a prime number to ensure there are no cycles. + + SlotReservationsConfig* = object + maxReservations*: uint8 + +func fromTuple(_: type ProofConfig, tupl: tuple): ProofConfig = + ProofConfig( + period: tupl[0], + timeout: tupl[1], + downtime: tupl[2], + downtimeProduct: tupl[3], + zkeyHash: tupl[4], + ) + +func fromTuple(_: type SlotReservationsConfig, tupl: tuple): SlotReservationsConfig = + SlotReservationsConfig(maxReservations: tupl[0]) + +func fromTuple(_: type CollateralConfig, tupl: tuple): CollateralConfig = + CollateralConfig( + repairRewardPercentage: tupl[0], + maxNumberOfSlashes: tupl[1], + slashPercentage: tupl[2], + validatorRewardPercentage: tupl[3], + ) + +func fromTuple(_: type MarketplaceConfig, tupl: tuple): MarketplaceConfig = + MarketplaceConfig( + collateral: tupl[0], + proofs: tupl[1], + reservations: tupl[2], + requestDurationLimit: tupl[3], + ) + +func solidityType*(_: type SlotReservationsConfig): string = + solidityType(SlotReservationsConfig.fieldTypes) + +func solidityType*(_: type ProofConfig): string = + solidityType(ProofConfig.fieldTypes) + +func solidityType*(_: type CollateralConfig): string = + solidityType(CollateralConfig.fieldTypes) + +func solidityType*(_: type MarketplaceConfig): string = + solidityType(MarketplaceConfig.fieldTypes) + +func encode*(encoder: var AbiEncoder, slot: SlotReservationsConfig) = + encoder.write(slot.fieldValues) + +func encode*(encoder: var AbiEncoder, slot: ProofConfig) = + encoder.write(slot.fieldValues) + +func encode*(encoder: var AbiEncoder, slot: CollateralConfig) = + encoder.write(slot.fieldValues) + +func encode*(encoder: var AbiEncoder, slot: MarketplaceConfig) = + encoder.write(slot.fieldValues) + +func decode*(decoder: var AbiDecoder, T: type ProofConfig): ?!T = + let tupl = ?decoder.read(ProofConfig.fieldTypes) + success ProofConfig.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type SlotReservationsConfig): ?!T = + let tupl = ?decoder.read(SlotReservationsConfig.fieldTypes) + success SlotReservationsConfig.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type CollateralConfig): ?!T = + let tupl = ?decoder.read(CollateralConfig.fieldTypes) + success CollateralConfig.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type MarketplaceConfig): ?!T = + let tupl = ?decoder.read(MarketplaceConfig.fieldTypes) + success MarketplaceConfig.fromTuple(tupl) diff --git a/codexcrawler/services/marketplace/logutils.nim b/codexcrawler/services/marketplace/logutils.nim new file mode 100644 index 0000000..39f99fb --- /dev/null +++ b/codexcrawler/services/marketplace/logutils.nim @@ -0,0 +1,285 @@ +## logutils is a module that has several goals: +## 1. Fix json logging output (run with `--log-format=json`) which was +## effectively broken for many types using default Chronicles json +## serialization. +## 2. Ability to specify log output for textlines and json sinks together or +## separately +## - This is useful if consuming json in some kind of log parser and need +## valid json with real values +## - eg a shortened Cid is nice to see in a text log in stdout, but won't +## provide a real Cid when parsed in json +## 4. Remove usages of `nim-json-serialization` from the codebase +## 5. Remove need to declare `writeValue` for new types +## 6. Remove need to [avoid importing or exporting `toJson`, `%`, `%*` to prevent +## conflicts](https://github.com/codex-storage/nim-codex/pull/645#issuecomment-1838834467) +## +## When declaring a new type, one should consider importing the `codex/logutils` +## module, and specifying `formatIt`. If textlines log output and json log output +## need to be different, overload `formatIt` and specify a `LogFormat`. If json +## serialization is needed, it can be declared with a `%` proc. `logutils` +## imports and exports `nim-serde` which handles the de/serialization, examples +## below. **Only `codex/logutils` needs to be imported.** +## +## Using `logutils` in the Codex codebase: +## - Instead of importing `pkg/chronicles`, import `pkg/codex/logutils` +## - most of `chronicles` is exported by `logutils` +## - Instead of importing `std/json`, import `pkg/serde/json` +## - `std/json` is exported by `serde` which is exported by `logutils` +## - Instead of importing `pkg/nim-json-serialization`, import +## `pkg/serde/json` or use codex-specific overloads by importing `utils/json` +## - one of the goals is to remove the use of `nim-json-serialization` +## +## ```nim +## import pkg/codex/logutils +## +## type +## BlockAddress* = object +## case leaf*: bool +## of true: +## treeCid* {.serialize.}: Cid +## index* {.serialize.}: Natural +## else: +## cid* {.serialize.}: Cid +## +## logutils.formatIt(LogFormat.textLines, BlockAddress): +## if it.leaf: +## "treeCid: " & shortLog($it.treeCid) & ", index: " & $it.index +## else: +## "cid: " & shortLog($it.cid) +## +## logutils.formatIt(LogFormat.json, BlockAddress): %it +## +## # chronicles textlines output +## TRC test tid=14397405 ba="treeCid: zb2*fndjU1, index: 0" +## # chronicles json output +## {"lvl":"TRC","msg":"test","tid":14397405,"ba":{"treeCid":"zb2rhgsDE16rLtbwTFeNKbdSobtKiWdjJPvKEuPgrQAfndjU1","index":0}} +## ``` +## In this case, `BlockAddress` is just an object, so `nim-serde` can handle +## serializing it without issue (only fields annotated with `{.serialize.}` will +## serialize (aka opt-in serialization)). +## +## If one so wished, another option for the textlines log output, would be to +## simply `toString` the serialised json: +## ```nim +## logutils.formatIt(LogFormat.textLines, BlockAddress): $ %it +## # or, more succinctly: +## logutils.formatIt(LogFormat.textLines, BlockAddress): it.toJson +## ``` +## In that case, both the textlines and json sinks would have the same output, +## so we could reduce this even further by not specifying a `LogFormat`: +## ```nim +## type +## BlockAddress* = object +## case leaf*: bool +## of true: +## treeCid* {.serialize.}: Cid +## index* {.serialize.}: Natural +## else: +## cid* {.serialize.}: Cid +## +## logutils.formatIt(BlockAddress): %it +## +## # chronicles textlines output +## TRC test tid=14400673 ba="{\"treeCid\":\"zb2rhgsDE16rLtbwTFeNKbdSobtKiWdjJPvKEuPgrQAfndjU1\",\"index\":0}" +## # chronicles json output +## {"lvl":"TRC","msg":"test","tid":14400673,"ba":{"treeCid":"zb2rhgsDE16rLtbwTFeNKbdSobtKiWdjJPvKEuPgrQAfndjU1","index":0}} +## ``` + +import std/options +import std/sequtils +import std/strutils +import std/sugar +import std/typetraits + +import pkg/chronicles except toJson, `%` +from pkg/libp2p import Cid, PeerId, SignedPeerRecord, MultiAddress, AddressInfo, init, `$` +from pkg/ethers import Address +import pkg/questionable +import pkg/questionable/results +import pkg/stew/byteutils +import pkg/stint +import pkg/serde/json +import pkg/codexdht/discv5/node as dn +import pkg/contractabi + +export byteutils +export chronicles except toJson, formatIt, `%` +export questionable +export sequtils +export json except formatIt +export strutils +export sugar +export results + +proc fromJson*(_: type Cid, json: JsonNode): ?!Cid = + expectJsonKind(Cid, JString, json) + Cid.init(json.str).mapFailure + +func `%`*(cid: Cid): JsonNode = + % $cid + +func `%`*(obj: PeerId): JsonNode = + % $obj + +func `%`*(obj: SignedPeerRecord): JsonNode = + % $obj + +func `%`*(obj: dn.Address): JsonNode = + % $obj + +func `%`*(obj: AddressInfo): JsonNode = + % $obj.address + +func `%`*(obj: MultiAddress): JsonNode = + % $obj + +func `%`*(address: ethers.Address): JsonNode = + % $address + +func shortLog*(long: string, ellipses = "*", start = 3, stop = 6): string = + ## Returns compact string representation of ``long``. + var short = long + let minLen = start + ellipses.len + stop + if len(short) > minLen: + short.insert(ellipses, start) + + when (NimMajor, NimMinor) > (1, 4): + short.delete(start + ellipses.len .. short.high - stop) + else: + short.delete(start + ellipses.len, short.high - stop) + + short + +func shortHexLog*(long: string): string = + if long[0 .. 1] == "0x": + result &= "0x" + result &= long[2 .. long.high].shortLog("..", 4, 4) + +func short0xHexLog*[N: static[int], T: array[N, byte]](v: T): string = + v.to0xHex.shortHexLog + +func short0xHexLog*[T: distinct](v: T): string = + type BaseType = T.distinctBase + BaseType(v).short0xHexLog + +func short0xHexLog*[U: distinct, T: seq[U]](v: T): string = + type BaseType = U.distinctBase + "@[" & v.map(x => BaseType(x).short0xHexLog).join(",") & "]" + +func to0xHexLog*[T: distinct](v: T): string = + type BaseType = T.distinctBase + BaseType(v).to0xHex + +func to0xHexLog*[U: distinct, T: seq[U]](v: T): string = + type BaseType = U.distinctBase + "@[" & v.map(x => BaseType(x).to0xHex).join(",") & "]" + +proc formatTextLineSeq*(val: seq[string]): string = + "@[" & val.join(", ") & "]" + +template formatIt*(format: LogFormat, T: typedesc, body: untyped) = + # Provides formatters for logging with Chronicles for the given type and + # `LogFormat`. + # NOTE: `seq[T]`, `Option[T]`, and `seq[Option[T]]` are overridden + # since the base `setProperty` is generic using `auto` and conflicts with + # providing a generic `seq` and `Option` override. + when format == LogFormat.json: + proc formatJsonOption(val: ?T): JsonNode = + if it =? val: + json.`%`(body) + else: + newJNull() + + proc formatJsonResult*(val: ?!T): JsonNode = + without it =? val, error: + let jObj = newJObject() + jObj["error"] = newJString(error.msg) + return jObj + json.`%`(body) + + proc setProperty*(r: var JsonRecord, key: string, res: ?!T) = + var it {.inject, used.}: T + setProperty(r, key, res.formatJsonResult) + + proc setProperty*(r: var JsonRecord, key: string, opt: ?T) = + var it {.inject, used.}: T + let v = opt.formatJsonOption + setProperty(r, key, v) + + proc setProperty*(r: var JsonRecord, key: string, opts: seq[?T]) = + var it {.inject, used.}: T + let v = opts.map(opt => opt.formatJsonOption) + setProperty(r, key, json.`%`(v)) + + proc setProperty*( + r: var JsonRecord, key: string, val: seq[T] + ) {.raises: [ValueError, IOError].} = + var it {.inject, used.}: T + let v = val.map(it => body) + setProperty(r, key, json.`%`(v)) + + proc setProperty*( + r: var JsonRecord, key: string, val: T + ) {.raises: [ValueError, IOError].} = + var it {.inject, used.}: T = val + let v = body + setProperty(r, key, json.`%`(v)) + + elif format == LogFormat.textLines: + proc formatTextLineOption*(val: ?T): string = + var v = "none(" & $T & ")" + if it =? val: + v = "some(" & $(body) & ")" # that I used to know :) + v + + proc formatTextLineResult*(val: ?!T): string = + without it =? val, error: + return "Error: " & error.msg + $(body) + + proc setProperty*(r: var TextLineRecord, key: string, res: ?!T) = + var it {.inject, used.}: T + setProperty(r, key, res.formatTextLineResult) + + proc setProperty*(r: var TextLineRecord, key: string, opt: ?T) = + var it {.inject, used.}: T + let v = opt.formatTextLineOption + setProperty(r, key, v) + + proc setProperty*(r: var TextLineRecord, key: string, opts: seq[?T]) = + var it {.inject, used.}: T + let v = opts.map(opt => opt.formatTextLineOption) + setProperty(r, key, v.formatTextLineSeq) + + proc setProperty*( + r: var TextLineRecord, key: string, val: seq[T] + ) {.raises: [ValueError, IOError].} = + var it {.inject, used.}: T + let v = val.map(it => body) + setProperty(r, key, v.formatTextLineSeq) + + proc setProperty*( + r: var TextLineRecord, key: string, val: T + ) {.raises: [ValueError, IOError].} = + var it {.inject, used.}: T = val + let v = body + setProperty(r, key, v) + +template formatIt*(T: type, body: untyped) {.dirty.} = + formatIt(LogFormat.textLines, T): + body + formatIt(LogFormat.json, T): + body + +formatIt(LogFormat.textLines, Cid): + shortLog($it) +formatIt(LogFormat.json, Cid): + $it +formatIt(UInt256): + $it +formatIt(MultiAddress): + $it +formatIt(LogFormat.textLines, array[32, byte]): + it.short0xHexLog +formatIt(LogFormat.json, array[32, byte]): + it.to0xHex diff --git a/codexcrawler/services/marketplace/marketplace.nim b/codexcrawler/services/marketplace/marketplace.nim new file mode 100644 index 0000000..1f0f90d --- /dev/null +++ b/codexcrawler/services/marketplace/marketplace.nim @@ -0,0 +1,184 @@ +import pkg/ethers +import pkg/ethers/erc20 +import pkg/json_rpc/rpcclient +import pkg/stint +import pkg/chronos +import ./requests +import ./proofs +import ./config + +export stint +export ethers except `%`, `%*`, toJson +export erc20 except `%`, `%*`, toJson +export config +export requests + +type + Marketplace* = ref object of Contract + + Marketplace_RepairRewardPercentageTooHigh* = object of SolidityError + Marketplace_SlashPercentageTooHigh* = object of SolidityError + Marketplace_MaximumSlashingTooHigh* = object of SolidityError + Marketplace_InvalidExpiry* = object of SolidityError + Marketplace_InvalidMaxSlotLoss* = object of SolidityError + Marketplace_InsufficientSlots* = object of SolidityError + Marketplace_InvalidClientAddress* = object of SolidityError + Marketplace_RequestAlreadyExists* = object of SolidityError + Marketplace_InvalidSlot* = object of SolidityError + Marketplace_SlotNotFree* = object of SolidityError + Marketplace_InvalidSlotHost* = object of SolidityError + Marketplace_AlreadyPaid* = object of SolidityError + Marketplace_TransferFailed* = object of SolidityError + Marketplace_UnknownRequest* = object of SolidityError + Marketplace_InvalidState* = object of SolidityError + Marketplace_StartNotBeforeExpiry* = object of SolidityError + Marketplace_SlotNotAcceptingProofs* = object of SolidityError + Marketplace_SlotIsFree* = object of SolidityError + Marketplace_ReservationRequired* = object of SolidityError + Marketplace_NothingToWithdraw* = object of SolidityError + Marketplace_InsufficientDuration* = object of SolidityError + Marketplace_InsufficientProofProbability* = object of SolidityError + Marketplace_InsufficientCollateral* = object of SolidityError + Marketplace_InsufficientReward* = object of SolidityError + Marketplace_InvalidCid* = object of SolidityError + Marketplace_DurationExceedsLimit* = object of SolidityError + Proofs_InsufficientBlockHeight* = object of SolidityError + Proofs_InvalidProof* = object of SolidityError + Proofs_ProofAlreadySubmitted* = object of SolidityError + Proofs_PeriodNotEnded* = object of SolidityError + Proofs_ValidationTimedOut* = object of SolidityError + Proofs_ProofNotMissing* = object of SolidityError + Proofs_ProofNotRequired* = object of SolidityError + Proofs_ProofAlreadyMarkedMissing* = object of SolidityError + Proofs_InvalidProbability* = object of SolidityError + Periods_InvalidSecondsPerPeriod* = object of SolidityError + +proc configuration*(marketplace: Marketplace): MarketplaceConfig {.contract, view.} +proc token*(marketplace: Marketplace): Address {.contract, view.} +proc currentCollateral*( + marketplace: Marketplace, id: SlotId +): UInt256 {.contract, view.} + +proc requestStorage*( + marketplace: Marketplace, request: StorageRequest +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidClientAddress, Marketplace_RequestAlreadyExists, + Marketplace_InvalidExpiry, Marketplace_InsufficientSlots, + Marketplace_InvalidMaxSlotLoss, + ] +.} + +proc fillSlot*( + marketplace: Marketplace, requestId: RequestId, slotIndex: uint64, proof: Groth16Proof +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidSlot, Marketplace_ReservationRequired, Marketplace_SlotNotFree, + Marketplace_StartNotBeforeExpiry, Marketplace_UnknownRequest, + ] +.} + +proc withdrawFunds*( + marketplace: Marketplace, requestId: RequestId +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidClientAddress, Marketplace_InvalidState, + Marketplace_NothingToWithdraw, Marketplace_UnknownRequest, + ] +.} + +proc withdrawFunds*( + marketplace: Marketplace, requestId: RequestId, withdrawAddress: Address +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidClientAddress, Marketplace_InvalidState, + Marketplace_NothingToWithdraw, Marketplace_UnknownRequest, + ] +.} + +proc freeSlot*( + marketplace: Marketplace, id: SlotId +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidSlotHost, Marketplace_AlreadyPaid, + Marketplace_StartNotBeforeExpiry, Marketplace_UnknownRequest, Marketplace_SlotIsFree, + ] +.} + +proc freeSlot*( + marketplace: Marketplace, + id: SlotId, + rewardRecipient: Address, + collateralRecipient: Address, +): Confirmable {. + contract, + errors: [ + Marketplace_InvalidSlotHost, Marketplace_AlreadyPaid, + Marketplace_StartNotBeforeExpiry, Marketplace_UnknownRequest, Marketplace_SlotIsFree, + ] +.} + +proc getRequest*( + marketplace: Marketplace, id: RequestId +): StorageRequest {.contract, view, errors: [Marketplace_UnknownRequest].} + +proc getHost*(marketplace: Marketplace, id: SlotId): Address {.contract, view.} +proc getActiveSlot*( + marketplace: Marketplace, id: SlotId +): Slot {.contract, view, errors: [Marketplace_SlotIsFree].} + +proc myRequests*(marketplace: Marketplace): seq[RequestId] {.contract, view.} +proc mySlots*(marketplace: Marketplace): seq[SlotId] {.contract, view.} +proc requestState*( + marketplace: Marketplace, requestId: RequestId +): RequestState {.contract, view, errors: [Marketplace_UnknownRequest].} + +proc slotState*(marketplace: Marketplace, slotId: SlotId): SlotState {.contract, view.} +proc requestEnd*( + marketplace: Marketplace, requestId: RequestId +): int64 {.contract, view.} + +proc requestExpiry*( + marketplace: Marketplace, requestId: RequestId +): int64 {.contract, view.} + +proc missingProofs*(marketplace: Marketplace, id: SlotId): UInt256 {.contract, view.} +proc isProofRequired*(marketplace: Marketplace, id: SlotId): bool {.contract, view.} +proc willProofBeRequired*(marketplace: Marketplace, id: SlotId): bool {.contract, view.} +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 +): Confirmable {. + contract, + errors: + [Proofs_ProofAlreadySubmitted, Proofs_InvalidProof, Marketplace_UnknownRequest] +.} + +proc markProofAsMissing*( + marketplace: Marketplace, id: SlotId, period: uint64 +): Confirmable {. + contract, + errors: [ + Marketplace_SlotNotAcceptingProofs, Marketplace_StartNotBeforeExpiry, + Proofs_PeriodNotEnded, Proofs_ValidationTimedOut, Proofs_ProofNotMissing, + Proofs_ProofNotRequired, Proofs_ProofAlreadyMarkedMissing, + ] +.} + +proc reserveSlot*( + marketplace: Marketplace, requestId: RequestId, slotIndex: uint64 +): Confirmable {.contract.} + +proc canReserveSlot*( + marketplace: Marketplace, requestId: RequestId, slotIndex: uint64 +): bool {.contract, view.} diff --git a/codexcrawler/services/marketplace/proofs.nim b/codexcrawler/services/marketplace/proofs.nim new file mode 100644 index 0000000..771d685 --- /dev/null +++ b/codexcrawler/services/marketplace/proofs.nim @@ -0,0 +1,46 @@ +import pkg/stint +import pkg/contractabi +import pkg/ethers/fields + +type + Groth16Proof* = object + a*: G1Point + b*: G2Point + c*: G1Point + + G1Point* = object + x*: UInt256 + y*: UInt256 + + # A field element F_{p^2} encoded as `real + i * imag` + Fp2Element* = object + real*: UInt256 + imag*: UInt256 + + G2Point* = object + x*: Fp2Element + y*: Fp2Element + +func solidityType*(_: type G1Point): string = + solidityType(G1Point.fieldTypes) + +func solidityType*(_: type Fp2Element): string = + solidityType(Fp2Element.fieldTypes) + +func solidityType*(_: type G2Point): string = + solidityType(G2Point.fieldTypes) + +func solidityType*(_: type Groth16Proof): string = + solidityType(Groth16Proof.fieldTypes) + +func encode*(encoder: var AbiEncoder, point: G1Point) = + encoder.write(point.fieldValues) + +func encode*(encoder: var AbiEncoder, element: Fp2Element) = + encoder.write(element.fieldValues) + +func encode*(encoder: var AbiEncoder, point: G2Point) = + encoder.write(point.fieldValues) + +func encode*(encoder: var AbiEncoder, proof: Groth16Proof) = + encoder.write(proof.fieldValues) diff --git a/codexcrawler/services/marketplace/readme.md b/codexcrawler/services/marketplace/readme.md new file mode 100644 index 0000000..fd2b28a --- /dev/null +++ b/codexcrawler/services/marketplace/readme.md @@ -0,0 +1,2 @@ +These are copied from nim-codex v0.2.0. +There are plans to extract/refactor the contract interoperability code from nim-codex into its own submodule. But this isn't prioritized atm. So we're copying it here until that's been handled. diff --git a/codexcrawler/services/marketplace/requests.nim b/codexcrawler/services/marketplace/requests.nim new file mode 100644 index 0000000..a6e4281 --- /dev/null +++ b/codexcrawler/services/marketplace/requests.nim @@ -0,0 +1,220 @@ +import std/hashes +import std/sequtils +import std/typetraits +import pkg/contractabi +import pkg/nimcrypto +import pkg/ethers/fields +import pkg/results +import pkg/questionable/results +import pkg/stew/byteutils +import pkg/libp2p/[cid, multicodec] +import pkg/serde/json +import ./logutils + +export contractabi + +type + StorageRequest* = object + client* {.serialize.}: Address + ask* {.serialize.}: StorageAsk + content* {.serialize.}: StorageContent + expiry* {.serialize.}: uint64 + nonce*: Nonce + + StorageAsk* = object + proofProbability* {.serialize.}: UInt256 + pricePerBytePerSecond* {.serialize.}: UInt256 + collateralPerByte* {.serialize.}: UInt256 + slots* {.serialize.}: uint64 + slotSize* {.serialize.}: uint64 + duration* {.serialize.}: uint64 + maxSlotLoss* {.serialize.}: uint64 + + StorageContent* = object + cid* {.serialize.}: Cid + merkleRoot*: array[32, byte] + + Slot* = object + request* {.serialize.}: StorageRequest + slotIndex* {.serialize.}: uint64 + + SlotId* = distinct array[32, byte] + RequestId* = distinct array[32, byte] + Nonce* = distinct array[32, byte] + RequestState* {.pure.} = enum + New + Started + Cancelled + Finished + Failed + + SlotState* {.pure.} = enum + Free + Filled + Finished + Failed + Paid + Cancelled + Repair + +template mapFailure*[T, V, E]( + exp: Result[T, V], exc: typedesc[E] +): Result[T, ref CatchableError] = + ## Convert `Result[T, E]` to `Result[E, ref CatchableError]` + ## + + exp.mapErr( + proc(e: V): ref CatchableError = + (ref exc)(msg: $e) + ) + +template mapFailure*[T, V](exp: Result[T, V]): Result[T, ref CatchableError] = + mapFailure(exp, CatchableError) + +proc `==`*(x, y: Nonce): bool {.borrow.} +proc `==`*(x, y: RequestId): bool {.borrow.} +proc `==`*(x, y: SlotId): bool {.borrow.} +proc hash*(x: SlotId): Hash {.borrow.} +proc hash*(x: Nonce): Hash {.borrow.} +proc hash*(x: Address): Hash {.borrow.} + +func toArray*(id: RequestId | SlotId | Nonce): array[32, byte] = + array[32, byte](id) + +proc `$`*(id: RequestId | SlotId | Nonce): string = + id.toArray.toHex + +proc fromHex*(T: type RequestId, hex: string): T = + T array[32, byte].fromHex(hex) + +proc fromHex*(T: type SlotId, hex: string): T = + T array[32, byte].fromHex(hex) + +proc fromHex*(T: type Nonce, hex: string): T = + T array[32, byte].fromHex(hex) + +proc fromHex*[T: distinct](_: type T, hex: string): T = + type baseType = T.distinctBase + T baseType.fromHex(hex) + +proc toHex*[T: distinct](id: T): string = + type baseType = T.distinctBase + baseType(id).toHex + +logutils.formatIt(LogFormat.textLines, Nonce): + it.short0xHexLog +logutils.formatIt(LogFormat.textLines, RequestId): + it.short0xHexLog +logutils.formatIt(LogFormat.textLines, SlotId): + it.short0xHexLog +logutils.formatIt(LogFormat.json, Nonce): + it.to0xHexLog +logutils.formatIt(LogFormat.json, RequestId): + it.to0xHexLog +logutils.formatIt(LogFormat.json, SlotId): + it.to0xHexLog + +func fromTuple(_: type StorageRequest, tupl: tuple): StorageRequest = + StorageRequest( + client: tupl[0], ask: tupl[1], content: tupl[2], expiry: tupl[3], nonce: tupl[4] + ) + +func fromTuple(_: type Slot, tupl: tuple): Slot = + Slot(request: tupl[0], slotIndex: tupl[1]) + +func fromTuple(_: type StorageAsk, tupl: tuple): StorageAsk = + StorageAsk( + proofProbability: tupl[0], + pricePerBytePerSecond: tupl[1], + collateralPerByte: tupl[2], + slots: tupl[3], + slotSize: tupl[4], + duration: tupl[5], + maxSlotLoss: tupl[6], + ) + +func fromTuple(_: type StorageContent, tupl: tuple): StorageContent = + StorageContent(cid: tupl[0], merkleRoot: tupl[1]) + +func solidityType*(_: type Cid): string = + solidityType(seq[byte]) + +func solidityType*(_: type StorageContent): string = + solidityType(StorageContent.fieldTypes) + +func solidityType*(_: type StorageAsk): string = + solidityType(StorageAsk.fieldTypes) + +func solidityType*(_: type StorageRequest): string = + solidityType(StorageRequest.fieldTypes) + +# Note: it seems to be ok to ignore the vbuffer offset for now +func encode*(encoder: var AbiEncoder, cid: Cid) = + encoder.write(cid.data.buffer) + +func encode*(encoder: var AbiEncoder, content: StorageContent) = + encoder.write(content.fieldValues) + +func encode*(encoder: var AbiEncoder, ask: StorageAsk) = + encoder.write(ask.fieldValues) + +func encode*(encoder: var AbiEncoder, id: RequestId | SlotId | Nonce) = + encoder.write(id.toArray) + +func encode*(encoder: var AbiEncoder, request: StorageRequest) = + encoder.write(request.fieldValues) + +func encode*(encoder: var AbiEncoder, slot: Slot) = + encoder.write(slot.fieldValues) + +func decode*(decoder: var AbiDecoder, T: type Cid): ?!T = + let data = ?decoder.read(seq[byte]) + Cid.init(data).mapFailure + +func decode*(decoder: var AbiDecoder, T: type StorageContent): ?!T = + let tupl = ?decoder.read(StorageContent.fieldTypes) + success StorageContent.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type StorageAsk): ?!T = + let tupl = ?decoder.read(StorageAsk.fieldTypes) + success StorageAsk.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type StorageRequest): ?!T = + let tupl = ?decoder.read(StorageRequest.fieldTypes) + success StorageRequest.fromTuple(tupl) + +func decode*(decoder: var AbiDecoder, T: type Slot): ?!T = + let tupl = ?decoder.read(Slot.fieldTypes) + success Slot.fromTuple(tupl) + +func id*(request: StorageRequest): RequestId = + let encoding = AbiEncoder.encode((request,)) + RequestId(keccak256.digest(encoding).data) + +func slotId*(requestId: RequestId, slotIndex: uint64): SlotId = + let encoding = AbiEncoder.encode((requestId, slotIndex)) + SlotId(keccak256.digest(encoding).data) + +func slotId*(request: StorageRequest, slotIndex: uint64): SlotId = + slotId(request.id, slotIndex) + +func id*(slot: Slot): SlotId = + slotId(slot.request, slot.slotIndex) + +func pricePerSlotPerSecond*(ask: StorageAsk): UInt256 = + ask.pricePerBytePerSecond * ask.slotSize.u256 + +func pricePerSlot*(ask: StorageAsk): UInt256 = + ask.duration.u256 * ask.pricePerSlotPerSecond + +func totalPrice*(ask: StorageAsk): UInt256 = + ask.slots.u256 * ask.pricePerSlot + +func totalPrice*(request: StorageRequest): UInt256 = + request.ask.totalPrice + +func collateralPerSlot*(ask: StorageAsk): UInt256 = + ask.collateralPerByte * ask.slotSize.u256 + +func size*(ask: StorageAsk): uint64 = + ask.slots * ask.slotSize