Move events to their own store for notifications centre

This commit is contained in:
Eric 2024-07-02 16:22:08 +10:00
parent 4f75651962
commit 31b4bd95a1
No known key found for this signature in database
3 changed files with 270 additions and 187 deletions

175
src/stores/events.js Normal file
View File

@ -0,0 +1,175 @@
import { ref, inject } from 'vue'
import { defineStore } from 'pinia'
import { slotId } from '@/utils/ids'
import { RequestState } from '@/utils/requests'
import { SlotState } from '@/utils/slots'
import { StorageEvent } from '@/utils/events'
import { useRequestsStore } from './requests'
import serializer from './serializer'
export const useEventsStore = defineStore(
'events',
() => {
// let fetched = false
const marketplace = inject('marketplace')
const requests = useRequestsStore()
let {
StorageRequested,
RequestFulfilled,
RequestCancelled,
RequestFailed,
SlotFilled,
SlotFreed
} = marketplace.filters
const events = ref([]) // {event: 'SlotFreed',blockNumber,requestId,slotIdx,state: 'Free'}
// const subscriptions = 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
function add({ event, blockNumber, requestId, slotIdx, state }) {
events.value.push({ event, blockNumber, requestId, slotIdx, state })
}
async function listenForNewEvents() {
async function onStorageRequested(requestId, ask, expiry, event) {
let { blockNumber, blockHash } = event.log
const request = await requests.add(requestId, ask, expiry, blockHash)
add({
event: StorageEvent.StorageRequested,
blockNumber,
requestId,
state: RequestState.New
})
}
async function onRequestFulfilled(requestId, event) {
let state = RequestState.Fulfilled
try {
requests.updateRequestState(requestId, state)
} catch (error) {
if (error instanceof requests.RequestNotFoundError) {
await requests.fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
add({ event: StorageEvent.RequestFulfilled, blockNumber, requestId, state })
}
async function onRequestCancelled(requestId, event) {
let state = RequestState.Cancelled
try {
requests.updateRequestState(requestId, state)
requests.cancelWaitForRequestFinished(requestId)
} catch (error) {
if (error instanceof requests.RequestNotFoundError) {
await requests.fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
add({ event: StorageEvent.RequestCancelled, blockNumber, requestId, state })
}
async function onRequestFailed(requestId, event) {
let state = RequestState.Failed
try {
requests.updateRequestState(requestId, state)
requests.cancelWaitForRequestFinished(requestId)
} catch (error) {
if (error instanceof requests.RequestNotFoundError) {
await requests.fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
add({ event: StorageEvent.RequestFailed, blockNumber, requestId, state })
}
async function onSlotFreed(requestId, slotIdx, event) {
let state = SlotState.Free
try {
requests.updateRequestSlotState(requestId, slotIdx, state)
requests.updateRequestSlotProvider(requestId, slotIdx, null)
} catch (error) {
if (error instanceof requests.RequestNotFoundError) {
await requests.fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
add({ event: StorageEvent.SlotFreed, blockNumber, requestId, slotIdx, state })
}
async function onSlotFilled(requestId, slotIdx, event) {
let state = SlotState.Filled
try {
requests.updateRequestSlotState(requestId, slotIdx, state)
let id = slotId(requestId, slotIdx)
let provider = await marketplace.getHost(id)
requests.updateRequestSlotProvider(requestId, slotIdx, provider)
} catch (error) {
if (error instanceof requests.RequestNotFoundError) {
await requests.fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
add({ event: StorageEvent.SlotFilled, blockNumber, requestId, slotIdx, state })
}
await marketplace.removeAllListeners(StorageRequested)
await marketplace.on(StorageRequested, onStorageRequested)
// subscriptions.value[StorageEvent.StorageRequested] = { subscribed: true }
await marketplace.removeAllListeners(RequestFulfilled)
await marketplace.on(RequestFulfilled, onRequestFulfilled)
await marketplace.removeAllListeners(RequestCancelled)
await marketplace.on(RequestCancelled, onRequestCancelled)
await marketplace.removeAllListeners(RequestFailed)
await marketplace.on(RequestFailed, onRequestFailed)
await marketplace.removeAllListeners(SlotFreed)
await marketplace.on(SlotFreed, onSlotFreed)
await marketplace.removeAllListeners(SlotFilled)
await marketplace.on(SlotFilled, onSlotFilled)
}
return {
events,
listenForNewEvents,
add
}
},
{
persist: {
serializer,
paths: ['events']
}
}
)

View File

@ -1,9 +1,11 @@
import { ref, inject } from 'vue'
import { defineStore } from 'pinia'
import { slotId } from '../utils/ids'
import { arrayToObject, requestState, timestampsFor } from '@/utils/requests'
import { slotState } from '@/utils/slots'
import { arrayToObject, toRequestState, timestampsFor } from '@/utils/requests'
import { toSlotState } from '@/utils/slots'
import { RequestState } from '@/utils/requests'
import serializer from './serializer'
import { useEventsStore } from './events'
class RequestNotFoundError extends Error {
constructor(requestId, ...params) {
@ -24,64 +26,25 @@ class RequestNotFoundError extends Error {
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 events = useEventsStore()
let { StorageRequested } = 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 loadingRecent = ref(false)
const loadingRequestStates = 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]
return toRequestState(Number(stateIdx))
}
const getSlotState = async (slotId) => {
let stateIdx = await marketplace.slotState(slotId)
return slotState[stateIdx]
return toSlotState(Number(stateIdx))
}
const getSlots = async (requestId, numSlots) => {
@ -117,31 +80,12 @@ export const useRequestsStore = defineStore(
}
}
async function addRequest(requestId, ask, expiry, blockHash) {
async function add(requestId, ask, expiry, blockHash) {
let state = await getRequestState(requestId)
let { timestamp } = await getBlock(blockHash)
let requestFinishedId = waitForRequestFinished(requestId, ask, expiry, state, timestamp)
let reqExisting = requests.value[requestId] || {} // just in case it already exists
let requestFinishedId = null
if (['Fulfilled', 'New'].includes(state)) {
try {
// set request state to finished at the end of the request -- there's no
// other way to know when a request finishes
let { endsAt } = timestampsFor(ask, expiry, timestamp)
let msFromNow = endsAt * 1000 - Date.now() // time remaining until finish, in ms
requestFinishedId = waitForRequestFinished(
requestId,
msFromNow,
onRequestFinishedCallback
)
} catch (e) {
console.error(
`Failed to set timeout for RequestFinished event for request ${requestId}. There will be no alert for this event.`,
e
)
}
}
let request = {
...reqExisting,
state,
@ -154,12 +98,6 @@ export const useRequestsStore = defineStore(
return request
}
async function handleStorageRequestEvent(event) {
let { requestId, ask, expiry } = event.args
let { blockHash, blockNumber } = event
await addRequest(requestId, ask, expiry, blockHash)
}
// Returns an array of Promises, where each Promise represents the fetching
// of one StorageRequested event
async function fetchPastRequestsFrom(fromBlock = null) {
@ -171,7 +109,11 @@ export const useRequestsStore = defineStore(
return []
}
return events.map((event, i) => handleStorageRequestEvent(event)) //{
return events.map(async (event, i) => {
let { requestId, ask, expiry } = event.args
let { blockHash, blockNumber } = event
await add(requestId, ask, expiry, blockHash)
})
} catch (error) {
console.error(`failed to load past contract events: ${error.message}`)
return []
@ -181,6 +123,17 @@ export const useRequestsStore = defineStore(
async function refetchRequestStates() {
async function refetchRequestState(requestId) {
requests.value[requestId].state = await getRequestState(requestId)
let { ask, expiry, state, requestedAt } = requests.value[requestId]
// refetching of requests states happen on page load, so if we're
// loading the page, we need to reset any timeouts for RequestFinished
// events
requests.value[requestId].requestFinishedId = waitForRequestFinished(
requestId,
ask,
expiry,
state,
requestedAt
)
}
// array of asynchronously-executed Promises, each requesting a request
// state
@ -294,7 +247,29 @@ export const useRequestsStore = defineStore(
requests.value[requestId] = { slots, ...rest }
}
function waitForRequestFinished(requestId, msFromNow, onRequestFinished) {
function updateRequestSlotProvider(requestId, slotIdx, provider) {
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.provider = provider
}
return slot
})
requests.value[requestId] = { slots, ...rest }
}
function waitForRequestFinished(requestId, ask, expiry, state, requestedAt) {
if (!['Fulfilled', 'New'].includes(state)) {
return null
}
// set request state to finished at the end of the request -- there's no
// other way to know when a request finishes
let { endsAt } = timestampsFor(ask, expiry, requestedAt)
let msFromNow = endsAt * 1000 - Date.now() // time remaining until finish, in ms
return setTimeout(async () => {
try {
// the state may actually have been cancelled, but RequestCancelled
@ -305,13 +280,18 @@ export const useRequestsStore = defineStore(
} catch (error) {
if (error instanceof RequestNotFoundError) {
await fetchRequestDetails(requestId)
} else {
throw error
}
}
if (onRequestFinished) {
let blockNumber = await ethProvider.getBlockNumber()
onRequestFinished(blockNumber, requestId)
}
}, msFromNow)
let blockNumber = await ethProvider.getBlockNumber()
events.add({
event: 'RequestFinished',
blockNumber,
requestId,
state: RequestState.Finished
})
}, msFromNow + 1000) // add additional second to ensure state has changed
}
function cancelWaitForRequestFinished(requestId) {
@ -324,127 +304,29 @@ export const useRequestsStore = defineStore(
}
}
let onRequestFinishedCallback
async function listenForNewEvents(
onStorageRequested,
onRequestFulfilled,
onRequestCancelled,
onRequestFailed,
onRequestFinished,
onSlotFreed,
onSlotFilled
) {
onRequestFinishedCallback = onRequestFinished
marketplace.on(StorageRequested, async (requestId, ask, expiry, event) => {
let { blockNumber, blockHash } = event.log
const request = await addRequest(requestId, ask, expiry, blockHash)
// callback
if (onStorageRequested) {
onStorageRequested(blockNumber, requestId, request.state)
}
})
marketplace.on(RequestFulfilled, async (requestId, event) => {
try {
updateRequestState(requestId, 'Fulfilled')
} catch (error) {
if (error instanceof RequestNotFoundError) {
await fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
if (onRequestFulfilled) {
onRequestFulfilled(blockNumber, requestId)
}
})
marketplace.on(RequestCancelled, async (requestId, event) => {
try {
updateRequestState(requestId, 'Cancelled')
cancelWaitForRequestFinished(requestId)
} catch (error) {
if (error instanceof RequestNotFoundError) {
await fetchRequestDetails(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 fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
if (onRequestFailed) {
onRequestFailed(blockNumber, requestId)
}
})
marketplace.on(SlotFreed, async (requestId, slotIdx, event) => {
try {
updateRequestSlotState(requestId, slotIdx, 'Free')
} catch (error) {
if (error instanceof RequestNotFoundError) {
await fetchRequestDetails(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 fetchRequestDetails(requestId)
}
}
let { blockNumber } = event.log
if (onSlotFilled) {
onSlotFilled(blockNumber, requestId, slotIdx)
}
})
}
return {
requests,
blocks,
// slots,
// blockNumbers,
// storageRequestedEvents,
// slotFilledEvents,
// slotFreedEvents,
// requestStartedEvents,
// requestCancelledEvents,
// requestFailedEvents,
// requestFinishedEvents,
add,
fetchPastRequests,
refetchRequestStates,
fetchRequestDetails,
listenForNewEvents,
updateRequestState,
updateRequestSlotState,
updateRequestSlotProvider,
updateRequestFinishedId,
cancelWaitForRequestFinished,
loading,
loadingRecent,
loadingRequestStates,
fetched
fetched,
RequestNotFoundError
}
},
{
persist: {
serializer
serializer,
paths: ['requests', 'blocks', 'fetched', 'loading', 'loadingRecent']
}
}
)

26
src/utils/events.js Normal file
View File

@ -0,0 +1,26 @@
export const StorageEvent = {
StorageRequested: 'StorageRequested', // storage was requested
RequestFulfilled: 'RequestFulfilled', // request has started (all slots filled)
RequestCancelled: 'RequestCancelled', // request was cancelled when not all slots were filled by the expiry
RequestFailed: 'RequestFailed', // the request has failed (too many slots freed)
SlotFilled: 'SlotFilled', // slot was filled by provider (providing proof and collateral)
SlotFreed: 'SlotFreed' // slot freed after provider missed too many proofs
}
export function toSlotState(idx) {
return Object.values(SlotState).at(idx)
}
export function getStateColour(state) {
if (state === SlotState.Free) {
return 'yellow'
} else if (state === SlotState.Filled) {
return 'green'
} else if (state === SlotState.Finished) {
return 'blue'
} else if (state === SlotState.Paid) {
return 'gray'
} else {
return 'red'
}
}