[marketplace] Add erasure coding and PoR attributes to request

This commit is contained in:
Mark Spanbroek 2022-04-06 14:26:56 +02:00 committed by markspanbroek
parent 29b5775951
commit b8ce6c3682
6 changed files with 108 additions and 57 deletions

View File

@ -31,11 +31,11 @@ contract Marketplace is Collateral {
_createLock(id, request.expiry);
funds.received += request.maxPrice;
funds.balance += request.maxPrice;
transferFrom(msg.sender, request.maxPrice);
funds.received += request.ask.maxPrice;
funds.balance += request.ask.maxPrice;
transferFrom(msg.sender, request.ask.maxPrice);
emit StorageRequested(id, request);
emit StorageRequested(id, request.ask);
}
function offerStorage(Offer calldata offer) public marketplaceInvariant {
@ -46,7 +46,7 @@ contract Marketplace is Collateral {
require(request.client != address(0), "Unknown request");
require(request.expiry > block.timestamp, "Request expired");
require(offer.price <= request.maxPrice, "Price too high");
require(offer.price <= request.ask.maxPrice, "Price too high");
bytes32 id = keccak256(abi.encode(offer));
require(offers[id].host == address(0), "Offer already exists");
@ -75,7 +75,7 @@ contract Marketplace is Collateral {
_lock(offer.host, id);
_unlock(offer.requestId);
uint256 difference = request.maxPrice - offer.price;
uint256 difference = request.ask.maxPrice - offer.price;
funds.sent += difference;
funds.balance -= difference;
token.transfer(request.client, difference);
@ -95,15 +95,39 @@ contract Marketplace is Collateral {
return requestState[requestId].selectedOffer;
}
uint256 private constant POR_SECTORS = 10; // amount of sectors in PoR scheme
struct Request {
address client;
uint256 duration;
uint256 size;
bytes32 contentHash;
uint256 proofProbability;
uint256 maxPrice;
uint256 expiry;
bytes32 nonce;
Ask ask;
Content content;
uint256 expiry; // time at which this request expires
bytes32 nonce; // random nonce to differentiate between similar requests
}
struct Ask {
uint256 size; // size of requested storage in number of bytes
uint256 duration; // how long content should be stored (in seconds)
uint256 proofProbability; // how often storage proofs are required
uint256 maxPrice; // maximum price client will pay (in number of tokens)
}
struct Content {
string cid; // content id (if part of a larger set, the chunk cid)
Erasure erasure; // Erasure coding attributes
PoR por; // Proof of Retrievability parameters
}
struct Erasure {
uint64 totalChunks; // the total number of chunks in the larger data set
uint64 totalNodes; // the total number of nodes that store the data set
uint64 nodeId; // index of this node in the list of total nodes
}
struct PoR {
bytes1[POR_SECTORS * 48] u; // parameters u_1..u_s
bytes1[96] publicKey; // public key
bytes1[512] name; // random name
}
struct RequestState {
@ -117,7 +141,7 @@ contract Marketplace is Collateral {
uint256 expiry;
}
event StorageRequested(bytes32 requestId, Request request);
event StorageRequested(bytes32 requestId, Ask ask);
event StorageOffered(bytes32 offerId, Offer offer, bytes32 indexed requestId);
event OfferSelected(bytes32 offerId, bytes32 indexed requestId);

View File

@ -35,7 +35,7 @@ contract Storage is Collateral, Marketplace, Proofs {
require(_selectedOffer(offer.requestId) == id, "Offer was not selected");
contractState[id] = ContractState.started;
Request storage request = _request(offer.requestId);
_expectProofs(id, request.proofProbability, request.duration);
_expectProofs(id, request.ask.proofProbability, request.ask.duration);
}
function finishContract(bytes32 id) public {

View File

@ -2,7 +2,7 @@ const { ethers } = require("hardhat")
const { expect } = require("chai")
const { exampleRequest, exampleOffer } = require("./examples")
const { now, hours } = require("./time")
const { requestId, offerId, requestToArray, offerToArray } = require("./ids")
const { requestId, offerId, offerToArray, askToArray } = require("./ids")
describe("Marketplace", function () {
const collateral = 100
@ -44,22 +44,22 @@ describe("Marketplace", function () {
})
it("emits event when storage is requested", async function () {
await token.approve(marketplace.address, request.maxPrice)
await token.approve(marketplace.address, request.ask.maxPrice)
await expect(marketplace.requestStorage(request))
.to.emit(marketplace, "StorageRequested")
.withArgs(requestId(request), requestToArray(request))
.withArgs(requestId(request), askToArray(request.ask))
})
it("rejects request with invalid client address", async function () {
let invalid = { ...request, client: host.address }
await token.approve(marketplace.address, invalid.maxPrice)
await token.approve(marketplace.address, invalid.ask.maxPrice)
await expect(marketplace.requestStorage(invalid)).to.be.revertedWith(
"Invalid client address"
)
})
it("rejects request with insufficient payment", async function () {
let insufficient = request.maxPrice - 1
let insufficient = request.ask.maxPrice - 1
await token.approve(marketplace.address, insufficient)
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
"ERC20: insufficient allowance"
@ -67,7 +67,7 @@ describe("Marketplace", function () {
})
it("rejects resubmission of request", async function () {
await token.approve(marketplace.address, request.maxPrice * 2)
await token.approve(marketplace.address, request.ask.maxPrice * 2)
await marketplace.requestStorage(request)
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
"Request already exists"
@ -78,7 +78,7 @@ describe("Marketplace", function () {
describe("offering storage", function () {
beforeEach(async function () {
switchAccount(client)
await token.approve(marketplace.address, request.maxPrice)
await token.approve(marketplace.address, request.ask.maxPrice)
await marketplace.requestStorage(request)
switchAccount(host)
await token.approve(marketplace.address, collateral)
@ -114,7 +114,7 @@ describe("Marketplace", function () {
it("rejects offer for expired request", async function () {
switchAccount(client)
let expired = { ...request, expiry: now() - hours(1) }
await token.approve(marketplace.address, request.maxPrice)
await token.approve(marketplace.address, request.ask.maxPrice)
await marketplace.requestStorage(expired)
switchAccount(host)
let invalid = { ...offer, requestId: requestId(expired) }
@ -124,7 +124,7 @@ describe("Marketplace", function () {
})
it("rejects an offer that exceeds the maximum price", async function () {
let invalid = { ...offer, price: request.maxPrice + 1 }
let invalid = { ...offer, price: request.ask.maxPrice + 1 }
await expect(marketplace.offerStorage(invalid)).to.be.revertedWith(
"Price too high"
)
@ -151,7 +151,7 @@ describe("Marketplace", function () {
describe("selecting an offer", async function () {
beforeEach(async function () {
switchAccount(client)
await token.approve(marketplace.address, request.maxPrice)
await token.approve(marketplace.address, request.ask.maxPrice)
await marketplace.requestStorage(request)
for (host of [host1, host2, host3]) {
switchAccount(host)
@ -170,7 +170,7 @@ describe("Marketplace", function () {
})
it("returns price difference to client", async function () {
let difference = request.maxPrice - offer.price
let difference = request.ask.maxPrice - offer.price
let before = await token.balanceOf(client.address)
await marketplace.selectOffer(offerId(offer))
let after = await token.balanceOf(client.address)

View File

@ -40,7 +40,7 @@ describe("Storage", function () {
offer.requestId = requestId(request)
switchAccount(client)
await token.approve(storage.address, request.maxPrice)
await token.approve(storage.address, request.ask.maxPrice)
await storage.requestStorage(request)
switchAccount(host)
await token.approve(storage.address, collateralAmount)

View File

@ -1,14 +1,28 @@
const { ethers } = require("hardhat")
const { now, hours } = require("./time")
const { sha256, hexlify, randomBytes } = ethers.utils
const { hexlify, randomBytes } = ethers.utils
const exampleRequest = () => ({
client: hexlify(randomBytes(20)),
duration: hours(10),
ask: {
size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte
contentHash: sha256("0xdeadbeef"),
duration: hours(10),
proofProbability: 4, // require a proof roughly once every 4 periods
maxPrice: 84,
},
content: {
cid: "zb2rhheVmk3bLks5MgzTqyznLu1zqGH5jrfTA1eAZXrjx7Vob",
erasure: {
totalChunks: 12,
totalNodes: 4,
nodeId: 3,
},
por: {
u: Array.from(randomBytes(480)),
publicKey: Array.from(randomBytes(96)),
name: Array.from(randomBytes(512)),
},
},
expiry: now() + hours(1),
nonce: hexlify(randomBytes(32)),
})

View File

@ -2,21 +2,13 @@ const { ethers } = require("hardhat")
const { keccak256, defaultAbiCoder } = ethers.utils
function requestId(request) {
return keccak256(
defaultAbiCoder.encode(
[
"address",
"uint256",
"uint256",
"bytes32",
"uint256",
"uint256",
"uint256",
"bytes32",
],
requestToArray(request)
)
)
const Ask = "tuple(uint256, uint256, uint256, uint256)"
const Erasure = "tuple(uint64, uint64, uint64)"
const PoR = "tuple(bytes1[480], bytes1[96], bytes1[512])"
const Content = "tuple(string, " + Erasure + ", " + PoR + ")"
const Request =
"tuple(address, " + Ask + ", " + Content + ", uint256, bytes32)"
return keccak256(defaultAbiCoder.encode([Request], requestToArray(request)))
}
function offerId(offer) {
@ -28,16 +20,31 @@ function offerId(offer) {
)
}
function askToArray(ask) {
return [ask.size, ask.duration, ask.proofProbability, ask.maxPrice]
}
function erasureToArray(erasure) {
return [erasure.totalChunks, erasure.totalNodes, erasure.nodeId]
}
function porToArray(por) {
return [por.u, por.publicKey, por.name]
}
function contentToArray(content) {
return [content.cid, erasureToArray(content.erasure), porToArray(content.por)]
}
function requestToArray(request) {
return [
[
request.client,
request.duration,
request.size,
request.contentHash,
request.proofProbability,
request.maxPrice,
askToArray(request.ask),
contentToArray(request.content),
request.expiry,
request.nonce,
],
]
}
@ -45,4 +52,10 @@ function offerToArray(offer) {
return [offer.host, offer.requestId, offer.price, offer.expiry]
}
module.exports = { requestId, offerId, requestToArray, offerToArray }
module.exports = {
requestId,
offerId,
requestToArray,
askToArray,
offerToArray,
}