import { ref, inject } from 'vue' import { defineStore } from 'pinia' import { slotId } from '../utils/ids' import { arrayToObject, requestState } from '@/utils/requests' import { slotState } from '@/utils/slots' class RequestNotFoundError extends Error { constructor(requestId, ...params) { // Pass remaining arguments (including vendor specific ones) to parent constructor super(...params) // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { Error.captureStackTrace(this, RequestNotFoundError) } this.name = 'RequestNotFoundError' // Custom debugging information this.requestId = requestId } } export const useRequestsStore = defineStore( 'requests', () => { // let fetched = false const marketplace = inject('marketplace') const ethProvider = inject('ethProvider') let { StorageRequested, RequestFulfilled, RequestCancelled, RequestFailed, SlotFilled, SlotFreed } = marketplace.filters const requests = ref({}) // key: requestId, val: {request, state, slots: [{slotId, slotIdx, state}]} // const slots = ref(new Map()) // key: slotId, val: {requestId, slotIdx, state} // const blockNumbers = ref(new Set()) // includes blocks that had events // const storageRequestedEvents = ref([]) // {blockNumber, requestId} // const slotFilledEvents = ref([]) // {blockNumber, requestId, slotIdx, slotId} // const slotFreedEvents = ref([]) // {blockNumber, requestId, slotIdx, slotId} // const requestFulfilledEvents = ref([]) // {blockNumber, requestId} // const requestCancelledEvents = ref([]) // {blockNumber, requestId} // const requestFailedEvents = ref([]) // {blockNumber, requestId} // const requestFinishedEvents = ref([]) // {blockNumber, requestId} const loading = ref(false) const fetched = ref(false) // indicates if past events were fetched const blocks = ref({}) // const request = computed(() => count.value * 2) // onStorageRequested => add request to requests ref, along with slots // => add to storageRequestedEvents {blockNumber, requestId} // => add blockNumber to blockNumbers // onRequestFulfilled => update requests[requestId].state with marketplace.getRequestState(requestId) // => add to requestStartedEvents {blockNumber, requestId} // => add blockNumber to blockNumbers // onRequestCancelled => update requests[requestId].state with marketplace.getRequestState(requestId) // => add to requestCancelledEvents {blockNumber, requestId} // => add blockNumber to blockNumbers // onRequestFailed => update requests[requestId].state with marketplace.getRequestState(requestId) // => add to requestFailedEvents {blockNumber, requestId} // => add blockNumber to blockNumbers // onRequestFinished => update requests[requestId].state with marketplace.getRequestState(requestId) // => add to requestFinishedEvents {blockNumber, requestId} // => add blockNumber to blockNumbers // onSlotFilled => update request.slots[slotId].state with getSlotState // => add to slotFilledEvents {blockNumber, slotId, slotIdx} // => add blockNumber to blockNumbers // onSlotFreed => update slots[slotId].state with getSlotState // => add to slotFreedEvents {blockNumber, slotId, slotIdx} // => add blockNumber to blockNumbers const getRequestState = async (requestId) => { let stateIdx = await marketplace.requestState(requestId) return requestState[stateIdx] } const getSlotState = async (slotId) => { let stateIdx = await marketplace.slotState(slotId) return slotState[stateIdx] } const getSlots = async (requestId, numSlots) => { console.log(`fetching ${numSlots} slots`) let start = Date.now() let slots = [] for (let slotIdx = 0; slotIdx < numSlots; slotIdx++) { let id = slotId(requestId, slotIdx) const startSlotState = Date.now() let state = await getSlotState(id) console.log(`fetched slot state in ${(Date.now() - startSlotState) / 1000}s`) const startGetHost = Date.now() let provider = await marketplace.getHost(id) console.log(`fetched slot provider in ${(Date.now() - startGetHost) / 1000}s`) slots.push({ slotId: id, slotIdx, state, provider }) } console.log(`fetched ${numSlots} slots in ${(Date.now() - start) / 1000}s`) return slots // blockNumbers.value.add(blockNumber) } const getBlock = async (blockHash) => { if (Object.keys(blocks.value).includes(blockHash)) { return blocks.value[blockHash] } else { let block = await ethProvider.getBlock(blockHash) blocks.value[blockHash] = block return block } } async function addRequest(requestId, blockHash) { let state = await getRequestState(requestId) let block = await getBlock(blockHash) let reqExisting = requests.value[requestId] || {} // just in case it already exists let request = { ...reqExisting, state, requestedAt: block.timestamp, requestFinishedId: null, detailsFetched: false, moderated: 'pending' } requests.value[requestId] = request return request } async function fetchPastRequests() { // query past events if (fetched.value) { console.log('skipping fetching past requests, already fetched') return } console.log('fetching past requests') loading.value = true try { let events = await marketplace.queryFilter(StorageRequested) console.log('got ', events.length, ' StorageRequested events') events.forEach(async (event, i) => { console.log('getting details for StorageRequested event ', i) let start = Date.now() let { requestId, ask, expiry } = event.args let { blockHash, blockNumber } = event await addRequest(requestId, blockHash) console.log(`got details for ${i} in ${(Date.now() - start) / 1000} seconds`) if (i === events.length - 1) { loading.value = false fetched.value = true } }) if (events.length === 0) { loading.value = false fetched.value = true } } catch (error) { console.error(`failed to load past contract events: ${error.message}`) } } async function fetchRequest(requestId) { let start = Date.now() console.log('fetching request ', requestId) const preFetched = requests.value[requestId] || {} if (preFetched?.detailsFetched) { return } loading.value = true try { let arrRequest = await marketplace.getRequest(requestId) let request = arrayToObject(arrRequest) let slots = await getSlots(requestId, request.ask.slots) const reqExisting = requests.value[requestId] || {} requests.value[requestId] = { ...reqExisting, // state, requestedAt, requestFinishedId (null) ...request, slots, detailsFetched: true } } catch (error) { console.error(`failed to load slots for request ${requestId}: ${error}`) throw error } finally { console.log(`fetched request in ${(Date.now() - start) / 1000}s`) loading.value = false } } function updateRequestState(requestId, newState) { if (!Object.keys(requests.value).includes(requestId)) { throw new RequestNotFoundError(requestId, `Request not found`) } let { state, ...rest } = requests.value[requestId] state = newState requests.value[requestId] = { state, ...rest } } function updateRequestFinishedId(requestId, newRequestFinishedId) { if (!Object.keys(requests.value).includes(requestId)) { throw new RequestNotFoundError(requestId, `Request not found`) } let { requestFinishedId, ...rest } = requests.value[requestId] requestFinishedId = newRequestFinishedId requests.value[requestId] = { requestFinishedId, ...rest } } function updateRequestSlotState(requestId, slotIdx, newState) { if (!Object.keys(requests.value).includes(requestId)) { throw new RequestNotFoundError(requestId, `Request not found`) } let { slots, ...rest } = requests.value[requestId] slots = slots.map((slot) => { if (slot.slotIdx == slotIdx) { slot.state = newState } }) requests.value[requestId] = { slots, ...rest } } function waitForRequestFinished(requestId, duration, onRequestFinished) { return setTimeout(async () => { try { updateRequestState(requestId, 'Finished') updateRequestFinishedId(requestId, null) } catch (error) { if (error instanceof RequestNotFoundError) { await fetchRequest(requestId) } } if (onRequestFinished) { let blockNumber = await ethProvider.getBlockNumber() onRequestFinished(blockNumber, requestId) } }, duration) } function cancelWaitForRequestFinished(requestId) { if (!Object.keys(requests.value).includes(requestId)) { throw new RequestNotFoundError(requestId, `Request not found`) } let { requestFinishedId } = requests.value[requestId] if (requestFinishedId) { clearTimeout(requestFinishedId) } } async function listenForNewEvents( onStorageRequested, onRequestFulfilled, onRequestCancelled, onRequestFailed, onRequestFinished, onSlotFreed, onSlotFilled ) { marketplace.on(StorageRequested, async (requestId, ask, expiry, event) => { let { blockNumber, blockHash } = event.log const request = addRequest(requestId, blockHash) // callback if (onStorageRequested) { onStorageRequested(blockNumber, requestId, request.state) } }) marketplace.on(RequestFulfilled, async (requestId, event) => { let requestOnChain = await marketplace.getRequest(requestId) let ask = requestOnChain[1] let duration = ask[1] // set request state to finished at the end of the request -- there's no // other way to know when a request finishes console.log('request ', requestOnChain) let requestFinishedId = waitForRequestFinished(requestId, duration, onRequestFinished) updateRequestState(requestId, 'Fulfilled') updateRequestFinishedId(requestId, requestFinishedId) let { blockNumber } = event.log if (onRequestFulfilled) { onRequestFulfilled(blockNumber, requestId) } }) marketplace.on(RequestCancelled, async (requestId, event) => { try { updateRequestState(requestId, 'Cancelled') } catch (error) { if (error instanceof RequestNotFoundError) { await fetchRequest(requestId) } } let { blockNumber } = event.log if (onRequestCancelled) { onRequestCancelled(blockNumber, requestId) } }) marketplace.on(RequestFailed, async (requestId, event) => { try { updateRequestState(requestId, 'Failed') cancelWaitForRequestFinished(requestId) } catch (error) { if (error instanceof RequestNotFoundError) { await fetchRequest(requestId) } } let { blockNumber } = event.log if (onRequestFailed) { onRequestFailed(blockNumber, requestId) } }) marketplace.on(SlotFreed, async (requestId, slotIdx, event) => { try { updateRequestSlotState(requestId, slotIdx, 'Freed') } catch (error) { if (error instanceof RequestNotFoundError) { await fetchRequest(requestId) } } let { blockNumber } = event.log if (onSlotFreed) { onSlotFreed(blockNumber, requestId, slotIdx) } }) marketplace.on(SlotFilled, async (requestId, slotIdx, event) => { try { updateRequestSlotState(requestId, slotIdx, 'Filled') } catch (error) { if (error instanceof RequestNotFoundError) { await fetchRequest(requestId) } } let { blockNumber } = event.log if (onSlotFilled) { onSlotFilled(blockNumber, requestId, slotIdx) } }) } return { requests, // slots, // blockNumbers, // storageRequestedEvents, // slotFilledEvents, // slotFreedEvents, // requestStartedEvents, // requestCancelledEvents, // requestFailedEvents, // requestFinishedEvents, fetchPastRequests, fetchRequest, listenForNewEvents, loading, fetched } }, { persist: { serializer: { serialize: (state) => { return JSON.stringify(state, (_, v) => (typeof v === 'bigint' ? v.toString() : v)) }, deserialize: (serialized) => { // TODO: deserialize bigints properly return JSON.parse(serialized) } } } } )