diff --git a/backend/database.js b/backend/database.js index 26fcbd59a2..8677cef9ae 100644 --- a/backend/database.js +++ b/backend/database.js @@ -42,12 +42,11 @@ if (!fs.existsSync(dataFolder)) { // Streams stored contract log entries since the given entry hash (inclusive!). export default ((sbp('sbp/selectors/register', { 'backend/db/streamEntriesAfter': async function (contractID: string, height: string, requestedLimit: ?number): Promise<*> { - const resource = await sbp('chelonia/db/get', contractID) - if (resource === '') { - throw Boom.resourceGone(`contractID ${contractID} has been deleted!`) - } const limit = Math.min(requestedLimit ?? Number.POSITIVE_INFINITY, process.env.MAX_EVENTS_BATCH_SIZE ?? 500) const latestHEADinfo = await sbp('chelonia/db/latestHEADinfo', contractID) + if (latestHEADinfo === '') { + throw Boom.resourceGone(`contractID ${contractID} has been deleted!`) + } if (!latestHEADinfo) { throw Boom.notFound(`contractID ${contractID} doesn't exist!`) } diff --git a/backend/index.js b/backend/index.js index e5569f4c59..f2fcdb50b0 100644 --- a/backend/index.js +++ b/backend/index.js @@ -50,7 +50,11 @@ console.error = logger.error.bind(logger) console.info('NODE_ENV =', process.env.NODE_ENV) -const dontLog = { 'backend/server/broadcastEntry': true, 'backend/server/broadcastKV': true } +const dontLog = { + 'backend/server/broadcastEntry': true, + 'backend/server/broadcastDeletion': true, + 'backend/server/broadcastKV': true +} function logSBP (domain, selector, data: Array<*>) { if (!dontLog[selector]) { diff --git a/backend/routes.js b/backend/routes.js index ee9f4d860f..6d1f035c1f 100644 --- a/backend/routes.js +++ b/backend/routes.js @@ -275,6 +275,9 @@ route.GET('/latestHEADinfo/{contractID}', { try { if (contractID.startsWith('_private')) return Boom.notFound() const HEADinfo = await sbp('chelonia/db/latestHEADinfo', contractID) + if (HEADinfo === '') { + return Boom.resourceGone() + } if (!HEADinfo) { console.warn(`[backend] latestHEADinfo not found for ${contractID}`) return Boom.notFound() diff --git a/backend/server.js b/backend/server.js index ba873f833f..99a50617b3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -153,6 +153,13 @@ sbp('sbp/selectors/register', { console.debug(chalk.blue.bold(`[pubsub] Broadcasting ${deserializedHEAD.description()}`)) await pubsub.broadcast(pubsubMessage, { to: subscribers }) }, + 'backend/server/broadcastDeletion': async function (contractID: string) { + const pubsub = sbp('okTurtles.data/get', PUBSUB_INSTANCE) + const pubsubMessage = createMessage(NOTIFICATION_TYPE.DELETION, contractID) + const subscribers = pubsub.enumerateSubscribers(contractID) + console.debug(chalk.blue.bold(`[pubsub] Broadcasting deletion of ${contractID}`)) + await pubsub.broadcast(pubsubMessage, { to: subscribers }) + }, 'backend/server/handleEntry': async function (deserializedHEAD: Object, entry: string) { const contractID = deserializedHEAD.contractID if (deserializedHEAD.head.op === GIMessage.OP_CONTRACT) { @@ -306,6 +313,9 @@ sbp('sbp/selectors/register', { await sbp('chelonia/db/delete', `_private_cheloniaState_${cid}`) await removeFromIndexFactory('_private_cheloniaState_index')(cid) await removeFromIndexFactory('_private_billable_entities')(cid) + sbp('backend/server/broadcastDeletion', cid).catch(e => { + console.error(e, 'Error broadcasting contract deletion', cid) + }) }).finally(() => { contractsPendingDeletion.delete(cid) }) diff --git a/frontend/controller/utils/misc.js b/frontend/controller/utils/misc.js index b59c1fbfcc..9c34ca8556 100644 --- a/frontend/controller/utils/misc.js +++ b/frontend/controller/utils/misc.js @@ -1,8 +1,10 @@ 'use strict' +import { ChelErrorUnexpectedHttpResponseCode } from '~/shared/domains/chelonia/errors.js' + export function handleFetchResult (type: string): ((r: any) => any) { return function (r: Object) { - if (!r.ok) throw new Error(`${r.status}: ${r.statusText}`) + if (!r.ok) throw new ChelErrorUnexpectedHttpResponseCode(`${r.status}: ${r.statusText}`) return r[type]() } } diff --git a/frontend/setupChelonia.js b/frontend/setupChelonia.js index a3f5217fa1..bb2d4438d1 100644 --- a/frontend/setupChelonia.js +++ b/frontend/setupChelonia.js @@ -139,6 +139,12 @@ const setupChelonia = async (): Promise<*> => { } }, hooks: { + syncContractError: (e, contractID) => { + // TODO + if (e?.name === 'ChelErrorUnexpectedHttpResponseCode' && e.message.startsWith('410:')) { + console.error('@@@@[syncContractError] Contract ID ' + contractID + ' has been deleted') + } + }, handleEventError: (e: Error, message: GIMessage) => { if (e.name === 'ChelErrorUnrecoverable') { sbp('okTurtles.events/emit', SERIOUS_ERROR, e) @@ -254,6 +260,10 @@ const setupChelonia = async (): Promise<*> => { if (!data) return sbp('okTurtles.events/emit', KV_EVENT, { contractID, key, data }) + }, + [NOTIFICATION_TYPE.DELETION] (contractID) { + // TODO + console.error('@@@@[NOTIFICATION] Contract ID ' + contractID + ' has been deleted') } }, handlers: { diff --git a/shared/domains/chelonia/chelonia.js b/shared/domains/chelonia/chelonia.js index 3602437ba1..81d2363ce7 100644 --- a/shared/domains/chelonia/chelonia.js +++ b/shared/domains/chelonia/chelonia.js @@ -626,6 +626,8 @@ export default (sbp('sbp/selectors/register', { console.error(`[chelonia] Error processing kv event for ${msg.channelID} and key ${msg.key}`, msg, e) }) }] + case NOTIFICATION_TYPE.DELETION: + return [k, (msg) => v(msg.data)] default: return [k, v] } diff --git a/shared/domains/chelonia/db.js b/shared/domains/chelonia/db.js index 3def3f356a..16b5aac9c1 100644 --- a/shared/domains/chelonia/db.js +++ b/shared/domains/chelonia/db.js @@ -102,7 +102,7 @@ export default (sbp('sbp/selectors/register', { return sbp('chelonia/db/get', getLogHead(contractID)).then((r) => r && JSON.parse(r)) }, 'chelonia/db/deleteLatestHEADinfo': (contractID: string): Promise => { - return sbp('chelonia/db/delete', getLogHead(contractID)) + return sbp('chelonia/db/set', getLogHead(contractID), '') }, 'chelonia/db/getEntry': async function (hash: string): Promise { try { diff --git a/shared/domains/chelonia/errors.js b/shared/domains/chelonia/errors.js index 381c485edd..b082cbd92b 100644 --- a/shared/domains/chelonia/errors.js +++ b/shared/domains/chelonia/errors.js @@ -34,3 +34,4 @@ export const ChelErrorSignatureError: typeof Error = ChelErrorGenerator('ChelErr export const ChelErrorSignatureKeyUnauthorized: typeof Error = ChelErrorGenerator('ChelErrorSignatureKeyUnauthorized', ChelErrorSignatureError) export const ChelErrorSignatureKeyNotFound: typeof Error = ChelErrorGenerator('ChelErrorSignatureKeyNotFound', ChelErrorSignatureError) export const ChelErrorFetchServerTimeFailed: typeof Error = ChelErrorGenerator('ChelErrorFetchServerTimeFailed') +export const ChelErrorUnexpectedHttpResponseCode: typeof Error = ChelErrorGenerator('ChelErrorUnexpectedHttpResponseCode') diff --git a/shared/pubsub.js b/shared/pubsub.js index 1c80b14d8a..22d652fd53 100644 --- a/shared/pubsub.js +++ b/shared/pubsub.js @@ -96,6 +96,7 @@ export type UnsubMessage = { export const NOTIFICATION_TYPE = Object.freeze({ ENTRY: 'entry', + DELETION: 'deletion', KV: 'kv', PING: 'ping', PONG: 'pong',