import EventEmitter from "events"; import DHTCache from "@lumeweb/dht-cache"; import { RPCCacheData, RPCCacheItem, RPCRequest, RPCResponse, } from "@lumeweb/relay-types"; import { getRpcByPeer } from "../rpc"; import b4a from "b4a"; import { get as getSwarm } from "../swarm"; import { RPCServer } from "./server"; // @ts-ignore import jsonStringify from "json-stringify-deterministic"; // @ts-ignore import crypto from "hypercore-crypto"; import NodeCache from "node-cache"; export class RPCCache extends EventEmitter { private dhtCache?: DHTCache; private server: RPCServer; private _swarm?: any; get swarm(): any { return this._swarm; } private _data: NodeCache = new NodeCache({ stdTTL: 60 * 60 * 24 }); get data(): NodeCache { return this._data; } constructor(server: RPCServer) { super(); this.server = server; this._swarm = getSwarm(); this.dhtCache = new DHTCache(this._swarm, { protocol: "lumeweb.rpccache", }); this._data.on("del", (key: string) => { try { this.deleteItem(key); } catch {} }); } public signResponse(item: RPCCacheItem): string { const field = item.value.signedField || "data"; const updated = item.value.updated; // @ts-ignore const data = item.value[field]; const json = jsonStringify(data); return this.server.signData(`${updated}${json}`); } public verifyResponse(pubkey: Buffer, item: RPCCacheItem): boolean | Buffer { const field = item.value.signedField || "data"; const updated = item.value.updated; // @ts-ignore const data = item.value[field]; const json = jsonStringify(data); try { if ( !crypto.verify( Buffer.from(`${updated}${json}`), Buffer.from(item?.signature as string, "hex"), pubkey ) ) { return false; } } catch { return false; } return true; } public addItem(query: RPCRequest, response: RPCResponse) { const queryHash = RPCServer.hashQuery(query); const clonedResponse = { ...response }; clonedResponse.updated = Date.now(); const item = { value: clonedResponse, signature: "", }; item.signature = this.signResponse(item); this.dhtCache?.addItem(queryHash); this._data.set(queryHash, item); } public deleteItem(queryHash: string): boolean { const cache = this.dhtCache?.cache; if (!cache?.includes(queryHash)) { throw Error("item does not exist"); } this.dhtCache?.removeItem(queryHash); this._data.del(queryHash); return true; } }