diff --git a/.dockerignore b/.dockerignore index b725cafd..8f048326 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,5 @@ node_modules dist .secrets/sw-gateway-ec2-key.cer gateway.lock +dump* +network-cache.json diff --git a/.gitignore b/.gitignore index 4b6ea42c..2403ca12 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,6 @@ node_modules .idea db*.sqlite .secrets -.secrets/.env -.secrets/local.env gateway.lock network-cache.json .DS_Store diff --git a/Dockerfile b/Dockerfile index 3eb5a2b9..545f8055 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.18.2-alpine +FROM node:18 RUN mkdir /app WORKDIR /app @@ -6,7 +6,7 @@ WORKDIR /app # Installing required npm packages COPY package.json package.json COPY yarn.lock yarn.lock -RUN yarn +RUN yarn && yarn global add pm2 # Copying all files COPY . . @@ -17,4 +17,4 @@ RUN yarn build EXPOSE 5666 # Running the gateway -CMD yarn start:prod --env_path .secrets/.env +CMD [ "pm2-runtime", "start", "dist/gateway/init.js", "--name", "gateway", "-i", "max", "--", "--noSync" ] diff --git a/docker-build.sh b/docker-build.sh index 3b62aca4..373dd368 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1 +1 @@ -docker build -t redstone-sw-gateway . +docker build -t warp-gateway . diff --git a/docker-run.sh b/docker-run.sh index 7ea587a0..3fb3811b 100755 --- a/docker-run.sh +++ b/docker-run.sh @@ -1 +1 @@ -docker run -t -i -p 8080:5666 redstone-sw-gateway +docker run -t -i -p 8080:5666 warp-gateway diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 00000000..2b1a974c --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node + labels: + name: node +spec: + replicas: 1 + selector: + matchLabels: + app: node + template: + metadata: + labels: + app: node + spec: + containers: + - name: node + image: gcr.io/warp-372209/docker-image:2 + imagePullPolicy: Always + ports: + - containerPort: 5666 + protocol: TCP + restartPolicy: Always \ No newline at end of file diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 00000000..2f8f78db --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gke-warp-gw-ingress + annotations: + kubernetes.io/ingress.global-static-ip-name: "gke-warp-gw-ip" +spec: + rules: + - host: "chart-example.local" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gke-warp-gw-deployment + port: + number: 5666 \ No newline at end of file diff --git a/k8s/podscaler.yaml b/k8s/podscaler.yaml new file mode 100644 index 00000000..209cfaaf --- /dev/null +++ b/k8s/podscaler.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: node + namespace: default + labels: + app: node +spec: + scaleTargetRef: + kind: Deployment + name: node + apiVersion: apps/v1 + minReplicas: 2 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: cpu + target: + averageValue: 70 + type: Utilization \ No newline at end of file diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 00000000..c9e3e8db --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: node + labels: + service: node +spec: + selector: + app: node + type: LoadBalancer + ports: + - port: 5666 \ No newline at end of file diff --git a/package.json b/package.json index 988892cb..08a05819 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "dev:network:watch": "ts-node-dev --watch -- src/gateway/init.ts", "build": "tsc", "start:prod": "node dist/gateway/init.js", + "start:prod:noSync": "node dist/gateway/init.js --noSync", "start:prod:replica": "node dist/gateway/init.js --replica", "start:local": "node dist/gateway/init.js --env_path .secrets/local.env --local", "start:local:replica": "node dist/gateway/init.js --env_path .secrets/local.env --replica --local", "start:local:testnet": "node dist/gateway/init.js --env_path .secrets/local-testnet.env --local", + "start:local:noSync": "node dist/gateway/init.js --env_path .secrets/local.env --local --noSync", "start:prod:testnet": "node dist/gateway/init.js --env_path .secrets/prod-testnet.env", "format": "prettier --write .", "test": "jest", @@ -19,31 +21,35 @@ "license": "MIT", "author": "Redstone Team ", "dependencies": { - "@bundlr-network/client": "0.9.6", + "@irys/sdk": "0.1.1", "@idena/vrf-js": "^1.0.1", "@koa/cors": "3.2.0", "@koa/router": "10.1.1", - "@types/yargs": "17.0.7", "JSONStream": "^1.3.5", - "arbundles": "^0.7.0", - "arweave": "1.11.8", + "arbundles": "^0.9.6", + "arweave": "1.13.7", "axios": "^0.26.1", "dotenv": "16.0.3", "elliptic": "^6.5.4", "ethers": "^5.7.2", "exponential-backoff": "3.1.0", "ioredis": "^5.2.4", - "knex": "0.95.14", - "koa": "2.13.4", - "koa-bodyparser": "4.3.0", + "knex": "2.4.2", + "koa": "2.14.1", + "koa-bodyparser": "4.4.0", "koa-compress": "5.1.0", "nodemailer": "^6.9.1", "parse-json-stream": "^2.4.0", - "pg": "8.7.3", + "pg": "8.10.0", "pg-query-stream": "^4.2.3", "raw-body": "^2.5.1", - "undici": "5.14.0", - "warp-contracts": "1.2.48", + "undici": "5.21.0", + "uuid": "^9.0.0", + "warp-arbundles": "1.0.0", + "warp-contracts": "1.4.26-beta.0", + "warp-contracts-new": "npm:warp-contracts", + "warp-contracts-old": "npm:warp-contracts@1.4.12", + "warp-contracts-plugin-signature": "^1.0.16", "warp-contracts-pubsub": "^1.0.3", "warp-contracts-subscription-plugin": "1.0.4", "warp-signature": "1.0.4", @@ -56,6 +62,8 @@ "@types/koa__router": "8.0.11", "@types/nodemailer": "^6.4.7", "@types/object-hash": "2.2.1", + "@types/uuid": "^9.0.1", + "@types/yargs": "17.0.7", "@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/parser": "4.33.0", "autocannon": "^7.6.0", @@ -68,6 +76,6 @@ "ts-jest": "27.1.2", "ts-node": "^10.2.1", "tsconfig-paths": "^3.10.1", - "typescript": "4.5.2" + "typescript": "4.9.5" } } diff --git a/src/bundlr/connect.ts b/src/bundlr/connect.ts index e107fbe8..45e2f1a6 100644 --- a/src/bundlr/connect.ts +++ b/src/bundlr/connect.ts @@ -1,25 +1,30 @@ -import { WarpLogger } from 'warp-contracts'; -import Bundlr from '@bundlr-network/client'; -import fs from 'fs'; -import { TaskRunner } from '../gateway/tasks/TaskRunner'; -import { GatewayContext } from '../gateway/init'; -import { JWKInterface } from 'arweave/node/lib/wallet'; -import { BUNDLR_NODE1_URL } from '../constants'; +import { WarpLogger } from "warp-contracts"; +import fs from "fs"; +import { TaskRunner } from "../gateway/tasks/TaskRunner"; +import { GatewayContext } from "../gateway/init"; +import { JWKInterface } from "arweave/node/lib/wallet"; +import { BUNDLR_NODE1_URL } from "../constants"; +import Irys from "@irys/sdk"; const BUNDLR_CHECK_INTERVAL = 3600000; export async function runBundlrCheck(context: GatewayContext) { - await TaskRunner.from('[bundlr balance check]', checkBalance, context).runSyncEvery(BUNDLR_CHECK_INTERVAL, true); + await TaskRunner.from("[bundlr balance check]", checkBalance, context).runSyncEvery(BUNDLR_CHECK_INTERVAL, true); } -export function initBundlr(logger: WarpLogger): { bundlr: Bundlr; jwk: JWKInterface } { - const jwk = JSON.parse(fs.readFileSync('.secrets/warp-wallet-jwk.json').toString()); - const bundlr = new Bundlr(BUNDLR_NODE1_URL, 'arweave', jwk, { - timeout: 5000, +export function initBundlr(logger: WarpLogger): { bundlr: Irys; jwk: JWKInterface } { + const jwk = JSON.parse(fs.readFileSync(".secrets/warp-wallet-jwk.json").toString()); + const bundlr = new Irys({ + url: BUNDLR_NODE1_URL, + token: "arweave", + key: jwk, + config: { + timeout: 5000 + } }); - logger.info('Running bundlr on', { + logger.info("Running bundlr on", { address: bundlr.address, - currency: bundlr.currency, + currency: bundlr.token }); return { bundlr, jwk }; @@ -27,15 +32,15 @@ export function initBundlr(logger: WarpLogger): { bundlr: Bundlr; jwk: JWKInterf async function checkBalance(context: GatewayContext) { const { bundlr, logger } = context; - logger.debug('Checking Bundlr balance'); + logger.debug("Checking Bundlr balance"); // Check your balance const balance = await bundlr.getLoadedBalance(); - logger.debug('Current Bundlr balance', balance); + logger.debug("Current Bundlr balance", balance); // If balance is < 0.5 AR if (balance.isLessThan(5e11)) { - logger.debug('Funding Bundlr'); + logger.debug("Funding Bundlr"); // Fund your account with 0.5 AR //const fundResult = await bundlr.fund(5e11); //logger.debug("Fund result", fundResult); diff --git a/src/db/databaseSource.ts b/src/db/databaseSource.ts index 0beeef54..3e191596 100644 --- a/src/db/databaseSource.ts +++ b/src/db/databaseSource.ts @@ -24,9 +24,10 @@ interface DbData { export class DatabaseSource { public db: Knex[] = []; public primaryDb: Knex | null = null; + public healthCheckConnection: Knex | null = null; private mailClient: Transporter; - constructor(dbData: DbData[]) { + constructor(dbData: DbData[], healthCheckConnection?: DbData) { for (let i = 0; i < dbData.length; i++) { this.db[i] = this.connectDb(dbData[i]); if (dbData[i].primaryDb) { @@ -39,6 +40,9 @@ export class DatabaseSource { if (this.primaryDb == null) { throw new Error('Exactly one db must be set as primary'); } + if (healthCheckConnection != null) { + this.healthCheckConnection = this.connectDb(healthCheckConnection); + } this.mailClient = client(); } @@ -251,6 +255,14 @@ export class DatabaseSource { return db.raw(query, bindings); } + public healthCheckEnabled(): boolean { + return this.healthCheckConnection != null; + } + + public healthCheck(query: string, bindings?: any) { + return this.healthCheckConnection!!.raw(query, bindings); + } + public async loopThroughDb(callback: any, recordName: string): Promise { let result: any; try { @@ -278,7 +290,7 @@ export class DatabaseSource { useNullAsDefault: true, pool: { min: 5, - max: 30, + max: 20, createTimeoutMillis: 3000, acquireTimeoutMillis: 30000, idleTimeoutMillis: 30000, diff --git a/src/db/insertInterfaces.ts b/src/db/insertInterfaces.ts index 5fbaab90..c6c15e62 100644 --- a/src/db/insertInterfaces.ts +++ b/src/db/insertInterfaces.ts @@ -1,5 +1,5 @@ import { GQLTagInterface, Tags } from 'warp-contracts'; -import { WarpDeployment } from '../gateway/router/routes/deployContractRoute'; +import { WarpDeployment } from '../gateway/router/routes/deploy/deployContractRoute'; export interface SequencerInsert { original_sig: string; @@ -49,7 +49,7 @@ export interface ContractInsert { block_timestamp: number; content_type: string | undefined; contract_tx: { - tags: Tags; + tags: { name: string; value: string }[]; }; bundler_contract_tx_id: string; bundler_contract_node: string; diff --git a/src/gateway/LastTxSyncer.ts b/src/gateway/LastTxSyncer.ts deleted file mode 100644 index 346bd476..00000000 --- a/src/gateway/LastTxSyncer.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Knex} from "knex"; -import {Benchmark, LoggerFactory} from "warp-contracts"; -import { createHash } from 'crypto' - -export class LastTxSync { - - private readonly logger = LoggerFactory.INST.create(LastTxSync.name); - - async acquireMutex(contractTxId: string, trx: Knex.Transaction): Promise { - const lockId = this.strToKey(contractTxId); - this.logger.debug('Locking for', { - contractTxId, - lockId - }); - - // https://stackoverflow.com/a/20963803 - const benchmark = Benchmark.measure(); - await trx.raw(`SET LOCAL lock_timeout = '2s';`) - - await trx.raw(` - SELECT pg_advisory_xact_lock(?, ?); - `, [lockId[0], lockId[1]]); - this.logger.debug("Acquiring pg_advisory_xact_lock", benchmark.elapsed()); - - return this.loadLastSortKey(contractTxId, trx); - } - - private async loadLastSortKey(contractTxId: string, trx: Knex.Transaction): Promise { - const benchmark = Benchmark.measure(); - const result = await trx.raw( - `SELECT max(sort_key) AS "lastSortKey" - FROM interactions - WHERE contract_id = ?`, - [contractTxId] - ); - this.logger.debug("Loading lastSortKey", benchmark.elapsed()); - - // note: this will return null if we're registering the very first tx for the contract - return result?.rows[0].lastSortKey; - } - - // https://github.com/binded/advisory-lock/blob/master/src/index.js#L8 - private strToKey(id: string) { - const buf = createHash('sha256').update(id).digest() - // Read the first 4 bytes and the next 4 bytes - // The parameter here is the byte offset, not the sizeof(int32) offset - return [buf.readInt32LE(0), buf.readInt32LE(4)] - } -} diff --git a/src/gateway/PgAdvisoryLocks.ts b/src/gateway/PgAdvisoryLocks.ts new file mode 100644 index 00000000..4a632c1d --- /dev/null +++ b/src/gateway/PgAdvisoryLocks.ts @@ -0,0 +1,115 @@ +import { Knex } from "knex"; +import { Benchmark, LoggerFactory } from "warp-contracts"; +import { createHash } from "crypto"; + +export type SortKeyMutexResult = { + lastSortKey: string | null, + blockHeight: number, + blockHash: string, + blockTimestamp: number +} + +export class PgAdvisoryLocks { + + private readonly logger = LoggerFactory.INST.create(PgAdvisoryLocks.name); + + async acquireSortKeyMutex(contractTxId: string, trx: Knex.Transaction): Promise { + const lockFor = contractTxId; + const lockId = this.strToKey(lockFor); + this.logger.debug("Locking for", { + lockFor, + lockId + }); + await this.doAcquireLock(lockId, trx); + return await this.loadLastSortKey(contractTxId, trx); + } + + async acquireArweaveHeightMutex(trx: Knex.Transaction): Promise<{ blockHeight: number, blockHash: string, blockTimestamp: string } | null | undefined> { + const lockFor = "ArweaveHeight"; + const lockId = this.strToKey(lockFor); + this.logger.debug("Locking for", { + lockFor, + lockId + }); + const hasLock = await this.tryLock(lockId, trx); + if (hasLock) { + const result = await trx.raw( + ` + SELECT finished_block_height AS "blockHeight", + finished_block_hash AS "blockHash", + finished_block_timestamp as "blockTimestamp", + additional_data as "additionalData" + FROM sync_state + WHERE name = 'Arweave';` + ); + if (result?.rows?.length !== 1) { + return null; + } + + return result.rows[0]; + } else { + return undefined; + } + } + + private async tryLock(lockId: number[], trx: Knex.Transaction): Promise { + const benchmark = Benchmark.measure(); + await trx.raw(`SET LOCAL lock_timeout = '5s';`); + const result = await trx.raw( + `SELECT pg_try_advisory_xact_lock(?, ?);`, [lockId[0], lockId[1]] + ); + const hasLock = result.rows[0]["pg_try_advisory_xact_lock"]; + this.logger.debug("Acquiring pg_try_advisory_xact_lock", benchmark.elapsed(), hasLock); + + return hasLock; + } + + private async doAcquireLock(lockId: number[], trx: Knex.Transaction): Promise { + // https://stackoverflow.com/a/20963803 + await trx.raw(`SET LOCAL lock_timeout = '5s';`); + const benchmark = Benchmark.measure(); + await trx.raw(` + SELECT pg_advisory_xact_lock(?, ?); + `, [lockId[0], lockId[1]]); + this.logger.debug("Acquiring pg_advisory_xact_lock", benchmark.elapsed()); + } + + private async loadLastSortKey(contractTxId: string, trx: Knex.Transaction): Promise { + const benchmark = Benchmark.measure(); + this.logger.debug("Loading lastSortKey", benchmark.elapsed()); + + const result = await trx.raw( + `SELECT 'sort_key' as type, + max(sort_key) AS "lastSortKey", + null as "finishedBlockHeight", + null as "finishedBlockHash", + null as "finishedBlockTimestamp" + FROM interactions + WHERE contract_id = ? + UNION ALL + SELECT 'finished_block' as type, null, finished_block_height, finished_block_hash, finished_block_timestamp + FROM sync_state + WHERE name = 'Interactions';`, [contractTxId] + ); + if (result?.rows.length !== 2) { + throw new Error("Acquire mutex result should have exactly 2 rows in result"); + } + const sortKeyRow = result?.rows[0].type === "sort_key" ? 0 : 1; + const finishedBlockRow = result?.rows[0].type === "finished_block" ? 0 : 1; + + return { + lastSortKey: result?.rows[sortKeyRow].lastSortKey, // note: this will return null if we're registering the very first tx for the contract + blockHeight: result?.rows[finishedBlockRow].finishedBlockHeight, + blockHash: result?.rows[finishedBlockRow].finishedBlockHash, + blockTimestamp: result?.rows[finishedBlockRow].finishedBlockTimestamp + }; + } + + // https://github.com/binded/advisory-lock/blob/master/src/index.js#L8 + private strToKey(id: string) { + const buf = createHash("sha256").update(id).digest(); + // Read the first 4 bytes and the next 4 bytes + // The parameter here is the byte offset, not the sizeof(int32) offset + return [buf.readInt32LE(0), buf.readInt32LE(4)]; + } +} diff --git a/src/gateway/accessLogMiddleware.ts b/src/gateway/accessLogMiddleware.ts new file mode 100644 index 00000000..a50a3590 --- /dev/null +++ b/src/gateway/accessLogMiddleware.ts @@ -0,0 +1,32 @@ +import * as util from "util"; +import { v4 as uuidv4 } from 'uuid'; +import {DefaultState, Next, ParameterizedContext} from "koa"; +import {GatewayContext} from "./init"; + +const LOG_FORMAT = '%s %s "%s %s HTTP/%s" %d %s %s[ms]'; + +export async function accessLogMiddleware(ctx: ParameterizedContext, next: Next): Promise { + ctx.state.requestId = uuidv4(); + const t0 = performance.now(); + await next(); + const t1 = performance.now(); + try { + if (ctx.path == '/gateway/gcp/alive' || ctx.path == '/gateway/arweave/info') { + return; + } + ctx.accessLogger.debug(util.format( + LOG_FORMAT, + ctx.state.requestId, + ctx.ip, + ctx.method, + `${ctx.path}${ctx.search}`, + ctx.req.httpVersion, + ctx.status, + ctx.length ? ctx.length.toString() : '-', + (t1 - t0).toFixed(3) + ) + ); + } catch (err: any) { + console.error(err); + } +} diff --git a/src/gateway/errorHandlerMiddleware.ts b/src/gateway/errorHandlerMiddleware.ts new file mode 100644 index 00000000..2e7d882a --- /dev/null +++ b/src/gateway/errorHandlerMiddleware.ts @@ -0,0 +1,47 @@ +import * as util from 'util'; +import { DefaultState, Next, ParameterizedContext } from 'koa'; +import { GatewayContext } from './init'; + +const ERROR_LOG_FORMAT = '[%s][%s]: %s %s'; + +export class GatewayError extends Error { + constructor(message: string, readonly status: number = 500, readonly properties: any = null, readonly log = true) { + super(message); + this.name = 'GatewayError'; + } +} + +export async function errorHandlerMiddleware( + ctx: ParameterizedContext, + next: Next +): Promise { + try { + await next(); + } catch (err: any) { + if (err.name == 'GatewayError') { + ctx.status = err.status; + ctx.message = `[${ctx.state.requestId}]: ${err.message.replace(/\n/g, '')}`; + if (err.log) { + ctx.logger.error( + util.format( + ERROR_LOG_FORMAT, + ctx.state.requestId, + `${ctx.path}${ctx.search}`, + err.message, + err.properties ? JSON.stringify(err.properties) : '' + ), + err + ); + } + } else { + ctx.status = 500; + ctx.message = `[${ctx.state.requestId}]${ + err?.message ? `: ${err.message.replace(/\n/g, '')}` : `Unknown gateway error.` + }`; + ctx.logger.error( + util.format(ERROR_LOG_FORMAT, ctx.state.requestId, `${ctx.path}${ctx.search}`, err.message || err), + err + ); + } + } +} diff --git a/src/gateway/init.ts b/src/gateway/init.ts index d4d45210..a0f1b4a6 100644 --- a/src/gateway/init.ts +++ b/src/gateway/init.ts @@ -1,45 +1,52 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { Knex } from 'knex'; import Koa from 'koa'; import Application from 'koa'; import bodyParser from 'koa-bodyparser'; -import { ArweaveWrapper, LexicographicalInteractionsSorter, LoggerFactory, WarpLogger } from 'warp-contracts'; +import { + ArweaveWrapper, + LexicographicalInteractionsSorter, + LoggerFactory, + WarpFactory, + WarpLogger, + defaultCacheOptions, +} from 'warp-contracts'; import Arweave from 'arweave'; -import { runGatewayTasks } from './runGatewayTasks'; import gatewayRouter from './router/gatewayRouter'; import * as fs from 'fs'; -import cluster from 'cluster'; import welcomeRouter from './router/welcomeRouter'; -import Bundlr from '@bundlr-network/client'; import { initBundlr } from '../bundlr/connect'; import { JWKInterface } from 'arweave/node/lib/wallet'; import { runNetworkInfoCacheTask } from './tasks/networkInfoCache'; -import path from 'path'; import Redis from 'ioredis'; -import { LastTxSync } from './LastTxSyncer'; +import { PgAdvisoryLocks } from './PgAdvisoryLocks'; import { initPubSub } from 'warp-contracts-pubsub'; // @ts-ignore import { EvmSignatureVerificationServerPlugin } from 'warp-signature/server'; import { DatabaseSource } from '../db/databaseSource'; - +import { accessLogMiddleware } from './accessLogMiddleware'; +import { errorHandlerMiddleware } from './errorHandlerMiddleware'; +import Irys from "@irys/sdk"; const argv = yargs(hideBin(process.argv)).parseSync(); const envPath = argv.env_path || '.secrets/prod.env'; const replica = (argv.replica as boolean) || false; -const localEnv = (argv.local as boolean) || false; +const noSync = (argv.noSync as boolean) || false; const elliptic = require('elliptic'); const EC = new elliptic.ec('secp256k1'); const cors = require('@koa/cors'); +export type EnvType = 'local' | 'dev' | 'main'; + export type VRF = { pubKeyHex: string; privKey: any; ec: any }; export interface GatewayContext { dbSource: DatabaseSource; logger: WarpLogger; sLogger: WarpLogger; + accessLogger: WarpLogger; arweave: Arweave; - bundlr: Bundlr; + bundlr: Irys; jwk: JWKInterface; arweaveWrapper: ArweaveWrapper; arweaveWrapperGqlGoldsky: ArweaveWrapper; @@ -47,10 +54,11 @@ export interface GatewayContext { sorter: LexicographicalInteractionsSorter; publisher: Redis; publisher_v2: Redis; - lastTxSync: LastTxSync; - localEnv: boolean; + pgAdvisoryLocks: PgAdvisoryLocks; + env: EnvType; appSync?: string; signatureVerification: EvmSignatureVerificationServerPlugin; + replica: boolean; } (async () => { @@ -77,51 +85,88 @@ export interface GatewayContext { LoggerFactory.INST.logLevel('info'); LoggerFactory.INST.logLevel('info', 'gateway'); LoggerFactory.INST.logLevel('debug', 'sequencer'); - LoggerFactory.INST.logLevel('debug', 'LastTxSync'); + LoggerFactory.INST.logLevel('debug', 'PgAdvisoryLocks'); + LoggerFactory.INST.logLevel('debug', 'access'); const logger = LoggerFactory.INST.create('gateway'); const sLogger = LoggerFactory.INST.create('sequencer'); + const accessLogger = LoggerFactory.INST.create('access'); + const warp = WarpFactory.forMainnet(); + const warpGqlGoldsky = WarpFactory.forMainnet( + defaultCacheOptions, + false, + Arweave.init({ + host: 'arweave-search.goldsky.com', + port: 443, + protocol: 'https', + timeout: 20000, + logging: false, + }) + ); - logger.info(`🚀🚀🚀 Starting gateway in ${replica ? 'replica' : 'normal'} mode.`); + const env = process.env.ENV as string; + if (!env) { + logger.error(`Set 'ENV' value in ${envPath} to either 'local', 'dev' or 'main'`); + process.exit(0); + } + logger.info(`🚀🚀🚀 Starting gateway in ${replica ? 'replica' : 'normal'} mode.\nnoSync = ${noSync}.\nENV: ${env}`); const arweave = initArweave(); const { bundlr, jwk } = initBundlr(logger); - const dbSource = new DatabaseSource([ - { - client: 'pg', - url: process.env.DB_URL_GCP as string, - ssl: { - rejectUnauthorized: false, - ca: fs.readFileSync('.secrets/ca.pem'), - cert: fs.readFileSync('.secrets/cert.pem'), - key: fs.readFileSync('.secrets/key.pem'), + const gcpDataOptions = { + client: 'pg' as 'pg', + url: process.env.DB_URL_GCP as string, + ssl: + env === 'local' + ? undefined + : { + rejectUnauthorized: false, + ca: fs.readFileSync('.secrets/ca.pem'), + cert: fs.readFileSync('.secrets/cert.pem'), + key: fs.readFileSync('.secrets/key.pem'), + }, + primaryDb: true, + }; + + const healthCheckOptions = { + ...gcpDataOptions, + primaryDb: false, + options: { + pool: { + min: 1, + max: 2, + createTimeoutMillis: 500, + acquireTimeoutMillis: 500, + idleTimeoutMillis: 500, + reapIntervalMillis: 500, + createRetryIntervalMillis: 100, + propagateCreateError: false, }, - primaryDb: true, }, - ]); + }; + + const dbSource = new DatabaseSource([gcpDataOptions], healthCheckOptions); const app = new Koa(); - const signatureVerification = new EvmSignatureVerificationServerPlugin(); app.context.dbSource = dbSource; app.context.logger = logger; app.context.sLogger = sLogger; + app.context.accessLogger = accessLogger; app.context.arweave = arweave; app.context.bundlr = bundlr; app.context.jwk = jwk; - app.context.arweaveWrapper = new ArweaveWrapper(arweave); - app.context.arweaveWrapperGqlGoldsky = new ArweaveWrapper(Arweave.init({ - host: 'arweave-search.goldsky.com', - port: 443, - protocol: 'https', - timeout: 20000, - logging: false, - })); + app.context.arweaveWrapper = new ArweaveWrapper(warp); + app.context.arweaveWrapperGqlGoldsky = new ArweaveWrapper(warpGqlGoldsky); app.context.sorter = new LexicographicalInteractionsSorter(arweave); - app.context.lastTxSync = new LastTxSync(); - app.context.localEnv = localEnv; + app.context.pgAdvisoryLocks = new PgAdvisoryLocks(); app.context.appSync = appSync; - app.context.signatureVerification = signatureVerification; + app.context.signatureVerification = new EvmSignatureVerificationServerPlugin(); + app.context.replica = replica; + app.context.env = env as EnvType; + + app.use(errorHandlerMiddleware); + app.use(accessLogMiddleware); app.use( cors({ @@ -158,69 +203,49 @@ export interface GatewayContext { logger.info('vrf', app.context.vrf); - const connectionOptions = readGwPubSubConfig('gw-pubsub.json'); - logger.info('Redis connection options', connectionOptions); - if (connectionOptions) { - const publisher = new Redis(connectionOptions); - await publisher.connect(); - logger.info(`Publisher status`, { - host: connectionOptions.host, - status: publisher.status, - }); - app.context.publisher = publisher; - } + if (env !== 'local') { + const connectionOptions = readGwPubSubConfig('gw-pubsub.json'); + logger.info('Redis connection options', connectionOptions); + if (connectionOptions) { + const publisher = new Redis(connectionOptions); + await publisher.connect(); + logger.info(`Publisher status`, { + host: connectionOptions.host, + status: publisher.status, + }); + app.context.publisher = publisher; + } - // temporary.. - const connectionOptions2 = readGwPubSubConfig('gw-pubsub_2.json'); - if (connectionOptions2) { - console.log({ - ...connectionOptions2, - tls: { - ca: [process.env.GW_TLS_CA_CERT], - checkServerIdentity: () => { - return null; + // temporary.. + const connectionOptions2 = readGwPubSubConfig('gw-pubsub_2.json'); + if (connectionOptions2) { + console.log({ + ...connectionOptions2, + tls: { + ca: [process.env.GW_TLS_CA_CERT], + checkServerIdentity: () => { + return null; + }, }, - }, - }); - - const publisher2 = new Redis({ - ...connectionOptions2, - tls: { - ca: [process.env.GW_TLS_CA_CERT], - checkServerIdentity: () => { - return null; + }); + const publisher2 = new Redis({ + ...connectionOptions2, + tls: { + ca: [process.env.GW_TLS_CA_CERT], + checkServerIdentity: () => { + return null; + }, }, - }, - }); - await publisher2.connect(); - logger.info(`Publisher 2 status`, { - host: connectionOptions2.host, - status: publisher2.status, - }); - app.context.publisher_v2 = publisher2; - } - - if (!fs.existsSync('gateway.lock')) { - try { - logger.info(`Creating lock file for ${cluster.worker?.id}`); - // note: if another process in cluster have already created the file - writing here - // will fail thanks to wx flags. https://stackoverflow.com/a/31777314 - fs.writeFileSync('gateway.lock', '' + cluster.worker?.id, { - flag: 'wx', }); - removeLock = true; - - await runNetworkInfoCacheTask(app.context); - // note: only one worker in cluster runs the gateway tasks - // all workers in cluster run the http server - if (!localEnv) { - logger.info(`Starting gateway tasks for ${cluster.worker?.id}`); - await runGatewayTasks(app.context); - } - } catch (e: any) { - logger.error('Error from gateway', e); + await publisher2.connect(); + logger.info(`Publisher 2 status`, { + host: connectionOptions2.host, + status: publisher2.status, + }); + app.context.publisher_v2 = publisher2; } } + await runNetworkInfoCacheTask(app.context); } })(); @@ -235,11 +260,6 @@ function initArweave(): Arweave { } function readGwPubSubConfig(filename: string) { - const pubSubConfigPath = path.join('.secrets', filename); - if (fs.existsSync(pubSubConfigPath)) { - const json = fs.readFileSync(pubSubConfigPath, 'utf-8'); - return JSON.parse(json); - } else { - return false; - } + const json = fs.readFileSync(`./.secrets/${filename}`, 'utf-8'); + return JSON.parse(json); } diff --git a/src/gateway/publisher.ts b/src/gateway/publisher.ts index 65f49802..ae6852f3 100644 --- a/src/gateway/publisher.ts +++ b/src/gateway/publisher.ts @@ -10,6 +10,7 @@ export function sendNotification( contractTxId: string, contractData?: { initState: any; + srcTxId: string; tags: { name: string; value: string; @@ -19,7 +20,7 @@ export function sendNotification( ) { const { logger } = ctx; - if (ctx.localEnv) { + if (ctx.env === 'local') { logger.info('Skipping publish contract notification for local env'); return; } @@ -32,6 +33,7 @@ export function sendNotification( if (contractData) { message.initialState = contractData.initState; message.tags = contractData.tags; + message.srcTxId = contractData.srcTxId; } if (interaction) { message.interaction = interaction; @@ -61,7 +63,7 @@ export function publishInteraction( ) { const { logger, appSync } = ctx; - if (!appSync) { + if (!appSync || ctx.env === 'local') { logger.warn('App sync key not set'); return; } @@ -129,7 +131,9 @@ function publish( return; } - appSyncPublish(`${ctx.localEnv ? 'local/' : ''}${testnet ? 'testnet/' : ''}${channel}`, txToPublish, appSync) + const prefix = ctx.env === 'main' ? '' : `${ctx.env}/`; + + appSyncPublish(`${prefix}${testnet ? 'testnet/' : ''}${channel}`, txToPublish, appSync) .then((r) => { logger.debug(infoMessage); }) diff --git a/src/gateway/router/gatewayRouter.ts b/src/gateway/router/gatewayRouter.ts index 4ec0d11d..3971e610 100644 --- a/src/gateway/router/gatewayRouter.ts +++ b/src/gateway/router/gatewayRouter.ts @@ -1,33 +1,37 @@ import Router from '@koa/router'; -import { contractsRoute } from './routes/contractsRoute'; -import { interactionsRoute } from './routes/interactionsRoute'; +import { contractsRoute } from './routes/contracts/contractsRoute'; +import { interactionsRoute } from './routes/interactions/interactionsRoute'; import { searchRoute } from './routes/searchRoute'; import { totalTxsRoute } from './routes/stats/totalTxsRoute'; -import { contractRoute } from './routes/contractRoute'; -import { contractWithSourceRoute } from './routes/contractWithSourceRoute'; -import { contractWithSourceRoute_v2 } from './routes/contractWithSourceRoute_v2'; -import { interactionRoute } from './routes/interactionRoute'; -import { safeContractsRoute } from './routes/safeContractsRoute'; +import { contractRoute } from './routes/contracts/contractRoute'; +import { contractWithSourceRoute } from './routes/contracts/contractWithSourceRoute'; +import { contractWithSourceRoute_v2 } from './routes/contracts/contractWithSourceRoute_v2'; +import { interactionRoute } from './routes/interactions/interactionRoute'; +import { sequencerAddressRoute } from './routes/sequencerAddress'; import { sequencerRoute } from './routes/sequencerRoute'; -import { interactionsStreamRoute } from './routes/interactionsStreamRoute'; -import { deployContractRoute } from './routes/deployContractRoute'; +import { sequencerRoute_v2 } from './routes/sequencerRoute_v2'; +import { interactionsStreamRoute } from './routes/interactions/interactionsStreamRoute'; +import { deployContractRoute } from './routes/deploy/deployContractRoute'; import { arweaveBlockRoute, arweaveInfoRoute } from './routes/arweaveInfoRoute'; -import { interactionsSortKeyRoute } from './routes/interactionsSortKeyRoute'; -import { contractDataRoute } from './routes/contractDataRoute'; +import { interactionsSortKeyRoute } from './routes/interactions/interactionsSortKeyRoute'; +import { contractDataRoute } from './routes/contracts/contractDataRoute'; import { nftsOwnedByAddressRoute } from './routes/nftsOwnedByAddressRoute'; import { txsPerDayRoute } from './routes/stats/txsPerDayRoute'; -import { interactionsContractGroupsRoute } from './routes/interactionsContractGroupsRoute'; -import { interactionsSortKeyRoute_v2 } from './routes/interactionsSortKeyRoute_v2'; -import { contractSourceRoute } from './routes/contractSourceRoute'; -import { contractsBySourceRoute } from './routes/contractsBySourceRoute'; +import { interactionsSortKeyRoute_v2 } from './routes/interactions/interactionsSortKeyRoute_v2'; +import { interactionsSortKeyRoute_v3 } from './routes/interactions/interactionsSortKeyRoute_v3'; +import { contractSourceRoute } from './routes/contracts/contractSourceRoute'; +import { contractsBySourceRoute } from './routes/contracts/contractsBySourceRoute'; import { creatorRoute } from './routes/creatorRoute'; -import { interactionsSonar } from './routes/interactionsSonar'; -import { deployBundledRoute } from './routes/deployBundledRoute'; -import { deploySourceRoute } from './routes/deploySourceRoute'; -import { deploySourceRoute_v2 } from './routes/deploySourceRoute_v2'; -import { deployContractRoute_v2 } from './routes/deployContractRoute_v2'; -import { registerContractRoute } from './routes/registerContractRoute'; +import { interactionsSonar } from './routes/interactions/interactionsSonar'; +import { deployBundledRoute } from './routes/deploy/deployBundledRoute'; +import { deploySourceRoute } from './routes/deploy/deploySourceRoute'; +import { deploySourceRoute_v2 } from './routes/deploy/deploySourceRoute_v2'; +import { deployContractRoute_v2 } from './routes/deploy/deployContractRoute_v2'; +import { registerContractRoute } from './routes/deploy/registerContractRoute'; import { dashboardRoute } from './routes/dashboardRoute'; +import { gcpAliveRoute } from './routes/gcpAliveRoute'; +import { contractsByTags } from './routes/contracts/contractsByTags'; +import { joinSeason3 } from './routes/warpy/joinSeason3'; const gatewayRouter = (replica: boolean): Router => { const router = new Router({ prefix: '/gateway' }); @@ -37,7 +41,6 @@ const gatewayRouter = (replica: boolean): Router => { router.get('/v2/contract', contractWithSourceRoute_v2); router.get('/contract-data/:id', contractDataRoute); router.get('/contracts/:id', contractRoute); - router.get('/contracts-safe', safeContractsRoute); router.get('/dashboard', dashboardRoute); router.get('/search/:phrase', searchRoute); router.get('/nft/owner/:address', nftsOwnedByAddressRoute); @@ -48,8 +51,8 @@ const gatewayRouter = (replica: boolean): Router => { router.get('/interactions-sonar', interactionsSonar); router.get('/interactions-sort-key', interactionsSortKeyRoute); router.get('/v2/interactions-sort-key', interactionsSortKeyRoute_v2); + router.get('/v3/interactions-sort-key', interactionsSortKeyRoute_v3); router.get('/interactions-stream', interactionsStreamRoute); - router.get('/interactions-contract-groups', interactionsContractGroupsRoute); router.get('/interactions/:id', interactionRoute); router.get('/stats', totalTxsRoute); router.get('/stats/per-day', txsPerDayRoute); @@ -58,12 +61,17 @@ const gatewayRouter = (replica: boolean): Router => { router.get('/contract-source', contractSourceRoute); router.get('/contracts-by-source', contractsBySourceRoute); router.get('/creator', creatorRoute); + router.get('/gcp/alive', gcpAliveRoute); + router.get('/sequencer/address', sequencerAddressRoute); + router.get('/contracts-by-tags', contractsByTags); + router.get('/warpy/join-season-3', joinSeason3); // post if (!replica) { + router.post('/sequencer/register', sequencerRoute); + router.post('/v2/sequencer/register', sequencerRoute_v2); router.post('/contracts/deploy', deployContractRoute); router.post('/contracts/deploy-bundled', deployBundledRoute); - router.post('/sequencer/register', sequencerRoute); router.post('/sources/deploy', deploySourceRoute); router.post('/v2/sources/deploy', deploySourceRoute_v2); router.post('/v2/contracts/deploy', deployContractRoute_v2); diff --git a/src/gateway/router/routes/arweaveInfoRoute.ts b/src/gateway/router/routes/arweaveInfoRoute.ts index d132fbaf..aae38118 100644 --- a/src/gateway/router/routes/arweaveInfoRoute.ts +++ b/src/gateway/router/routes/arweaveInfoRoute.ts @@ -1,14 +1,13 @@ import Router from '@koa/router'; import { getCachedNetworkData } from '../../tasks/networkInfoCache'; +import {GatewayError} from "../../errorHandlerMiddleware"; export async function arweaveInfoRoute(ctx: Router.RouterContext) { - const { logger } = ctx; + const { logger, dbSource } = ctx; - const result = getCachedNetworkData().cachedNetworkInfo; + const result = (await getCachedNetworkData(dbSource)).cachedNetworkInfo; if (result == null) { - logger.error('Network info not yet available.'); - ctx.status = 500; - ctx.body = { message: 'Network info not yet available.' }; + throw new GatewayError('Network info not yet available.') } else { logger.debug('Returning network info with height', result.height); ctx.body = { @@ -18,13 +17,11 @@ export async function arweaveInfoRoute(ctx: Router.RouterContext) { } export async function arweaveBlockRoute(ctx: Router.RouterContext) { - const { logger } = ctx; + const { logger, dbSource } = ctx; - const result = getCachedNetworkData().cachedBlockInfo; + const result = (await getCachedNetworkData(dbSource)).cachedBlockInfo; if (result == null) { - logger.error('Block info not yet available.'); - ctx.status = 500; - ctx.body = { message: 'Block info not yet available.' }; + throw new GatewayError('Block info not yet available.'); } else { logger.debug('Returning block info with block height', result.height); ctx.body = { diff --git a/src/gateway/router/routes/contractDataRoute.ts b/src/gateway/router/routes/contractDataRoute.ts deleted file mode 100644 index 34236fc7..00000000 --- a/src/gateway/router/routes/contractDataRoute.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Router from '@koa/router'; -import Arweave from 'arweave'; -import { ArweaveWrapper, Benchmark, Tags, WarpLogger } from 'warp-contracts'; -import { decodeTags, getTagByName, isTxIdValid } from '../../../utils'; -import Transaction from 'arweave/node/lib/transaction'; -import { BUNDLR_NODE1_URL } from '../../../constants'; -import { WarpDeployment } from './deployContractRoute'; - -export async function contractDataRoute(ctx: Router.RouterContext) { - const { logger, dbSource, arweave, arweaveWrapper } = ctx; - - const { id } = ctx.params; - - if (!isTxIdValid(id as string)) { - logger.error('Incorrect contract transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract transaction id.' }; - return; - } - - try { - const benchmark = Benchmark.measure(); - logger.debug('ContractDataRoute id: ', id); - - const result: any = await dbSource.raw( - ` - SELECT bundler_contract_tx_id as "bundlerContractTxId", - contract_tx -> 'tags' as "contractTags", - deployment_type as "deploymentType", - bundler_contract_node as "bundlrContractNode" - FROM contracts - WHERE contract_id = ?; - `, - [id] - ); - if (result?.rows[0] == null || result?.rows[0].bundlerContractTxId == null) { - ctx.status = 500; - ctx.body = { message: 'Contract not indexed as bundled.' }; - } else { - let tags: Tags = []; - if (result?.rows[0].contractTags) { - tags = decodeTags(result?.rows[0].contractTags); - } - - const { data, contentType } = await getContractData( - arweave, - logger, - result?.rows[0].bundlerContractTxId, - tags, - arweaveWrapper, - result?.rows[0].deploymentType, - result.rows[0].bundlrContractNode - ); - ctx.body = data; - ctx.set('Content-Type', contentType); - logger.debug('Contract data loaded in', benchmark.elapsed()); - } - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} - -async function getContractData( - arweave: Arweave, - logger: WarpLogger, - id: string, - tags: { name: string; value: string }[], - arweaveWrapper: ArweaveWrapper, - deploymentType: string, - bundlrContractNode: string -) { - let data: ArrayBuffer | Buffer; - - try { - data = await arweaveWrapper.txData(id); - } catch (e) { - logger.error(`Error from Arweave Gateway while loading data: `, e); - - data = await fetch(`${bundlrContractNode}/tx/${id}/data`).then((res) => { - return res.arrayBuffer(); - }); - } - const strData = arweave.utils.bufferToString(data); - - logger.debug('strData', strData); - - if (deploymentType == WarpDeployment.External) { - const contentType = getTagByName(tags, 'Content-Type'); - logger.debug(`Content type for id: ${id}: `, contentType); - return { data: strData, contentType }; - } else { - const tx = new Transaction({ ...JSON.parse(strData) }); - const txData = Buffer.from(tx.data); - const contentType = getContentTypeFromTx(tx); - logger.debug(`Content type for id: ${id}: `, contentType); - return { data: txData, contentType }; - } -} - -function getContentTypeFromTx(tx: Transaction) { - const tagContentType = tx - .get('tags') - // @ts-ignore - .find((tag: BaseObject) => tag.get('name', { decode: true, string: true }) == 'Content-Type'); - - return tagContentType.get('value', { decode: true, string: true }); -} diff --git a/src/gateway/router/routes/contractRoute.ts b/src/gateway/router/routes/contractRoute.ts deleted file mode 100644 index 48bf9ae2..00000000 --- a/src/gateway/router/routes/contractRoute.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -/** - * @deprecated Following route has been replaced with `contractWithSourceRoute` and is not used in the SDK - * anymore. It should be deleted in the future, leaving due to backwards compatibility of the introduction of - * the new endpoint. - */ - -export async function contractRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { id } = ctx.params; - - if (id?.length != 43) { - ctx.body = {}; - return; - } - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT c.contract_id as "txId", - c.src_tx_id as "srcTxId", - (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, - (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", - c.init_state as "initState", - c.owner as "owner", - c.pst_ticker as "pstTicker", - c.pst_name as "pstName", - s.src_wasm_lang as "srcWasmLang", - c.contract_tx as "contractTx", - s.src_tx as "srcTx", - c.testnet as "testnet" - FROM contracts c - JOIN contracts_src s on c.src_tx_id = s.src_tx_id - WHERE contract_id = ?; - `, - [id] - ); - - if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { - ctx.status = 500; - ctx.body = { message: 'Contract not properly indexed.' }; - } else { - ctx.body = result?.rows[0]; - logger.debug('Contract data loaded in', benchmark.elapsed()); - } - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/contractSourceRoute.ts b/src/gateway/router/routes/contractSourceRoute.ts deleted file mode 100644 index f66e7339..00000000 --- a/src/gateway/router/routes/contractSourceRoute.ts +++ /dev/null @@ -1,49 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; -import { isTxIdValid } from '../../../utils'; - -export async function contractSourceRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { id } = ctx.query; - - if (!isTxIdValid(id as string)) { - logger.error('Incorrect contract source transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract source transaction id.' }; - return; - } - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT s.src_tx_id as "srcTxId", - (case when not s.owner = 'error' then s.owner else null end) as "owner", - s.src_content_type as "srcContentType", - (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, - (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", - s.src_wasm_lang as "srcWasmLang", - s.bundler_src_tx_id as "bundlerSrcTxId", - s.src_tx as "srcTx" - FROM contracts_src s - WHERE src_tx_id = ? AND src IS DISTINCT FROM 'error'; - `, - [id] - ); - - if (!result?.rows[0]) { - ctx.status = 500; - ctx.body = { message: 'Could not load contract source.' }; - logger.error('Could not load contract source.'); - } else { - ctx.body = result?.rows[0]; - } - - logger.debug('Source loaded in', benchmark.elapsed()); - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/contractWithSourceRoute.ts b/src/gateway/router/routes/contractWithSourceRoute.ts deleted file mode 100644 index e05a68a9..00000000 --- a/src/gateway/router/routes/contractWithSourceRoute.ts +++ /dev/null @@ -1,65 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; -import { isTxIdValid } from '../../../utils'; - -export async function contractWithSourceRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { txId, srcTxId } = ctx.query; - - if (!isTxIdValid(txId as string)) { - logger.error('Incorrect contract transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract transaction id.' }; - return; - } - - if (srcTxId && !isTxIdValid(srcTxId as string)) { - logger.error('Incorrect contract source transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract source transaction id.' }; - return; - } - - const bindings: any[] = []; - srcTxId && bindings.push(srcTxId); - bindings.push(txId); - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT c.contract_id as "txId", - c.bundler_contract_tx_id as "bundlerTxId", - s.src_tx_id as "srcTxId", - (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, - (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", - c.init_state as "initState", - c.owner as "owner", - c.pst_ticker as "pstTicker", - c.pst_name as "pstName", - s.src_wasm_lang as "srcWasmLang", - c.contract_tx as "contractTx", - s.src_tx as "srcTx", - c.testnet as "testnet", - c.manifest as "manifest" - FROM contracts c - ${srcTxId ? 'JOIN contracts_src s on ? = s.src_tx_id' : 'JOIN contracts_src s on c.src_tx_id = s.src_tx_id'} - WHERE contract_id = ?; - `, - bindings - ); - - if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { - ctx.status = 500; - ctx.body = { message: 'Contract not properly indexed.' }; - } else { - ctx.body = result?.rows[0]; - logger.debug('Contract data loaded in', benchmark.elapsed()); - } - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/contractWithSourceRoute_v2.ts b/src/gateway/router/routes/contractWithSourceRoute_v2.ts deleted file mode 100644 index 6749d3c8..00000000 --- a/src/gateway/router/routes/contractWithSourceRoute_v2.ts +++ /dev/null @@ -1,72 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; -import { isTxIdValid } from '../../../utils'; - -export async function contractWithSourceRoute_v2(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { txId } = ctx.query; - - if (!isTxIdValid(txId as string)) { - logger.error('Incorrect contract transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract transaction id.' }; - return; - } - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT c.contract_id as "txId", - c.bundler_contract_tx_id as "bundlerTxId", - s.src_tx_id as "srcTxId", - (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, - (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", - c.init_state as "initState", - c.owner as "owner", - c.pst_ticker as "pstTicker", - c.pst_name as "pstName", - s.src_wasm_lang as "srcWasmLang", - c.contract_tx as "contractTx", - s.src_tx as "srcTx", - c.testnet as "testnet", - c.manifest as "manifest", - c.block_timestamp as "blockTimestamp" - FROM contracts c - JOIN contracts_src s on c.src_tx_id = s.src_tx_id - WHERE contract_id = ?; - `, - txId - ); - - const srcResult = await dbSource.raw( - ` - SELECT s.src_tx_id as "srcTxId", - i.sort_key as "sortKey", - i.block_timestamp as "blockTimestamp", - (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, - (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", - s.src_wasm_lang as "srcWasmLang" - FROM interactions i - JOIN contracts_src s on s.src_tx_id = i.evolve - WHERE i.evolve IS NOT NULL and i.contract_id = ? ORDER BY i.sort_key DESC;`, - txId - ); - - if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { - ctx.status = 500; - ctx.body = { message: 'Contract not properly indexed.' }; - } else { - ctx.body = { - ...result?.rows[0], - evolvedSrc: srcResult.rows, - }; - logger.debug('Contract data loaded in', benchmark.elapsed()); - } - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/contracts/contractDataRoute.ts b/src/gateway/router/routes/contracts/contractDataRoute.ts new file mode 100644 index 00000000..c34ce1c3 --- /dev/null +++ b/src/gateway/router/routes/contracts/contractDataRoute.ts @@ -0,0 +1,101 @@ +import Router from '@koa/router'; +import Arweave from 'arweave'; +import { ArweaveWrapper, Benchmark, Tags, WarpLogger } from 'warp-contracts'; +import { decodeTags, getTagByName, isTxIdValid } from '../../../../utils'; +import Transaction from 'arweave/node/lib/transaction'; +import { WarpDeployment } from '../deploy/deployContractRoute'; +import { GatewayError } from '../../../errorHandlerMiddleware'; + +export async function contractDataRoute(ctx: Router.RouterContext) { + const { logger, dbSource, arweave, arweaveWrapper } = ctx; + + const { id } = ctx.params; + + if (!isTxIdValid(id as string)) { + throw new GatewayError('Incorrect contract transaction id.', 403); + } + + const benchmark = Benchmark.measure(); + logger.debug('ContractDataRoute id: ', id); + + const result: any = await dbSource.raw( + ` + SELECT bundler_contract_tx_id as "bundlerContractTxId", + contract_tx -> 'tags' as "contractTags", + deployment_type as "deploymentType", + bundler_contract_node as "bundlrContractNode", + contract_id as "contractId" + FROM contracts + WHERE contract_id = ?; + `, + [id] + ); + if (result?.rows[0] == null || result?.rows[0].bundlerContractTxId == null) { + throw new GatewayError('Contract not indexed as bundled.'); + } else { + let tags: Tags = []; + if (result?.rows[0].contractTags) { + tags = decodeTags(result?.rows[0].contractTags); + } + + const { data, contentType } = await getContractData( + arweave, + logger, + result?.rows[0].contractId, + result?.rows[0].bundlerContractTxId, + tags, + arweaveWrapper, + result?.rows[0].deploymentType, + result.rows[0].bundlrContractNode + ); + ctx.body = data; + ctx.set('Content-Type', contentType); + logger.debug('Contract data loaded in', benchmark.elapsed()); + } +} + +async function getContractData( + arweave: Arweave, + logger: WarpLogger, + id: string, + bundlrId: string, + tags: Tags, + arweaveWrapper: ArweaveWrapper, + deploymentType: string, + bundlrContractNode: string +) { + let data: ArrayBuffer | Buffer; + + const effectiveId = deploymentType == WarpDeployment.Wrapped ? bundlrId : id; + try { + data = await arweaveWrapper.txData(effectiveId); + } catch (e) { + logger.error(`Error from Arweave Gateway while loading data: `, e); + + data = await fetch(`${bundlrContractNode}/tx/${effectiveId}/data`).then((res) => { + return res.arrayBuffer(); + }); + } + + if (deploymentType == WarpDeployment.External || deploymentType == WarpDeployment.Direct) { + const contentType = getTagByName(tags, 'Content-Type'); + logger.debug(`Content type for id: ${id}: `, contentType); + return { data, contentType }; + } else { + const strData = arweave.utils.bufferToString(data); + const tx = new Transaction({ ...JSON.parse(strData) }); + const txData = Buffer.from(tx.data); + const contentType = getContentTypeFromTx(tx); + logger.debug(`Content type for id: ${id}: `, contentType); + return { data: txData, contentType }; + } +} + +function getContentTypeFromTx(tx: Transaction) { + const tagContentType = tx + .get('tags') + // @ts-ignore + .find((tag: BaseObject) => tag.get('name', { decode: true, string: true }) == 'Content-Type'); + + return tagContentType.get('value', { decode: true, string: true }); +} diff --git a/src/gateway/router/routes/contracts/contractRoute.ts b/src/gateway/router/routes/contracts/contractRoute.ts new file mode 100644 index 00000000..7c53f02c --- /dev/null +++ b/src/gateway/router/routes/contracts/contractRoute.ts @@ -0,0 +1,48 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; +import {GatewayError} from "../../../errorHandlerMiddleware"; + +/** + * @deprecated Following route has been replaced with `contractWithSourceRoute` and is not used in the SDK + * anymore. It should be deleted in the future, leaving due to backwards compatibility of the introduction of + * the new endpoint. + */ + +export async function contractRoute(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {id} = ctx.params; + + if (id?.length != 43) { + throw new GatewayError('Wrong transaction id format', 403); + } + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT c.contract_id as "txId", + c.src_tx_id as "srcTxId", + (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, + (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", + c.init_state as "initState", + c.owner as "owner", + c.pst_ticker as "pstTicker", + c.pst_name as "pstName", + s.src_wasm_lang as "srcWasmLang", + c.contract_tx as "contractTx", + s.src_tx as "srcTx", + c.testnet as "testnet" + FROM contracts c + JOIN contracts_src s on c.src_tx_id = s.src_tx_id + WHERE contract_id = ?; + `, + [id] + ); + + if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { + throw new GatewayError('Contract not properly indexed.'); + } else { + ctx.body = result?.rows[0]; + logger.debug('Contract data loaded in', benchmark.elapsed()); + } +} diff --git a/src/gateway/router/routes/contracts/contractSourceRoute.ts b/src/gateway/router/routes/contracts/contractSourceRoute.ts new file mode 100644 index 00000000..e58ea19e --- /dev/null +++ b/src/gateway/router/routes/contracts/contractSourceRoute.ts @@ -0,0 +1,40 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; +import {isTxIdValid} from '../../../../utils'; +import {GatewayError} from "../../../errorHandlerMiddleware"; + +export async function contractSourceRoute(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {id} = ctx.query; + + if (!isTxIdValid(id as string)) { + throw new GatewayError('Incorrect contract source transaction id.', 403); + } + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT s.src_tx_id as "srcTxId", + (case when not s.owner = 'error' then s.owner else null end) as "owner", + s.src_content_type as "srcContentType", + (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, + (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", + s.src_wasm_lang as "srcWasmLang", + s.bundler_src_tx_id as "bundlerSrcTxId", + s.src_tx as "srcTx" + FROM contracts_src s + WHERE src_tx_id = ? + AND src IS DISTINCT FROM 'error'; + `, + [id] + ); + + if (!result?.rows[0]) { + throw new GatewayError('Could not load contract source.', 400); + } else { + ctx.body = result?.rows[0]; + } + + logger.debug('Source loaded in', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/contracts/contractWithSourceRoute.ts b/src/gateway/router/routes/contracts/contractWithSourceRoute.ts new file mode 100644 index 00000000..c51728db --- /dev/null +++ b/src/gateway/router/routes/contracts/contractWithSourceRoute.ts @@ -0,0 +1,55 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; +import {isTxIdValid} from '../../../../utils'; +import {GatewayError} from "../../../errorHandlerMiddleware"; + +export async function contractWithSourceRoute(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {txId, srcTxId} = ctx.query; + + if (!isTxIdValid(txId as string)) { + throw new GatewayError('Incorrect contract transaction id.', 403); + } + + if (srcTxId && !isTxIdValid(srcTxId as string)) { + throw new GatewayError('Incorrect contract source transaction id.', 403); + } + + const bindings: any[] = []; + srcTxId && bindings.push(srcTxId); + bindings.push(txId); + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT c.contract_id as "txId", + c.bundler_contract_tx_id as "bundlerTxId", + c.src_tx_id as "originalSrcTxId", + s.src_tx_id as "srcTxId", + (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, + (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", + c.init_state as "initState", + c.owner as "owner", + c.pst_ticker as "pstTicker", + c.pst_name as "pstName", + s.src_wasm_lang as "srcWasmLang", + c.contract_tx as "contractTx", + s.src_tx as "srcTx", + c.testnet as "testnet", + c.manifest as "manifest" + FROM contracts c + ${srcTxId ? 'JOIN contracts_src s on ? = s.src_tx_id' : 'JOIN contracts_src s on c.src_tx_id = s.src_tx_id'} + WHERE contract_id = ?; + `, + bindings + ); + + if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { + throw new GatewayError('Contract not properly indexed.'); + } else { + ctx.body = result?.rows[0]; + logger.debug('Contract data loaded in', benchmark.elapsed()); + } + +} diff --git a/src/gateway/router/routes/contracts/contractWithSourceRoute_v2.ts b/src/gateway/router/routes/contracts/contractWithSourceRoute_v2.ts new file mode 100644 index 00000000..60e7040d --- /dev/null +++ b/src/gateway/router/routes/contracts/contractWithSourceRoute_v2.ts @@ -0,0 +1,65 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; +import {isTxIdValid} from '../../../../utils'; +import {GatewayError} from "../../../errorHandlerMiddleware"; + +export async function contractWithSourceRoute_v2(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {txId} = ctx.query; + + if (!isTxIdValid(txId as string)) { + throw new GatewayError('Incorrect contract transaction id.', 403); + } + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT c.contract_id as "txId", + c.bundler_contract_tx_id as "bundlerTxId", + s.src_tx_id as "srcTxId", + (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, + (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", + c.init_state as "initState", + c.owner as "owner", + c.pst_ticker as "pstTicker", + c.pst_name as "pstName", + s.src_wasm_lang as "srcWasmLang", + c.contract_tx as "contractTx", + s.src_tx as "srcTx", + c.testnet as "testnet", + c.manifest as "manifest", + c.block_timestamp as "blockTimestamp" + FROM contracts c + JOIN contracts_src s on c.src_tx_id = s.src_tx_id + WHERE contract_id = ?; + `, + txId + ); + + const srcResult = await dbSource.raw( + ` + SELECT s.src_tx_id as "srcTxId", + i.sort_key as "sortKey", + i.block_timestamp as "blockTimestamp", + (case when s.src_content_type = 'application/javascript' then s.src else null end) as src, + (case when s.src_content_type = 'application/wasm' then s.src_binary else null end) as "srcBinary", + s.src_wasm_lang as "srcWasmLang" + FROM interactions i + JOIN contracts_src s on s.src_tx_id = i.evolve + WHERE i.evolve IS NOT NULL + and i.contract_id = ? + ORDER BY i.sort_key DESC;`, + txId + ); + + if (result?.rows[0].src == null && result?.rows[0].srcBinary == null) { + throw new GatewayError('Contract not properly indexed.'); + } else { + ctx.body = { + ...result?.rows[0], + evolvedSrc: srcResult.rows, + }; + logger.debug('Contract data loaded in', benchmark.elapsed()); + } +} diff --git a/src/gateway/router/routes/contractsBySourceRoute.ts b/src/gateway/router/routes/contracts/contractsBySourceRoute.ts similarity index 56% rename from src/gateway/router/routes/contractsBySourceRoute.ts rename to src/gateway/router/routes/contracts/contractsBySourceRoute.ts index bddf704e..2c76bcaa 100644 --- a/src/gateway/router/routes/contractsBySourceRoute.ts +++ b/src/gateway/router/routes/contracts/contractsBySourceRoute.ts @@ -1,13 +1,14 @@ import Router from '@koa/router'; import { Benchmark } from 'warp-contracts'; -import { isTxIdValid } from '../../../utils'; +import { isTxIdValid } from '../../../../utils'; +import { GatewayError } from '../../../errorHandlerMiddleware'; const MAX_INTERACTIONS_PER_PAGE = 5000; export async function contractsBySourceRoute(ctx: Router.RouterContext) { const { logger, dbSource } = ctx; - const { id, page, limit, sort } = ctx.query; + const { id, page, limit, sort, totalInteractions } = ctx.query; const parsedPage = page ? parseInt(page as string) : 1; @@ -17,25 +18,19 @@ export async function contractsBySourceRoute(ctx: Router.RouterContext) { const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; if (!isTxIdValid(id as string)) { - logger.error('Incorrect contract source transaction id.'); - ctx.status = 500; - ctx.body = { message: 'Incorrect contract source transaction id.' }; - return; + throw new GatewayError('Incorrect contract source transaction id.', 403); } - logger.info(`contractsBySourceRoute [ip: ${ctx.request?.ip}, srcId: ${id}]`); - const bindings: any = []; id && bindings.push(id); parsedPage && bindings.push(parsedLimit); parsedPage && bindings.push(offset); id && bindings.push(id); - try { - const benchmark = Benchmark.measure(); + const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` + const result: any = await dbSource.raw( + ` with c as (select contract_id, owner, bundler_contract_tx_id, block_height, block_timestamp from contracts where src_tx_id = ? @@ -46,44 +41,40 @@ export async function contractsBySourceRoute(ctx: Router.RouterContext) { src as (select count(*) AS total from contracts where src_tx_id = ? - and type <> 'error'), + and type <> 'error') + + ${totalInteractions == 'true' ? + `, interactions as (select c.contract_id, count(*) as interactions + from c + join interactions on interactions.contract_id = c.contract_id + group by c.contract_id)` : ''} - interactions as (select c.contract_id, count(*) as interactions - from c - join interactions on interactions.contract_id = c.contract_id - group by c.contract_id) SELECT c.contract_id AS "contractId", c.owner AS "owner", c.bundler_contract_tx_id AS "bundlerTxId", c.block_height AS "blockHeight", c.block_timestamp AS "blockTimestamp", - coalesce(i.interactions, 0) AS "interactions", + ${totalInteractions ? 'coalesce(i.interactions, 0) AS "interactions",' : ''} coalesce(src.total, 0) AS "total" from c - LEFT JOIN interactions i ON c.contract_id = i.contract_id + ${totalInteractions ? 'LEFT JOIN interactions i ON c.contract_id = i.contract_id' : ''} LEFT JOIN src ON TRUE ${sort == 'desc' || sort == 'asc' ? `ORDER BY c.block_height ${sort.toUpperCase()}, c.contract_id` : ''}; `, - bindings - ); + bindings + ); - console.log(result.rows.length); - const total = result?.rows?.length > 0 ? result?.rows[0].total : 0; + const total = result?.rows?.length > 0 ? result?.rows[0].total : 0; - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - contracts: result?.rows, - }; - logger.debug('Source loaded in', benchmark.elapsed()); - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + contracts: result?.rows, + }; + logger.debug('Source loaded in', benchmark.elapsed()); } diff --git a/src/gateway/router/routes/contracts/contractsByTags.ts b/src/gateway/router/routes/contracts/contractsByTags.ts new file mode 100644 index 00000000..f8ff6780 --- /dev/null +++ b/src/gateway/router/routes/contracts/contractsByTags.ts @@ -0,0 +1,123 @@ +import Router from '@koa/router'; +import { Benchmark } from 'warp-contracts'; +import { GatewayError } from '../../../errorHandlerMiddleware'; +import { encodeTag } from '../../../../utils'; + +const MAX_CONTRACTS_PER_PAGE = 100; +const MAX_TAGS_LIST_LENGTH = 5; +const MAX_TAGS_VALUES_LIST_LENGTH = 5; + +export async function contractsByTags(ctx: Router.RouterContext) { + const { logger, dbSource, arweave } = ctx; + + const { tags, owner, page, limit, testnet, srcId } = ctx.query; + + if (!tags) { + throw new GatewayError('Tag parameter must be provided.', 422); + } + + logger.debug('Contracts by tag route', { tags, owner, page, limit }); + + const parsedPage = page ? parseInt(page as string) : 1; + const parsedLimit = limit ? Math.min(parseInt(limit as string), MAX_CONTRACTS_PER_PAGE) : MAX_CONTRACTS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const bindings: any[] = []; + + const parsedTag = JSON.parse(tags as string); + + if (parsedTag.length > 5) { + throw new GatewayError( + `Maximum ${MAX_TAGS_LIST_LENGTH} tags are excepted in the query. Current tags list length: ${parsedTag.length}`, + 422 + ); + } + + if (parsedTag.length < 1) { + throw new GatewayError( + `At least one tag in the list is required. Current tags list length: ${parsedTag.length}`, + 422 + ); + } + + let tagsQuery = ``; + + for (let i = 0; i < parsedTag.length; i++) { + if (parsedTag[i].values.length > 1) { + if (parsedTag[i].values.length > 5) { + throw new GatewayError( + `Tag with name ${parsedTag[i].name} has too many values assigned. Maximum values list length: ${MAX_TAGS_VALUES_LIST_LENGTH}.`, + 422 + ); + } + let partialTagQuery = ` AND (`; + partialTagQuery += + parsedTag[i].values + .map( + (v: string) => `c.contract_tx->'tags' @> '[${JSON.stringify(encodeTag(parsedTag[i].name, v, arweave))}]'` + ) + .join(' OR ') + ')'; + tagsQuery += partialTagQuery; + } else { + const tagEncoded = JSON.stringify(encodeTag(parsedTag[i].name, parsedTag[i].values[0], arweave)); + tagsQuery += ` AND c.contract_tx->'tags' @> '[${tagEncoded}]'`; + } + } + + owner && bindings.push(owner); + srcId && bindings.push(srcId); + parsedPage && bindings.push(parsedLimit); + parsedPage && bindings.push(offset); + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT c.contract_id AS contract, + c.src_tx_id AS srcTxId, + c.owner AS owner, + c.testnet AS testnet, + c.contract_tx AS contractTx, + c.sync_timestamp AS syncTimestamp, + count(*) OVER () AS total + FROM contracts c + WHERE c.contract_id != '' + AND c.type != 'error' + ${testnet ? ' AND c.testnet IS NOT NULL' : ''} + ${owner ? ` AND c.owner = ?` : ''} + ${srcId ? ` AND c.src_tx_id = ?` : ''} + ${tagsQuery} + GROUP BY c.contract_id, c.owner + ORDER BY c.sync_timestamp DESC + ${parsedPage ? ' LIMIT ? OFFSET ?' : ''}; + `, + bindings + ); + + const total = result?.rows?.length > 0 ? parseInt(result.rows[0].total) : 0; + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + contracts: result?.rows.map((r: any) => { + return { + ...r, + contracttx: { + tags: r.contracttx.tags.map((t: any) => { + try { + const name = arweave.utils.b64UrlToString(t.name); + const value = arweave.utils.b64UrlToString(t.value); + return { name, value }; + } catch (e) { + return; + } + }), + }, + }; + }), + }; + logger.debug('Contracts loaded in', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/contracts/contractsRoute.ts b/src/gateway/router/routes/contracts/contractsRoute.ts new file mode 100644 index 00000000..76985fcb --- /dev/null +++ b/src/gateway/router/routes/contracts/contractsRoute.ts @@ -0,0 +1,69 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; + +const MAX_CONTRACTS_PER_PAGE = 100; + +export async function contractsRoute(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {contractType, sourceType, page, limit, testnet} = ctx.query; + + logger.debug('Contracts route', {contractType, sourceType, page, limit}); + + const parsedPage = page ? parseInt(page as string) : 1; + const parsedLimit = limit ? Math.min(parseInt(limit as string), MAX_CONTRACTS_PER_PAGE) : MAX_CONTRACTS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const bindings: any[] = []; + contractType && bindings.push(contractType); + sourceType && bindings.push(sourceType); + parsedPage && bindings.push(parsedLimit); + parsedPage && bindings.push(offset); + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT c.contract_id AS contract, + c.owner AS owner, + c.type AS contract_type, + c.pst_ticker AS pst_ticker, + c.pst_name AS pst_name, + c.testnet AS testnet, + s.src_content_type AS src_content_type, + s.src_wasm_lang AS src_wasm_lang, + count(i.contract_id) AS interactions, + count(case when i.confirmation_status = 'corrupted' then 1 end) AS corrupted, + count(case when i.confirmation_status = 'confirmed' then 1 end) AS confirmed, + max(i.block_height) AS last_interaction_height, + count(*) OVER () AS total + FROM contracts c + LEFT JOIN interactions i + ON c.contract_id = i.contract_id + LEFT JOIN contracts_src s + ON c.src_tx_id = s.src_tx_id + WHERE c.contract_id != '' + AND c.type != 'error' + ${contractType ? ' AND c.type = ?' : ''} + ${sourceType ? ' AND s.src_content_type = ?' : ''} + ${testnet ? ' AND c.testnet IS NOT NULL' : ''} + GROUP BY c.contract_id, c.owner, c.type, c.pst_ticker, c.pst_name, s.src_content_type, s.src_wasm_lang + ORDER BY last_interaction_height DESC NULLS LAST, interactions DESC NULLS LAST ${ + parsedPage ? ' LIMIT ? OFFSET ?' : '' + }; + `, + bindings + ); + + const total = result?.rows?.length > 0 ? parseInt(result.rows[0].total) : 0; + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + contracts: result?.rows, + }; + logger.debug('Contracts loaded in', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/contractsRoute.ts b/src/gateway/router/routes/contractsRoute.ts deleted file mode 100644 index 79730490..00000000 --- a/src/gateway/router/routes/contractsRoute.ts +++ /dev/null @@ -1,75 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -const MAX_CONTRACTS_PER_PAGE = 100; - -export async function contractsRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { contractType, sourceType, page, limit, testnet } = ctx.query; - - logger.debug('Contracts route', { contractType, sourceType, page, limit }); - - const parsedPage = page ? parseInt(page as string) : 1; - const parsedLimit = limit ? Math.min(parseInt(limit as string), MAX_CONTRACTS_PER_PAGE) : MAX_CONTRACTS_PER_PAGE; - const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; - - const bindings: any[] = []; - contractType && bindings.push(contractType); - sourceType && bindings.push(sourceType); - parsedPage && bindings.push(parsedLimit); - parsedPage && bindings.push(offset); - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT c.contract_id AS contract, - c.owner AS owner, - c.type AS contract_type, - c.pst_ticker AS pst_ticker, - c.pst_name AS pst_name, - c.testnet AS testnet, - s.src_content_type AS src_content_type, - s.src_wasm_lang AS src_wasm_lang, - count(i.contract_id) AS interactions, - count(case when i.confirmation_status = 'corrupted' then 1 end) AS corrupted, - count(case when i.confirmation_status = 'confirmed' then 1 end) AS confirmed, - max(i.block_height) AS last_interaction_height, - count(*) OVER () AS total - FROM contracts c - LEFT JOIN interactions i - ON c.contract_id = i.contract_id - LEFT JOIN contracts_src s - ON c.src_tx_id = s.src_tx_id - WHERE c.contract_id != '' - AND c.type != 'error' - ${contractType ? ' AND c.type = ?' : ''} - ${sourceType ? ' AND s.src_content_type = ?' : ''} - ${testnet ? ' AND c.testnet IS NOT NULL' : ''} - GROUP BY c.contract_id, c.owner, c.type, c.pst_ticker, c.pst_name, s.src_content_type, s.src_wasm_lang - ORDER BY last_interaction_height DESC NULLS LAST, interactions DESC NULLS LAST ${ - parsedPage ? ' LIMIT ? OFFSET ?' : '' - }; - `, - bindings - ); - - const total = result?.rows?.length > 0 ? parseInt(result.rows[0].total) : 0; - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - contracts: result?.rows, - }; - logger.debug('Contracts loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/creatorRoute.ts b/src/gateway/router/routes/creatorRoute.ts index 50ae9f1f..7da3a30f 100644 --- a/src/gateway/router/routes/creatorRoute.ts +++ b/src/gateway/router/routes/creatorRoute.ts @@ -23,55 +23,49 @@ export async function creatorRoute(ctx: Router.RouterContext) { parsedPage && bindings.push(parsedLimit); parsedPage && bindings.push(offset); - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - WITH all_transactions AS (${ - txType != 'contract' - ? `SELECT - interaction_id AS id, - bundler_tx_id AS bundler_id, - block_height, - interaction->'block'->>'timestamp' AS block_timestamp, - 'interaction' AS type - FROM interactions - where owner = ?` - : '' - } - ${!txType ? 'UNION all' : ''} - ${ - txType != 'interaction' - ? `SELECT - contract_id AS id, - bundler_contract_tx_id AS bundler_id, - block_height, - block_timestamp::text AS block_timestamp, - 'contract' AS type - FROM contracts where owner = ?` - : '' - }) - SELECT *, COUNT(*) OVER() AS total FROM all_transactions ORDER BY all_transactions.block_timestamp DESC LIMIT ? OFFSET ?;`, - bindings - ); + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + WITH all_transactions AS (${ + txType != 'contract' + ? `SELECT + interaction_id AS id, + bundler_tx_id AS bundler_id, + block_height, + interaction->'block'->>'timestamp' AS block_timestamp, + 'interaction' AS type + FROM interactions + where owner = ?` + : '' + } + ${!txType ? 'UNION all' : ''} + ${ + txType != 'interaction' + ? `SELECT + contract_id AS id, + bundler_contract_tx_id AS bundler_id, + block_height, + block_timestamp::text AS block_timestamp, + 'contract' AS type + FROM contracts where owner = ?` + : '' + }) + SELECT *, COUNT(*) OVER() AS total FROM all_transactions ORDER BY all_transactions.block_timestamp DESC LIMIT ? OFFSET ?;`, + bindings + ); - const total = result?.rows?.length > 0 ? parseInt(result.rows[0].total) : 0; + const total = result?.rows?.length > 0 ? parseInt(result.rows[0].total) : 0; - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - transactions: result?.rows, - }; + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + transactions: result?.rows, + }; - logger.debug(`Owner's transactions loaded in ${benchmark.elapsed()}`); - } catch (e: any) { - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + logger.debug(`Owner's transactions loaded in ${benchmark.elapsed()}`); } diff --git a/src/gateway/router/routes/dashboardRoute.ts b/src/gateway/router/routes/dashboardRoute.ts index 0b0f21e3..b268d309 100644 --- a/src/gateway/router/routes/dashboardRoute.ts +++ b/src/gateway/router/routes/dashboardRoute.ts @@ -12,72 +12,66 @@ export async function dashboardRoute(ctx: Router.RouterContext) { contractLimit && bindings.push(contractLimit); interactionLimit && bindings.push(interactionLimit); - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - with contract as (select 'contract' AS contract_or_interaction, - contract_id AS contract_id, - '' AS interaction_id, - owner AS owner, - type AS contract_type, - '' AS function, - block_height AS block_height, - sync_timestamp AS sync_timestamp, - '' AS sort_key, - CASE contracts.deployment_type - WHEN 'warp-external' THEN 'warp' - WHEN 'warp-direct' THEN 'warp' - WHEN 'warp-wrapped' THEN 'warp' - ELSE contracts.deployment_type - END AS source - from contracts + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + with contract as (select 'contract' AS contract_or_interaction, + contract_id AS contract_id, + '' AS interaction_id, + owner AS owner, + type AS contract_type, + '' AS function, + block_height AS block_height, + sync_timestamp AS sync_timestamp, + '' AS sort_key, + CASE contracts.deployment_type + WHEN 'warp-external' THEN 'warp' + WHEN 'warp-direct' THEN 'warp' + WHEN 'warp-wrapped' THEN 'warp' + ELSE contracts.deployment_type + END AS source + from contracts + where contract_id != '' + AND type != 'error' + AND testnet IS ${testnet ? ' NOT NULL ' : ' NULL '} + AND sync_timestamp IS NOT NULL + order by sync_timestamp DESC + LIMIT ${contractLimit ? ' ? ' : ' 100'} + ), + interaction as (select 'interaction' AS contract_or_interaction, + contract_id AS contract_id, + interaction_id AS interaction_id, + owner AS owner, + '' AS contract_type, + function AS function, + block_height AS block_height, + sync_timestamp AS sync_timestamp, + sort_key AS sort_key, + CASE source + WHEN 'redstone-sequencer' THEN 'sequencer' + ELSE source + END AS source + from interactions where contract_id != '' - AND type != 'error' - AND testnet IS ${testnet ? ' NOT NULL ' : ' NULL '} - AND sync_timestamp IS NOT NULL - order by sync_timestamp DESC - LIMIT ${contractLimit ? ' ? ' : ' 100'} - ), - interaction as (select 'interaction' AS contract_or_interaction, - contract_id AS contract_id, - interaction_id AS interaction_id, - owner AS owner, - '' AS contract_type, - function AS function, - block_height AS block_height, - sync_timestamp AS sync_timestamp, - sort_key AS sort_key, - CASE source - WHEN 'redstone-sequencer' THEN 'sequencer' - ELSE source - END AS source - from interactions - where contract_id != '' - AND testnet IS ${testnet ? ' NOT NULL ' : ' NULL '} - AND sync_timestamp IS NOT NULL - order by sync_timestamp desc - LIMIT ${interactionLimit ? ' ? ' : ' 100'} - ) - select * from contract - union all - select * from interaction; - `, - bindings - ); + AND testnet IS ${testnet ? ' NOT NULL ' : ' NULL '} + AND sync_timestamp IS NOT NULL + order by sync_timestamp desc + LIMIT ${interactionLimit ? ' ? ' : ' 100'} + ) + select * from contract + union all + select * from interaction; + `, + bindings + ); - ctx.body = { - summary: { - contractLimit: contractLimit, - interactionLimit: interactionLimit, - itemsCount: result?.rows.length, - }, - contracts: result?.rows, - }; - logger.debug('Contracts loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + ctx.body = { + summary: { + contractLimit: contractLimit, + interactionLimit: interactionLimit, + itemsCount: result?.rows.length, + }, + contracts: result?.rows, + }; + logger.debug('Contracts loaded in', benchmark.elapsed()); } diff --git a/src/gateway/router/routes/deployBundledRoute.ts b/src/gateway/router/routes/deploy/deployBundledRoute.ts similarity index 65% rename from src/gateway/router/routes/deployBundledRoute.ts rename to src/gateway/router/routes/deploy/deployBundledRoute.ts index b3a9192a..6f551ffb 100644 --- a/src/gateway/router/routes/deployBundledRoute.ts +++ b/src/gateway/router/routes/deploy/deployBundledRoute.ts @@ -1,43 +1,43 @@ import Router from '@koa/router'; -import { evalType } from '../../tasks/contractsMetadata'; -import { BUNDLR_NODE1_URL } from '../../../constants'; +import { BUNDLR_NODE1_URL } from '../../../../constants'; import { DataItem } from 'arbundles'; import rawBody from 'raw-body'; -import { getCachedNetworkData } from '../../tasks/networkInfoCache'; -import { publishContract, sendNotification } from '../../publisher'; +import { getCachedNetworkData } from '../../../tasks/networkInfoCache'; +import { publishContract, sendNotification } from '../../../publisher'; import { evalManifest, WarpDeployment } from './deployContractRoute'; -import { ContractInsert } from '../../../db/insertInterfaces'; +import { ContractInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; +import { getDataItemWithoutData } from './deployContractRoute_v2'; +import { evalType } from "../../../../utils"; export async function deployBundledRoute(ctx: Router.RouterContext) { const { logger, dbSource, arweave, bundlr } = ctx; let initStateRaw, dataItem; - try { - const rawDataItem: Buffer = await rawBody(ctx.req); - dataItem = new DataItem(rawDataItem); - const isValid = await dataItem.isValid(); - if (!isValid) { - ctx.throw(400, 'Data item binary is not valid.'); - } - - const areContractTagsValid = await verifyContractTags(dataItem, ctx); - if (!areContractTagsValid) { - ctx.throw(400, 'Contract tags are not valid.'); - } + const rawDataItem: Buffer = await rawBody(ctx.req); + dataItem = new DataItem(rawDataItem); + const isValid = await dataItem.isValid(); + if (!isValid) { + throw new GatewayError('Data item binary is not valid.', 400); + } - const bundlrResponse = await bundlr.uploader.uploadTransaction(dataItem, { getReceiptSignature: true }); + const areContractTagsValid = await verifyContractTags(dataItem, ctx); + if (!areContractTagsValid) { + throw new GatewayError('Contract tags are not valid.', 400); + } - if (bundlrResponse.status !== 200 || !bundlrResponse.data.public || !bundlrResponse.data.signature) { - throw new Error( - `Bundlr did not upload transaction correctly. Bundlr responded with status ${bundlrResponse.status}.` - ); - } - - logger.debug('Data item successfully bundled.', { - id: bundlrResponse.data.id, - }); + const bundlrResponse = await bundlr.uploader.uploadTransaction(dataItem, { getReceiptSignature: true }); + if (bundlrResponse.status !== 200 || !bundlrResponse.data.public || !bundlrResponse.data.signature) { + throw new GatewayError( + `Bundlr did not upload transaction correctly. Bundlr responded with status ${bundlrResponse.status}.` + ); + } + logger.debug('Data item successfully bundled.', { + id: bundlrResponse.data.id, + }); + try { const srcTxId = dataItem.tags.find((t) => t.name == 'Contract-Src')!.value; initStateRaw = dataItem.tags.find((t) => t.name == 'Init-State')!.value; const initState = JSON.parse(initStateRaw); @@ -46,8 +46,9 @@ export async function deployBundledRoute(ctx: Router.RouterContext) { const contentType = dataItem.tags.find((t) => t.name == 'Content-Type')!.value; const testnet = getTestnetTag(dataItem.tags); const manifest = evalManifest(dataItem.tags); - const blockHeight = getCachedNetworkData().cachedNetworkInfo.height; - const blockTimestamp = getCachedNetworkData().cachedBlockInfo.timestamp; + const cachedNetworkData = await getCachedNetworkData(dbSource); + const blockHeight = cachedNetworkData.cachedNetworkInfo.height; + const blockTimestamp = cachedNetworkData.cachedBlockInfo.timestamp; const syncTimestamp = Date.now(); const insert: ContractInsert = { @@ -71,7 +72,7 @@ export async function deployBundledRoute(ctx: Router.RouterContext) { }; await dbSource.insertContract(insert); - sendNotification(ctx, bundlrResponse.data.id, { initState, tags: dataItem.tags }); + sendNotification(ctx, bundlrResponse.data.id, { initState, tags: dataItem.tags, srcTxId }); publishContract( ctx, bundlrResponse.data.id, @@ -92,14 +93,11 @@ export async function deployBundledRoute(ctx: Router.RouterContext) { contractTxId: bundlrResponse.data.id, }; } catch (e: any) { - logger.error('Error while inserting bundled transaction.', { + throw new GatewayError(`Error while inserting bundled transaction.`, 500, { dataItemId: dataItem?.id, - contractTx: dataItem?.toJSON(), + contractTx: getDataItemWithoutData(dataItem), initStateRaw: initStateRaw, }); - logger.error(e); - ctx.body = e; - ctx.status = e.status ? e.status : 500; } } diff --git a/src/gateway/router/routes/deployContractRoute.ts b/src/gateway/router/routes/deploy/deployContractRoute.ts similarity index 85% rename from src/gateway/router/routes/deployContractRoute.ts rename to src/gateway/router/routes/deploy/deployContractRoute.ts index 6cafb4b2..87b3a21a 100644 --- a/src/gateway/router/routes/deployContractRoute.ts +++ b/src/gateway/router/routes/deploy/deployContractRoute.ts @@ -1,13 +1,14 @@ import Router, { RouterContext } from '@koa/router'; import Transaction from 'arweave/node/lib/transaction'; import Arweave from 'arweave'; -import { GQLTagInterface, SmartWeaveTags } from 'warp-contracts'; -import { evalType } from '../../tasks/contractsMetadata'; -import { getCachedNetworkData } from '../../tasks/networkInfoCache'; -import { BUNDLR_NODE1_URL } from '../../../constants'; -import { uploadToBundlr } from './sequencerRoute'; -import { publishContract, sendNotification } from '../../publisher'; -import { ContractInsert, ContractSourceInsert } from '../../../db/insertInterfaces'; +import { GQLTagInterface, SMART_WEAVE_TAGS, WARP_TAGS } from 'warp-contracts'; +import { getCachedNetworkData } from '../../../tasks/networkInfoCache'; +import { BUNDLR_NODE1_URL } from '../../../../constants'; +import { uploadToBundlr } from '../sequencerRoute'; +import { publishContract, sendNotification } from '../../../publisher'; +import { ContractInsert, ContractSourceInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; +import { evalType } from '../../../../utils'; /* - warp-wrapped - contract or source is wrapped in another transaction - it is posted by Warp Gateway to the Bundlr network and sent @@ -62,8 +63,8 @@ export async function deployContractRoute(ctx: Router.RouterContext) { srcTxOwner = srcTagsData.originalAddress; srcTestnet = srcTagsData.testnet; - srcContentType = tagValue(SmartWeaveTags.CONTENT_TYPE, srcTagsData.tags); - srcWasmLang = tagValue(SmartWeaveTags.WASM_LANG, srcTagsData.tags); + srcContentType = tagValue(SMART_WEAVE_TAGS.CONTENT_TYPE, srcTagsData.tags); + srcWasmLang = tagValue(WARP_TAGS.WASM_LANG, srcTagsData.tags); if (srcContentType == 'application/javascript') { src = Arweave.utils.bufferToString(srcTx.data); } else { @@ -77,7 +78,7 @@ export async function deployContractRoute(ctx: Router.RouterContext) { bundled_tx_id: bundlerSrcTxId, }); } else { - srcTxId = tagValue(SmartWeaveTags.CONTRACT_SRC_TX_ID, contractTags); + srcTxId = tagValue(SMART_WEAVE_TAGS.CONTRACT_SRC_TX_ID, contractTags); if (!srcTxId) { throw new Error('SrcTxId not defined'); } @@ -95,15 +96,16 @@ export async function deployContractRoute(ctx: Router.RouterContext) { bundled_tx_id: bundlerContractTx.id, }); - let initStateRaw = tagValue(SmartWeaveTags.INIT_STATE, contractTags); + let initStateRaw = tagValue(WARP_TAGS.INIT_STATE, contractTags); if (!initStateRaw) { initStateRaw = Arweave.utils.bufferToString(contractTx.data); } const initState = JSON.parse(initStateRaw); const type = evalType(initState); const manifest = evalManifest(contractTags); - const blockHeight = getCachedNetworkData().cachedNetworkInfo.height; - const blockTimestamp = getCachedNetworkData().cachedBlockInfo.timestamp; + const cachedNetworkData = await getCachedNetworkData(dbSource); + const blockHeight = cachedNetworkData.cachedNetworkInfo.height; + const blockTimestamp = cachedNetworkData.cachedBlockInfo.timestamp; const syncTimestamp = Date.now(); const insert: ContractInsert = { @@ -116,7 +118,7 @@ export async function deployContractRoute(ctx: Router.RouterContext) { pst_name: type == 'pst' ? initState?.name : null, block_height: blockHeight, block_timestamp: blockTimestamp, - content_type: tagValue(SmartWeaveTags.CONTENT_TYPE, contractTags), + content_type: tagValue(SMART_WEAVE_TAGS.CONTENT_TYPE, contractTags), contract_tx: { tags: contractTx.toJSON().tags }, bundler_contract_tx_id: bundlerContractTx.id, bundler_contract_node: BUNDLR_NODE1_URL, @@ -147,7 +149,7 @@ export async function deployContractRoute(ctx: Router.RouterContext) { await dbSource.insertContractSource(contracts_src_insert); } - sendNotification(ctx, contractTx.id, { initState, tags: contractTags }); + sendNotification(ctx, contractTx.id, { initState, tags: contractTags, srcTxId }); publishContract( ctx, contractTx.id, @@ -169,10 +171,7 @@ export async function deployContractRoute(ctx: Router.RouterContext) { bundleSrcId: bundlerSrcTxId, }; } catch (e) { - logger.error('Error while inserting bundled transaction'); - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; + throw new GatewayError(`Error while inserting bundled transaction: ${e}`); } } diff --git a/src/gateway/router/routes/deployContractRoute_v2.ts b/src/gateway/router/routes/deploy/deployContractRoute_v2.ts similarity index 82% rename from src/gateway/router/routes/deployContractRoute_v2.ts rename to src/gateway/router/routes/deploy/deployContractRoute_v2.ts index c47abdd4..28c82a9e 100644 --- a/src/gateway/router/routes/deployContractRoute_v2.ts +++ b/src/gateway/router/routes/deploy/deployContractRoute_v2.ts @@ -1,16 +1,17 @@ import Router from '@koa/router'; -import { evalType } from '../../tasks/contractsMetadata'; -import { BUNDLR_NODE1_URL } from '../../../constants'; +import { BUNDLR_NODE1_URL } from '../../../../constants'; import { Bundle, DataItem } from 'arbundles'; -import { ContractSource, sleep, SmartWeaveTags } from 'warp-contracts'; -import { getCachedNetworkData } from '../../tasks/networkInfoCache'; -import { publishContract, sendNotification } from '../../publisher'; +import { SMART_WEAVE_TAGS, Tags, WARP_TAGS } from 'warp-contracts'; +import { getCachedNetworkData } from '../../../tasks/networkInfoCache'; +import { publishContract, sendNotification } from '../../../publisher'; import { evalManifest, WarpDeployment } from './deployContractRoute'; import Arweave from 'arweave'; -import { SignatureConfig } from 'arbundles/src/constants'; -import { Contract, utils } from 'ethers'; -import { longTo32ByteArray } from 'arbundles/src/utils'; -import { ContractInsert, ContractSourceInsert } from '../../../db/insertInterfaces'; +import { SignatureConfig } from 'arbundles'; +import { utils } from 'ethers'; +import { longTo32ByteArray } from 'arbundles'; +import { ContractInsert, ContractSourceInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; +import { evalType } from '../../../../utils'; export async function deployContractRoute_v2(ctx: Router.RouterContext) { const { logger, arweave, dbSource } = ctx; @@ -51,7 +52,7 @@ export async function deployContractRoute_v2(ctx: Router.RouterContext) { srcOwner = await determineOwner(srcDataItem, arweave); srcTestnet = getTestnetTag(srcDataItem.tags); srcContentType = srcDataItem.tags.find((t) => t.name == 'Content-Type')!.value; - srcWasmLang = srcDataItem.tags.find((t) => t.name == SmartWeaveTags.WASM_LANG)?.value; + srcWasmLang = srcDataItem.tags.find((t) => t.name == WARP_TAGS.WASM_LANG)?.value; if (srcContentType == 'application/javascript') { src = Arweave.utils.bufferToString(srcDataItem.rawData); } else { @@ -98,8 +99,9 @@ export async function deployContractRoute_v2(ctx: Router.RouterContext) { const contentType = contractDataItem.tags.find((t) => t.name == 'Content-Type')!.value; const testnet = getTestnetTag(contractDataItem.tags); const manifest = evalManifest(contractDataItem.tags); - const blockHeight = getCachedNetworkData().cachedNetworkInfo.height; - const blockTimestamp = getCachedNetworkData().cachedBlockInfo.timestamp; + const cachedNetworkData = await getCachedNetworkData(dbSource); + const blockHeight = cachedNetworkData.cachedNetworkInfo.height; + const blockTimestamp = cachedNetworkData.cachedBlockInfo.timestamp; const syncTimestamp = Date.now(); const insert: ContractInsert = { @@ -124,7 +126,7 @@ export async function deployContractRoute_v2(ctx: Router.RouterContext) { await dbSource.insertContract(insert); - sendNotification(ctx, contractDataItem.id, { initState, tags: contractDataItem.tags }); + sendNotification(ctx, contractDataItem.id, { initState, tags: contractDataItem.tags, srcTxId: srcId }); publishContract( ctx, contractDataItem.id, @@ -149,14 +151,11 @@ export async function deployContractRoute_v2(ctx: Router.RouterContext) { bundlrTxId: bundlrResponse.data.id, }; } catch (e: any) { - logger.error('Error while inserting bundled transaction.', { + throw new GatewayError(`Error while inserting bundled transaction: ${e}.`, 500, { dataItemId: contractDataItem?.id, - contract: contractDataItem?.toJSON(), + contract: getDataItemWithoutData(contractDataItem), initStateRaw: initStateRaw, }); - logger.error(e); - ctx.body = e; - ctx.status = e.status ? e.status : 500; } } @@ -164,9 +163,9 @@ export async function verifyDeployTags(dataItem: DataItem, opts?: { contract: bo const tags = dataItem.tags; const deployTags = [ - { name: SmartWeaveTags.APP_NAME, value: opts?.contract ? 'SmartWeaveContract' : 'SmartWeaveContractSource' }, - { name: SmartWeaveTags.APP_VERSION, value: '0.3.0' }, - { name: SmartWeaveTags.SDK, value: 'Warp' }, + { name: SMART_WEAVE_TAGS.APP_NAME, value: opts?.contract ? 'SmartWeaveContract' : 'SmartWeaveContractSource' }, + { name: SMART_WEAVE_TAGS.APP_VERSION, value: '0.3.0' }, + { name: SMART_WEAVE_TAGS.SDK, value: 'Warp' }, ]; const contractNameTags = ['Contract-Src', 'Nonce']; @@ -187,11 +186,13 @@ export function getTestnetTag(tags: { name: string; value: string }[]) { } } -export async function determineOwner(dataItem: DataItem, arweave: Arweave) { +export async function determineOwner(dataItem: DataItem, arweave: Arweave): Promise { if (dataItem.signatureType == SignatureConfig.ARWEAVE) { return await arweave.wallets.ownerToAddress(dataItem.owner); } else if (dataItem.signatureType == SignatureConfig.ETHEREUM) { return utils.computeAddress(utils.hexlify(dataItem.rawOwner)); + } else { + throw new Error(`Signature type: ${dataItem.signatureType} is not supported.`); } } @@ -255,3 +256,12 @@ export async function bundleData(dataItems: DataItem[]): Promise { return new Bundle(buffer); } + +export function getDataItemWithoutData(dataItem: DataItem | undefined) { + if (dataItem) { + const { data, ...dataItemWithoutData } = dataItem.toJSON(); + return JSON.stringify(dataItemWithoutData); + } else { + return undefined; + } +} diff --git a/src/gateway/router/routes/deploySourceRoute.ts b/src/gateway/router/routes/deploy/deploySourceRoute.ts similarity index 79% rename from src/gateway/router/routes/deploySourceRoute.ts rename to src/gateway/router/routes/deploy/deploySourceRoute.ts index cf350754..5c97a61c 100644 --- a/src/gateway/router/routes/deploySourceRoute.ts +++ b/src/gateway/router/routes/deploy/deploySourceRoute.ts @@ -1,11 +1,12 @@ import Router from '@koa/router'; import Transaction from 'arweave/node/lib/transaction'; import Arweave from 'arweave'; -import { SmartWeaveTags } from 'warp-contracts'; -import { BUNDLR_NODE1_URL } from '../../../constants'; -import { uploadToBundlr } from './sequencerRoute'; +import { SMART_WEAVE_TAGS, WARP_TAGS } from 'warp-contracts'; +import { BUNDLR_NODE1_URL } from '../../../../constants'; +import { uploadToBundlr } from '../sequencerRoute'; import { prepareTags, tagValue, verifyEvmSignature, WarpDeployment } from './deployContractRoute'; -import { ContractSourceInsert } from '../../../db/insertInterfaces'; +import { ContractSourceInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; export async function deploySourceRoute(ctx: Router.RouterContext) { const { logger, arweave, bundlr, dbSource } = ctx; @@ -24,8 +25,8 @@ export async function deploySourceRoute(ctx: Router.RouterContext) { srcTxOwner = srcTagsData.originalAddress; srcTestnet = srcTagsData.testnet; - srcContentType = tagValue(SmartWeaveTags.CONTENT_TYPE, srcTagsData.tags); - srcWasmLang = tagValue(SmartWeaveTags.WASM_LANG, srcTagsData.tags); + srcContentType = tagValue(SMART_WEAVE_TAGS.CONTENT_TYPE, srcTagsData.tags); + srcWasmLang = tagValue(WARP_TAGS.WASM_LANG, srcTagsData.tags); if (srcContentType == 'application/javascript') { src = Arweave.utils.bufferToString(srcTx.data); } else { @@ -66,9 +67,6 @@ export async function deploySourceRoute(ctx: Router.RouterContext) { bundlrSrcTxId, }; } catch (e) { - logger.error('Error while inserting bundled source transaction'); - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; + throw new GatewayError(`Error while inserting bundled source transaction ${e}`); } } diff --git a/src/gateway/router/routes/deploySourceRoute_v2.ts b/src/gateway/router/routes/deploy/deploySourceRoute_v2.ts similarity index 65% rename from src/gateway/router/routes/deploySourceRoute_v2.ts rename to src/gateway/router/routes/deploy/deploySourceRoute_v2.ts index fc9fe02b..210f7db8 100644 --- a/src/gateway/router/routes/deploySourceRoute_v2.ts +++ b/src/gateway/router/routes/deploy/deploySourceRoute_v2.ts @@ -1,41 +1,42 @@ import Router from '@koa/router'; import Arweave from 'arweave'; -import { SmartWeaveTags } from 'warp-contracts'; -import { BUNDLR_NODE1_URL } from '../../../constants'; +import { SMART_WEAVE_TAGS, WARP_TAGS } from 'warp-contracts'; +import { BUNDLR_NODE1_URL } from '../../../../constants'; import { WarpDeployment } from './deployContractRoute'; import rawBody from 'raw-body'; import { DataItem } from 'arbundles'; import { getTestnetTag } from './deployBundledRoute'; -import { bundleAndUpload, determineOwner, verifyDeployTags } from './deployContractRoute_v2'; -import { ContractSourceInsert } from '../../../db/insertInterfaces'; +import { bundleAndUpload, determineOwner, getDataItemWithoutData, verifyDeployTags } from './deployContractRoute_v2'; +import { ContractSourceInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; export async function deploySourceRoute_v2(ctx: Router.RouterContext) { const { logger, arweave, dbSource } = ctx; let dataItem; - try { - const rawDataItem: Buffer = await rawBody(ctx.req); - dataItem = new DataItem(rawDataItem); - logger.debug('New deploy source transaction', dataItem.id); + const rawDataItem: Buffer = await rawBody(ctx.req); + dataItem = new DataItem(rawDataItem); + logger.debug('New deploy source transaction', dataItem.id); - const isValid = await dataItem.isValid(); - if (!isValid) { - ctx.throw(400, 'Source data item binary is not valid.'); - } + const isValid = await dataItem.isValid(); + if (!isValid) { + throw new GatewayError('Source data item binary is not valid.', 400); + } - const areSrcTagsValid = await verifyDeployTags(dataItem); - if (!areSrcTagsValid) { - ctx.throw(400, 'Contract source tags are not valid.'); - } + const areSrcTagsValid = await verifyDeployTags(dataItem); + if (!areSrcTagsValid) { + throw new GatewayError('Contract source tags are not valid.', 400); + } + try { let srcId, srcContentType, src, srcBinary, srcWasmLang, bundlrSrcTxId, srcOwner, srcTestnet, srcBundlrResponse; srcId = dataItem.id; srcOwner = await determineOwner(dataItem, arweave); srcTestnet = getTestnetTag(dataItem.tags); srcContentType = dataItem.tags.find((t) => t.name == 'Content-Type')!.value; - srcWasmLang = dataItem.tags.find((t) => t.name == SmartWeaveTags.WASM_LANG)?.value; + srcWasmLang = dataItem.tags.find((t) => t.name == WARP_TAGS.WASM_LANG)?.value; if (srcContentType == 'application/javascript') { src = Arweave.utils.bufferToString(dataItem.rawData); } else { @@ -75,12 +76,9 @@ export async function deploySourceRoute_v2(ctx: Router.RouterContext) { bundlrSrcTxId, }; } catch (e) { - logger.error('Error while inserting bundled transaction.', { + throw new GatewayError(`Error while inserting bundled transaction: ${e}.`, 500, { dataItemId: dataItem?.id, - contractTx: dataItem?.toJSON(), + contractTx: getDataItemWithoutData(dataItem), }); - logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; } } diff --git a/src/gateway/router/routes/deploy/registerContractRoute.ts b/src/gateway/router/routes/deploy/registerContractRoute.ts new file mode 100644 index 00000000..211825aa --- /dev/null +++ b/src/gateway/router/routes/deploy/registerContractRoute.ts @@ -0,0 +1,228 @@ +import Router from '@koa/router'; +import { getCachedNetworkData } from '../../../tasks/networkInfoCache'; +import { publishContract, sendNotification } from '../../../publisher'; +import { evalManifest, WarpDeployment } from './deployContractRoute'; +import { Tag } from 'arweave/node/lib/transaction'; +import { stringToB64Url } from 'arweave/node/lib/utils'; +import { fetch } from 'undici'; +import { backOff } from 'exponential-backoff'; +import { getTestnetTag } from './deployBundledRoute'; +import { ContractInsert } from '../../../../db/insertInterfaces'; +import { GatewayError } from '../../../errorHandlerMiddleware'; +import { evalType } from "../../../../utils"; + +const ARWEAVE_QUERY = `query Transaction($ids: [ID!]) { + transactions(ids: $ids) { + edges { + node { + id + owner { address } + tags { + name + value + } + signature + } + } + } + }`; + +const REGISTER_PROVIDER = ['node1', 'node2', 'arweave'] as const; +type RegisterProviderType = typeof REGISTER_PROVIDER[number]; + +export async function registerContractRoute(ctx: Router.RouterContext) { + const { logger, dbSource } = ctx; + + let initStateRaw = ''; + let contractTx = null; + let txId = ''; + + const registerProvider = ctx.request.body.registerProvider || ctx.request.body.bundlrNode; + + if (!registerProvider || !isRegisterProvider(registerProvider)) { + throw new GatewayError( + `Invalid register type. Should be equal to one of the following values: ${REGISTER_PROVIDER.map((n) => n).join( + ', ' + )}, found: ${registerProvider}.`, + 400 + ); + } + + txId = ctx.request.body.id; + + const txMetadata: { tags: Tag[]; address: string; signature: string } = + registerProvider == 'arweave' + ? await getArweaveGqlMetadata(txId) + : await getBundlrNetworkMetadata(txId, registerProvider); + + const tags = txMetadata.tags; + const contractTagsIncluded = await verifyContractTags(tags); + if (!contractTagsIncluded) { + throw new GatewayError('Bundlr transaction is not valid contract transaction.', 400); + } + + logger.debug('Contract transaction marked as valid contract transaction.'); + + let encodedTags: Tag[] = []; + + for (const tag of tags) { + try { + encodedTags.push(new Tag(stringToB64Url(tag.name), stringToB64Url(tag.value))); + } catch (e: any) { + throw new GatewayError(`Unable to encode tag ${tag.name}: ${e.status}`, 400); + } + } + + try { + const srcTxId = tags.find((t: Tag) => t.name == 'Contract-Src')!.value; + initStateRaw = tags.find((t: Tag) => t.name == 'Init-State')!.value; + const initState = JSON.parse(initStateRaw); + const type = evalType(initState); + const ownerAddress = txMetadata.address; + const contentType = tags.find((t: Tag) => t.name == 'Content-Type')!.value; + const testnet = getTestnetTag(tags); + const manifest = evalManifest(tags); + const cachedNetworkData = await getCachedNetworkData(dbSource); + const blockHeight = cachedNetworkData.cachedNetworkInfo.height; + const blockTimestamp = cachedNetworkData.cachedBlockInfo.timestamp; + const syncTimestamp = Date.now(); + + contractTx = { + id: txId, + owner: ownerAddress, + data: null, + signature: txMetadata.signature, + target: '', + tags: encodedTags, + }; + + const insert: ContractInsert = { + contract_id: txId, + src_tx_id: srcTxId, + init_state: initState, + owner: ownerAddress, + type: type, + pst_ticker: type == 'pst' ? initState?.ticker : null, + pst_name: type == 'pst' ? initState?.name : null, + block_height: blockHeight, + block_timestamp: blockTimestamp, + content_type: contentType, + contract_tx: { tags: contractTx.tags }, + bundler_contract_tx_id: txId, + bundler_contract_node: ['node1', 'node2'].includes(registerProvider) + ? `https://${registerProvider}.bundlr.network` + : `https://arweave.net`, + testnet, + deployment_type: WarpDeployment.External, + manifest, + sync_timestamp: syncTimestamp, + }; + + await dbSource.insertContract(insert); + + sendNotification(ctx, txId, { initState, tags, srcTxId }); + publishContract( + ctx, + txId, + ownerAddress, + type, + blockHeight, + blockTimestamp, + WarpDeployment.External, + syncTimestamp, + testnet + ); + + logger.info('Contract successfully registered.', { + contractTxId: txId, + }); + + ctx.body = { + contractTxId: txId, + }; + } catch (e: any) { + throw new GatewayError(`Error while registering bundled transaction: ${e}.`, 500, { + txId, + contractTx, + initStateRaw, + }); + } +} + +export async function verifyContractTags(tags: Tag[]) { + const tagsIncluded = [ + { name: 'App-Name', value: 'SmartWeaveContract' }, + { name: 'App-Version', value: '0.3.0' }, + ]; + + const nameTagsIncluded = ['Contract-Src', 'Init-State', 'Content-Type']; + + const contractTagsIncluded = + tagsIncluded.every((ti) => tags.some((t: Tag) => t.name == ti.name && t.value == ti.value)) && + nameTagsIncluded.every((nti) => tags.some((t: Tag) => t.name == nti)); + + return contractTagsIncluded; +} + +export async function getBundlrNetworkMetadata( + id: string, + bundlrNode: string +): Promise<{ tags: Tag[]; address: string; signature: string }> { + let response: any; + const request = async () => { + return fetch(`https://${bundlrNode}.bundlr.network/tx/${id}`).then((res) => { + return res.ok ? res.json() : Promise.reject(res); + }); + }; + try { + response = (await backOff(request, { + delayFirstAttempt: false, + maxDelay: 2000, + numOfAttempts: 5, + })) as any; + } catch (error: any) { + throw new Error(`Unable to retrieve Bundlr network tags response. ${error.status}.`); + } + + return { tags: response.tags, address: response.address, signature: response.signature }; +} + +export async function getArweaveGqlMetadata(id: string): Promise<{ tags: Tag[]; address: string; signature: string }> { + const data = JSON.stringify({ + query: ARWEAVE_QUERY, + variables: { ids: [id] }, + }); + + let response: any; + + const request = async () => { + return fetch(`https://arweave.net/graphql`, { + method: 'POST', + body: data, + headers: { + 'Accept-Encoding': 'gzip, deflate, br', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }).then((res) => { + return res.ok ? res.json() : Promise.reject(res); + }); + }; + try { + response = ( + (await backOff(request, { + delayFirstAttempt: false, + maxDelay: 2000, + numOfAttempts: 5, + })) as any + ).data.transactions.edges[0].node; + } catch (error: any) { + throw new Error(`Unable to retrieve Arweave gql response. ${error.status}.`); + } + + return { tags: response.tags, address: response.owner.address, signature: response.signature }; +} + +export function isRegisterProvider(value: string): value is RegisterProviderType { + return REGISTER_PROVIDER.includes(value as RegisterProviderType); +} diff --git a/src/gateway/router/routes/gcpAliveRoute.ts b/src/gateway/router/routes/gcpAliveRoute.ts new file mode 100644 index 00000000..3c76b626 --- /dev/null +++ b/src/gateway/router/routes/gcpAliveRoute.ts @@ -0,0 +1,40 @@ +import Router from '@koa/router'; +import { getCachedNetworkData } from '../../tasks/networkInfoCache'; + +export async function gcpAliveRoute(ctx: Router.RouterContext) { + const arBlockHeight = (await getCachedNetworkData(ctx.dbSource)).cachedBlockInfo.height; + + ctx.body = { + gateway: 'ok', + db: await dbAccessible(ctx, arBlockHeight), + ...(arBlockHeight && { ar_block_height: arBlockHeight }), + }; +} + +async function dbAccessible(ctx: Router.RouterContext, arBlockHeight: number | null) { + const { dbSource } = ctx; + + if (dbSource.healthCheckEnabled()) { + try { + const result = await dbSource + .healthCheck( + `select max(block_height) as l1_max_bh + from interactions + where source = 'arweave';` + ) + .timeout(500, { cancel: true }); + return { + up: 1, + l1_last_interaction_height: result.rows[0].l1_max_bh, + ...(arBlockHeight && { l1_interaction_height_diff: arBlockHeight - result.rows[0].l1_max_bh }), + }; + } catch (e: any) { + return { + up: 0, + error: e.message, + }; + } + } + + return 'health check disabled'; +} diff --git a/src/gateway/router/routes/interactionRoute.ts b/src/gateway/router/routes/interactionRoute.ts deleted file mode 100644 index 5ed6ad09..00000000 --- a/src/gateway/router/routes/interactionRoute.ts +++ /dev/null @@ -1,43 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -export async function interactionRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - const { id } = ctx.params; - - if (id?.length != 43) { - ctx.body = {}; - return; - } - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT interaction_id as interactionId, - bundler_tx_id as bundlerTxId, - interaction as interaction, - block_height as blockHeight, - block_id as blockId, - contract_id as contractId, - function as function, - input as input, - confirmation_status as confirmationStatus, - confirming_peer as confirmingPeer, - confirmed_at_height as confirmedAtHeight, - confirmations as confirmations, - sort_key as sortKey - FROM interactions - WHERE interaction_id = ?; - `, - [id] - ); - ctx.body = result?.rows[0]; - logger.debug('Contract data loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/interactions/interactionRoute.ts b/src/gateway/router/routes/interactions/interactionRoute.ts new file mode 100644 index 00000000..0c82c642 --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionRoute.ts @@ -0,0 +1,37 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; +import {GatewayError} from "../../../errorHandlerMiddleware"; + +export async function interactionRoute(ctx: Router.RouterContext) { + const {logger, dbSource} = ctx; + + const {id} = ctx.params; + + if (id?.length != 43) { + throw new GatewayError('Incorrect contract source transaction id.', 403); + } + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT interaction_id as interactionId, + bundler_tx_id as bundlerTxId, + interaction as interaction, + block_height as blockHeight, + block_id as blockId, + contract_id as contractId, + function as function, + input as input, + confirmation_status as confirmationStatus, + confirming_peer as confirmingPeer, + confirmed_at_height as confirmedAtHeight, + confirmations as confirmations, + sort_key as sortKey + FROM interactions + WHERE interaction_id = ?; + `, + [id] + ); + ctx.body = result?.rows[0]; + logger.debug('Contract data loaded in', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/interactions/interactionsRoute.ts b/src/gateway/router/routes/interactions/interactionsRoute.ts new file mode 100644 index 00000000..affc8d8e --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionsRoute.ts @@ -0,0 +1,110 @@ +import Router from '@koa/router'; + +const MAX_INTERACTIONS_PER_PAGE = 5000; + +export async function interactionsRoute(ctx: Router.RouterContext) { + const {dbSource} = ctx; + + const {contractId, confirmationStatus, page, limit, from, to, totalCount, source, upToTransactionId, minimize} = + ctx.query; + + const parsedPage = page ? parseInt(page as string) : 1; + + const parsedLimit = limit + ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) + : MAX_INTERACTIONS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const parsedConfirmationStatus = confirmationStatus + ? confirmationStatus == 'not_corrupted' + ? ['confirmed', 'not_processed'] + : [confirmationStatus] + : undefined; + + const shouldMinimize = minimize === 'true'; + + const bindings: any[] = []; + bindings.push(contractId); + bindings.push(contractId); + // cannot use IN with bindings https://github.com/knex/knex/issues/791 + // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) + from && bindings.push(from as string); + to && bindings.push(to as string); + source && bindings.push(source as string); + upToTransactionId && bindings.push(upToTransactionId as string); + parsedPage && bindings.push(parsedLimit); + parsedPage && bindings.push(offset); + + const result: any = await dbSource.raw( + ` + SELECT interaction, + confirmation_status + ${shouldMinimize ? '' : ',confirming_peer, confirmations, bundler_tx_id '} + ${shouldMinimize ? '' : ',count(*) OVER () AS total'} + FROM interactions + WHERE (contract_id = ? OR interact_write @> ARRAY [?]) + ${ + parsedConfirmationStatus + ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` + : '' + } ${from ? ' AND block_height >= ?' : ''} ${to ? ' AND block_height <= ?' : ''} ${source ? `AND source = ?` : ''} ${upToTransactionId ? `AND id <= (SELECT id FROM interactions WHERE interaction_id = ?)` : ''} + ORDER BY block_height ${shouldMinimize ? 'ASC' : 'DESC'}, interaction_id DESC ${ + parsedPage ? ' LIMIT ? OFFSET ?' : '' + }; + `, + bindings + ); + + const totalInteractions: any = + totalCount == 'true' && + (await dbSource.raw( + ` + SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, + count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, + count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, + count(case when confirmation_status = 'forked' then 1 else null end) AS forked + FROM interactions + WHERE contract_id = ?; + `, + contractId + )); + + const total = result?.rows?.length > 0 ? result?.rows[0].total : 0; + + const mappedInteractions = shouldMinimize + ? result?.rows?.map((r: any) => ({ + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + }, + confirmationStatus: r.confirmation_status, + })) + : result?.rows?.map((r: any) => ({ + status: r.confirmation_status, + confirming_peers: r.confirming_peer, + confirmations: r.confirmations, + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + }, + })); + + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + ...(totalInteractions && { + total: { + confirmed: totalInteractions?.rows[0].confirmed, + corrupted: totalInteractions?.rows[0].corrupted, + not_processed: totalInteractions?.rows[0].not_processed, + forked: totalInteractions?.rows[0].forked, + }, + }), + interactions: mappedInteractions, + }; +} diff --git a/src/gateway/router/routes/interactions/interactionsSonar.ts b/src/gateway/router/routes/interactions/interactionsSonar.ts new file mode 100644 index 00000000..d7b22616 --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionsSonar.ts @@ -0,0 +1,103 @@ +import Router from '@koa/router'; +import { Benchmark } from 'warp-contracts'; + +const MAX_INTERACTIONS_PER_PAGE = 5000; + +export async function interactionsSonar(ctx: Router.RouterContext) { + const { dbSource, logger } = ctx; + + const { contractId, confirmationStatus, page, limit, from, to, source } = ctx.query; + + const parsedPage = page ? parseInt(page as string) : 1; + + const parsedLimit = limit + ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) + : MAX_INTERACTIONS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const parsedConfirmationStatus = confirmationStatus + ? confirmationStatus == 'not_corrupted' + ? ['confirmed', 'not_processed'] + : [confirmationStatus] + : undefined; + + const bindings: any[] = []; + bindings.push(contractId); + bindings.push(contractId); + from && bindings.push(from as string); + to && bindings.push(to as string); + source && bindings.push(source as string); + parsedPage && bindings.push(parsedLimit); + parsedPage && bindings.push(offset); + + const query = ` + SELECT interaction, + confirmation_status, + sort_key, + confirming_peer, + confirmations, + bundler_tx_id + FROM interactions + WHERE (contract_id = ? OR interact_write @> ARRAY [?]) + ${ + parsedConfirmationStatus + ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` + : '' + } ${from ? ' AND sort_key > ?' : ''} ${to ? ' AND sort_key <= ?' : ''} ${source ? `AND source = ?` : ''} + ORDER BY sort_key DESC ${parsedPage ? ' LIMIT ? OFFSET ?' : ''}; + `; + + const result: any = await dbSource.raw(query, bindings); + + const totalInteractions: any = await dbSource.raw( + ` + SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, + count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, + count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, + count(case when confirmation_status = 'forked' then 1 else null end) AS forked + FROM interactions + WHERE (contract_id = ? OR interact_write @> ARRAY [?]); + `, + [contractId, contractId] + ); + + const total = + parseInt(totalInteractions.rows[0].confirmed) + + parseInt(totalInteractions.rows[0].corrupted) + + parseInt(totalInteractions.rows[0].not_processed) + + parseInt(totalInteractions.rows[0].forked); + + const benchmark = Benchmark.measure(); + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + ...(totalInteractions && { + total: { + confirmed: totalInteractions.rows[0].confirmed, + corrupted: totalInteractions.rows[0].corrupted, + not_processed: totalInteractions.rows[0].not_processed, + forked: totalInteractions.rows[0].forked, + }, + }), + + interactions: result?.rows?.map((r: any) => { + return { + status: r.confirmation_status, + confirming_peers: r.confirming_peer, + confirmations: r.confirmations, + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + sortKey: r.sort_key, + }, + }; + }), + }; + + logger.info('Mapping interactions: ', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/interactions/interactionsSortKeyRoute.ts b/src/gateway/router/routes/interactions/interactionsSortKeyRoute.ts new file mode 100644 index 00000000..ebc0d451 --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionsSortKeyRoute.ts @@ -0,0 +1,118 @@ +import Router from '@koa/router'; +import {Benchmark} from 'warp-contracts'; + +const MAX_INTERACTIONS_PER_PAGE = 5000; + +export async function interactionsSortKeyRoute(ctx: Router.RouterContext) { + const {dbSource, logger} = ctx; + + const {contractId, confirmationStatus, page, limit, from, to, totalCount, source, minimize} = ctx.query; + + const parsedPage = page ? parseInt(page as string) : 1; + + const parsedLimit = limit + ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) + : MAX_INTERACTIONS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const parsedConfirmationStatus = confirmationStatus + ? confirmationStatus == 'not_corrupted' + ? ['confirmed', 'not_processed'] + : [confirmationStatus] + : undefined; + + // 'should minimize' means that we're making a call from the SDK + // this affects: + // 1. the amount of returned data (we're trying to minimize amount of data in this case) + // 2. sorting order (SDK requires ASC order, SonAR requires DESC order) + const shouldMinimize = minimize === 'true'; + + const bindings: any[] = []; + bindings.push(contractId); + bindings.push(contractId); + // cannot use IN with bindings https://github.com/knex/knex/issues/791 + parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) + from && bindings.push(from as string); + to && bindings.push(to as string); + source && bindings.push(source as string); + parsedPage && bindings.push(parsedLimit); + parsedPage && bindings.push(offset); + + const query = ` + SELECT interaction, + confirmation_status, + sort_key + ${shouldMinimize ? '' : ',confirming_peer, confirmations, bundler_tx_id '} + ${shouldMinimize ? '' : ',count(*) OVER () AS total'} + FROM interactions + WHERE (contract_id = ? OR interact_write @> ARRAY [?]) + ${ + parsedConfirmationStatus + ? ` AND confirmation_status IN (` + parsedConfirmationStatus.map((_) => '?').join(',') + `) ` + : '' + } + ${from ? ' AND sort_key > ?' : ''} + ${to ? ' AND sort_key <= ?' : ''} + ${source ? `AND source = ?` : ''} + ORDER BY sort_key ${shouldMinimize ? 'ASC' : 'DESC'} ${parsedPage ? ' LIMIT ? OFFSET ?' : ''}; + `; + + const result: any = await dbSource.raw(query, bindings); + + const totalInteractions: any = + totalCount == 'true' && + (await dbSource.raw( + ` + SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, + count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, + count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, + count(case when confirmation_status = 'forked' then 1 else null end) AS forked + FROM interactions + WHERE contract_id = ?; + `, + contractId + )); + + const total = result?.rows?.length > 0 ? parseInt(result?.rows[0].total) : 0; + + const benchmark = Benchmark.measure(); + const mappedInteractions = shouldMinimize + ? result?.rows?.map((r: any) => ({ + ...r.interaction, + sortKey: r.sort_key, + confirmationStatus: r.confirmation_status, + })) + : result?.rows?.map((r: any) => ({ + status: r.confirmation_status, + confirming_peers: r.confirming_peer, + confirmations: r.confirmations, + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + sortKey: r.sort_key, + }, + })); + + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + ...(totalInteractions && { + total: { + confirmed: totalInteractions?.rows[0].confirmed, + corrupted: totalInteractions?.rows[0].corrupted, + not_processed: totalInteractions?.rows[0].not_processed, + forked: totalInteractions?.rows[0].forked, + }, + }), + // TODO: this mapping here is kinda dumb. + + interactions: mappedInteractions, + }; + + logger.info('Mapping interactions: ', benchmark.elapsed()); +} diff --git a/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v2.ts b/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v2.ts new file mode 100644 index 00000000..e80f1e1b --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v2.ts @@ -0,0 +1,111 @@ +import Router from '@koa/router'; + +const MAX_INTERACTIONS_PER_PAGE = 5000; + +export async function interactionsSortKeyRoute_v2(ctx: Router.RouterContext) { + const { dbSource, logger } = ctx; + + const { contractId, confirmationStatus, page, limit, from, to, totalCount, source, fromSdk } = ctx.query; + + const parsedPage = page ? parseInt(page as string) : 1; + + const parsedLimit = limit + ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) + : MAX_INTERACTIONS_PER_PAGE; + const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; + + const parsedConfirmationStatus = confirmationStatus + ? confirmationStatus == 'not_corrupted' + ? undefined // ['confirmed', 'not_processed'] + : [confirmationStatus] + : undefined; + + // 'isFromSdk' means that we're making a call from the SDK + // this affects: + // 1. the amount of returned data (we're trying to minimize amount of data in this case) + // 2. sorting order (SDK requires ASC order, SonAR requires DESC order) + const isFromSdk = fromSdk === 'true'; + + const bindings: any[] = []; + bindings.push(contractId); + bindings.push(contractId); + // cannot use IN with bindings https://github.com/knex/knex/issues/791 + // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) + from && bindings.push(from as string); + to && bindings.push(to as string); + source && bindings.push(source as string); + bindings.push(parsedLimit); + bindings.push(offset); + + const query = ` + SELECT interaction, + confirmation_status, + sort_key + ${isFromSdk ? '' : ',confirming_peer, confirmations, bundler_tx_id '} + ${isFromSdk ? '' : ',count(*) OVER () AS total'} + FROM interactions + WHERE (contract_id = ? + OR interact_write @> ARRAY [?]) ${ + parsedConfirmationStatus + ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` + : '' + } ${from ? ' AND sort_key > ?' : ''} ${to ? ' AND sort_key <= ?' : ''} ${source ? `AND source = ?` : ''} + ORDER BY sort_key ${isFromSdk ? 'ASC' : 'DESC'} + LIMIT ? OFFSET ?; + `; + + const result: any = await dbSource.raw(query, bindings); + + const totalInteractions: any = + totalCount == 'true' && + (await dbSource.raw( + ` + SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, + count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, + count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, + count(case when confirmation_status = 'forked' then 1 else null end) AS forked + FROM interactions + WHERE contract_id = ?; + `, + contractId + )); + + const total = result?.rows?.length > 0 ? parseInt(result?.rows[0].total) : 0; + + const mappedInteractions = isFromSdk + ? result?.rows?.map((r: any) => ({ + ...r.interaction, + sortKey: r.sort_key, + confirmationStatus: r.confirmation_status, + })) + : result?.rows?.map((r: any) => ({ + status: r.confirmation_status, + confirming_peers: r.confirming_peer, + confirmations: r.confirmations, + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + sortKey: r.sort_key, + }, + })); + + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + page: parsedPage, + pages: Math.ceil(total / parsedLimit), + }, + ...(totalInteractions && { + total: { + confirmed: totalInteractions?.rows[0].confirmed, + corrupted: totalInteractions?.rows[0].corrupted, + not_processed: totalInteractions?.rows[0].not_processed, + forked: totalInteractions?.rows[0].forked, + }, + }), + // TODO: this mapping here is kinda dumb. + interactions: mappedInteractions, + }; +} diff --git a/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v3.ts b/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v3.ts new file mode 100644 index 00000000..9bd1d7aa --- /dev/null +++ b/src/gateway/router/routes/interactions/interactionsSortKeyRoute_v3.ts @@ -0,0 +1,108 @@ +import Router from '@koa/router'; + +const MAX_INTERACTIONS_PER_PAGE = 5000; + +export async function interactionsSortKeyRoute_v3(ctx: Router.RouterContext) { + const { dbSource, logger } = ctx; + + const { contractId, confirmationStatus, limit, from, to, totalCount, source, fromSdk } = ctx.query; + + const parsedLimit = limit + ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) + : MAX_INTERACTIONS_PER_PAGE; + + const parsedConfirmationStatus = confirmationStatus + ? confirmationStatus == 'not_corrupted' + ? undefined // ['confirmed', 'not_processed'] + : [confirmationStatus] + : undefined; + + // 'isFromSdk' means that we're making a call from the SDK + // this affects: + // 1. the amount of returned data (we're trying to minimize amount of data in this case) + // 2. sorting order (SDK requires ASC order, SonAR requires DESC order) + const isFromSdk = fromSdk === 'true'; + + const bindings: any[] = []; + bindings.push(contractId); + bindings.push(contractId); + bindings.push(contractId); + // cannot use IN with bindings https://github.com/knex/knex/issues/791 + // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) + from && bindings.push(from as string); + to && bindings.push(to as string); + source && bindings.push(source as string); + bindings.push(parsedLimit); + + const query = ` + SELECT interaction, + confirmation_status, + sort_key + ${isFromSdk ? '' : ',confirming_peer, confirmations, bundler_tx_id '} + ${isFromSdk ? '' : ',count(*) OVER () AS total'} + FROM interactions + WHERE (contract_id = ? + OR interact_write @> ARRAY [?]) + AND sort_key >= COALESCE((SELECT min_sk FROM contracts_info WHERE contract_id = ?), '0') ${ + parsedConfirmationStatus + ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` + : '' + } ${from ? ' AND sort_key > ?' : ''} ${to ? ' AND sort_key <= ?' : ''} ${source ? `AND source = ?` : ''} + ORDER BY sort_key ${isFromSdk ? 'ASC' : 'DESC'} + LIMIT ?; + `; + + const result: any = await dbSource.raw(query, bindings); + + const totalInteractions: any = + totalCount == 'true' && + (await dbSource.raw( + ` + SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, + count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, + count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, + count(case when confirmation_status = 'forked' then 1 else null end) AS forked + FROM interactions + WHERE contract_id = ?; + `, + contractId + )); + + const total = result?.rows?.length > 0 ? parseInt(result?.rows[0].total) : 0; + + const mappedInteractions = isFromSdk + ? result?.rows?.map((r: any) => ({ + ...r.interaction, + sortKey: r.sort_key, + confirmationStatus: r.confirmation_status, + })) + : result?.rows?.map((r: any) => ({ + status: r.confirmation_status, + confirming_peers: r.confirming_peer, + confirmations: r.confirmations, + interaction: { + ...r.interaction, + bundlerTxId: r.bundler_tx_id, + sortKey: r.sort_key, + }, + })); + + ctx.body = { + paging: { + total, + limit: parsedLimit, + items: result?.rows.length, + pages: Math.ceil(total / parsedLimit), + }, + ...(totalInteractions && { + total: { + confirmed: totalInteractions?.rows[0].confirmed, + corrupted: totalInteractions?.rows[0].corrupted, + not_processed: totalInteractions?.rows[0].not_processed, + forked: totalInteractions?.rows[0].forked, + }, + }), + // TODO: this mapping here is kinda dumb. + interactions: mappedInteractions, + }; +} diff --git a/src/gateway/router/routes/interactionsStreamRoute.ts b/src/gateway/router/routes/interactions/interactionsStreamRoute.ts similarity index 50% rename from src/gateway/router/routes/interactionsStreamRoute.ts rename to src/gateway/router/routes/interactions/interactionsStreamRoute.ts index e1c7fed9..fff83d76 100644 --- a/src/gateway/router/routes/interactionsStreamRoute.ts +++ b/src/gateway/router/routes/interactions/interactionsStreamRoute.ts @@ -1,10 +1,10 @@ import Router from '@koa/router'; -import { stringify } from 'JSONStream'; +import {stringify} from 'JSONStream'; export async function interactionsStreamRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; + const {logger, dbSource} = ctx; - const { contractId, confirmationStatus, from, to } = ctx.query; + const {contractId, confirmationStatus, from, to} = ctx.query; logger.debug('Interactions stream route', { contractId, @@ -24,31 +24,25 @@ export async function interactionsStreamRoute(ctx: Router.RouterContext) { from && bindings.push(from as string); to && bindings.push(to as string); - try { - const result: any = dbSource - .raw( - ` + const result: any = dbSource + .raw( + ` SELECT interaction FROM interactions WHERE contract_id = ? ${ - parsedConfirmationStatus - ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` - : '' + parsedConfirmationStatus + ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` + : '' } ${from ? ' AND block_height >= ?' : ''} ${to ? ' AND block_height <= ?' : ''} ORDER BY sort_key ASC; `, - bindings - ) - .stream() // note: https://www.npmjs.com/package/pg-query-stream is required for stream to work - .pipe(stringify()); - - ctx.set('Content-Type', 'application/json; charset=utf-8'); - ctx.set('Transfer-Encoding', 'chunked'); - - ctx.body = result; - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + bindings + ) + .stream() // note: https://www.npmjs.com/package/pg-query-stream is required for stream to work + .pipe(stringify()); + + ctx.set('Content-Type', 'application/json; charset=utf-8'); + ctx.set('Transfer-Encoding', 'chunked'); + + ctx.body = result; } diff --git a/src/gateway/router/routes/interactionsContractGroupsRoute.ts b/src/gateway/router/routes/interactionsContractGroupsRoute.ts deleted file mode 100644 index 63f5cbe7..00000000 --- a/src/gateway/router/routes/interactionsContractGroupsRoute.ts +++ /dev/null @@ -1,172 +0,0 @@ -import Router from '@koa/router'; -import { isTxIdValid } from '../../../utils'; -import { Benchmark } from 'warp-contracts'; -import { DatabaseSource } from '../../../db/databaseSource'; - -const MAX_INTERACTIONS_PER_PAGE = 50000; - -function loadInteractionsForSrcTx( - dbSource: DatabaseSource, - group: string, - fromSortKey: string, - fromBlockHeight: number | null, - limit: number, - offset: number -) { - const bindings: any[] = []; - - const parsedGroup = group.split(','); - - fromSortKey && bindings.push(fromSortKey); - fromBlockHeight && bindings.push(fromBlockHeight); - bindings.push(limit); - bindings.push(offset); - - const query = ` - SELECT c.contract_id as "contractId", - c.block_height as "contractCreation", - c.src_tx_id as "contractSourceId", - (CASE WHEN i.sort_key IS NULL THEN c.init_state END) as "initState", - i.sort_key as "sortKey", - i.interaction as "interaction", - i.confirmation_status as "confirmationStatus" - FROM contracts c - LEFT JOIN interactions i ON c.contract_id = i.contract_id - WHERE c.src_tx_id IN (${parsedGroup.map((group) => `'${group}'`).join(', ')}) - AND ( - (i.sort_key is not null AND i.confirmation_status IN ('confirmed', 'not_processed') - ${fromSortKey ? ' AND i.sort_key > ?' : ''}) - OR (i.sort_key is null ${fromBlockHeight ? ' AND c.block_height >= ?' : ''}) - ) - -- note: there might multiple contracts at same block_height, so to get stable results during pagination - -- - the c.contract_id column is added to sorting - ORDER BY i.sort_key ASC NULLS LAST, c.block_height ASC, c.contract_id ASC - LIMIT ? OFFSET ?; - `; - - return dbSource.raw(query, bindings); -} - -function loadInteractionsForGroup( - dbSource: DatabaseSource, - group: string, - fromSortKey: string, - fromBlockHeight: number | null, - limit: number, - offset: number -) { - const bindings: any[] = []; - if (group != 'all_pst') { - throw new Error(`Unknown group ${group}`); - } - - fromSortKey && bindings.push(fromSortKey); - fromBlockHeight && bindings.push(fromBlockHeight); - bindings.push(limit); - bindings.push(offset); - - const query = ` - SELECT c.contract_id as "contractId", - c.block_height as "contractCreation", - c.src_tx_id as "contractSourceId", - (CASE WHEN i.sort_key IS NULL THEN c.init_state END) as "initState", - i.sort_key as "sortKey", - i.interaction as "interaction", - i.confirmation_status as "confirmationStatus" - FROM contracts c - LEFT JOIN interactions i ON c.contract_id = i.contract_id - JOIN contracts_src s ON s.src_tx_id = c.src_tx_id - WHERE c.type = 'pst' - AND c.content_type = 'application/json' - AND c.contract_id NOT IN ( - 'LkfzZvdl_vfjRXZOPjnov18cGnnK3aDKj0qSQCgkCX8', /* kyve */ - 'l6S4oMyzw_rggjt4yt4LrnRmggHQ2CdM1hna2MK4o_c', /* kyve */ - 'B1SRLyFzWJjeA0ywW41Qu1j7ZpBLHsXSSrWLrT3ebd8', /* kyve */ - 'cETTyJQYxJLVQ6nC3VxzsZf1x2-6TW2LFkGZa91gUWc', /* koi */ - 'QA7AIFVx1KBBmzC7WUNhJbDsHlSJArUT0jWrhZMZPS8', /* koi */ - '8cq1wbjWHNiPg7GwYpoDT2m9HX99LY7tklRQWfh1L6c', /* kyve */ - 'NwaSMGCdz6Yu5vNjlMtCNBmfEkjYfT-dfYkbQQDGn5s', /* koi */ - 'qzVAzvhwr1JFTPE8lIU9ZG_fuihOmBr7ewZFcT3lIUc', /* koi */ - 'OFD4GqQcqp-Y_Iqh8DN_0s3a_68oMvvnekeOEu_a45I', /* kyve */ - 'CdPAQNONoR83Shj3CbI_9seC-LqgI1oLaRJhSwP90-o', /* koi */ - 'dNXaqE_eATp2SRvyFjydcIPHbsXAe9UT-Fktcqs7MDk' /* kyve */) - AND c.src_tx_id NOT IN ('Qa7IR-xvPkBtcYUBZXd8z-Tu611VeJH33uEA5XiFUNA') /* Hoh */ - AND ( - (i.sort_key is not null AND i.confirmation_status IN ('confirmed', 'not_processed') - ${fromSortKey ? ' AND i.sort_key > ?' : ''}) - OR (i.sort_key is null ${fromBlockHeight ? ' AND c.block_height >= ?' : ''}) - ) - AND ((s.src_content_type = 'application/javascript' - AND (s.src NOT LIKE '%readContractState%' AND s.src NOT LIKE '%unsafeClient%')) - OR s.src_content_type = 'application/wasm') - -- note: there might multiple contracts at same block_height, so to get stable results during pagination - -- - the c.contract_id column is added to sorting - ORDER BY i.sort_key ASC NULLS LAST, c.block_height ASC, c.contract_id ASC - LIMIT ? OFFSET ?; - `; - - return dbSource.raw(query, bindings); -} - -export async function interactionsContractGroupsRoute(ctx: Router.RouterContext) { - const { dbSource, logger } = ctx; - const { group, fromSortKey, fromBlockHeight, page, limit } = ctx.query; - const parsedPage = page ? parseInt(page as string) : 1; - const parsedLimit = limit - ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) - : MAX_INTERACTIONS_PER_PAGE; - const offset = (parsedPage - 1) * parsedLimit; - const parsedGroup = group as string; - const parsedBlockHeight = fromBlockHeight ? parseInt(fromBlockHeight as string) : null; - - let result; - - try { - const benchmark = Benchmark.measure(); - result = isTxIdValid(parsedGroup) - ? await loadInteractionsForSrcTx( - dbSource, - parsedGroup, - fromSortKey as string, - parsedBlockHeight, - parsedLimit, - offset - ) - : await loadInteractionsForGroup( - dbSource, - parsedGroup, - fromSortKey as string, - parsedBlockHeight, - parsedLimit, - offset - ); - - logger.info(`Loading contract groups interactions: ${benchmark.elapsed()}`); - - const interactions = []; - for (let row of result?.rows) { - interactions.push({ - contractId: row.contractId, - ...row.interaction, - confirmationStatus: row.confirmationStatus, - sortKey: row.sortKey, - contractCreation: row.contractCreation, - initState: row.initState, - contractSourceId: row.contractSourceId, - }); - } - - ctx.body = { - paging: { - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - }, - interactions, - }; - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/interactionsRoute.ts b/src/gateway/router/routes/interactionsRoute.ts deleted file mode 100644 index 5bd72dc4..00000000 --- a/src/gateway/router/routes/interactionsRoute.ts +++ /dev/null @@ -1,120 +0,0 @@ -import Router from '@koa/router'; - -const MAX_INTERACTIONS_PER_PAGE = 5000; - -export async function interactionsRoute(ctx: Router.RouterContext) { - const { dbSource } = ctx; - - const { contractId, confirmationStatus, page, limit, from, to, totalCount, source, upToTransactionId, minimize } = - ctx.query; - - const parsedPage = page ? parseInt(page as string) : 1; - - const parsedLimit = limit - ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) - : MAX_INTERACTIONS_PER_PAGE; - const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; - - const parsedConfirmationStatus = confirmationStatus - ? confirmationStatus == 'not_corrupted' - ? ['confirmed', 'not_processed'] - : [confirmationStatus] - : undefined; - - const shouldMinimize = minimize === 'true'; - - const bindings: any[] = []; - bindings.push(contractId); - bindings.push(contractId); - // cannot use IN with bindings https://github.com/knex/knex/issues/791 - // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) - from && bindings.push(from as string); - to && bindings.push(to as string); - source && bindings.push(source as string); - upToTransactionId && bindings.push(upToTransactionId as string); - parsedPage && bindings.push(parsedLimit); - parsedPage && bindings.push(offset); - - try { - const result: any = await dbSource.raw( - ` - SELECT interaction, - confirmation_status - ${shouldMinimize ? '' : ',confirming_peer, confirmations, bundler_tx_id '} - ${shouldMinimize ? '' : ',count(*) OVER () AS total'} - FROM interactions - WHERE (contract_id = ? OR interact_write @> ARRAY[?]) - ${ - parsedConfirmationStatus - ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` - : '' - } - ${from ? ' AND block_height >= ?' : ''} - ${to ? ' AND block_height <= ?' : ''} - ${source ? `AND source = ?` : ''} - ${upToTransactionId ? `AND id <= (SELECT id FROM interactions WHERE interaction_id = ?)` : ''} - ORDER BY block_height ${shouldMinimize ? 'ASC' : 'DESC'}, interaction_id DESC ${ - parsedPage ? ' LIMIT ? OFFSET ?' : '' - }; - `, - bindings - ); - - const totalInteractions: any = - totalCount == 'true' && - (await dbSource.raw( - ` - SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, - count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, - count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, - count(case when confirmation_status = 'forked' then 1 else null end) AS forked - FROM interactions - WHERE contract_id = ?; - `, - contractId - )); - - const total = result?.rows?.length > 0 ? result?.rows[0].total : 0; - - const mappedInteractions = shouldMinimize - ? result?.rows?.map((r: any) => ({ - interaction: { - ...r.interaction, - bundlerTxId: r.bundler_tx_id, - }, - confirmationStatus: r.confirmation_status, - })) - : result?.rows?.map((r: any) => ({ - status: r.confirmation_status, - confirming_peers: r.confirming_peer, - confirmations: r.confirmations, - interaction: { - ...r.interaction, - bundlerTxId: r.bundler_tx_id, - }, - })); - - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - ...(totalInteractions && { - total: { - confirmed: totalInteractions?.rows[0].confirmed, - corrupted: totalInteractions?.rows[0].corrupted, - not_processed: totalInteractions?.rows[0].not_processed, - forked: totalInteractions?.rows[0].forked, - }, - }), - interactions: mappedInteractions, - }; - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/interactionsSonar.ts b/src/gateway/router/routes/interactionsSonar.ts deleted file mode 100644 index 5fff2efc..00000000 --- a/src/gateway/router/routes/interactionsSonar.ts +++ /dev/null @@ -1,110 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -const MAX_INTERACTIONS_PER_PAGE = 5000; - -export async function interactionsSonar(ctx: Router.RouterContext) { - const { dbSource, logger } = ctx; - - const { contractId, confirmationStatus, page, limit, from, to, source } = ctx.query; - - const parsedPage = page ? parseInt(page as string) : 1; - - const parsedLimit = limit - ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) - : MAX_INTERACTIONS_PER_PAGE; - const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; - - const parsedConfirmationStatus = confirmationStatus - ? confirmationStatus == 'not_corrupted' - ? ['confirmed', 'not_processed'] - : [confirmationStatus] - : undefined; - - const bindings: any[] = []; - bindings.push(contractId); - bindings.push(contractId); - from && bindings.push(from as string); - to && bindings.push(to as string); - source && bindings.push(source as string); - parsedPage && bindings.push(parsedLimit); - parsedPage && bindings.push(offset); - - try { - const query = ` - SELECT interaction, - confirmation_status, - sort_key, - confirming_peer, - confirmations, - bundler_tx_id - FROM interactions - WHERE (contract_id = ? OR interact_write @> ARRAY[?]) - ${ - parsedConfirmationStatus - ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` - : '' - } - ${from ? ' AND sort_key > ?' : ''} - ${to ? ' AND sort_key <= ?' : ''} - ${source ? `AND source = ?` : ''} - ORDER BY sort_key DESC ${parsedPage ? ' LIMIT ? OFFSET ?' : ''}; - `; - - const result: any = await dbSource.raw(query, bindings); - - const totalInteractions: any = await dbSource.raw( - ` - SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, - count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, - count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, - count(case when confirmation_status = 'forked' then 1 else null end) AS forked - FROM interactions - WHERE (contract_id = ? OR interact_write @> ARRAY[?]); - `, - [contractId, contractId] - ); - - const total = - parseInt(totalInteractions.rows[0].confirmed) + - parseInt(totalInteractions.rows[0].corrupted) + - parseInt(totalInteractions.rows[0].not_processed) + - parseInt(totalInteractions.rows[0].forked); - - const benchmark = Benchmark.measure(); - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - ...(totalInteractions && { - total: { - confirmed: totalInteractions.rows[0].confirmed, - corrupted: totalInteractions.rows[0].corrupted, - not_processed: totalInteractions.rows[0].not_processed, - forked: totalInteractions.rows[0].forked, - }, - }), - - interactions: result?.rows?.map((r: any) => ({ - status: r.confirmation_status, - confirming_peers: r.confirming_peer, - confirmations: r.confirmations, - interaction: { - ...r.interaction, - bundlerTxId: r.bundler_tx_id, - sortKey: r.sort_key, - }, - })), - }; - - logger.info('Mapping interactions: ', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/interactionsSortKeyRoute.ts b/src/gateway/router/routes/interactionsSortKeyRoute.ts deleted file mode 100644 index 0e829aa4..00000000 --- a/src/gateway/router/routes/interactionsSortKeyRoute.ts +++ /dev/null @@ -1,124 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -const MAX_INTERACTIONS_PER_PAGE = 5000; - -export async function interactionsSortKeyRoute(ctx: Router.RouterContext) { - const { dbSource, logger } = ctx; - - const { contractId, confirmationStatus, page, limit, from, to, totalCount, source, minimize } = ctx.query; - - const parsedPage = page ? parseInt(page as string) : 1; - - const parsedLimit = limit - ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) - : MAX_INTERACTIONS_PER_PAGE; - const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; - - const parsedConfirmationStatus = confirmationStatus - ? confirmationStatus == 'not_corrupted' - ? ['confirmed', 'not_processed'] - : [confirmationStatus] - : undefined; - - // 'should minimize' means that we're making a call from the SDK - // this affects: - // 1. the amount of returned data (we're trying to minimize amount of data in this case) - // 2. sorting order (SDK requires ASC order, SonAR requires DESC order) - const shouldMinimize = minimize === 'true'; - - const bindings: any[] = []; - bindings.push(contractId); - bindings.push(contractId); - // cannot use IN with bindings https://github.com/knex/knex/issues/791 - // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) - from && bindings.push(from as string); - to && bindings.push(to as string); - source && bindings.push(source as string); - parsedPage && bindings.push(parsedLimit); - parsedPage && bindings.push(offset); - - try { - const query = ` - SELECT interaction, - confirmation_status, - sort_key - ${shouldMinimize ? '' : ',confirming_peer, confirmations, bundler_tx_id '} - ${shouldMinimize ? '' : ',count(*) OVER () AS total'} - FROM interactions - WHERE (contract_id = ? OR interact_write @> ARRAY[?]) - ${ - parsedConfirmationStatus - ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` - : '' - } - ${from ? ' AND sort_key > ?' : ''} - ${to ? ' AND sort_key <= ?' : ''} - ${source ? `AND source = ?` : ''} - ORDER BY sort_key ${shouldMinimize ? 'ASC' : 'DESC'} ${parsedPage ? ' LIMIT ? OFFSET ?' : ''}; - `; - - const result: any = await dbSource.raw(query, bindings); - - const totalInteractions: any = - totalCount == 'true' && - (await dbSource.raw( - ` - SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, - count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, - count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, - count(case when confirmation_status = 'forked' then 1 else null end) AS forked - FROM interactions - WHERE contract_id = ?; - `, - contractId - )); - - const total = result?.rows?.length > 0 ? parseInt(result?.rows[0].total) : 0; - - const benchmark = Benchmark.measure(); - const mappedInteractions = shouldMinimize - ? result?.rows?.map((r: any) => ({ - ...r.interaction, - sortKey: r.sort_key, - confirmationStatus: r.confirmation_status, - })) - : result?.rows?.map((r: any) => ({ - status: r.confirmation_status, - confirming_peers: r.confirming_peer, - confirmations: r.confirmations, - interaction: { - ...r.interaction, - bundlerTxId: r.bundler_tx_id, - sortKey: r.sort_key, - }, - })); - - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - ...(totalInteractions && { - total: { - confirmed: totalInteractions?.rows[0].confirmed, - corrupted: totalInteractions?.rows[0].corrupted, - not_processed: totalInteractions?.rows[0].not_processed, - forked: totalInteractions?.rows[0].forked, - }, - }), - // TODO: this mapping here is kinda dumb. - - interactions: mappedInteractions, - }; - - logger.info('Mapping interactions: ', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/interactionsSortKeyRoute_v2.ts b/src/gateway/router/routes/interactionsSortKeyRoute_v2.ts deleted file mode 100644 index 07145318..00000000 --- a/src/gateway/router/routes/interactionsSortKeyRoute_v2.ts +++ /dev/null @@ -1,122 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -const MAX_INTERACTIONS_PER_PAGE = 5000; - -export async function interactionsSortKeyRoute_v2(ctx: Router.RouterContext) { - const { dbSource, logger } = ctx; - - const { contractId, confirmationStatus, page, limit, from, to, totalCount, source, fromSdk } = ctx.query; - - logger.info(`interactionsSortKeyRoute [ip: ${ctx.request?.ip}, contractId: ${contractId}]`); - - const parsedPage = page ? parseInt(page as string) : 1; - - const parsedLimit = limit - ? Math.min(parseInt(limit as string), MAX_INTERACTIONS_PER_PAGE) - : MAX_INTERACTIONS_PER_PAGE; - const offset = parsedPage ? (parsedPage - 1) * parsedLimit : 0; - - const parsedConfirmationStatus = confirmationStatus - ? confirmationStatus == 'not_corrupted' - ? ['confirmed', 'not_processed'] - : [confirmationStatus] - : undefined; - - // 'isFromSdk' means that we're making a call from the SDK - // this affects: - // 1. the amount of returned data (we're trying to minimize amount of data in this case) - // 2. sorting order (SDK requires ASC order, SonAR requires DESC order) - const isFromSdk = fromSdk === 'true'; - - const bindings: any[] = []; - bindings.push(contractId); - bindings.push(contractId); - // cannot use IN with bindings https://github.com/knex/knex/issues/791 - // parsedConfirmationStatus && bindings.push(parsedConfirmationStatus) - from && bindings.push(from as string); - to && bindings.push(to as string); - source && bindings.push(source as string); - bindings.push(parsedLimit); - bindings.push(offset); - - try { - const query = ` - SELECT interaction, - confirmation_status, - sort_key - ${isFromSdk ? '' : ',confirming_peer, confirmations, bundler_tx_id '} - ${isFromSdk ? '' : ',count(*) OVER () AS total'} - FROM interactions - WHERE (contract_id = ? - OR interact_write @> ARRAY [?]) ${ - parsedConfirmationStatus - ? ` AND confirmation_status IN (${parsedConfirmationStatus.map((status) => `'${status}'`).join(', ')})` - : '' - } ${from ? ' AND sort_key > ?' : ''} ${to ? ' AND sort_key <= ?' : ''} ${source ? `AND source = ?` : ''} - ORDER BY sort_key ${isFromSdk ? 'ASC' : 'DESC'} - LIMIT ? OFFSET ?; - `; - - const result: any = await dbSource.raw(query, bindings); - - const totalInteractions: any = - totalCount == 'true' && - (await dbSource.raw( - ` - SELECT count(case when confirmation_status = 'corrupted' then 1 else null end) AS corrupted, - count(case when confirmation_status = 'confirmed' then 1 else null end) AS confirmed, - count(case when confirmation_status = 'not_processed' then 1 else null end) AS not_processed, - count(case when confirmation_status = 'forked' then 1 else null end) AS forked - FROM interactions - WHERE contract_id = ?; - `, - contractId - )); - - const total = result?.rows?.length > 0 ? parseInt(result?.rows[0].total) : 0; - - const benchmark = Benchmark.measure(); - - const mappedInteractions = isFromSdk - ? result?.rows?.map((r: any) => ({ - ...r.interaction, - sortKey: r.sort_key, - confirmationStatus: r.confirmation_status, - })) - : result?.rows?.map((r: any) => ({ - status: r.confirmation_status, - confirming_peers: r.confirming_peer, - confirmations: r.confirmations, - interaction: { - ...r.interaction, - bundlerTxId: r.bundler_tx_id, - sortKey: r.sort_key, - }, - })); - - ctx.body = { - paging: { - total, - limit: parsedLimit, - items: result?.rows.length, - page: parsedPage, - pages: Math.ceil(total / parsedLimit), - }, - ...(totalInteractions && { - total: { - confirmed: totalInteractions?.rows[0].confirmed, - corrupted: totalInteractions?.rows[0].corrupted, - not_processed: totalInteractions?.rows[0].not_processed, - forked: totalInteractions?.rows[0].forked, - }, - }), - // TODO: this mapping here is kinda dumb. - interactions: mappedInteractions, - }; - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/nftsOwnedByAddressRoute.ts b/src/gateway/router/routes/nftsOwnedByAddressRoute.ts index a99b2d89..9636d379 100644 --- a/src/gateway/router/routes/nftsOwnedByAddressRoute.ts +++ b/src/gateway/router/routes/nftsOwnedByAddressRoute.ts @@ -1,6 +1,7 @@ import Router from '@koa/router'; import { Benchmark } from 'warp-contracts'; import { isTxIdValid } from '../../../utils'; +import {GatewayError} from "../../errorHandlerMiddleware"; export async function nftsOwnedByAddressRoute(ctx: Router.RouterContext) { const { logger, dbSource } = ctx; @@ -8,38 +9,28 @@ export async function nftsOwnedByAddressRoute(ctx: Router.RouterContext) { const { address } = ctx.params; if (!isTxIdValid(address)) { - ctx.status = 500; - ctx.body = { message: 'Wrong wallet address format.' }; - return; + throw new GatewayError('Wrong wallet address format.', 412); } const { srcTxId } = ctx.query; if (!isTxIdValid(srcTxId as string)) { - ctx.status = 500; - ctx.body = { message: 'Wrong src tx id address format.' }; - return; + throw new GatewayError('Wrong src tx id address format.', 412); } - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - WITH wallet_balances AS ( - SELECT contract_id as contract, - init_state->'ticker' AS ticker, - init_state->'name' AS name, - (init_state->'balances'->?)::integer AS balance - FROM contracts - WHERE src_tx_id = ?) - SELECT * from wallet_balances WHERE balance = 1; - `, - [address, srcTxId] - ); - ctx.body = result?.rows; - logger.debug('Nfts ownership loaded', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + WITH wallet_balances AS ( + SELECT contract_id as contract, + init_state->'ticker' AS ticker, + init_state->'name' AS name, + (init_state->'balances'->?)::integer AS balance + FROM contracts + WHERE src_tx_id = ?) + SELECT * from wallet_balances WHERE balance = 1; + `, + [address, srcTxId] + ); + ctx.body = result?.rows; + logger.debug('Nfts ownership loaded', benchmark.elapsed()); } diff --git a/src/gateway/router/routes/registerContractRoute.ts b/src/gateway/router/routes/registerContractRoute.ts deleted file mode 100644 index 4e2a95e4..00000000 --- a/src/gateway/router/routes/registerContractRoute.ts +++ /dev/null @@ -1,197 +0,0 @@ -import Router from '@koa/router'; -import { evalType } from '../../tasks/contractsMetadata'; -import { getCachedNetworkData } from '../../tasks/networkInfoCache'; -import { publishContract, sendNotification } from '../../publisher'; -import { evalManifest, WarpDeployment } from './deployContractRoute'; -import { Tag } from 'arweave/node/lib/transaction'; -import { stringToB64Url } from 'arweave/node/lib/utils'; -import { fetch } from 'undici'; -import { backOff } from 'exponential-backoff'; -import { getTestnetTag } from './deployBundledRoute'; -import { ContractInsert } from '../../../db/insertInterfaces'; - -const BUNDLR_QUERY = `query Transactions($ids: [String!]) { - transactions(ids: $ids) { - edges { - node { - address - } - } - } - }`; - -const BUNDLR_NODES = ['node1', 'node2'] as const; -type BundlrNodeType = typeof BUNDLR_NODES[number]; - -export async function registerContractRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - let initStateRaw = ''; - let contractTx = null; - let txId = ''; - - try { - const bundlrNode = ctx.request.body.bundlrNode; - if (!bundlrNode || !isBundlrNodeType(bundlrNode)) { - throw new Error( - `Invalid Bundlr Node. Should be equal to one of the following values: ${BUNDLR_NODES.map((n) => n).join( - ', ' - )}, found: ${bundlrNode}.` - ); - } - - txId = ctx.request.body.id; - - const txMetadata = ((await getBundlrGqlMetadata(txId, bundlrNode)) as any).transactions.edges[0].node; - - const { contractTagsIncluded, tags, signature } = await verifyContractTags(txId, bundlrNode); - if (!contractTagsIncluded) { - ctx.throw(400, 'Bundlr transaction is not valid contract transaction.'); - } - - logger.debug('Bundlr transaction marked as valid contract transaction.'); - - let encodedTags: Tag[] = []; - - for (const tag of tags) { - try { - encodedTags.push(new Tag(stringToB64Url(tag.name), stringToB64Url(tag.value))); - } catch (e: any) { - throw new Error(`Unable to encode tag ${tag.name}: ${e.status}`); - } - } - - const srcTxId = tags.find((t: Tag) => t.name == 'Contract-Src')!.value; - initStateRaw = tags.find((t: Tag) => t.name == 'Init-State')!.value; - const initState = JSON.parse(initStateRaw); - const type = evalType(initState); - const ownerAddress = txMetadata.address; - const contentType = tags.find((t: Tag) => t.name == 'Content-Type')!.value; - const testnet = getTestnetTag(tags); - const manifest = evalManifest(tags); - const blockHeight = getCachedNetworkData().cachedNetworkInfo.height; - const blockTimestamp = getCachedNetworkData().cachedBlockInfo.timestamp; - const syncTimestamp = Date.now(); - - contractTx = { - id: txId, - owner: ownerAddress, - data: null, - signature, - target: '', - tags: encodedTags, - }; - - const insert: ContractInsert = { - contract_id: txId, - src_tx_id: srcTxId, - init_state: initState, - owner: ownerAddress, - type: type, - pst_ticker: type == 'pst' ? initState?.ticker : null, - pst_name: type == 'pst' ? initState?.name : null, - block_height: blockHeight, - block_timestamp: blockTimestamp, - content_type: contentType, - contract_tx: { tags: contractTx.tags }, - bundler_contract_tx_id: txId, - bundler_contract_node: `https://${bundlrNode}.bundlr.network`, - testnet, - deployment_type: WarpDeployment.External, - manifest, - sync_timestamp: syncTimestamp, - }; - - await dbSource.insertContract(insert); - - sendNotification(ctx, txId, { initState, tags }); - publishContract( - ctx, - txId, - ownerAddress, - type, - blockHeight, - blockTimestamp, - WarpDeployment.External, - syncTimestamp, - testnet - ); - - logger.info('Contract successfully registered.', { - contractTxId: txId, - }); - - ctx.body = { - contractTxId: txId, - }; - } catch (e: any) { - logger.error('Error while registering bundled transaction.', { - txId, - contractTx, - initStateRaw, - }); - logger.error(e); - ctx.body = e; - ctx.status = e.status ? e.status : 500; - } -} - -export async function verifyContractTags(id: string, bundlrNode: string) { - let response: any; - const request = async () => { - return fetch(`https://${bundlrNode}.bundlr.network/tx/${id}`).then((res) => { - return res.ok ? res.json() : Promise.reject(res); - }); - }; - try { - response = await backOff(request, { - delayFirstAttempt: false, - maxDelay: 2000, - numOfAttempts: 5, - }); - } catch (error: any) { - throw new Error(`Unable to retrieve Bundlr network tags response. ${error.status}.`); - } - const tags = response.tags; - const signature = response.signature; - const tagsIncluded = [ - { name: 'App-Name', value: 'SmartWeaveContract' }, - { name: 'App-Version', value: '0.3.0' }, - ]; - - const nameTagsIncluded = ['Contract-Src', 'Init-State', 'Content-Type']; - - const contractTagsIncluded = - tagsIncluded.every((ti) => tags.some((t: Tag) => t.name == ti.name && t.value == ti.value)) && - nameTagsIncluded.every((nti) => tags.some((t: Tag) => t.name == nti)); - - return { contractTagsIncluded, tags, signature }; -} - -export async function getBundlrGqlMetadata(id: string, bundlrNode: string) { - const data = JSON.stringify({ - query: BUNDLR_QUERY, - variables: { ids: [id] }, - }); - - const response = await fetch(`https://${bundlrNode}.bundlr.network/graphql`, { - method: 'POST', - body: data, - headers: { - 'Accept-Encoding': 'gzip, deflate, br', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - }) - .then((res) => { - return res.ok ? res.json() : Promise.reject(res); - }) - .catch((error) => { - throw new Error(`Unable to retrieve Bundlr gql response. ${error.status}.`); - }); - return (response as any).data; -} - -export function isBundlrNodeType(value: string): value is BundlrNodeType { - return BUNDLR_NODES.includes(value as BundlrNodeType); -} diff --git a/src/gateway/router/routes/safeContractsRoute.ts b/src/gateway/router/routes/safeContractsRoute.ts deleted file mode 100644 index cd3d04d9..00000000 --- a/src/gateway/router/routes/safeContractsRoute.ts +++ /dev/null @@ -1,31 +0,0 @@ -import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; - -export async function safeContractsRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT i.contract_id, count(i) AS interactions - FROM contracts c - JOIN interactions i ON i.contract_id = c.contract_id - JOIN contracts_src s ON s.src_tx_id = c.src_tx_id - WHERE - (s.src_content_type = 'application/javascript' - AND (s.src NOT LIKE '%readContractState%' AND s.src NOT LIKE '%unsafeClient%')) - OR s.src_content_type = 'application/wasm' - GROUP BY i.contract_id - HAVING count(i) < 20000 AND count(i) >= 1 - ORDER BY count(i) DESC; - ` - ); - ctx.body = result?.rows; - logger.debug('Safe contracts loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } -} diff --git a/src/gateway/router/routes/searchRoute.ts b/src/gateway/router/routes/searchRoute.ts index 691c2312..60dee5df 100644 --- a/src/gateway/router/routes/searchRoute.ts +++ b/src/gateway/router/routes/searchRoute.ts @@ -19,24 +19,18 @@ export async function searchRoute(ctx: Router.RouterContext) { const isEthWallet = utils.isAddress(phrase); const validTs = isTxIdValid(phrase); - try { - const benchmark = Benchmark.measure(); - let result: any; - if (isEthWallet) { - result = await fetchCreatorOnly(dbSource, phrase); - } else if (validTs) { - result = await fetchTransaction(dbSource, phrase, testnet); - } else { - result = await fetchPst(dbSource, phrase, testnet); - } - - ctx.body = result?.rows; - logger.debug('Contracts loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; + const benchmark = Benchmark.measure(); + let result: any; + if (isEthWallet) { + result = await fetchCreatorOnly(dbSource, phrase); + } else if (validTs) { + result = await fetchTransaction(dbSource, phrase, testnet); + } else { + result = await fetchPst(dbSource, phrase, testnet); } + + ctx.body = result?.rows; + logger.debug('Contracts loaded in', benchmark.elapsed()); } async function fetchCreatorOnly(dbSource: DatabaseSource, wallet: string) { diff --git a/src/gateway/router/routes/sequencerAddress.ts b/src/gateway/router/routes/sequencerAddress.ts new file mode 100644 index 00000000..d35bc8a0 --- /dev/null +++ b/src/gateway/router/routes/sequencerAddress.ts @@ -0,0 +1,8 @@ +import Router from '@koa/router'; + +export async function sequencerAddressRoute(ctx: Router.RouterContext) { + ctx.body = { + urls: ['https://gw.warp.cc'], + type: 'centralized' + }; +} diff --git a/src/gateway/router/routes/sequencerRoute.ts b/src/gateway/router/routes/sequencerRoute.ts index 2320416c..17be0471 100644 --- a/src/gateway/router/routes/sequencerRoute.ts +++ b/src/gateway/router/routes/sequencerRoute.ts @@ -1,19 +1,23 @@ import Router from '@koa/router'; import Transaction from 'arweave/node/lib/transaction'; -import { parseFunctionName } from '../../tasks/syncTransactions'; import Arweave from 'arweave'; import { JWKInterface } from 'arweave/node/lib/wallet'; -import { arrayToHex, Benchmark, GQLTagInterface, SmartWeaveTags, WarpLogger } from 'warp-contracts'; -import { getCachedNetworkData } from '../../tasks/networkInfoCache'; -import Bundlr from '@bundlr-network/client'; -import { BlockData } from 'arweave/node/blocks'; -import { VRF } from '../../init'; -import { isTxIdValid } from '../../../utils'; +import { + arrayToHex, + Benchmark, + GQLTagInterface, + SMART_WEAVE_TAGS, + timeout, + WARP_TAGS, + WarpLogger, +} from 'warp-contracts'; +import { isTxIdValid, parseFunctionName } from '../../../utils'; import { BUNDLR_NODE1_URL } from '../../../constants'; -import { publishInteraction, sendNotification } from '../../publisher'; import { Knex } from 'knex'; -import { InteractionInsert } from '../../../db/insertInterfaces'; - +import { GatewayError } from '../../errorHandlerMiddleware'; +import { VRF } from '../../init'; +import { DataItem, serializeTags } from 'arbundles'; +import Irys from '@irys/sdk'; const { Evaluate } = require('@idena/vrf-js'); export type VrfData = { @@ -23,194 +27,232 @@ export type VrfData = { pubkey: string; }; -export async function sequencerRoute(ctx: Router.RouterContext) { - const { sLogger, arweave, bundlr, jwk, vrf, lastTxSync, dbSource, signatureVerification } = ctx; - const trx: Knex.Transaction = await dbSource.primaryDb.transaction(); - - try { - const cachedNetworkData = getCachedNetworkData(); - - const benchmark = Benchmark.measure(); +export type SequencerResult = { + sortKey: string; + prevSortKey: string | null; + id: string; + internalWrites: string[]; + timestamp: number; +}; - const transaction: Transaction = new Transaction({ ...ctx.request.body }); - sLogger.debug('New sequencer tx', transaction.id); +export async function sequencerRoute(ctx: Router.RouterContext) { + const { dbSource } = ctx; + const trx = (await dbSource.primaryDb.transaction()) as Knex.Transaction; - const originalSignature = transaction.signature; - const originalOwner = transaction.owner; + const { timeoutId, timeoutPromise } = timeout(0.5); - if (cachedNetworkData == null) { - throw new Error('Network or block info not yet cached.'); + try { + const result = await Promise.race([timeoutPromise, doGenerateSequence(ctx, trx)]); + await trx.commit(); + ctx.body = result; + } catch (e: any) { + if (trx != null) { + await trx.rollback(); } + throw new GatewayError(e?.message || e); + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + } +} - const currentHeight = cachedNetworkData.cachedBlockInfo.height; - sLogger.debug(`Sequencer height: ${transaction.id}: ${currentHeight}`); +async function doGenerateSequence(ctx: Router.RouterContext, trx: Knex.Transaction): Promise { + const { sLogger, arweave, jwk, vrf, pgAdvisoryLocks, signatureVerification } = ctx; - if (!currentHeight) { - throw new Error('Current height not set'); - } + const initialBenchmark = Benchmark.measure(); - const currentBlockTimestamp = cachedNetworkData.cachedBlockInfo.timestamp; - if (!currentBlockTimestamp) { - throw new Error('Current block timestamp not set'); - } + const benchmark = Benchmark.measure(); - const currentBlockId = cachedNetworkData.cachedNetworkInfo.current; - if (!currentBlockId) { - throw new Error('Current block not set'); - } + const transaction: Transaction = new Transaction({ ...ctx.request.body }); + sLogger.debug('New sequencer tx', transaction.id); - let { - contractTag, - inputTag, - requestVrfTag, - internalWrites, - decodedTags, - tags, - originalAddress, - isEvmSigner, - testnetVersion, - } = await prepareTags( - sLogger, - transaction, - originalOwner, - currentHeight, - currentBlockId, - currentBlockTimestamp, - arweave - ); + const originalSignature = transaction.signature; + const originalOwner = transaction.owner; - const contractLastSortKey: string | null = await lastTxSync.acquireMutex(contractTag, trx); + let { + contractTag, + inputTag, + requestVrfTag, + internalWrites, + decodedTags, + originalAddress, + isEvmSigner, + testnetVersion, + } = await prepareTags(sLogger, transaction, originalOwner, arweave); + + const acquireMutexResult = await pgAdvisoryLocks.acquireSortKeyMutex(contractTag, trx); + sLogger.debug('Acquire mutex result', acquireMutexResult); + // note: lastSortKey can be null if that's a very first interaction with a contract. + if ( + acquireMutexResult.blockHash == null || + acquireMutexResult.blockHeight == null || + acquireMutexResult.blockTimestamp == null + ) { + throw new Error(`Missing data in acquireSortKeyMutex: ${JSON.stringify(acquireMutexResult)}`); + } - const millis = Date.now(); - const sortKey = await createSortKey(arweave, jwk, currentBlockId, millis, transaction.id, currentHeight); + const millis = Date.now(); + const sortKey = await createSortKey( + arweave, + jwk, + acquireMutexResult.blockHash, + millis, + transaction.id, + acquireMutexResult.blockHeight + ); + if (acquireMutexResult.lastSortKey !== null && sortKey.localeCompare(acquireMutexResult.lastSortKey) <= 0) { + throw new Error(`New sortKey (${sortKey}) <= lastSortKey (${acquireMutexResult.lastSortKey})!`); + } - tags.push({ name: 'Sequencer-Mills', value: '' + millis }); - tags.push({ name: 'Sequencer-Sort-Key', value: sortKey }); - tags.push({ name: 'Sequencer-Last-Sort-Key', value: contractLastSortKey || 'null' }); - let vrfData = null; - if (requestVrfTag !== '') { - const vrfGen = generateVrfTags(sortKey, vrf, arweave); - tags.push(...vrfGen.vrfTags); - vrfData = vrfGen.vrfData; - } + const tags = [ + { name: 'Sequencer', value: 'RedStone' }, + { name: 'Sequencer-Owner', value: originalAddress }, + { name: 'Sequencer-Tx-Id', value: transaction.id }, + { name: 'Sequencer-Block-Height', value: '' + acquireMutexResult.blockHeight }, + { name: 'Sequencer-Block-Id', value: acquireMutexResult.blockHash }, + { name: 'Sequencer-Block-Timestamp', value: '' + acquireMutexResult.blockTimestamp }, + { name: 'Sequencer-Mills', value: '' + millis }, + { name: 'Sequencer-Sort-Key', value: sortKey }, + { name: 'Sequencer-Prev-Sort-Key', value: acquireMutexResult.lastSortKey || 'null' }, + ...decodedTags, + ]; - sLogger.info('Original address before create interaction', originalAddress); - const interaction = createInteraction( - transaction, - originalAddress, - decodedTags, - currentHeight, - currentBlockId, - cachedNetworkData.cachedBlockInfo, - sortKey, - vrfData, - isEvmSigner ? originalSignature : null, - testnetVersion, - contractLastSortKey - ); + let vrfData = null; + if (requestVrfTag !== '') { + const vrfGen = generateVrfTags(sortKey, vrf, arweave); + tags.push(...vrfGen.vrfTags); + vrfData = vrfGen.vrfData; + } - let verified = false; - if (isEvmSigner) { - verified = await signatureVerification.process(interaction); - } else { - verified = await arweave.transactions.verify(transaction); - } + const interaction = createInteraction( + transaction, + originalAddress, + decodedTags, + acquireMutexResult.blockHeight, + acquireMutexResult.blockHash, + acquireMutexResult.blockTimestamp, + sortKey, + vrfData, + isEvmSigner ? originalSignature : null, + testnetVersion, + acquireMutexResult.lastSortKey + ); + + const verified = isEvmSigner + ? await signatureVerification.process(interaction) + : await arweave.transactions.verify(transaction); + if (!verified) { + throw new Error('Could not properly verify transaction.'); + } - if (!verified) { - throw new Error('Naughty boy (interaction)!'); - } else { - sLogger.info('Transaction verified properly'); - } + const parsedInput = JSON.parse(inputTag); + const functionName = parseFunctionName(inputTag, sLogger); - // TODO: add fallback to other bundlr nodes. - const { bTx, bundlrResponse } = await uploadToBundlr(transaction, bundlr, tags, sLogger); + checkWhitelistedWallet(originalAddress, contractTag); + checkBlacklistedFunction(functionName, contractTag); - const parsedInput = JSON.parse(inputTag); - const functionName = parseFunctionName(inputTag, sLogger); - let evolve: string | null; - evolve = functionName == 'evolve' && parsedInput.value && isTxIdValid(parsedInput.value) ? parsedInput.value : null; + let evolve: string | null; + evolve = functionName == 'evolve' && parsedInput.value && isTxIdValid(parsedInput.value) ? parsedInput.value : null; - if (isEvmSigner) { - sLogger.info(`Interaction for ${transaction.id}`, JSON.stringify(interaction)); - } + sLogger.debug('Initial benchmark', initialBenchmark.elapsed()); + sLogger.debug('inserting into tables'); + + try { + serializeTags(tags); + } catch (e) { + throw new Error(`Tags could not be serialized properly. It may be due to the big input size.`); + } - const sequencerInsert = { - original_sig: originalSignature, - original_owner: originalOwner, - original_address: originalAddress, - sequence_block_id: currentBlockId, - sequence_block_height: currentHeight, - sequence_transaction_id: transaction.id, - sequence_millis: '' + millis, - sequence_sort_key: sortKey, - bundler_tx_id: bTx.id, - bundler_response: JSON.stringify(bundlrResponse.data), - last_sort_key: contractLastSortKey, - }; - - const interactionsInsert: InteractionInsert = { + await trx.raw( + ` + WITH ins_interaction AS ( + INSERT + INTO interactions (interaction_id, + interaction, + block_height, + block_id, + contract_id, + function, + input, + confirmation_status, + confirming_peer, + source, + block_timestamp, + interact_write, + sort_key, + evolve, + testnet, + last_sort_key, + owner, + sync_timestamp) + VALUES ( + :interaction_id, + :interaction, + :block_height, + :block_id, + :contract_id, + :function, + :input, + :confirmation_status, + :confirming_peer, + :source, + :block_timestamp, + :interact_write, + :sort_key, + :evolve, + :testnet, + :prev_sort_key, + :owner, + :sync_timestamp) + RETURNING id) + INSERT + INTO bundle_items (interaction_id, state, transaction, tags) + SELECT i.id, 'PENDING', :original_transaction, :tags + FROM ins_interaction i; + `, + { interaction_id: transaction.id, - interaction: JSON.stringify(interaction), - block_height: currentHeight, - block_timestamp: currentBlockTimestamp, - block_id: currentBlockId, + interaction: interaction, + block_height: acquireMutexResult.blockHeight, + block_id: acquireMutexResult.blockHash, contract_id: contractTag, function: functionName, input: inputTag, confirmation_status: 'confirmed', confirming_peer: BUNDLR_NODE1_URL, source: 'redstone-sequencer', - bundler_tx_id: bTx.id, + block_timestamp: acquireMutexResult.blockTimestamp, interact_write: internalWrites, sort_key: sortKey, evolve: evolve, testnet: testnetVersion, - last_sort_key: contractLastSortKey, + prev_sort_key: acquireMutexResult.lastSortKey, owner: originalOwner, + original_transaction: ctx.request.body, + tags: JSON.stringify(tags), sync_timestamp: millis, - }; - - await dbSource.insertSequencerAndInteraction(sequencerInsert, interactionsInsert, trx, sLogger); + } + ); - sLogger.debug('Transaction successfully bundled', { - id: transaction.id, - bundled_tx_id: bTx.id, - }); + sLogger.info('Total sequencer processing', benchmark.elapsed()); - ctx.body = bundlrResponse.data; - - sLogger.info('Total sequencer processing', benchmark.elapsed()); - - sendNotification(ctx, contractTag, undefined, interaction); - publishInteraction( - ctx, - contractTag, - interaction, - sortKey, - contractLastSortKey, - functionName, - 'redstone-sequencer', - millis, - testnetVersion - ); - } catch (e) { - if (!trx.isCompleted()) { - await trx.rollback(); - } - sLogger.error('Error while inserting bundled transaction'); - sLogger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + return { + id: transaction.id, + sortKey, + timestamp: millis, + prevSortKey: acquireMutexResult.lastSortKey, + internalWrites, + }; } -function createInteraction( - transaction: Transaction, +export function createInteraction( + transactionOrDataItem: Transaction | DataItem, originalAddress: string, decodedTags: GQLTagInterface[], currentHeight: number, currentBlockId: string, - blockInfo: BlockData, + currentBlockTimestamp: number, sortKey: string, vrfData: VrfData | null, signature: string | null, @@ -218,20 +260,20 @@ function createInteraction( lastSortKey: string | null ) { const interaction: any = { - id: transaction.id, + id: transactionOrDataItem.id, owner: { address: originalAddress }, - recipient: transaction.target, + recipient: transactionOrDataItem.target, tags: decodedTags, block: { height: currentHeight, id: currentBlockId, - timestamp: blockInfo.timestamp, + timestamp: currentBlockTimestamp, }, fee: { - winston: transaction.reward, + winston: isTransaction(transactionOrDataItem) ? transactionOrDataItem.reward : '0', }, quantity: { - winston: transaction.quantity, + winston: isTransaction(transactionOrDataItem) ? transactionOrDataItem.quantity : '', }, sortKey: sortKey, source: 'redstone-sequencer', @@ -247,7 +289,11 @@ function createInteraction( return interaction; } -function generateVrfTags(sortKey: string, vrf: VRF, arweave: Arweave) { +function isTransaction(transactionOrDataItem: Transaction | DataItem): transactionOrDataItem is Transaction { + return (transactionOrDataItem as Transaction).last_tx != undefined; +} + +export function generateVrfTags(sortKey: string, vrf: VRF, arweave: Arweave) { const privateKey = vrf.privKey.toArray(); const data = arweave.utils.stringToBuffer(sortKey); const [index, proof] = Evaluate(privateKey, data); @@ -285,15 +331,7 @@ function bufToBn(buf: Array) { return BigInt('0x' + hex.join('')); } -async function prepareTags( - logger: any, - transaction: Transaction, - originalOwner: string, - currentHeight: number, - currentBlockId: string, - currentBlockTimestamp: number, - arweave: Arweave -) { +async function prepareTags(logger: any, transaction: Transaction, originalOwner: string, arweave: Arweave) { let contractTag: string = '', inputTag: string = '', requestVrfTag = '', @@ -308,16 +346,16 @@ async function prepareTags( for (const tag of transaction.tags) { const key = tag.get('name', { decode: true, string: true }); const value = tag.get('value', { decode: true, string: true }); - if (key == SmartWeaveTags.CONTRACT_TX_ID) { + if (key == SMART_WEAVE_TAGS.CONTRACT_TX_ID) { contractTag = value; } - if (key == SmartWeaveTags.INPUT) { + if (key == SMART_WEAVE_TAGS.INPUT) { inputTag = value; } - if (key == SmartWeaveTags.INTERACT_WRITE) { + if (key == WARP_TAGS.INTERACT_WRITE) { internalWrites.push(value); } - if (key == SmartWeaveTags.REQUEST_VRF) { + if (key == WARP_TAGS.REQUEST_VRF) { requestVrfTag = value; } if (key == 'Signature-Type' && value == 'ethereum') { @@ -339,23 +377,12 @@ async function prepareTags( originalAddress = await arweave.wallets.ownerToAddress(originalOwner); } - const tags = [ - { name: 'Sequencer', value: 'RedStone' }, - { name: 'Sequencer-Owner', value: originalAddress }, - { name: 'Sequencer-Tx-Id', value: transaction.id }, - { name: 'Sequencer-Block-Height', value: '' + currentHeight }, - { name: 'Sequencer-Block-Id', value: currentBlockId }, - { name: 'Sequencer-Block-Timestamp', value: '' + currentBlockTimestamp }, - ...decodedTags, - ]; - return { contractTag, inputTag, requestVrfTag, internalWrites, decodedTags, - tags, originalAddress, isEvmSigner, testnetVersion, @@ -364,7 +391,7 @@ async function prepareTags( export async function uploadToBundlr( transaction: Transaction, - bundlr: Bundlr, + bundlr: Irys, tags: GQLTagInterface[], logger: WarpLogger ) { @@ -372,7 +399,7 @@ export async function uploadToBundlr( const bTx = bundlr.createTransaction(JSON.stringify(transaction), { tags }); await bTx.sign(); - const bundlrResponse = await bundlr.uploader.uploadTransaction(bTx, { getReceiptSignature: true }); + const bundlrResponse = await bundlr.uploader.uploadTransaction(bTx); logger.debug('Uploading to bundlr', { elapsed: uploadBenchmark.elapsed(), @@ -389,7 +416,7 @@ export async function uploadToBundlr( return { bTx, bundlrResponse }; } -async function createSortKey( +export async function createSortKey( arweave: Arweave, jwk: JWKInterface, blockId: string, @@ -407,3 +434,31 @@ async function createSortKey( return `${blockHeightString},${mills},${hashed}`; } + +export function checkWhitelistedWallet(walletAddr: string, contractTxId: string) { + if ( + contractTxId === 'p5OI99-BaY4QbZts266T7EDwofZqs-wVuYJmMCS0SUU' && + walletAddr !== 'ESCLmn6txFGgK-XO2U6svB1543n2SoGTB29Aptnj9v0' + ) { + throw new Error(`Wallet blacklisted: ${walletAddr}`); + } +} + +export function checkBlacklistedFunction(functionName: string, contractTxId: string) { + const blacklistedFunctions: string[] = [ + 'getAddress', + 'getCounter', + 'balance', + 'getBoost', + 'getRoulettePick', + 'getRouletteSwitch', + 'getRanking', + ]; + + if ( + contractTxId === 'p5OI99-BaY4QbZts266T7EDwofZqs-wVuYJmMCS0SUU' && + blacklistedFunctions.includes(functionName?.trim()) + ) { + throw new Error(`"View" function blacklisted: ${functionName}`); + } +} diff --git a/src/gateway/router/routes/sequencerRoute_v2.ts b/src/gateway/router/routes/sequencerRoute_v2.ts new file mode 100644 index 00000000..fba030b6 --- /dev/null +++ b/src/gateway/router/routes/sequencerRoute_v2.ts @@ -0,0 +1,272 @@ +import Router from '@koa/router'; +import { Benchmark, SMART_WEAVE_TAGS, Tags, VrfData, WARP_TAGS, timeout } from 'warp-contracts'; +import { isTxIdValid, parseFunctionName, safeParse } from '../../../utils'; +import { BUNDLR_NODE1_URL } from '../../../constants'; +import { Knex } from 'knex'; +import { GatewayError } from '../../errorHandlerMiddleware'; +import { DataItem } from 'arbundles'; +import { + checkBlacklistedFunction, + checkWhitelistedWallet, + createInteraction, + generateVrfTags, + SequencerResult +} from "./sequencerRoute"; +import { createSortKey } from './sequencerRoute'; +import { tagsExceedLimit } from 'warp-arbundles'; +import rawBody from 'raw-body'; +import { b64UrlToString } from 'arweave/node/lib/utils'; +import { determineOwner } from './deploy/deployContractRoute_v2'; + +export const MAX_INTERACTION_DATA_ITEM_SIZE_BYTES = 20000; + +export async function sequencerRoute_v2(ctx: Router.RouterContext) { + const { dbSource } = ctx; + const trx = (await dbSource.primaryDb.transaction()) as Knex.Transaction; + + const { timeoutId, timeoutPromise } = timeout(0.5); + + try { + const result = await Promise.race([timeoutPromise, doGenerateSequence(ctx, trx)]); + await trx.commit(); + ctx.body = result; + } catch (e: any) { + if (trx != null) { + await trx.rollback(); + } + throw new GatewayError(e?.message || e); + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + } +} + +async function doGenerateSequence(ctx: Router.RouterContext, trx: Knex.Transaction): Promise { + const { sLogger, arweave, jwk, vrf, pgAdvisoryLocks } = ctx; + + const initialBenchmark = Benchmark.measure(); + + const benchmark = Benchmark.measure(); + + const rawDataItem: Buffer = await rawBody(ctx.req); + const interactionDataItem = new DataItem(rawDataItem); + + if (tagsExceedLimit(interactionDataItem.tags)) { + throw new Error(`Interaction data item tags exceed limit.`); + } + + if (interactionDataItem.getRaw().length > MAX_INTERACTION_DATA_ITEM_SIZE_BYTES) { + throw new Error( + `Interaction data item size: ${ + interactionDataItem.getRaw().length + } exceeds maximum interaction data item size limit: ${MAX_INTERACTION_DATA_ITEM_SIZE_BYTES}.` + ); + } + + sLogger.debug('New sequencer data item', interactionDataItem.id); + + const isInteractionDataItemValid = await interactionDataItem.isValid(); + if (!isInteractionDataItemValid) { + ctx.throw(400, 'Interaction data item binary is not valid.'); + } + + const contractTag = interactionDataItem.tags.find((t) => t.name == SMART_WEAVE_TAGS.CONTRACT_TX_ID)!.value; + + const acquireMutexResult = await pgAdvisoryLocks.acquireSortKeyMutex(contractTag, trx); + sLogger.debug('Acquire mutex result', acquireMutexResult); + // note: lastSortKey can be null if that's a very first interaction with a contract. + if ( + acquireMutexResult.blockHash == null || + acquireMutexResult.blockHeight == null || + acquireMutexResult.blockTimestamp == null + ) { + throw new Error(`Missing data in acquireMutexResult: ${JSON.stringify(acquireMutexResult)}`); + } + + const millis = Date.now(); + const sortKey = await createSortKey( + arweave, + jwk, + acquireMutexResult.blockHash, + millis, + interactionDataItem.id, + acquireMutexResult.blockHeight + ); + if (acquireMutexResult.lastSortKey !== null && sortKey.localeCompare(acquireMutexResult.lastSortKey) <= 0) { + throw new Error(`New sortKey (${sortKey}) <= lastSortKey (${acquireMutexResult.lastSortKey})!`); + } + + const data = b64UrlToString(interactionDataItem.data); + const originalSignature = interactionDataItem.signature; + const originalAddress = await determineOwner(interactionDataItem, arweave); + const testnetVersion = interactionDataItem.tags.find((t) => t.name == WARP_TAGS.WARP_TESTNET)?.value || null; + const internalWrites: string[] = []; + interactionDataItem.tags + .filter((t) => t.name == WARP_TAGS.INTERACT_WRITE) + .forEach((t) => internalWrites.push(t.value)); + + const interactionTags = interactionDataItem.tags; + let input: string | null; + const inputFormat = interactionDataItem.tags.find((t) => t.name == WARP_TAGS.INPUT_FORMAT)?.value; + if (inputFormat == 'tag') { + input = getInputFromTag(interactionDataItem.tags); + } else if (inputFormat == 'data') { + input = getInputFromData(data, interactionTags); + } else { + input = getInputFromTag(interactionDataItem.tags) || getInputFromData(data, interactionTags); + } + + if (!input) { + throw new Error(`Input not specifided`); + } + + const tags = [ + { name: 'Sequencer', value: 'RedStone' }, + { name: 'Sequencer-Owner', value: originalAddress }, + { name: 'Sequencer-Tx-Id', value: interactionDataItem.id }, + { name: 'Sequencer-Block-Height', value: '' + acquireMutexResult.blockHeight }, + { name: 'Sequencer-Block-Id', value: acquireMutexResult.blockHash }, + { name: 'Sequencer-Block-Timestamp', value: '' + acquireMutexResult.blockTimestamp }, + { name: 'Sequencer-Mills', value: '' + millis }, + { name: 'Sequencer-Sort-Key', value: sortKey }, + { name: 'Sequencer-Prev-Sort-Key', value: acquireMutexResult.lastSortKey || 'null' }, + ...interactionDataItem.tags, + ]; + + if (tagsExceedLimit(tags)) { + throw new Error(`Nested bundle tags exceed limit.`); + } + + let vrfData: VrfData | null = null; + const requestVrfTag = interactionDataItem.tags.find((t) => t.name == WARP_TAGS.REQUEST_VRF)?.value || null; + if (requestVrfTag) { + const vrfGen = generateVrfTags(sortKey, vrf, arweave); + tags.push(...vrfGen.vrfTags); + vrfData = vrfGen.vrfData; + } + + const interaction = createInteraction( + interactionDataItem, + originalAddress, + interactionTags, + acquireMutexResult.blockHeight, + acquireMutexResult.blockHash, + acquireMutexResult.blockTimestamp, + sortKey, + vrfData, + originalSignature, + testnetVersion, + acquireMutexResult.lastSortKey + ); + + const parsedInput = safeParse(input, sLogger); + const functionName = parseFunctionName(input, sLogger); + + checkWhitelistedWallet(originalAddress, contractTag); + checkBlacklistedFunction(functionName, contractTag); + + let evolve: string | null; + evolve = functionName == 'evolve' && parsedInput.value && isTxIdValid(parsedInput.value) ? parsedInput.value : null; + const manifest = safeParse(data, sLogger)?.manifest || null; + + sLogger.debug('Initial benchmark', initialBenchmark.elapsed()); + sLogger.debug('inserting into tables'); + + await trx.raw( + ` + WITH ins_interaction AS ( + INSERT + INTO interactions (interaction_id, + interaction, + block_height, + block_id, + contract_id, + function, + confirmation_status, + confirming_peer, + source, + block_timestamp, + interact_write, + sort_key, + evolve, + testnet, + last_sort_key, + owner, + sync_timestamp, + manifest) + VALUES ( + :interaction_id, + :interaction, + :block_height, + :block_id, + :contract_id, + :function, + :confirmation_status, + :confirming_peer, + :source, + :block_timestamp, + :interact_write, + :sort_key, + :evolve, + :testnet, + :prev_sort_key, + :owner, + :sync_timestamp, + :manifest) + RETURNING id) + INSERT + INTO bundle_items (interaction_id, state, transaction, tags, data_item) + SELECT i.id, 'PENDING', :original_transaction, :tags, :data_item + FROM ins_interaction i; + `, + { + interaction_id: interactionDataItem.id, + interaction: interaction, + block_height: acquireMutexResult.blockHeight, + block_id: acquireMutexResult.blockHash, + contract_id: contractTag, + function: functionName, + confirmation_status: 'confirmed', + confirming_peer: BUNDLR_NODE1_URL, + source: 'redstone-sequencer', + block_timestamp: acquireMutexResult.blockTimestamp, + interact_write: internalWrites, + sort_key: sortKey, + evolve: evolve, + testnet: testnetVersion, + prev_sort_key: acquireMutexResult.lastSortKey, + owner: originalAddress, + original_transaction: ctx.request.body, + tags: JSON.stringify(tags), + sync_timestamp: millis, + data_item: interactionDataItem.getRaw(), + manifest: manifest, + } + ); + + sLogger.info('Total sequencer processing', benchmark.elapsed()); + + return { + id: interactionDataItem.id, + sortKey, + timestamp: millis, + prevSortKey: acquireMutexResult.lastSortKey, + internalWrites, + }; +} + +function getInputFromTag(tags: { name: string; value: string }[]) { + const inputFromTag = tags.find((t) => t.name == SMART_WEAVE_TAGS.INPUT); + return inputFromTag ? inputFromTag.value : null; +} + +function getInputFromData(data: string, interactionTags: { name: string; value: string }[]) { + const inputFromData = JSON.parse(data).input; + if (inputFromData) { + interactionTags.push({ name: SMART_WEAVE_TAGS.INPUT, value: JSON.stringify(inputFromData) }); + return JSON.stringify(inputFromData); + } else { + return null; + } +} diff --git a/src/gateway/router/routes/stats/totalTxsRoute.ts b/src/gateway/router/routes/stats/totalTxsRoute.ts index 235d1f52..1bd31354 100644 --- a/src/gateway/router/routes/stats/totalTxsRoute.ts +++ b/src/gateway/router/routes/stats/totalTxsRoute.ts @@ -1,32 +1,27 @@ import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; +import {Benchmark} from 'warp-contracts'; export async function totalTxsRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - const { testnet } = ctx.query; + const {logger, dbSource} = ctx; + const {testnet} = ctx.query; - try { - const benchmark = Benchmark.measure(); - const result: any = await dbSource.raw( - ` - SELECT 1 AS sort_order, count(i.id) AS total - FROM interactions i - ${testnet ? ' WHERE i.testnet IS NOT NULL' : ''} - UNION - SELECT 2 AS sort_order, count(c.contract_id) AS total - FROM contracts c WHERE c.type != 'error' ${testnet ? ' AND c.testnet IS NOT NULL' : ''} - ORDER BY sort_order; - ` - ); - ctx.body = { - total_interactions: result?.rows[0].total, - total_contracts: result?.rows[1].total, - }; + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + ` + SELECT 1 AS sort_order, count(i.id) AS total + FROM interactions i + ${testnet ? ' WHERE i.testnet IS NOT NULL' : ''} + UNION + SELECT 2 AS sort_order, count(c.contract_id) AS total + FROM contracts c + WHERE c.type != 'error' ${testnet ? ' AND c.testnet IS NOT NULL' : ''} + ORDER BY sort_order; + ` + ); + ctx.body = { + total_interactions: result?.rows[0].total, + total_contracts: result?.rows[1].total, + }; - logger.debug('Stats loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + logger.debug('Stats loaded in', benchmark.elapsed()); } diff --git a/src/gateway/router/routes/stats/txsPerDayRoute.ts b/src/gateway/router/routes/stats/txsPerDayRoute.ts index 7f222911..016150aa 100644 --- a/src/gateway/router/routes/stats/txsPerDayRoute.ts +++ b/src/gateway/router/routes/stats/txsPerDayRoute.ts @@ -1,44 +1,38 @@ import Router from '@koa/router'; -import { Benchmark } from 'warp-contracts'; +import {Benchmark} from 'warp-contracts'; export async function txsPerDayRoute(ctx: Router.RouterContext) { - const { logger, dbSource } = ctx; - const { testnet } = ctx.query; + const {logger, dbSource} = ctx; + const {testnet} = ctx.query; - try { - const benchmark = Benchmark.measure(); - const contracts: any = await dbSource.raw( - ` - WITH contracts_per_day AS ( - SELECT date(to_timestamp((block_timestamp)::integer)) as date, contract_id as interaction - FROM contracts - WHERE testnet ${testnet ? ' IS NOT NULL' : ' IS NULL'} - ) - SELECT date, count(*) as per_day FROM contracts_per_day + const benchmark = Benchmark.measure(); + const contracts: any = await dbSource.raw( + ` + WITH contracts_per_day AS (SELECT date(to_timestamp((block_timestamp)::integer)) as date, + contract_id as interaction + FROM contracts + WHERE testnet ${testnet ? ' IS NOT NULL' : ' IS NULL'}) + SELECT date, count(*) as per_day + FROM contracts_per_day GROUP BY date ORDER BY date ASC; - ` - ); - const interactions: any = await dbSource.raw( - ` - WITH transactions_per_day AS ( - SELECT date(to_timestamp((block_timestamp)::integer)) as date, interaction_id as interaction - FROM interactions - WHERE testnet ${testnet ? ' IS NOT NULL' : ' IS NULL'} - ) - SELECT date, count(*) as per_day FROM transactions_per_day + ` + ); + const interactions: any = await dbSource.raw( + ` + WITH transactions_per_day AS (SELECT date(to_timestamp((block_timestamp)::integer)) as date, + interaction_id as interaction + FROM interactions + WHERE testnet ${testnet ? ' IS NOT NULL' : ' IS NULL'}) + SELECT date, count(*) as per_day + FROM transactions_per_day GROUP BY date ORDER BY date ASC; - ` - ); - ctx.body = { - contracts_per_day: contracts.rows, - interactions_per_day: interactions.rows, - }; - logger.debug('Contracts stats loaded in', benchmark.elapsed()); - } catch (e: any) { - ctx.logger.error(e); - ctx.status = 500; - ctx.body = { message: e }; - } + ` + ); + ctx.body = { + contracts_per_day: contracts.rows, + interactions_per_day: interactions.rows, + }; + logger.debug('Contracts stats loaded in', benchmark.elapsed()); } diff --git a/src/gateway/router/routes/warpy/joinSeason3.ts b/src/gateway/router/routes/warpy/joinSeason3.ts new file mode 100644 index 00000000..e6733407 --- /dev/null +++ b/src/gateway/router/routes/warpy/joinSeason3.ts @@ -0,0 +1,43 @@ +import Router from '@koa/router'; +import { Benchmark } from 'warp-contracts'; + +export async function joinSeason3(ctx: Router.RouterContext) { + const { logger, dbSource } = ctx; + + const { contractId, userId } = ctx.query; + + const benchmark = Benchmark.measure(); + const result: any = await dbSource.raw( + `with joined_table as ( + select interaction_id, interaction + from interactions, + jsonb_array_elements(interaction -> 'tags') tags + where contract_id = '${contractId}' and function = 'addPoints' + and tags ->> 'name' = 'Reward-For' and tags ->> 'value' = 'Join-Season-3' + ), + joined_table_res as ( + select true as joined + from joined_table, jsonb_array_elements(interaction -> 'tags') as tags, jsonb_array_elements((tags ->> 'value')::jsonb -> 'members') as members + where tags ->> 'name' = 'Input' + and members::jsonb ->> 'id' = '${userId}' + ), + registration_table as ( + select '${userId}' as id, sync_timestamp as timestamp + from interactions, + jsonb_array_elements(interaction -> 'tags') tags + where contract_id = '${contractId}' and function = 'registerUser' + and tags ->> 'name' = 'Input' + and (tags ->> 'value')::jsonb ->> 'id' = '${userId}' + ) + select * from registration_table left join joined_table_res on true;` + ); + + const joinSeason3Result = result.rows[0]; + ctx.body = { + id: joinSeason3Result?.id, + joined: joinSeason3Result?.joined, + timestamp: joinSeason3Result?.timestamp, + }; + + logger.debug(`User's registration date loaded in ${benchmark.elapsed()}`); +} diff --git a/src/gateway/router/welcomeRouter.ts b/src/gateway/router/welcomeRouter.ts index 243a127b..ed6133c2 100644 --- a/src/gateway/router/welcomeRouter.ts +++ b/src/gateway/router/welcomeRouter.ts @@ -1,12 +1,14 @@ import Router from '@koa/router'; -import { createReadStream } from 'fs'; -import * as path from 'path'; +import { MAX_INTERACTION_DATA_ITEM_SIZE_BYTES } from './routes/sequencerRoute_v2'; const welcomeRouter = new Router(); welcomeRouter.get('/', (ctx: Router.RouterContext) => { - ctx.type = 'text/html'; - ctx.body = createReadStream('welcome.html'); + ctx.body = { + name: 'Warp Gateway', + id: process.env.pm_id, + maxInteractionDataItemSizeBytes: MAX_INTERACTION_DATA_ITEM_SIZE_BYTES, + }; }); export default welcomeRouter; diff --git a/src/gateway/runGatewayTasks.ts b/src/gateway/runGatewayTasks.ts deleted file mode 100644 index 251a2cad..00000000 --- a/src/gateway/runGatewayTasks.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { runLoadPeersTask } from './tasks/loadPeers'; -import { runVerifyInteractionsTask } from './tasks/verifyInteractions'; -import { runVerifyCorruptedTransactionsTask } from './tasks/verifyCorruptedTransactions'; -import { - runSyncLastSixHoursTransactionsTask, - runSyncLastHourTransactionsTask, - runSyncRecentTransactionsTask, -} from './tasks/syncTransactions'; -import { GatewayContext } from './init'; -import { runContractsMetadataTask, runLoadContractsFromGqlTask } from './tasks/contractsMetadata'; -import { runEvolvedContractSourcesTask } from './tasks/evolvedContractSources'; - -/** - * Gateway consists of four separate tasks, each runs with its own interval: - * - * 1. peers tasks - checks the status (ie. "/info" endpoint) of all the peers returned by the arweave.net/peers. - * If the given peer does not respond within MAX_ARWEAVE_PEER_INFO_TIMEOUT_MS - it is blacklisted 'till next round. - * "Blocks", "height" from the response to "/info" and response times are being stored in the db - so that it would - * be possible to rank peers be their "completeness" (ie. how many blocks do they store) and response times. - * - * 2. blocks sync task - listens for new blocks and loads the SmartWeave interaction transactions. - * - * 3. interactions verifier task - tries its best to confirm that transactions are not corrupted. - * It takes the first PARALLEL_REQUESTS non confirmed transactions with block height lower then - * current - MIN_CONFIRMATIONS. - * For each set of the selected 'interactionsToCheck' transactions it makes - * TX_CONFIRMATION_SUCCESSFUL_ROUNDS query rounds (to randomly selected at each round peers). - * Only if we get TX_CONFIRMATION_SUCCESSFUL_ROUNDS within TX_CONFIRMATION_MAX_ROUNDS - * AND response for the given transaction is the same for all the successful rounds - * - the "confirmation" info for given transaction is updated in the the database. - * - * 4. corrupted transactions verifier task - additional task that double-verifies interactions marked as corrupted. If during the - * re-check the interaction won't be recognized as corrupted - it is returned to the "not processed" pool. - * - * 5. contracts metadata task - loads the contracts metadata (src, init state, owner, etc.) - * - * note: as there are very little fully synced nodes and they often timeout/504 - this process is a real pain... - */ -export async function runGatewayTasks(context: GatewayContext) { - //await runBundlrCheck(context); - - await runLoadPeersTask(context); - - await runContractsMetadataTask(context); - - await runSyncRecentTransactionsTask(context); - - await runSyncLastHourTransactionsTask(context); - - await runVerifyInteractionsTask(context); - - await runVerifyCorruptedTransactionsTask(context); - - await runLoadContractsFromGqlTask(context); - - await runEvolvedContractSourcesTask(context); - - // await runSyncLastSixHoursTransactionsTask(context); -} diff --git a/src/gateway/tasks/TaskRunner.ts b/src/gateway/tasks/TaskRunner.ts index 44081fb3..ce9a87df 100644 --- a/src/gateway/tasks/TaskRunner.ts +++ b/src/gateway/tasks/TaskRunner.ts @@ -37,7 +37,6 @@ export class TaskRunner { // https://developer.mozilla.org/en-US/docs/Web/API/setInterval#ensure_that_execution_duration_is_shorter_than_interval_frequency setTimeout(async function () { if (initialRunDone) { - context.logger.info(`Starting ${name} task.`); await worker(context); context.logger.info(`Task ${name} completed.`); } diff --git a/src/gateway/tasks/contractsMetadata.ts b/src/gateway/tasks/contractsMetadata.ts deleted file mode 100644 index aa0349d8..00000000 --- a/src/gateway/tasks/contractsMetadata.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { TaskRunner } from './TaskRunner'; -import { GatewayContext } from '../init'; -import { ContractDefinition, ContractDefinitionLoader, GQLEdgeInterface, SmartWeaveTags } from 'warp-contracts'; -import { loadPages, MAX_GQL_REQUEST, ReqVariables } from '../../gql'; -import { FIRST_SW_TX_BLOCK_HEIGHT, MAX_BATCH_INSERT, testnetVersion } from './syncTransactions'; -import { getCachedNetworkData } from './networkInfoCache'; -import { publishContract, sendNotification } from '../publisher'; -import { DatabaseSource } from '../../db/databaseSource'; -import { ContractInsert } from '../../db/insertInterfaces'; -import fs from 'fs'; - -const CONTRACTS_METADATA_INTERVAL_MS = 10000; - -const CONTRACTS_QUERY = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) { - transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) { - pageInfo { - hasNextPage - } - edges { - node { - id - tags { - name - value - } - block { - height - timestamp - } - parent { id } - bundledIn { id } - } - cursor - } - } - }`; - -export async function runContractsMetadataTask(context: GatewayContext) { - await TaskRunner.from('[contracts metadata]', loadContractsMetadata, context).runSyncEvery( - CONTRACTS_METADATA_INTERVAL_MS - ); -} - -export async function runLoadContractsFromGqlTask(context: GatewayContext) { - await TaskRunner.from('[contracts from gql]', loadContractsFromGql, context).runSyncEvery( - CONTRACTS_METADATA_INTERVAL_MS - ); -} - -async function loadContractsFromGql(context: GatewayContext) { - const { logger, dbSource } = context; - - let lastProcessedBlockHeight; - if (fs.existsSync('contracts-sync-l1.json')) { - const data = JSON.parse(fs.readFileSync('contracts-sync-l1.json', 'utf-8')); - lastProcessedBlockHeight = data?.lastProcessedBlockHeight; - } else { - let result: any; - try { - result = await dbSource.selectLastContract(); - lastProcessedBlockHeight = result?.block_height; - } catch (e: any) { - logger.error('Error while checking new blocks', e.message); - return; - } - } - - const currentNetworkHeight = getCachedNetworkData().cachedNetworkInfo.height; - lastProcessedBlockHeight = lastProcessedBlockHeight || FIRST_SW_TX_BLOCK_HEIGHT; - const from = lastProcessedBlockHeight - 1; - const to = currentNetworkHeight - from <= 100 ? currentNetworkHeight : from + 100; - logger.info('Loading L1 contracts', { - from, - to, - }); - - let transactions: GQLEdgeInterface[]; - try { - transactions = await load(context, from, to); - } catch (e: any) { - logger.error('Error while loading contracts', e.message); - return; - } - - if (transactions.length === 0) { - logger.info('No new contracts'); - fs.writeFileSync('contracts-sync-l1.json', JSON.stringify({ lastProcessedBlockHeight: to }), 'utf-8'); - return; - } - - logger.info(`Found ${transactions.length} contracts`); - - let contractsInserts: Partial[] = []; - - const contractsInsertsIds = new Set(); - for (let transaction of transactions) { - const contractId = transaction.node.id; - if (!contractsInsertsIds.has(contractId)) { - const contentType = getContentTypeTag(transaction); - const testnet = testnetVersion(transaction); - if (!contentType) { - logger.warn(`Cannot determine contract content type for contract ${contractId}`); - } - contractsInserts.push({ - contract_id: transaction.node.id, - block_height: transaction.node.block.height, - block_timestamp: transaction.node.block.timestamp, - content_type: contentType || 'unknown', - deployment_type: 'arweave', - testnet, - sync_timestamp: Date.now(), - }); - contractsInsertsIds.add(contractId); - - if (contractsInserts.length === MAX_BATCH_INSERT) { - try { - logger.info(`Batch insert ${MAX_BATCH_INSERT} interactions.`); - await insertContracts(dbSource, contractsInserts); - contractsInserts = []; - } catch (e) { - logger.error(e); - return; - } - } - } - } - - logger.info(`Saving last`, contractsInserts.length); - - if (contractsInserts.length > 0) { - try { - await insertContracts(dbSource, contractsInserts); - } catch (e) { - logger.error(e); - return; - } finally { - fs.writeFileSync('contracts-sync-l1.json', JSON.stringify({ lastProcessedBlockHeight: to }), 'utf-8'); - } - } else { - fs.writeFileSync('contracts-sync-l1.json', JSON.stringify({ lastProcessedBlockHeight: to }), 'utf-8'); - } - - logger.info(`Inserted ${contractsInserts.length} contracts`); -} - -async function insertContracts(dbSource: DatabaseSource, contractsInserts: any[]) { - await dbSource.insertContractsMetadata(contractsInserts); -} - -function getContentTypeTag(interactionTransaction: GQLEdgeInterface): string | undefined { - return interactionTransaction.node.tags.find((tag) => tag.name === SmartWeaveTags.CONTENT_TYPE)?.value; -} - -async function load(context: GatewayContext, from: number, to: number): Promise { - const variables: ReqVariables = { - bundledIn: null, - tags: [ - { - name: SmartWeaveTags.APP_NAME, - values: ['SmartWeaveContract'], - }, - ], - blockFilter: { - min: from, - max: to, - }, - first: MAX_GQL_REQUEST, - }; - - const { logger, arweaveWrapperGqlGoldsky } = context; - return await loadPages({ logger, arweaveWrapper: arweaveWrapperGqlGoldsky }, CONTRACTS_QUERY, variables); -} - -async function loadContractsMetadata(context: GatewayContext) { - const { arweave, logger, dbSource, arweaveWrapper } = context; - const definitionLoader = new ContractDefinitionLoader(arweave, 'mainnet'); - - const result: { - contract: string; - blockHeight: number; - blockTimestamp: number; - syncTimestamp: number; - testnet: string; - }[] = ( - await dbSource.raw( - ` - SELECT contract_id AS contract, - block_height AS blockHeight, - block_timestamp AS blockTimestamp, - sync_timestamp AS syncTimestamp, - testnet AS testnet - FROM contracts - WHERE contract_id != '' - AND contract_id NOT ILIKE '()%' - AND src_tx_id IS NULL - AND type IS NULL; - ` - ) - ).rows; - - const missing = result?.length || 0; - logger.info(`Loading ${missing} contract definitions.`); - - if (missing == 0) { - return; - } - - for (const row of result) { - logger.debug(`Loading ${row.contract} definition.`); - try { - const contractId = row.contract.trim(); - const definition: ContractDefinition = await definitionLoader.load(contractId); - const type = evalType(definition.initState); - const srcTxOwner = await arweave.wallets.ownerToAddress(definition.srcTx.owner); - - let update: any = { - src_tx_id: definition.srcTxId, - init_state: definition.initState, - owner: definition.owner, - type, - pst_ticker: type == 'pst' ? definition.initState?.ticker : null, - pst_name: type == 'pst' ? definition.initState?.name : null, - contract_tx: { tags: definition.contractTx.tags }, - }; - - let contracts_src_insert: any = { - src_tx_id: definition.srcTxId, - owner: srcTxOwner, - src_content_type: definition.contractType == 'js' ? 'application/javascript' : 'application/wasm', - src_tx: definition.srcTx, - deployment_type: 'arweave', - }; - - if (definition.contractType == 'js') { - contracts_src_insert = { - ...contracts_src_insert, - src: definition.src, - }; - } else { - const rawTxData = await arweaveWrapper.txData(definition.srcTxId); - contracts_src_insert = { - ...contracts_src_insert, - src_binary: rawTxData, - src_wasm_lang: definition.srcWasmLang, - }; - } - - logger.debug(`Inserting ${row.contract} metadata into db`); - await dbSource.updateContractMetadata(definition.txId, update); - - await dbSource.updateContractSrc(contracts_src_insert); - - // TODO: add tags to ContractDefinition type in the SDK - sendNotification(context, definition.txId, { initState: definition.initState, tags: [] }); - publishContract( - context, - contractId, - definition.owner, - type, - row.blockHeight, - row.blockTimestamp, - 'arweave', - row.syncTimestamp, - row.testnet - ); - - logger.debug(`${row.contract} metadata inserted into db`); - } catch (e) { - logger.error(`Error while loading contract ${row.contract} definition`, e); - await dbSource.updateContractError(row); - } - } -} - -export function evalType(initState: any): string { - if (initState.ticker && initState.balances) { - return 'pst'; - } - - return 'other'; -} diff --git a/src/gateway/tasks/evolvedContractSources.ts b/src/gateway/tasks/evolvedContractSources.ts deleted file mode 100644 index e4bc984a..00000000 --- a/src/gateway/tasks/evolvedContractSources.ts +++ /dev/null @@ -1,156 +0,0 @@ -import Arweave from 'arweave'; -import Transaction from 'arweave/node/lib/transaction'; -import { ContractDefinitionLoader, ContractSource, SmartWeaveTags, TagsParser, WasmSrc } from 'warp-contracts'; -import { ContractSourceInsert } from '../../db/insertInterfaces'; -import { GatewayContext } from '../init'; -import { TaskRunner } from './TaskRunner'; - -const CONTRACTS_SOURCE_INTERVAL_MS = 10000; - -export async function runEvolvedContractSourcesTask(context: GatewayContext) { - await TaskRunner.from('[evolved contract sources]', loadEvolvedContractSources, context).runSyncEvery( - CONTRACTS_SOURCE_INTERVAL_MS - ); -} - -async function loadEvolvedContractSources(context: GatewayContext) { - const { logger, dbSource, arweave } = context; - const definitionLoader = new ContractDefinitionLoader(arweave, 'mainnet'); - const tagsParser = new TagsParser(); - - const result: { evolve: string }[] = ( - await dbSource.raw( - ` - SELECT evolve - FROM interactions - WHERE evolve NOT IN (SELECT src_tx_id from contracts_src) - AND evolve IS NOT NULL; - ` - ) - ).rows; - - const missing = result?.length || 0; - logger.info(`Loading ${missing} evolved contract sources.`); - - if (missing == 0) { - return; - } - - for (const row of result) { - logger.debug(`Loading evolved contract source: ${row.evolve}.`); - const srcTxId = row.evolve; - - let src, srcWasmLang, contractType, srcTx, srcBinary; - - let loaded = false; - - try { - ({ src, srcWasmLang, contractType, srcTx } = await definitionLoader.loadContractSource(srcTxId)); - await insertSourceToDb(srcTxId, contractType, srcTx, src, srcBinary, srcWasmLang, context, tagsParser); - loaded = true; - } catch (e) { - logger.debug(`Cannot load evolved contract source transaction. ${srcTxId}.`, e); - } - - if (!loaded) { - try { - ({ src, srcBinary, srcTx, srcWasmLang, contractType, srcBinary } = await loadSourceDataItem( - srcTxId, - tagsParser - )); - await insertSourceToDb(srcTxId, contractType, srcTx, src, srcBinary, srcWasmLang, context, tagsParser); - loaded = true; - } catch (e) { - logger.debug(`Cannot load evolved contract source data item ${srcTxId}.`, e); - } - } - - if (!loaded) { - try { - let contracts_src_insert: any = { - src_tx_id: srcTxId, - src: 'error', - }; - - await dbSource.insertContractSource(contracts_src_insert); - logger.debug(`${row.evolve} evolved contract source inserted into db as errored.`); - } catch (e) { - logger.error(`Error while loading evolved contract source ${srcTxId}`, e); - } - } - } - logger.info(`Loaded ${missing} evolved contract sources.`); -} - -export async function loadSourceDataItem(srcTxId: string, tagsParser: TagsParser) { - const jsonSrc = await fetch(`https://arweave.net/${srcTxId}`).then((res) => { - return res.json(); - }); - const srcTx = new Transaction(jsonSrc); - let src, srcBinary, srcWasmLang, srcContentType; - - srcContentType = tagsParser.getTag(srcTx, SmartWeaveTags.CONTENT_TYPE).value; - if (srcContentType == 'application/javascript') { - src = Arweave.utils.bufferToString(srcTx.data); - } else { - const bufData = Buffer.from(srcTx.data); - const wasmSrc = new WasmSrc(bufData); - srcBinary = await wasmSrc.sourceCode(); - srcWasmLang = tagsParser.getTag(srcTx, SmartWeaveTags.WASM_LANG).value; - } - - return { - src, - srcBinary, - srcTx, - srcWasmLang, - contractType: srcContentType == 'application/javascript' ? 'js' : 'wasm', - }; -} - -export async function insertSourceToDb( - srcTxId: string, - contractType: string, - srcTx: Transaction, - src: any, - srcBinary: any, - srcWasmLang: string | null, - ctx: GatewayContext, - tagsParser: TagsParser -): Promise { - const tx = new Transaction(srcTx); - - const decodedTags = tagsParser.decodeTags(tx); - const signatureType = decodedTags.find((t) => t.name == SmartWeaveTags.SIGNATURE_TYPE); - const testnet = decodedTags.find((t) => t.name == SmartWeaveTags.WARP_TESTNET); - - let contracts_src_insert: any = { - src_tx_id: srcTxId, - src_content_type: contractType == 'js' ? 'application/javascript' : 'application/wasm', - src_tx: srcTx, - owner: - signatureType && signatureType.name == 'ethereum' - ? srcTx.owner - : await ctx.arweave.wallets.ownerToAddress(srcTx.owner), - testnet: testnet ? testnet : null, - }; - - if (contractType == 'js') { - contracts_src_insert = { - ...contracts_src_insert, - src: src, - }; - } else { - contracts_src_insert = { - ...contracts_src_insert, - src_binary: srcBinary ? srcBinary : await ctx.arweaveWrapper.txData(srcTxId), - src_wasm_lang: srcWasmLang, - }; - } - - ctx.logger.debug(`Inserting ${srcTxId} evolved contract source into db`); - - await ctx.dbSource.insertContractSource(contracts_src_insert); - - ctx.logger.debug(`${srcTxId} evolved contract source inserted into db`); -} diff --git a/src/gateway/tasks/loadPeers.ts b/src/gateway/tasks/loadPeers.ts deleted file mode 100644 index 657ee295..00000000 --- a/src/gateway/tasks/loadPeers.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { PeerList } from 'arweave/node/network'; -import { Benchmark } from 'warp-contracts'; -import axios from 'axios'; -import { TaskRunner } from './TaskRunner'; -import { GatewayContext } from '../init'; -import { PeerInsert } from '../../db/insertInterfaces'; - -const MAX_ARWEAVE_PEER_INFO_TIMEOUT_MS = 3000; -const PEERS_CHECK_INTERVAL_MS = 1000 * 60 * 60; - -export async function runLoadPeersTask(context: GatewayContext) { - const { logger, dbSource } = context; - const currentPeers: { peer: string }[] = await dbSource.raw(`SELECT peer from peers;`); - if (currentPeers.length < 200) { - logger.info('Pre-loading peers...'); - await loadPeers(context); - } - - await TaskRunner.from('[load peers]', loadPeers, context).runAsyncEvery(PEERS_CHECK_INTERVAL_MS, false); -} - -async function loadPeers(context: GatewayContext) { - const { logger, arweave, dbSource } = context; - - logger.info('Updating peers...'); - - const newPeers: PeerList = await arweave.network.getPeers(); - const currentPeers: { peer: string }[] = await dbSource.raw(`SELECT peer FROM peers;`); - - const peersToRemove: string[] = []; - currentPeers.forEach((currentPeer) => { - if (!newPeers.find((peer) => currentPeer.peer === peer)) { - peersToRemove.push(currentPeer.peer); - } - }); - - logger.debug('Removing no longer available peers', peersToRemove); - - const removedCount = await dbSource.deletePeers(peersToRemove); - - logger.debug(`Removed ${removedCount} elements.`); - - for (const peer of newPeers) { - logger.debug(`Checking Arweave peer ${peer} [${newPeers.indexOf(peer) + 1} / ${newPeers.length}]`); - try { - const benchmark = Benchmark.measure(); - const result = await axios.get(`http://${peer}/info`, { - timeout: MAX_ARWEAVE_PEER_INFO_TIMEOUT_MS, - }); - const elapsed = benchmark.elapsed(true); - - const peerInsert: PeerInsert = { - peer: peer, - blocks: result.data.blocks, - height: result.data.height, - response_time: elapsed, - blacklisted: false, - }; - - await dbSource.insertPeer(peerInsert); - } catch (e: any) { - logger.error(`Error from ${peer}`, e.message); - await dbSource.insertPeerEror(peer); - } - } -} diff --git a/src/gateway/tasks/networkInfoCache.ts b/src/gateway/tasks/networkInfoCache.ts index 8f03aa83..c005876a 100644 --- a/src/gateway/tasks/networkInfoCache.ts +++ b/src/gateway/tasks/networkInfoCache.ts @@ -1,65 +1,120 @@ -import { NetworkInfoInterface } from 'arweave/node/network'; -import { BlockData } from 'arweave/node/blocks'; -import { GatewayContext } from '../init'; -import { TaskRunner } from './TaskRunner'; -import { BLOCKS_INTERVAL_MS } from './syncTransactions'; -import fs from 'fs'; +import { NetworkInfoInterface } from "arweave/node/network"; +import { BlockData } from "arweave/node/blocks"; +import { GatewayContext } from "../init"; +import { TaskRunner } from "./TaskRunner"; +import { Knex } from "knex"; +import Arweave from "arweave"; +import { DatabaseSource } from "../../db/databaseSource"; +import { sleep } from "../../utils"; export type NetworkCacheType = { cachedNetworkInfo: NetworkInfoInterface; cachedBlockInfo: BlockData; }; -let cache: NetworkCacheType; export async function runNetworkInfoCacheTask(context: GatewayContext) { - const { arweave, logger, arweaveWrapper } = context; + const { arweave, logger, arweaveWrapper, pgAdvisoryLocks, dbSource } = context; async function updateNetworkInfo() { + // @ts-ignore + const trx = (await dbSource.primaryDb.transaction()) as Knex.Transaction; + try { - const newNetworkInfo = await arweaveWrapper.info(); - if (cache?.cachedNetworkInfo && newNetworkInfo && newNetworkInfo.height < cache.cachedNetworkInfo.height) { - logger.warn('New network height lower than current, skipping.', { - currentHeight: cache?.cachedNetworkInfo.height, - newHeight: newNetworkInfo.height, - }); + const currentArweaveBlock = await pgAdvisoryLocks.acquireArweaveHeightMutex(trx); + if (currentArweaveBlock === undefined) { + logger.debug("Network info already locked, skipping"); + await trx.commit(); return; } - const cachedNetworkInfo = newNetworkInfo; - const cachedBlockInfo = await arweave.blocks.get(cachedNetworkInfo.current as string); - - (cachedBlockInfo as any).poa = {}; - (cachedBlockInfo as any).txs = []; - - cache = { - cachedNetworkInfo, - cachedBlockInfo, - }; + const newNetworkInfo = await arweaveWrapper.info(); + if (currentArweaveBlock === null) { + logger.debug("Current arweave block null, inserting"); + const additionalData = await prepareCacheData(arweave, newNetworkInfo); + await trx.raw(` + INSERT INTO sync_state(name, finished_block_height, finished_block_hash, additional_data) + VALUES ('Arweave', + :block_height, + :block_hash, + :additional_data); + `, { + block_height: newNetworkInfo.height, + block_hash: newNetworkInfo.current, + additional_data: additionalData + }); + } else { + if (newNetworkInfo && newNetworkInfo.height <= currentArweaveBlock.blockHeight) { + logger.debug("New network height lower or equal than current, skipping.", { + currentHeight: currentArweaveBlock.blockHeight, + newHeight: newNetworkInfo.height + }); + await sleep(1000); + await trx.commit(); + return; + } - fs.writeFileSync('network-cache.json', JSON.stringify(cache), 'utf-8'); - logger.debug('New network height', cache.cachedNetworkInfo.height); + const additionalData = await prepareCacheData(arweave, newNetworkInfo); + await trx.raw(` + UPDATE sync_state + SET finished_block_height=:block_height, + finished_block_hash=:block_hash, + additional_data=:additional_data + WHERE name = 'Arweave'; + `, { + block_height: newNetworkInfo.height, + block_hash: newNetworkInfo.current, + additional_data: additionalData + }); + } + // hold lock for a while, so that other process that are trying to check the height + // at about the same time - will get blocked + await sleep(1000); + await trx.commit(); + logger.debug("New network height", newNetworkInfo.height); } catch (e) { - logger.error('Error while loading network info', e); + if (trx != null) { + await trx.rollback(); + } + logger.error("Error while loading network info", e); } } await TaskRunner.from( - '[Arweave network info]', + "[Arweave network info]", async () => { - logger.debug('Loading network info'); - if (cache?.cachedNetworkInfo == null || cache?.cachedBlockInfo == null) { - while (cache?.cachedNetworkInfo == null || cache?.cachedBlockInfo == null) { - await updateNetworkInfo(); - } - } else { - await updateNetworkInfo(); - } + logger.debug("Loading network info"); + await updateNetworkInfo(); }, context - ).runSyncEvery(BLOCKS_INTERVAL_MS, true); + ).runSyncEvery(40 * 1000, true); } -export function getCachedNetworkData(): NetworkCacheType { - return JSON.parse(fs.readFileSync('network-cache.json', 'utf-8')); +export async function getCachedNetworkData(dbSource: DatabaseSource): Promise { + // @ts-ignore + const result = await dbSource.primaryDb.raw(` + SELECT additional_data + FROM sync_state + WHERE name = 'Arweave'; + `); + + if (result?.rows?.length !== 1) { + throw new Error("Cached Arweave network data not available."); + } + + return result.rows[0].additional_data; +} + +async function prepareCacheData(arweave: Arweave, newNetworkInfo: NetworkInfoInterface): Promise { + const cachedNetworkInfo = newNetworkInfo; + const cachedBlockInfo = await arweave.blocks.get(cachedNetworkInfo.current as string); + + (cachedBlockInfo as any).poa = {}; + (cachedBlockInfo as any).txs = []; + (cachedBlockInfo as any).poa2 = {}; + + return { + cachedNetworkInfo, + cachedBlockInfo + }; } diff --git a/src/gateway/tasks/syncTransactions.ts b/src/gateway/tasks/syncTransactions.ts deleted file mode 100644 index cf932631..00000000 --- a/src/gateway/tasks/syncTransactions.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { GQLEdgeInterface, WarpLogger, SmartWeaveTags, TagsParser } from 'warp-contracts'; -import { TaskRunner } from './TaskRunner'; -import { GatewayContext } from '../init'; -import { loadPages, MAX_GQL_REQUEST, ReqVariables } from '../../gql'; -import { isTxIdValid } from '../../utils'; -import { publishInteraction, sendNotification } from '../publisher'; -import { DatabaseSource } from '../../db/databaseSource'; -import { InteractionInsert } from '../../db/insertInterfaces'; -import fs from 'fs'; -import { getCachedNetworkData } from './networkInfoCache'; - -const INTERACTIONS_QUERY = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) { - transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) { - pageInfo { - hasNextPage - } - edges { - node { - id - owner { address } - recipient - tags { - name - value - } - block { - height - id - timestamp - } - fee { winston } - quantity { winston } - parent { id } - bundledIn { id } - } - cursor - } - } - }`; - -const tagsParser = new TagsParser(); - -// in theory avg. block time on Arweave is 120s (?) -// in fact, it varies from ~20s to minutes... -export const BLOCKS_INTERVAL_MS = 30 * 1000; -export const FIRST_SW_TX_BLOCK_HEIGHT = 472810; -const LOAD_PAST_BLOCKS = 50; // smartweave interaction are currently somewhat rare... -// that was a limit for sqlite, but let's leave it for now... -export const MAX_BATCH_INSERT = 500; - -const AVG_BLOCK_TIME_SECONDS = 60; -export const AVG_BLOCKS_PER_HOUR = (60 * 60) / AVG_BLOCK_TIME_SECONDS + 10; -const AVG_BLOCKS_PER_DAY = (60 * 60 * 24) / AVG_BLOCK_TIME_SECONDS + 60; - -const HOUR_INTERVAL_MS = 60 * 60 * 1000; -const DAY_INTERVAL_MS = HOUR_INTERVAL_MS * 24; - -export async function runSyncRecentTransactionsTask(context: GatewayContext) { - await TaskRunner.from('[sync latest transactions]', syncLastTransactions, context).runSyncEvery(BLOCKS_INTERVAL_MS); -} - -export async function runSyncLastHourTransactionsTask(context: GatewayContext) { - await TaskRunner.from('[sync last hour transactions]', syncLastHourTransactions, context).runSyncEvery( - HOUR_INTERVAL_MS - ); -} - -export async function runSyncLastSixHoursTransactionsTask(context: GatewayContext) { - await TaskRunner.from('[sync last 6 hours transactions]', syncLastSixHoursTransactionsTask, context).runSyncEvery( - DAY_INTERVAL_MS - ); -} - -function syncLastTransactions(context: GatewayContext) { - return syncTransactions(context, 5, true); -} - -function syncLastHourTransactions(context: GatewayContext) { - return syncTransactions(context, AVG_BLOCKS_PER_HOUR); -} - -function syncLastSixHoursTransactionsTask(context: GatewayContext) { - return syncTransactions(context, AVG_BLOCKS_PER_HOUR * 6); -} - -async function syncTransactions(context: GatewayContext, pastBlocksAmount: number, publish = false) { - const { dbSource, logger, sorter } = context; - - logger.info('Syncing L1 interactions'); - - let lastProcessedBlockHeight; - if (fs.existsSync('interactions-sync-l1.json')) { - const data = JSON.parse(fs.readFileSync('interactions-sync-l1.json', 'utf-8')); - lastProcessedBlockHeight = data?.lastProcessedBlockHeight; - } else { - let result: any; - try { - result = await dbSource.selectLastProcessedArweaveInteraction(); - lastProcessedBlockHeight = result?.block_height; - } catch (e: any) { - logger.error('Error while loading last loaded arweave interaction', e.message); - return; - } - } - - const currentNetworkHeight = getCachedNetworkData().cachedNetworkInfo.height; - // note: the first SW interaction was registered at 472810 block height - lastProcessedBlockHeight = lastProcessedBlockHeight || FIRST_SW_TX_BLOCK_HEIGHT; - - logger.debug('Network info', { - currentNetworkHeight, - lastProcessedBlockHeight, - }); - - const heightFrom = lastProcessedBlockHeight - pastBlocksAmount; - let heightTo = currentNetworkHeight; - - // note: only main task should have this protection. The 'last hour' and 'last 6 hours' tasks - // will obviously try to resync more blocks. - if (publish) { - if (heightTo > heightFrom + 20) { - heightTo = heightFrom + 20; - } - } - - logger.info('Loading interactions for blocks', { - heightFrom, - heightTo, - }); - - // 2. load interactions - let gqlInteractions: GQLEdgeInterface[]; - try { - gqlInteractions = await load( - context, - // Checking LOAD_PAST_BLOCKS blocks back in the past, as - // arweave.net GQL endpoint (very) rarely returns no transactions for the latest block - // - even if there are some transactions in this block... - // We want to be sure that we won't miss any transaction because of a random Arweave gateway quirk... - // There's no risk of duplicates, as transaction's id is the primary key of the table - // - and "ON CONFLICT" clause protects from unique constraint errors. - heightFrom, - heightTo - ); - } catch (e: any) { - logger.error('Error while loading interactions', e.message); - return; - } - - if (gqlInteractions.length === 0) { - logger.info('Now new interactions'); - // note: publish is set to true only for the main syncing task - and also only this task should - // store info about last processed height - if (publish) { - fs.writeFileSync('interactions-sync-l1.json', JSON.stringify({lastProcessedBlockHeight: heightTo}), 'utf-8'); - } - return; - } - - logger.info(`Found ${gqlInteractions.length} interactions`); - - // 3. map interactions into inserts to "interactions" table - let interactionsInserts: InteractionInsert[] = []; - const interactionsInsertsIds = new Set(); - - const contracts = new Map(); - - for (let i = 0; i < gqlInteractions.length; i++) { - const interaction = gqlInteractions[i]; - const blockId = interaction.node.block.id; - - const contractId = tagsParser.getContractTag(interaction.node); - const input = tagsParser.getInputTag(interaction.node, contractId)?.value; - const parsedInput = safeParseInput(input, logger); - - const functionName = parsedInput ? parsedInput.function : '[Error during parsing function name]'; - - let evolve: string | null; - - evolve = - functionName == 'evolve' && parsedInput?.value && isTxIdValid(parsedInput?.value) ? parsedInput?.value : null; - - const internalWrites = tagsParser.getInteractWritesContracts(interaction.node); - - if (contractId === undefined || input === undefined) { - logger.error('Contract or input tag not found for interaction', interaction); - continue; - } - - const sortKey = await sorter.createSortKey(blockId, interaction.node.id, interaction.node.block.height); - const testnet = testnetVersion(interaction); - const syncTimestamp = Date.now(); - // now this one is really fucked-up - if the interaction contains the same tag X-times, - // the default GQL endpoint will return this interaction X-times... - // this is causing "SQLITE_CONSTRAINT: UNIQUE constraint failed: interactions.id" - // - and using "ON CONFLICT" does not work here - as it works only for - // the rows currently stored in db - not the ones that we're trying to batch insert. - if (interactionsInsertsIds.has(interaction.node.id)) { - logger.warn('Interaction already added', interaction.node.id); - } else { - interactionsInsertsIds.add(interaction.node.id); - interactionsInserts.push({ - interaction_id: interaction.node.id, - interaction: JSON.stringify(interaction.node), - block_height: interaction.node.block.height, - block_timestamp: interaction.node.block.timestamp, - block_id: blockId, - contract_id: contractId, - function: functionName, - input: input, - confirmation_status: 'not_processed', - interact_write: internalWrites, - sort_key: sortKey, - evolve: evolve, - testnet, - owner: interaction.node.owner.address, - sync_timestamp: syncTimestamp, - }); - } - if (interactionsInserts.length === MAX_BATCH_INSERT) { - try { - logger.info(`Batch insert ${MAX_BATCH_INSERT} interactions.`); - const interactionsInsertResult: any = await insertInteractions(dbSource, interactionsInserts); - - logger.debug(`Inserted ${interactionsInsertResult.rowCount}`); - interactionsInserts = []; - } catch (e) { - // note: not sure how to behave in this case... - // if we continue the processing, there's a risk that some blocks/interactions will be skipped. - logger.error(e); - return; - } - } - contracts.set(interaction.node.id, { - contractId, - interaction: interaction.node, - blockHeight: interaction.node.block.height, - sortKey, - source: 'arweave', - syncTimestamp, - functionName, - testnet, - }); - } - - // 4. inserting the rest interactions into DB - logger.info(`Saving last`, interactionsInserts.length); - - if (interactionsInserts.length > 0) { - try { - const interactionsInsertResult: any = await insertInteractions(dbSource, interactionsInserts); - logger.debug(`Inserted ${interactionsInsertResult.rowCount}`); - } catch (e) { - logger.error(e); - return; - } finally { - if (publish) { - fs.writeFileSync('interactions-sync-l1.json', JSON.stringify({lastProcessedBlockHeight: heightTo}), 'utf-8'); - } - } - } else { - if (publish) { - fs.writeFileSync('interactions-sync-l1.json', JSON.stringify({lastProcessedBlockHeight: heightTo}), 'utf-8'); - } - } - - if (publish) { - for (let [key, value] of contracts) { - sendNotification(context, value.contractId, undefined, value.interaction); - publishInteraction( - context, - value.contractId, - value.interaction, - value.sortKey, - null, - value.functionName, - value.source, - value.syncTimestamp, - value.testnet - ); - } - } -} - -async function insertInteractions(dbSource: DatabaseSource, interactionsInserts: InteractionInsert[]) { - // why using onConflict.merge()? - // because it happened once that GQL endpoint returned the exact same transactions - // twice - for different block heights (827991 and then 827993) - // For the record, these transactions were: - // INmaBb6pk0MATLrs3mCw5bjeRCbR2e-j-v4swpWHPTg - // QIbp0CwxNUwA8xQSS36Au2Lj1QEgnO8n-shQ2d3AWps - // UJhsjQLhSr1mL4C-t3XvotAhYGIN-P7EkkxNyRRIQ-w - // UZ1XnYr4waM7Zm77TZduZ4Tx8uS8y9PeyX6kKEPQh10 - // cZHBNtzkSF_MtkZCz1RD8_D9lVjOOYAuEUk2xbdm7LA - // lwGTY3yEBfxTgPFO4DZMouHWVaXLJu7SxP-hpDb_S2M - // ouv9X3-ceGPhb2ALVaLq2qzj_ZDgbSmjGj9wz5k5qRo - // qT-ihh8K3J7Lek4774-GmFoAhU4pemWZPXv66B09xCI - // qUk-UuPAOaOkoqMP_btCJLYP-c-8kHRKjg_nefQVLgQ - - // note: the same issue occurred recently for tx IoGSPjQ--LY2KRgCBioaX0GTlohCq64IYSFolayuEPg - // it was first returned for block 868561, and then moved to 868562 - probably due to fork - return await dbSource.insertInteractionsSync(interactionsInserts); -} - -// TODO: verify internalWrites -async function load(context: GatewayContext, from: number, to: number): Promise { - const mainTransactionsVariables: ReqVariables = { - bundledIn: null, - tags: [ - { - name: SmartWeaveTags.APP_NAME, - values: ['SmartWeaveAction'], - }, - ], - blockFilter: { - min: from, - max: to, - }, - first: MAX_GQL_REQUEST, - }; - - const { logger, arweaveWrapperGqlGoldsky } = context; - return await loadPages({ logger, arweaveWrapper: arweaveWrapperGqlGoldsky }, INTERACTIONS_QUERY, mainTransactionsVariables); -} - -export function testnetVersion(tx: GQLEdgeInterface): string | null { - return tx.node.tags.find((tag) => tag.name === 'Warp-Testnet')?.value || null; -} - -export function parseFunctionName(input: string, logger: WarpLogger) { - try { - return JSON.parse(input).function; - } catch (e) { - logger.error('Could not parse function name', { - input: input, - }); - return '[Error during parsing function name]'; - } -} - -function safeParseInput(input: string, logger: WarpLogger) { - try { - return JSON.parse(input); - } catch (e) { - logger.error('Could not parse input', { - input, - }); - return null; - } -} diff --git a/src/gateway/tasks/verifyCorruptedTransactions.ts b/src/gateway/tasks/verifyCorruptedTransactions.ts deleted file mode 100644 index d3760831..00000000 --- a/src/gateway/tasks/verifyCorruptedTransactions.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { TaskRunner } from './TaskRunner'; -import { MIN_CONFIRMATIONS } from './verifyInteractions'; -import { GatewayContext } from '../init'; - -const CORRUPTED_CHECK_INTERVAL_MS = 1000 * 60 * 60; - -export async function runVerifyCorruptedTransactionsTask(context: GatewayContext) { - await TaskRunner.from('[corrupted transactions check]', verifyCorruptedTransactions, context).runAsyncEvery( - CORRUPTED_CHECK_INTERVAL_MS, - false - ); -} - -async function verifyCorruptedTransactions(context: GatewayContext) { - const { arweave, logger, dbSource } = context; - - let corruptedTransactions: { id: string }[]; - - try { - corruptedTransactions = ( - await dbSource.raw(` - SELECT interaction_id as id - FROM interactions - WHERE confirmation_status = 'corrupted'; - `) - ).rows; - } catch (e: any) { - logger.error('Error while checking corrupted transactions', e.message); - return; - } - - logger.debug(`Rechecking ${corruptedTransactions.length} corrupted transactions`); - - for (const corrupted of corruptedTransactions) { - try { - const result = await arweave.transactions.getStatus(corrupted.id); - if ( - result.status !== 404 && - result && - result.confirmed && - result.confirmed.number_of_confirmations >= MIN_CONFIRMATIONS - ) { - logger.warn( - `Transaction ${corrupted.id} is probably not corrupted, confirmations ${result.confirmed.number_of_confirmations}` - ); - - // returning transaction to "not_processed" pool. - await dbSource.updateNotProcessedInteraction(corrupted.id); - } else { - logger.info(`Transaction ${corrupted.id} confirmed as corrupted`); - } - } catch (e) { - logger.error(`Error while verifying ${corrupted.id}`); - } - } - - logger.info('Corrupted transactions confirmation done.'); -} diff --git a/src/gateway/tasks/verifyInteractions.ts b/src/gateway/tasks/verifyInteractions.ts deleted file mode 100644 index 82db1c18..00000000 --- a/src/gateway/tasks/verifyInteractions.ts +++ /dev/null @@ -1,290 +0,0 @@ -import axios from 'axios'; -import { TaskRunner } from './TaskRunner'; -import { GatewayContext } from '../init'; - -export const MIN_CONFIRMATIONS = 10; -const PARALLEL_REQUESTS = 10; -const TX_CONFIRMATION_SUCCESSFUL_ROUNDS = 3; -const TX_CONFIRMATION_MAX_ROUNDS = 4; -const TX_CONFIRMATION_MAX_ROUND_TIMEOUT_MS = 3000; -const CONFIRMATIONS_INTERVAL_MS = 30000; - -let lastVerificationHeight = 0; - -type ConfirmationRoundResult = { - txId: string; - peer: string; - result: string; - confirmations: number; -}[]; - -export async function runVerifyInteractionsTask(context: GatewayContext) { - await TaskRunner.from('[verify interactions]', verifyInteractions, context).runSyncEvery(CONFIRMATIONS_INTERVAL_MS); -} - -async function verifyInteractions(context: GatewayContext) { - const { logger, dbSource, arweaveWrapper } = context; - - let currentNetworkHeight; - try { - currentNetworkHeight = (await arweaveWrapper.info()).height as number; - } catch (e: any) { - logger.error('Error from Arweave', e.message); - return; - } - - const safeNetworkHeight = currentNetworkHeight - MIN_CONFIRMATIONS; - logger.debug('Verify confirmations params:', { - currentNetworkHeight, - safeNetworkHeight, - lastVerificationHeight, - }); - - // note: as the "status" endpoint for arweave.net sometime returns 504 - Bad Gateway for corrupted transactions, - // we need to ask peers directly... - // https://discord.com/channels/357957786904166400/812013044892172319/917819482787958806 - // only 7 nodes are currently fully synced, duh... - let peers: { peer: string }[]; - try { - peers = ( - await dbSource.raw(` - SELECT peer - FROM peers - WHERE height > 0 - AND blacklisted = false - ORDER BY height - blocks ASC, response_time ASC - LIMIT ${PARALLEL_REQUESTS}; - `) - ).rows; - } catch (e: any) { - logger.error('Error while fetching peers', e.message); - return; - } - - if (peers.length < PARALLEL_REQUESTS) { - logger.warn('Arweave peers not loaded yet.'); - return; - } - - // note: - // 1. excluding Kyve contracts, as they moved to Moonbeam (and their contracts have the most interactions) - // 2. excluding Koi contracts (well, those with the most interactions, as there are dozens of Koi contracts) - // - as they're using their own infrastructure and probably won't be interested in using this solution. - // TODO: make this list configurable. - let interactionsToCheck: { block_height: number; interaction_id: string }[]; - try { - interactionsToCheck = ( - await dbSource.raw( - ` - SELECT block_height, interaction_id - FROM interactions - WHERE block_height < (SELECT max(block_height) FROM interactions) - ? - AND block_height > ? - AND confirmation_status = 'not_processed' - AND source = 'arweave' - AND contract_id NOT IN ( - 'LkfzZvdl_vfjRXZOPjnov18cGnnK3aDKj0qSQCgkCX8', /* kyve */ - 'l6S4oMyzw_rggjt4yt4LrnRmggHQ2CdM1hna2MK4o_c', /* kyve */ - 'B1SRLyFzWJjeA0ywW41Qu1j7ZpBLHsXSSrWLrT3ebd8', /* kyve */ - 'cETTyJQYxJLVQ6nC3VxzsZf1x2-6TW2LFkGZa91gUWc', /* koi */ - /* 'QA7AIFVx1KBBmzC7WUNhJbDsHlSJArUT0jWrhZMZPS8', koi */ - '8cq1wbjWHNiPg7GwYpoDT2m9HX99LY7tklRQWfh1L6c', /* kyve */ - /* 'NwaSMGCdz6Yu5vNjlMtCNBmfEkjYfT-dfYkbQQDGn5s', koi */ - 'qzVAzvhwr1JFTPE8lIU9ZG_fuihOmBr7ewZFcT3lIUc', /* koi */ - 'OFD4GqQcqp-Y_Iqh8DN_0s3a_68oMvvnekeOEu_a45I', /* kyve */ - 'CdPAQNONoR83Shj3CbI_9seC-LqgI1oLaRJhSwP90-o', /* koi */ - 'dNXaqE_eATp2SRvyFjydcIPHbsXAe9UT-Fktcqs7MDk' /* kyve */ - ) - ORDER BY block_height ASC - LIMIT ?;`, - [MIN_CONFIRMATIONS, lastVerificationHeight, PARALLEL_REQUESTS] - ) - ).rows; - } catch (e: any) { - logger.error('Error while fetching interactions', e.message); - return; - } - - if (interactionsToCheck === undefined || interactionsToCheck.length === 0) { - logger.info('No new interactions to confirm.'); - // just in case - search for the non-confirmed interactions from the beginning in the next round - lastVerificationHeight = 0; - return; - } - - const prevVerificationHeight = lastVerificationHeight; - - lastVerificationHeight = [...interactionsToCheck].pop()?.block_height || lastVerificationHeight; - - logger.debug( - `Checking ${interactionsToCheck.length} interactions from height ${interactionsToCheck[0].block_height}.` - ); - - let statusesRounds: ConfirmationRoundResult[] = Array(TX_CONFIRMATION_SUCCESSFUL_ROUNDS); - let successfulRounds = 0; - let rounds = 0; - - // we need to make sure that each interaction in each round will be checked by a different peer. - // - that's why we keep the peers registry per interaction - const interactionsPeers = new Map(); - interactionsToCheck.forEach((i) => { - interactionsPeers.set(i.interaction_id, [...peers]); - }); - - // at some point we could probably generify the snowball and use it here to ask multiple peers. - while (successfulRounds < TX_CONFIRMATION_SUCCESSFUL_ROUNDS && rounds < TX_CONFIRMATION_MAX_ROUNDS) { - // too many rounds have already failed and there's no chance to get the minimal successful rounds... - if (successfulRounds + TX_CONFIRMATION_MAX_ROUNDS - rounds < TX_CONFIRMATION_SUCCESSFUL_ROUNDS) { - logger.warn("There's no point in trying, exiting.."); - lastVerificationHeight = prevVerificationHeight; - return; - } - - try { - const roundResult: ConfirmationRoundResult = []; - - // checking status of each of the interaction by a randomly selected peer. - // in each round each interaction will be checked by a different peer. - const statuses = await Promise.race([ - new Promise(function (resolve, reject) { - setTimeout( - () => reject('Status query timeout, better luck next time...'), - TX_CONFIRMATION_MAX_ROUND_TIMEOUT_MS - ); - }), - - Promise.allSettled( - interactionsToCheck.map((tx) => { - const interactionPeers = interactionsPeers.get(tx.interaction_id)!; - const randomPeer = interactionPeers[Math.floor(Math.random() * interactionPeers.length)]; - - // removing the selected peer for this interaction - // - so it won't be selected again in any of the next rounds. - interactionPeers.splice(peers.indexOf(randomPeer), 1); - const randomPeerUrl = `http://${randomPeer.peer}`; - - return axios.get(`${randomPeerUrl}/tx/${tx.interaction_id}/status`); - }) - ), - ]); - - // verifying responses from peers - for (let i = 0; i < statuses.length; i++) { - const statusResponse = statuses[i]; - const txId = interactionsToCheck[i].interaction_id; - if (statusResponse.status === 'rejected') { - // interaction is (probably) corrupted - if (statusResponse.reason.response?.status === 404) { - logger.warn(`Interaction ${txId} on ${statusResponse.reason.request.host} not found.`); - roundResult.push({ - txId: txId, - peer: statusResponse.reason.request.host, - result: 'corrupted', - confirmations: 0, - }); - } else { - // no proper response from peer (eg. 500) - // TODO: consider blacklisting such peer (after returning error X times?) 'till next peersCheckLoop - logger.error( - `Query for ${txId} to ${statusResponse.reason?.request?.host} rejected. ${statusResponse.reason}.` - ); - roundResult.push({ - txId: txId, - peer: statusResponse.reason?.request?.host, - result: 'error', - confirmations: 0, - }); - } - } else { - // transaction confirmed by given peer - const confirmations = parseInt(statusResponse.value.data['number_of_confirmations']); - logger.trace(`Confirmed ${txId} with ${confirmations}`); - - roundResult.push({ - txId: txId, - peer: statusResponse.value.request.host, - result: confirmations >= MIN_CONFIRMATIONS ? 'confirmed' : 'forked', - confirmations: statusResponse.value.data['number_of_confirmations'], - }); - } - } - statusesRounds[successfulRounds] = roundResult; - successfulRounds++; - } catch (e) { - logger.error(e); - } finally { - rounds++; - } - } - - if (successfulRounds != TX_CONFIRMATION_SUCCESSFUL_ROUNDS) { - logger.warn( - `Transactions verification was not successful, successful rounds ${successfulRounds}, required successful rounds ${TX_CONFIRMATION_SUCCESSFUL_ROUNDS}` - ); - lastVerificationHeight = prevVerificationHeight; - } else { - logger.info('Verifying rounds'); - - // sanity check...whether all rounds have the same amount of interactions checked. - for (let i = 0; i < statusesRounds.length; i++) { - const r = statusesRounds[i]; - if (r.length !== interactionsToCheck.length) { - logger.error(`Each round should have ${interactionsToCheck.length} results. Round ${i} has ${r.length}.`); - lastVerificationHeight = prevVerificationHeight; - return; - } - } - - // programming is just loops and if-s... - // For each interaction we're verifying whether the result returned in each round is the same. - // If it is the same for all rounds - we store the confirmation status in the db. - // It it is not the same - we're logging the difference and move to the next interaction. - for (let i = 0; i < interactionsToCheck.length; i++) { - let status = null; - let sameStatusOccurrence = 0; - const confirmingPeers = []; - const confirmations = []; - - for (let j = 0; j < TX_CONFIRMATION_SUCCESSFUL_ROUNDS; j++) { - const newStatus = statusesRounds[j][i].result; - if (status === null || newStatus === status) { - status = newStatus; - sameStatusOccurrence++; - confirmingPeers.push(statusesRounds[j][i].peer); - confirmations.push(statusesRounds[j][i].confirmations); - } else { - logger.warn('Different response from peers for', { - current_peer: statusesRounds[j][i], - // note: j - 1 is safe here, because in this branch j >= 1 - // - if the status is different, than it means we're checking at least - // second round for the given transaction - prev_peer: statusesRounds[j - 1][i], - }); - break; - } - } - - if (sameStatusOccurrence === TX_CONFIRMATION_SUCCESSFUL_ROUNDS) { - // sanity check... - if (status === null) { - logger.error('WTF? Status should not be null!'); - continue; - } - try { - logger.trace('Updating confirmation status in db'); - await dbSource.updateInteractionConfirmationStatus( - interactionsToCheck[i].interaction_id, - status, - confirmingPeers, - confirmations - ); - } catch (e) { - logger.error(e); - lastVerificationHeight = prevVerificationHeight; - } - } - } - } - - logger.info('Transactions confirmation done.'); -} diff --git a/src/utils.ts b/src/utils.ts index 44764d4a..bb84acd7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,8 @@ import fs from 'fs'; import { JWKInterface } from 'arweave/node/lib/wallet'; import { Tag } from 'arweave/node/lib/transaction'; -import { Tags } from 'warp-contracts'; +import { Tags, WarpLogger } from 'warp-contracts'; +import Arweave from 'arweave'; export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -22,14 +23,14 @@ export function isTxIdValid(txId: string): boolean { } export function decodeTags(tags: Tags) { - const decodedTags: { name: string; value: string }[] = []; + const decodedTags: Tags = []; const mappedTags = tags.map((tag: { name: string; value: string }) => { return new Tag(tag.name, tag.value); }); mappedTags.forEach((tag: Tag) => { let name = tag.get('name', { decode: true, string: true }); let value = tag.get('value', { decode: true, string: true }); - decodedTags.push({ name, value }); + decodedTags.push(new Tag(name, value)); }); return decodedTags; @@ -39,3 +40,40 @@ export function getTagByName(tags: Tags, name: string) { const tagContentType = tags.find((tag: any) => tag.name == name)?.value; return tagContentType; } + +export function encodeTag(name: string, value: string, arweave: Arweave) { + return { + name: arweave.utils.stringToB64Url(name), + value: arweave.utils.stringToB64Url(value), + }; +} + +export function evalType(initState: any): string { + if (initState.ticker && initState.balances) { + return 'pst'; + } + + return 'other'; +} + +export function parseFunctionName(input: string, logger: WarpLogger) { + try { + return JSON.parse(input).function; + } catch (e) { + logger.error('Could not parse function name', { + input: input, + }); + return '[Error during parsing function name]'; + } +} + +export function safeParse(jsonString: string, logger: WarpLogger) { + try { + return JSON.parse(jsonString); + } catch (e) { + logger.error('Could not parse JSON string', { + jsonString, + }); + return null; + } +} diff --git a/syncer/db/db.go b/syncer/db/db.go deleted file mode 100644 index e9a14c45..00000000 --- a/syncer/db/db.go +++ /dev/null @@ -1,157 +0,0 @@ -package db - -import ( - "bufio" - "database/sql" - "fmt" - "github.com/everFinance/arsyncer" - _ "github.com/lib/pq" - "github.com/redstone-finance/redstone-sw-gateway/syncer/sw_types" - "os" - "strings" -) - -var log = arsyncer.NewLog("db") - -type Db struct { - connectionParams ConnectionParams - connection *sql.DB -} - -type ConnectionParams struct { - Host string - Port int - User string - Password string - Dbname string -} - -type AppConfigProperties map[string]string - -func New(connectionParams ConnectionParams) *Db { - return &Db{ - connectionParams: connectionParams, - connection: connectDb(connectionParams), - } -} - -func (db *Db) Close() { - err := db.connection.Close() - if err != nil { - return - } -} - -func (db *Db) LoadLatestSyncedBlock() int64 { - sqlStatement := `SELECT last_checked_height AS blockHeight FROM arsyncer;` - var blockHeight int64 - row := db.connection.QueryRow(sqlStatement) - switch err := row.Scan(&blockHeight); err { - case sql.ErrNoRows: - log.Error("No rows were returned!") - case nil: - log.Info("Last synced block", "height", blockHeight) - default: - panic(err) - } - - return blockHeight -} - -func (db *Db) BatchInsertInteractions(interactions []sw_types.DbInteraction) error { - var valueStrings []string - var valueArgs []interface{} - for _, interaction := range interactions { - valueStrings = append(valueStrings, "(?, ?, ?, ?, ?, ?, ?, ?)") - - valueArgs = append(valueArgs, interaction.InteractionId) - valueArgs = append(valueArgs, interaction.Interaction) - valueArgs = append(valueArgs, interaction.BlockHeight) - valueArgs = append(valueArgs, interaction.BlockId) - valueArgs = append(valueArgs, interaction.ContractId) - valueArgs = append(valueArgs, interaction.Function) - valueArgs = append(valueArgs, interaction.Input) - valueArgs = append(valueArgs, interaction.ConfirmationStatus) - - smt := ` -INSERT INTO interactions( -interaction_id, interaction, block_height, block_id, contract_id, function, input, confirmation_status) -VALUES %s -ON CONFLICT (interaction_id) -DO NOTHING` - smt = fmt.Sprintf(smt, strings.Join(valueStrings, ",")) - log.Debug("smttt:", smt) - tx, _ := db.connection.Begin() - _, err := tx.Exec(smt, valueArgs...) - if err != nil { - tx.Rollback() - return err - } - return tx.Commit() - } - - return nil -} - -func (db *Db) UpdateLastProcessedInteractionHeight(height int64) { - sqlStatement := `UPDATE arsyncer SET last_checked_height = $1;` - - _, err := db.connection.Exec(sqlStatement, height) - if err != nil { - log.Error("Error while updating last checked height", err) - } -} - -func connectDb(params ConnectionParams) *sql.DB { - psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+ - "password=%s dbname=%s sslmode=disable", - params.Host, params.Port, params.User, params.Password, params.Dbname) - - db, err := sql.Open("postgres", psqlInfo) - if err != nil { - panic(err) - } - - err = db.Ping() - if err != nil { - panic(err) - } - - log.Info("Successfully connected!") - return db -} - -func ReadPropertiesFile(filename string) (AppConfigProperties, error) { - config := AppConfigProperties{} - - if len(filename) == 0 { - return config, nil - } - file, err := os.Open(filename) - if err != nil { - log.Error("Cannot open file", filename, err) - return nil, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if equal := strings.Index(line, "="); equal >= 0 { - if key := strings.TrimSpace(line[:equal]); len(key) > 0 { - value := "" - if len(line) > equal { - value = strings.TrimSpace(line[equal+1:]) - } - config[key] = value - } - } - } - - if err := scanner.Err(); err != nil { - log.Error("Cannot parse file", err) - return nil, err - } - - return config, nil -} diff --git a/syncer/go.mod b/syncer/go.mod deleted file mode 100644 index b9d768e6..00000000 --- a/syncer/go.mod +++ /dev/null @@ -1,26 +0,0 @@ -module github.com/redstone-finance/redstone-sw-gateway/syncer - -go 1.17 - -require ( - github.com/everFinance/arsyncer v1.0.5 - github.com/everFinance/goar v1.3.8 - github.com/lib/pq v1.10.4 -) - -require ( - github.com/everFinance/gojwk v1.0.0 // indirect - github.com/everFinance/ttcrsa v1.1.3 // indirect - github.com/getsentry/sentry-go v0.11.0 // indirect - github.com/go-stack/stack v1.8.1 // indirect - github.com/hamba/avro v1.5.6 // indirect - github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect - github.com/json-iterator/go v1.1.11 // indirect - github.com/mattn/go-colorable v0.1.11 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/panjf2000/ants/v2 v2.4.6 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect -) diff --git a/syncer/go.sum b/syncer/go.sum deleted file mode 100644 index f52aff1d..00000000 --- a/syncer/go.sum +++ /dev/null @@ -1,219 +0,0 @@ -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/everFinance/arsyncer v1.0.5 h1:yqINNFZ5KiWdR5t3zVo1aJajWsAT2nXA6j/t0zo+xdU= -github.com/everFinance/arsyncer v1.0.5/go.mod h1:LYNm4IAneJgUF/w87GH2OMkypERzyY0ctLc0uxvljro= -github.com/everFinance/goar v1.3.4/go.mod h1:x19A2ttl2SbZA0Q9nLBYerKFQiyVgj8doSAhDd505J4= -github.com/everFinance/goar v1.3.8 h1:6DtHpKalNUkkLDHNVwEnw+jLHNjXOoYWhW4BYzHMGY4= -github.com/everFinance/goar v1.3.8/go.mod h1:x19A2ttl2SbZA0Q9nLBYerKFQiyVgj8doSAhDd505J4= -github.com/everFinance/gojwk v1.0.0 h1:le/oI2NgXlrqg3MHU6ka+V30EWcD7TD6+Ilh+go7924= -github.com/everFinance/gojwk v1.0.0/go.mod h1:icXSXsIdpAczlpAtSljQlmABkMTRZENr73KHmo0GOGc= -github.com/everFinance/ttcrsa v1.1.3 h1:RJl9UizbevHZUiWPHVKz1aM6yA8cmkZWaCbOGTD/L0I= -github.com/everFinance/ttcrsa v1.1.3/go.mod h1:Ws7b/oDbYKaZlvyT17nm+zHmzVhGl51r/yPx/Ib5RQk= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8= -github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hamba/avro v1.5.6 h1:/UBljlJ9hLjkcY7PhpI/bFYb4RMEXHEwHr17gAm/+l8= -github.com/hamba/avro v1.5.6/go.mod h1:3vNT0RLXXpFm2Tb/5KC71ZRJlOroggq1Rcitb6k4Fr8= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI= -github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/panjf2000/ants/v2 v2.4.6 h1:drmj9mcygn2gawZ155dRbo+NfXEfAssjZNU1qoIb4gQ= -github.com/panjf2000/ants/v2 v2.4.6/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/syncer/sw_types/types.go b/syncer/sw_types/types.go deleted file mode 100644 index 1a27bb8b..00000000 --- a/syncer/sw_types/types.go +++ /dev/null @@ -1,38 +0,0 @@ -package sw_types - -import "github.com/everFinance/goar/types" - -type DbInteraction struct { - InteractionId string - Interaction string - BlockHeight int64 - BlockId string - ContractId string - Function string - Input string - ConfirmationStatus string -} - -type SwOwner struct { - Address string `json:"address"` -} - -type SwBlock struct { - Height int64 `json:"height"` - Id string `json:"id"` - Timestamp int64 `json:"timestamp"` -} - -type Amount struct { - Winston string `json:"winston"` -} - -type SwInteraction struct { - Id string `json:"id"` - Owner SwOwner `json:"owner"` - Recipient string `json:"recipient"` - Tags []types.Tag `json:"tags"` - Block SwBlock `json:"block"` - Fee Amount `json:"fee"` - Quantity Amount `json:"quantity"` -} diff --git a/syncer/syncer.go b/syncer/syncer.go deleted file mode 100644 index 56f520cf..00000000 --- a/syncer/syncer.go +++ /dev/null @@ -1,128 +0,0 @@ -package main - -import ( - "encoding/json" - "github.com/everFinance/arsyncer" - "github.com/everFinance/goar/types" - "github.com/everFinance/goar/utils" - "github.com/redstone-finance/redstone-sw-gateway/syncer/db" - "github.com/redstone-finance/redstone-sw-gateway/syncer/sw_types" - "os" - "strconv" -) - -var log = arsyncer.NewLog("syncer") - -func main() { - propertiesPath := os.Args[1] - props, err := db.ReadPropertiesFile(propertiesPath) - if err != nil { - log.Error("Cannot read properties file", err) - return - } - - port, err := strconv.Atoi(props["DB_PORT"]) - if err != nil { - log.Error("Cannot parse db port", err) - return - } - - db := db.New(db.ConnectionParams{ - Host: props["DB_HOST"], - Port: port, - User: props["DB_USER"], - Password: props["DB_PASSWORD"], - Dbname: props["DB_NAME"], - }) - defer db.Close() - - startHeight := db.LoadLatestSyncedBlock() - - swcFilterParams := arsyncer.FilterParams{ - Tags: []types.Tag{ - {Name: "App-Name", Value: "SmartWeaveAction"}, - }, - } - - arNode := "https://arweave.net" - concurrencyNumber := 50 - s := arsyncer.New(startHeight, swcFilterParams, arNode, concurrencyNumber, 15) - s.Run() - - for { - select { - case sTx := <-s.SubscribeTxCh(): - interactions := make([]sw_types.DbInteraction, 0) - var highestBlockHeight int64 = 0 - for _, tx := range sTx { - decodedTags, _ := utils.TagsDecode(tx.Tags) - var contract, input, function = "", "", "" - for _, t := range decodedTags { - if t.Name == "Contract" { - contract = t.Value - } - if t.Name == "Input" { - input = t.Value - var parsedInput map[string]interface{} - err := json.Unmarshal([]byte(input), &parsedInput) - if err != nil { - log.Error("Cannot parse function in input", input) - } - if val, ok := parsedInput["function"]; ok { - function = val.(string) - } - } - - if contract != "" && input != "" { - break - } - } - - swInteraction := sw_types.SwInteraction{ - Id: tx.ID, - Owner: sw_types.SwOwner{ - Address: tx.Owner, - }, - Recipient: tx.Target, - Tags: decodedTags, - Block: sw_types.SwBlock{ - Height: tx.BlockHeight, - Id: tx.BlockId, - Timestamp: tx.BlockTimestamp, - }, - Fee: sw_types.Amount{ - Winston: tx.Reward, - }, - Quantity: sw_types.Amount{ - Winston: tx.Quantity, - }, - } - - swInteractionJson, err := json.Marshal(swInteraction) - if err != nil { - log.Error("Error while marshalling interaction", err) - panic(err) - } - log.Debug("Interaction:", string(swInteractionJson)) - highestBlockHeight = tx.BlockHeight - - interactions = append(interactions, sw_types.DbInteraction{ - InteractionId: tx.ID, - Interaction: string(swInteractionJson), - BlockHeight: tx.BlockHeight, - BlockId: tx.BlockId, - ContractId: contract, - Function: function, - Input: input, - ConfirmationStatus: "not_processed", - }) - } - - err := db.BatchInsertInteractions(interactions) - if err == nil && highestBlockHeight != 0 { - log.Info("Updating last processed block height to ", highestBlockHeight) - db.UpdateLastProcessedInteractionHeight(highestBlockHeight) - } - } - } -} diff --git a/tools/nakurwiaj.js b/tools/nakurwiaj.js new file mode 100644 index 00000000..7bd3bd77 --- /dev/null +++ b/tools/nakurwiaj.js @@ -0,0 +1,168 @@ +const fs = require('fs'); +const warpContracts = require('warp-contracts-old'); +const warpContractsNew = require('warp-contracts-new'); +const { EthereumSigner } = require('warp-contracts-plugin-signature/server'); + +let errors_l1 = ``; +let errors_l2 = ``; + +const warp = warpContracts.WarpFactory.forMainnet({ + ...warpContracts.defaultCacheOptions, + dbLocation: 'warp/old', +}).useGwUrl('http://35.242.203.146:5666'); +const warpNew = warpContractsNew.WarpFactory.forMainnet({ + ...warpContractsNew.defaultCacheOptions, + dbLocation: 'warp/new', +}).useGwUrl('http://35.242.203.146:5666'); +const wallet = readJSON('./.secrets/warp-wallet-jwk.json'); +const ethWallet = fs.readFileSync('./.secrets/ethereum-priv-key.txt', 'utf-8').replace(/\n/g, ''); + +warpContracts.LoggerFactory.INST.logLevel('error'); +warpContractsNew.LoggerFactory.INST.logLevel('error'); + +const contractB = warp + .contract('YhTW-jV7ffbYciz1bcJ-SM-79cmt9MkoZYutyaghg9Y') + .setEvaluationOptions({ + sequencerUrl: 'http://35.242.203.146:5666/', + }) + .connect(wallet); + +const contractC = warp + .contract('-tU1YKqnwgzpZIxjqUPBzRFsCfXVNZ6t1lu5pi5cV3k') + .setEvaluationOptions({ + sequencerUrl: 'http://35.242.203.146:5666/', + }) + .connect(wallet); + +const contractD = warpNew + .contract('7AZv5bczZhJJpUfwhzjz3iGdzo2PaBi0elgoRwzwg4g') + .setEvaluationOptions({ + sequencerUrl: 'http://35.242.203.146:5666/', + }) + .connect(wallet); + +const contractE = warpNew + .contract('OsGmh1UtH4QytV0Tdxgg_TJ-VjfHkQYPLX7yuK1OfiQ') + .setEvaluationOptions({ + sequencerUrl: 'http://35.242.203.146:5666/', + }) + .connect(wallet); + +const contractF = warpNew + .contract('7AZv5bczZhJJpUfwhzjz3iGdzo2PaBi0elgoRwzwg4g') + .setEvaluationOptions({ + sequencerUrl: 'http://35.242.203.146:5666/', + }) + .connect(new EthereumSigner(ethWallet)); + +setInterval(async () => { + console.log('sending to L2...'); + try { + await Promise.all([ + /*contractB.writeInteraction({ + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }), + contractC.writeInteraction({ + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }),*/ + contractD.writeInteraction({ + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }), + contractE.writeInteraction({ + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }), + /*contractF.writeInteraction({ + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }),*/ + ]); + } catch (e) { + errors_l2 += `${e.stack}\n\n`; + } +}, 3000); + +/* +setInterval(async () => { + console.log('sending to L1...'); + try { + await Promise.all([ + contractB.writeInteraction( + { + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }, + { disableBundling: true } + ), + contractC.writeInteraction( + { + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }, + { disableBundling: true } + ), + contractD.writeInteraction( + { + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }, + { disableBundling: true } + ), + contractE.writeInteraction( + { + function: 'transfer', + target: 'M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI', + qty: 1, + }, + { disableBundling: true } + ), + ]); + } catch (e) { + errors_l1 += `${e.stack}\n\n`; + } +}, 5000); +*/ + +setInterval(() => { + saveErrors(); +}, 3600000); + +function saveErrors() { + fs.appendFileSync(`errors_l1.txt`, errors_l1); + fs.appendFileSync(`errors_l2.txt`, errors_l2); + errors_l1 = ``; + errors_l2 = ``; +} + +process.on('SIGINT', () => { + saveErrors(); + process.exit(-1); +}); + +function readJSON(path) { + const content = fs.readFileSync(path, 'utf-8'); + try { + return JSON.parse(content); + } catch (e) { + throw new Error(`File "${path}" does not contain a valid JSON`); + } +} + +process.on('uncaughtException', () => { + console.error('uncaughtException'); +}); + +process.on('unhandledRejection', (e) => { + console.error('unhandledRejection'); +}); diff --git a/tools/nakurwiaj_kontrakt.js b/tools/nakurwiaj_kontrakt.js new file mode 100644 index 00000000..92c38f48 --- /dev/null +++ b/tools/nakurwiaj_kontrakt.js @@ -0,0 +1,47 @@ +const fs = require('fs'); +const warpContractsNew = require('warp-contracts-new'); +const { DeployPlugin } = require("warp-contracts-plugin-deploy"); +const { ArweaveSigner } = require("warp-arbundles"); + +const warpNew = warpContractsNew.WarpFactory.forMainnet({ + ...warpContractsNew.defaultCacheOptions, + dbLocation: 'warp/new', +}) + .use(new DeployPlugin()) + .useGwUrl('http://35.242.203.146:5666'); +const wallet = readJSON('./.secrets/33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA.json'); + +warpContractsNew.LoggerFactory.INST.logLevel('error'); + +async function doDeploy() { + try { + const { contractTxId } = await warpNew.deploy({ + wallet: new ArweaveSigner(wallet), + initState: JSON.stringify({}), + src: 'export async function handle(state, action){}' + }); + console.log("new contract", contractTxId); + } catch(e) { + console.error(e); + } +} + +doDeploy().finally(() => console.log("done")); + + +function readJSON(path) { + const content = fs.readFileSync(path, 'utf-8'); + try { + return JSON.parse(content); + } catch (e) { + throw new Error(`File "${path}" does not contain a valid JSON`); + } +} + +process.on('uncaughtException', () => { + console.error('uncaughtException'); +}); + +process.on('unhandledRejection', (e) => { + console.error('unhandledRejection'); +}); diff --git a/tsconfig.json b/tsconfig.json index 1ed549b5..5a0ca0fa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "esModuleInterop": true, "downlevelIteration": true, "skipLibCheck": true, - "allowJs": true + "allowJs": true, + "resolveJsonModule": true }, "ts-node": { "files": true diff --git a/updates/2023_11_16_block_height_src_tx_id_idx.sql b/updates/2023_11_16_block_height_src_tx_id_idx.sql new file mode 100644 index 00000000..22548a22 --- /dev/null +++ b/updates/2023_11_16_block_height_src_tx_id_idx.sql @@ -0,0 +1 @@ +CREATE INDEX contracts_block_height_src_tx_id_index ON contracts (block_height, src_tx_id); diff --git a/updates/2023_11_16_contract_tx_tags_idx.sql b/updates/2023_11_16_contract_tx_tags_idx.sql new file mode 100644 index 00000000..e3824280 --- /dev/null +++ b/updates/2023_11_16_contract_tx_tags_idx.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_contract_tx_tags_gin + ON contracts USING gin ((contract_tx->'tags') jsonb_path_ops) +WHERE type != 'error'; diff --git a/updates/2023_11_22_manifest_column.sql b/updates/2023_11_22_manifest_column.sql new file mode 100644 index 00000000..fc59f6a0 --- /dev/null +++ b/updates/2023_11_22_manifest_column.sql @@ -0,0 +1 @@ +ALTER TABLE interactions ADD COLUMN manifest jsonb; \ No newline at end of file diff --git a/welcome.html b/welcome.html deleted file mode 100644 index e4c61e9f..00000000 --- a/welcome.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - RedStone SmartWeave Gateway - - - -

Hi! I'm the RedStone SmartWeave Gateway, ready to serve your SmartWeave contracts interactions!

-

If you want to learn more about me, check the - gateway docs. -

-

To explore indexed data - head over to - SonAR. -

- -

If you want to learn how to integrate me with the RedStone - Contracts SDK - check the SDK - docs and examples. -

-

If you need more help or happen to find any issues - join our Discord.

-

Powered by RedStone.

- - - - \ No newline at end of file diff --git a/yalc.lock b/yalc.lock deleted file mode 100644 index e1c2aedd..00000000 --- a/yalc.lock +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "v1", - "packages": { - "redstone-smartweave": { - "signature": "1bc3761907e2b09ac5f74e5a898b2fc6", - "file": true, - "replaced": "0.4.61" - } - } -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4fedee39..978aa839 100644 --- a/yarn.lock +++ b/yarn.lock @@ -916,32 +916,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bundlr-network/client@0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@bundlr-network/client/-/client-0.9.6.tgz#89464a697351aecb8071bbdb9f9b1a1e71a5fd60" - integrity sha512-4AhYsUAhqzpHxhGkgVpR2x7o5Kk+T8qxGba3wq/0kvMPnEGBdl1ramEN6B6cgdCGmgp5n+ov/hH5+q1sTvo9IQ== - dependencies: - "@solana/wallet-adapter-base" "^0.9.2" - "@solana/web3.js" "^1.36.0" - "@supercharge/promise-pool" "^2.1.0" - algosdk "^1.13.1" - aptos "^1.3.14" - arbundles "^0.7.0" - arweave "^1.11.4" - async-retry "^1.3.3" - axios "^0.25.0" - base64url "^3.0.1" - bignumber.js "^9.0.1" - bs58 "^4.0.1" - commander "^8.2.0" - csv "^6.0.5" - ethers "^5.5.1" - inquirer "^8.2.0" - js-sha256 "^0.9.0" - mime-types "^2.1.34" - near-api-js "^0.44.2" - near-seed-phrase "^0.2.0" - "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -1057,7 +1031,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -1160,7 +1134,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -1268,7 +1242,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/wallet@5.7.0": +"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== @@ -1311,6 +1285,11 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@fastify/busboy@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" + integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + "@humanwhocodes/config-array@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" @@ -1341,6 +1320,56 @@ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== +"@irys/arweave@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@irys/arweave/-/arweave-0.0.2.tgz#c0e73eb8c15e323342d33ea92701d4036fd22ae3" + integrity sha512-ddE5h4qXbl0xfGlxrtBIwzflaxZUDlDs43TuT0u1OMfyobHul4AA1VEX72Rpzw2bOh4vzoytSqA1jCM7x9YtHg== + dependencies: + asn1.js "^5.4.1" + async-retry "^1.3.3" + axios "^1.4.0" + base64-js "^1.5.1" + bignumber.js "^9.1.1" + +"@irys/query@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@irys/query/-/query-0.0.1.tgz#c0aa3eff9eef585d2b3d8d9e358b1c5942015414" + integrity sha512-7TCyR+Qn+F54IQQx5PlERgqNwgIQik8hY55iZl/silTHhCo1MI2pvx5BozqPUVCc8/KqRsc2nZd8Bc29XGUjRQ== + dependencies: + async-retry "^1.3.3" + axios "^1.4.0" + +"@irys/sdk@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@irys/sdk/-/sdk-0.1.1.tgz#c6ed31be5ffd5069db34f7d6c80b7c7a86f0b35b" + integrity sha512-GqyQX1HaXiHcux/VF9VlCX6yh+EQg4JqzqtCElq6BJZvnO7RasOsSjIq9Ie0NbdXXphEG/XP2saV5RR3zFKTIw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/wallet" "^5.7.0" + "@irys/query" "^0.0.1" + "@near-js/crypto" "^0.0.3" + "@near-js/keystores-browser" "^0.0.3" + "@near-js/providers" "^0.0.4" + "@near-js/transactions" "^0.1.0" + "@solana/web3.js" "^1.36.0" + "@supercharge/promise-pool" "^3.0.0" + algosdk "^1.13.1" + aptos "=1.8.5" + arbundles "^0.10.0" + async-retry "^1.3.3" + axios "^1.4.0" + base64url "^3.0.1" + bignumber.js "^9.0.1" + bs58 "5.0.0" + commander "^8.2.0" + csv "5.5.3" + inquirer "^8.2.0" + js-sha256 "^0.9.0" + mime-types "^2.1.34" + near-seed-phrase "^0.2.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1631,6 +1660,142 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q== +"@near-js/crypto@0.0.3", "@near-js/crypto@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/crypto/-/crypto-0.0.3.tgz#4a33e526ab5fa75b703427067985694a279ff8bd" + integrity sha512-3WC2A1a1cH8Cqrx+0iDjp1ASEEhxN/KHEMENYb0KZH6Hp5bXIY7Akt4quC7JlgJS5ESvEiLa40tS5h0zAhBWGw== + dependencies: + "@near-js/types" "0.0.3" + bn.js "5.2.1" + borsh "^0.7.0" + tweetnacl "^1.0.1" + +"@near-js/crypto@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/crypto/-/crypto-0.0.4.tgz#7bb991da25f06096de51466c6331cb185314fad8" + integrity sha512-2mSIVv6mZway1rQvmkktrXAFoUvy7POjrHNH3LekKZCMCs7qMM/23Hz2+APgxZPqoV2kjarSNOEYJjxO7zQ/rQ== + dependencies: + "@near-js/types" "0.0.4" + bn.js "5.2.1" + borsh "^0.7.0" + tweetnacl "^1.0.1" + +"@near-js/keystores-browser@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/keystores-browser/-/keystores-browser-0.0.3.tgz#110b847cd9c358076c2401e9462cc1140e12a908" + integrity sha512-Ve/JQ1SBxdNk3B49lElJ8Y54AoBY+yOStLvdnUIpe2FBOczzwDCkcnPcMDV0NMwVlHpEnOWICWHbRbAkI5Vs+A== + dependencies: + "@near-js/crypto" "0.0.3" + "@near-js/keystores" "0.0.3" + +"@near-js/keystores@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/keystores/-/keystores-0.0.3.tgz#eb1e8e06936da166b5ed8dab3123eaa1bf7a8dab" + integrity sha512-mnwLYUt4Td8u1I4QE1FBx2d9hMt3ofiriE93FfOluJ4XiqRqVFakFYiHg6pExg5iEkej/sXugBUFeQ4QizUnew== + dependencies: + "@near-js/crypto" "0.0.3" + "@near-js/types" "0.0.3" + +"@near-js/keystores@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/keystores/-/keystores-0.0.4.tgz#da03069497bb14741a4d97f7ad4746baf9a09ea7" + integrity sha512-+vKafmDpQGrz5py1liot2hYSjPGXwihveeN+BL11aJlLqZnWBgYJUWCXG+uyGjGXZORuy2hzkKK6Hi+lbKOfVA== + dependencies: + "@near-js/crypto" "0.0.4" + "@near-js/types" "0.0.4" + +"@near-js/providers@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/providers/-/providers-0.0.4.tgz#90f84d765ff90370599d027f47c098a3e7e745d0" + integrity sha512-g/2pJTYmsIlTW4mGqeRlqDN9pZeN+1E2/wfoMIf3p++boBVxVlaSebtQgawXAf2lkfhb9RqXz5pHqewXIkTBSw== + dependencies: + "@near-js/transactions" "0.1.0" + "@near-js/types" "0.0.3" + "@near-js/utils" "0.0.3" + bn.js "5.2.1" + borsh "^0.7.0" + http-errors "^1.7.2" + optionalDependencies: + node-fetch "^2.6.1" + +"@near-js/signers@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/signers/-/signers-0.0.3.tgz#bfc8386613295fc6b51982cf65c79bdc9307aa5e" + integrity sha512-u1R+DDIua5PY1PDFnpVYqdMgQ7c4dyeZsfqMjE7CtgzdqupgTYCXzJjBubqMlAyAx843PoXmLt6CSSKcMm0WUA== + dependencies: + "@near-js/crypto" "0.0.3" + "@near-js/keystores" "0.0.3" + js-sha256 "^0.9.0" + +"@near-js/signers@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/signers/-/signers-0.0.4.tgz#a1904ccc718d6f87b05cd2e168f33bde0cfb269a" + integrity sha512-xCglo3U/WIGsz/izPGFMegS5Q3PxOHYB8a1E7RtVhNm5QdqTlQldLCm/BuMg2G/u1l1ZZ0wdvkqRTG9joauf3Q== + dependencies: + "@near-js/crypto" "0.0.4" + "@near-js/keystores" "0.0.4" + js-sha256 "^0.9.0" + +"@near-js/transactions@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@near-js/transactions/-/transactions-0.1.0.tgz#a03f529da6bb2eaf9dd0590093f2d0763b8ae72a" + integrity sha512-OrrDFqhX0rtH+6MV3U3iS+zmzcPQI+L4GJi9na4Uf8FgpaVPF0mtSmVrpUrS5CC3LwWCzcYF833xGYbXOV4Kfg== + dependencies: + "@near-js/crypto" "0.0.3" + "@near-js/signers" "0.0.3" + "@near-js/types" "0.0.3" + "@near-js/utils" "0.0.3" + bn.js "5.2.1" + borsh "^0.7.0" + js-sha256 "^0.9.0" + +"@near-js/transactions@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@near-js/transactions/-/transactions-0.1.1.tgz#3d4c9d8e3cf2543642d660c0c0b126f0a97d5d43" + integrity sha512-Fk83oLLFK7nz4thawpdv9bGyMVQ2i48iUtZEVYhuuuqevl17tSXMlhle9Me1ZbNyguJG/cWPdNybe1UMKpyGxA== + dependencies: + "@near-js/crypto" "0.0.4" + "@near-js/signers" "0.0.4" + "@near-js/types" "0.0.4" + "@near-js/utils" "0.0.4" + bn.js "5.2.1" + borsh "^0.7.0" + js-sha256 "^0.9.0" + +"@near-js/types@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/types/-/types-0.0.3.tgz#d504222469f4d50a6299c522fb6905ba10905bd6" + integrity sha512-gC3iGUT+r2JjVsE31YharT+voat79ToMUMLCGozHjp/R/UW1M2z4hdpqTUoeWUBGBJuVc810gNTneHGx0jvzwQ== + dependencies: + bn.js "5.2.1" + +"@near-js/types@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/types/-/types-0.0.4.tgz#d941689df41c850aeeeaeb9d498418acec515404" + integrity sha512-8TTMbLMnmyG06R5YKWuS/qFG1tOA3/9lX4NgBqQPsvaWmDsa+D+QwOkrEHDegped0ZHQwcjAXjKML1S1TyGYKg== + dependencies: + bn.js "5.2.1" + +"@near-js/utils@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@near-js/utils/-/utils-0.0.3.tgz#5e631f3dbdb7f0c6985bcbef08644db83b519978" + integrity sha512-J72n/EL0VfLRRb4xNUF4rmVrdzMkcmkwJOhBZSTWz3PAZ8LqNeU9ZConPfMvEr6lwdaD33ZuVv70DN6IIjPr1A== + dependencies: + "@near-js/types" "0.0.3" + bn.js "5.2.1" + depd "^2.0.0" + mustache "^4.0.0" + +"@near-js/utils@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@near-js/utils/-/utils-0.0.4.tgz#1a387f81974ebbfa4521c92590232be97e3335dd" + integrity sha512-mPUEPJbTCMicGitjEGvQqOe8AS7O4KkRCxqd0xuE/X6gXF1jz1pYMZn4lNUeUz2C84YnVSGLAM0o9zcN6Y4hiA== + dependencies: + "@near-js/types" "0.0.4" + bn.js "5.2.1" + depd "^2.0.0" + mustache "^4.0.0" + "@noble/ed25519@^1.6.1", "@noble/ed25519@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" @@ -1713,13 +1878,6 @@ dependencies: buffer "~6.0.3" -"@solana/wallet-adapter-base@^0.9.2": - version "0.9.18" - resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.18.tgz#9365304a76977b4446a1167b240d588f2c5448d5" - integrity sha512-5HQFytLmb64j1Nzc6dwddZx+IUePN/PYqVMyf/ok7fN3z8Vw3EIFS8b+RFfBpj4HWbc2kqv5fpnLlaAH7q67pA== - dependencies: - eventemitter3 "^4.0.0" - "@solana/web3.js@^1.36.0": version "1.66.2" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.66.2.tgz#80b43c5868b846124fe3ebac7d3943930c3fa60c" @@ -1741,10 +1899,10 @@ rpc-websockets "^7.5.0" superstruct "^0.14.2" -"@supercharge/promise-pool@^2.1.0": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@supercharge/promise-pool/-/promise-pool-2.3.2.tgz#6366894a7e7bc699bb65e58d8c828113729cf481" - integrity sha512-f5+C7zv+QQivcUO1FH5lXi7GcuJ3CFuJF3Eg06iArhUs5ma0szCLEQwIY4+VQyh7m/RLVZdzvr4E4ZDnLe9MNg== +"@supercharge/promise-pool@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@supercharge/promise-pool/-/promise-pool-3.1.0.tgz#308b9f4d4bf1d607695f916d9454a3556cd4c2b4" + integrity sha512-gB3NukbIcYzRtPoE6dx9svQYPodxvnfQlaaQd8N/z87E6WaMfRE7o5HwB+LZ+KeM0nsNAq1n4TmBtfz1VCUR+Q== "@tootallnate/once@1": version "1.1.2" @@ -2049,6 +2207,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6" + integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA== + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -2325,10 +2488,10 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -aptos@^1.3.13, aptos@^1.3.14: - version "1.3.17" - resolved "https://registry.yarnpkg.com/aptos/-/aptos-1.3.17.tgz#bdfb8ab9790b52abbeefd862721007b4d13c9302" - integrity sha512-SPgWR9D0nHwLNpvxH943T9ieA2/Q0AVVsvT/qTUuWQSImxkxMVkb++XyWhiFyQpnZDqo6o2o0/fZakeUkqrc8w== +aptos@=1.8.5: + version "1.8.5" + resolved "https://registry.yarnpkg.com/aptos/-/aptos-1.8.5.tgz#a17ac721066914785902b03cf1e7304495f6cd9d" + integrity sha512-iQxliWesNHjGQ5YYXCyss9eg4+bDGQWqAZa73vprqGQ9tungK0cRjUI2fmnp63Ed6UG6rurHrL+b0ckbZAOZZQ== dependencies: "@noble/hashes" "1.1.3" "@scure/bip39" "1.1.0" @@ -2336,27 +2499,76 @@ aptos@^1.3.13, aptos@^1.3.14: form-data "4.0.0" tweetnacl "1.0.3" -arbundles@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/arbundles/-/arbundles-0.7.0.tgz#6378feae67cde2b61489d05932fc3833caea7040" - integrity sha512-VPrV4MP2PQyVnt140gv0zESQxdNKT1lhY8J+ngeS0DqLCCU964PBUK1W4lRcV6cMgdOwfY8RiFvNYz5mRoLSBQ== +arbundles@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/arbundles/-/arbundles-0.10.0.tgz#b3b0b86a3618bf5b967d2961647c3a4259cb6014" + integrity sha512-Prbkjb0RSR6ToXPaBFhsBiMYSq78vHWbG/Zzy1tALRGvnKYlNLq93cqtmCNHqaYP6YCBZZV05ZpbO5C6269saw== dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@irys/arweave" "^0.0.2" "@noble/ed25519" "^1.6.1" + base64url "^3.0.1" + bs58 "^4.0.1" + keccak "^3.0.2" + secp256k1 "^5.0.0" + optionalDependencies: "@randlabs/myalgo-connect" "^1.1.2" - "@solana/wallet-adapter-base" "^0.9.2" algosdk "^1.13.1" - aptos "^1.3.13" - arweave "^1.11.4" arweave-stream-tx "^1.1.0" - avsc "https://github.com/Bundlr-Network/avsc#csp-fixes" - axios "^0.21.3" + multistream "^4.1.0" + tmp-promise "^3.0.2" + +arbundles@^0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/arbundles/-/arbundles-0.9.6.tgz#7964653d91c87ce3fad232c38d08ace85ac3f004" + integrity sha512-YtNVHLJ99utKgwu7ss4N6A4tUHSitMho85DGLlHjlTMWlninL8QU6Cczjp8luOzG0ong3V3OQ3QsMHRe+GYrKw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@noble/ed25519" "^1.6.1" + arweave "=1.11.8" base64url "^3.0.1" bs58 "^4.0.1" - ethers "^5.5.1" keccak "^3.0.2" + secp256k1 "^4.0.2" + optionalDependencies: + "@randlabs/myalgo-connect" "^1.1.2" + algosdk "^1.13.1" + arweave-stream-tx "^1.1.0" multistream "^4.1.0" - process "^0.11.10" + tmp-promise "^3.0.2" + +arbundles@^0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/arbundles/-/arbundles-0.9.8.tgz#d02e1151aa1cac75e33058a67f65a627e6a669c5" + integrity sha512-UevirqeqzCSLPlgyxvn7vvuRm22+wAK+HXCYGnE1HTytgfAu8so/g41wkUziNzoeeTNmodJpEy6RbRfYOaRsTA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@noble/ed25519" "^1.6.1" + arweave "=1.11.8" + base64url "^3.0.1" + bs58 "^4.0.1" + keccak "^3.0.2" secp256k1 "^4.0.2" + optionalDependencies: + "@randlabs/myalgo-connect" "^1.1.2" + algosdk "^1.13.1" + arweave-stream-tx "^1.1.0" + multistream "^4.1.0" tmp-promise "^3.0.2" archiver-utils@^2.1.0: @@ -2424,7 +2636,7 @@ arweave-stream-tx@^1.1.0: dependencies: exponential-backoff "^3.1.0" -arweave@1.11.6, arweave@^1.10.13, arweave@^1.11.4, arweave@^1.11.6: +arweave@1.11.6, arweave@^1.10.13, arweave@^1.11.6: version "1.11.6" resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.11.6.tgz#5afcded201c6f123dd62e5bfae1b72ca793ec7c2" integrity sha512-D6N6e2z7oZoxFhu/qElLwQ2T8DxZ8xIqDB+Y16KHvZbassIrrS9iALwxLdaFYNInuyElg6i7qotBMcShWbFSTw== @@ -2436,7 +2648,17 @@ arweave@1.11.6, arweave@^1.10.13, arweave@^1.11.4, arweave@^1.11.6: bignumber.js "^9.0.2" util "^0.12.4" -arweave@1.11.8: +arweave@1.13.7, arweave@^1.13.7: + version "1.13.7" + resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.13.7.tgz#cda8534c833baec372a7052c61f53b4e39a886d7" + integrity sha512-Hv+x2bSI6UyBHpuVbUDMMpMje1ETfpJWj52kKfz44O0IqDRi/LukOkkDUptup1p6OT6KP1/DdpnUnsNHoskFeA== + dependencies: + arconnect "^0.4.2" + asn1.js "^5.4.1" + base64-js "^1.5.1" + bignumber.js "^9.0.2" + +arweave@=1.11.8: version "1.11.8" resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.11.8.tgz#09376e0c6cec40a661cbb27a306cb11c0a663cd8" integrity sha512-58ODeNPIC4OjaOCl2bXjKbOFGsiVZFs+DkQg3BvQGvFWNqw1zTJ4Jp01xGUz+GbdOaDyJcCC0g3l0HwdJfFPyw== @@ -2448,6 +2670,16 @@ arweave@1.11.8: bignumber.js "^9.0.2" util "^0.12.4" +arweave@^1.13.1: + version "1.14.4" + resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.14.4.tgz#5ba22136aa0e7fd9495258a3931fb770c9d6bf21" + integrity sha512-tmqU9fug8XAmFETYwgUhLaD3WKav5DaM4p1vgJpEj/Px2ORPPMikwnSySlFymmL2qgRh2ZBcZsg11+RXPPGLsA== + dependencies: + arconnect "^0.4.2" + asn1.js "^5.4.1" + base64-js "^1.5.1" + bignumber.js "^9.0.2" + asn1.js@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -2458,6 +2690,13 @@ asn1.js@^5.4.1: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" +async-mutex@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== + dependencies: + tslib "^2.4.0" + async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" @@ -2509,10 +2748,6 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -"avsc@https://github.com/Bundlr-Network/avsc#csp-fixes": - version "5.4.7" - resolved "https://github.com/Bundlr-Network/avsc#a730cc8018b79e114b6a3381bbb57760a24c6cef" - axios@0.26.0: version "0.26.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928" @@ -2528,20 +2763,6 @@ axios@0.27.2, axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^0.21.3: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== - dependencies: - follow-redirects "^1.14.7" - axios@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" @@ -2549,6 +2770,15 @@ axios@^0.26.1: dependencies: follow-redirects "^1.14.8" +axios@^1.4.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -2622,6 +2852,11 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.0.2, base64-js@^1.2.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2644,11 +2879,21 @@ bigint-buffer@^1.1.5: dependencies: bindings "^1.3.0" +bignumber.js@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: version "9.1.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== +bignumber.js@^9.1.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -2688,30 +2933,16 @@ bl@^4.0.3, bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -bn.js@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== +bn.js@5.2.1, bn.js@^5.0.0, bn.js@^5.1.3, bn.js@^5.2.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== bn.js@^4.0.0, bn.js@^4.11.6, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.3, bn.js@^5.2.0, bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -borsh@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.6.0.tgz#a7c9eeca6a31ca9e0607cb49f329cb659eb791e1" - integrity sha512-sl5k89ViqsThXQpYa9XDtz1sBl3l1lI313cFUY1HKr+wvMILnb+58xpkqTNrYbelh99dY7K8usxoCusQmqix9Q== - dependencies: - bn.js "^5.2.0" - bs58 "^4.0.0" - text-encoding-utf-8 "^1.0.2" - borsh@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" @@ -2790,6 +3021,13 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" +bs58@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2921,11 +3159,6 @@ caniuse-lite@^1.0.30001400: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5" integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA== -capability@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/capability/-/capability-0.2.5.tgz#51ad87353f1936ffd77f2f21c74633a4dea88801" - integrity sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg== - catering@^2.1.0, catering@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" @@ -3108,10 +3341,10 @@ color-support@^1.1.1: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colorette@2.0.16: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colorette@2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== colors@1.0.3: version "1.0.3" @@ -3135,16 +3368,16 @@ commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.1.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + compress-commons@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" @@ -3298,30 +3531,30 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csv-generate@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-4.2.1.tgz#2a0c5f0d9a5b6f7a0c1fee40f028707af048b31b" - integrity sha512-w6GFHjvApv6bcJ2xdi9JGsH6ZvUBfC+vUdfefnEzurXG6hMRwzkBLnhztU2H7v7+zfCk1I/knnQ+tGbgpxWrBw== +csv-generate@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-3.4.3.tgz#bc42d943b45aea52afa896874291da4b9108ffff" + integrity sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw== -csv-parse@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.2.tgz#a8ce2f8dec1b9c1013c9e73c6102fe0d2d436dbb" - integrity sha512-3jQ/JMs+voKxr4vwpmElS1d37J0o6rQdQyEKoPyA9HG8fYczpLaBJnmp5ykvkXL8ZeEGVP0qwLU645BZVykXKw== +csv-parse@^4.16.3: + version "4.16.3" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.16.3.tgz#7ca624d517212ebc520a36873c3478fa66efbaf7" + integrity sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg== -csv-stringify@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.2.2.tgz#3f398c79e14353f799c9d2583bafa06ebe68ac21" - integrity sha512-spGNdHxkAgoKk9ChAIR/k8JSFmvAyUQvODPUss5Djqgm/wBuU9qBRuGZ04LTAsGGnClQ8hD4TFz+hbBf1gpTMg== +csv-stringify@^5.6.5: + version "5.6.5" + resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-5.6.5.tgz#c6d74badda4b49a79bf4e72f91cce1e33b94de00" + integrity sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A== -csv@^6.0.5: - version "6.2.3" - resolved "https://registry.yarnpkg.com/csv/-/csv-6.2.3.tgz#4d78de93fc5a3ff4a93dc752c1cc2af781991905" - integrity sha512-LwpMgclTH2T386Ug/QgpJGtvWdQrg7ARO2BoYkevQ4H/zhiRCaDol4W2RyGoCSj+yKTeIf866mUnQAnmH0KhHA== +csv@5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/csv/-/csv-5.5.3.tgz#cd26c1e45eae00ce6a9b7b27dcb94955ec95207d" + integrity sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g== dependencies: - csv-generate "^4.2.1" - csv-parse "^5.3.2" - csv-stringify "^6.2.2" - stream-transform "^3.2.1" + csv-generate "^3.4.3" + csv-parse "^4.16.3" + csv-stringify "^5.6.5" + stream-transform "^2.1.3" data-urls@^2.0.0: version "2.0.0" @@ -3332,7 +3565,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3346,13 +3579,6 @@ debug@4.3.1: dependencies: ms "2.1.2" -debug@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" @@ -3535,15 +3761,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -error-polyfill@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/error-polyfill/-/error-polyfill-0.1.3.tgz#df848b61ad8834f7a5db69a70b9913df86721d15" - integrity sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg== - dependencies: - capability "^0.2.5" - o3 "^1.0.3" - u3 "^0.1.1" - es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -3725,7 +3942,7 @@ eth-rpc-errors@^4.0.2: dependencies: fast-safe-stringify "^2.0.6" -ethers@^5.5.1, ethers@^5.7.2: +ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3761,7 +3978,7 @@ ethers@^5.5.1, ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -eventemitter3@^4.0.0, eventemitter3@^4.0.7: +eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -3952,11 +4169,16 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.0, follow-redirects@^1.14.7, follow-redirects@^1.14.8, follow-redirects@^1.14.9: +follow-redirects@^1.14.8, follow-redirects@^1.14.9: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -4041,10 +4263,10 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -getopts@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" - integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== +getopts@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" + integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" @@ -5165,29 +5387,30 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -knex@0.95.14: - version "0.95.14" - resolved "https://registry.yarnpkg.com/knex/-/knex-0.95.14.tgz#47eca7757cbc5872b7c9a3c67ae3b7ac6d00cf10" - integrity sha512-j4qLjWySrC/JRRVtOpoR2LcS1yBOsd7Krc6mEukPvmTDX/w11pD52Pq9FYR56/kLXGeAV8jFdWBjsZFi1mscWg== +knex@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.2.tgz#a34a289d38406dc19a0447a78eeaf2d16ebedd61" + integrity sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg== dependencies: - colorette "2.0.16" - commander "^7.1.0" - debug "4.3.2" + colorette "2.0.19" + commander "^9.1.0" + debug "4.3.4" escalade "^3.1.1" esm "^3.2.25" - getopts "2.2.5" + get-package-type "^0.1.0" + getopts "2.3.0" interpret "^2.2.0" lodash "^4.17.21" pg-connection-string "2.5.0" - rechoir "0.7.0" + rechoir "^0.8.0" resolve-from "^5.0.0" - tarn "^3.0.1" + tarn "^3.0.2" tildify "2.0.0" -koa-bodyparser@4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz#274c778555ff48fa221ee7f36a9fbdbace22759a" - integrity sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw== +koa-bodyparser@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/koa-bodyparser/-/koa-bodyparser-4.4.0.tgz#2271cd7d603a08c47a47e82d2c00630379672e6c" + integrity sha512-AXPY7wwKZUmbgb8VkTEUFoRNOlx6aWRJwEnQD+zfNf33/7KSAkN4Oo9BqlIk80D+5TvuqlhpQT5dPVcyxl5Zsw== dependencies: co-body "^6.0.0" copy-to "^2.0.1" @@ -5221,10 +5444,10 @@ koa-is-json@^1.0.0: resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14" integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw== -koa@2.13.4: - version "2.13.4" - resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e" - integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g== +koa@2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.14.1.tgz#defb9589297d8eb1859936e777f3feecfc26925c" + integrity sha512-USJFyZgi2l0wDgqkfD27gL4YGno7TfUkcmOe6UOLFOVuN+J7FwnNu4Dydl4CUQzraM1lBAiGed0M9OVJoT0Kqw== dependencies: accepts "^1.3.5" cache-content-type "^1.0.0" @@ -5526,6 +5749,11 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +mixme@^0.5.1: + version "0.5.10" + resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.10.tgz#d653b2984b75d9018828f1ea333e51717ead5f51" + integrity sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q== + mocha@^8.1.2: version "8.4.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" @@ -5605,23 +5833,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -near-api-js@^0.44.2: - version "0.44.2" - resolved "https://registry.yarnpkg.com/near-api-js/-/near-api-js-0.44.2.tgz#e451f68f2c56bd885c7b918db5818a3e6e9423d0" - integrity sha512-eMnc4V+geggapEUa3nU2p8HSHn/njtloI4P2mceHQWO8vDE1NGpnAw8FuTBrLmXSgIv9m6oocgFc9t3VNf5zwg== - dependencies: - bn.js "5.2.0" - borsh "^0.6.0" - bs58 "^4.0.0" - depd "^2.0.0" - error-polyfill "^0.1.3" - http-errors "^1.7.2" - js-sha256 "^0.9.0" - mustache "^4.0.0" - node-fetch "^2.6.1" - text-encoding-utf-8 "^1.0.2" - tweetnacl "^1.0.1" - near-hd-key@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/near-hd-key/-/near-hd-key-1.2.1.tgz#f508ff15436cf8a439b543220f3cc72188a46756" @@ -5651,6 +5862,11 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -5695,13 +5911,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== -o3@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/o3/-/o3-1.0.3.tgz#192ce877a882dfa6751f0412a865fafb2da1dac0" - integrity sha512-f+4n+vC6s4ysy7YO7O2gslWZBUu8Qj2i2OUJOvjRxQva7jVjYjB29jrr9NCjmxZQR0gzrOcv1RnqoYOeMs5VRQ== - dependencies: - capability "^0.2.5" - object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" @@ -5918,15 +6127,15 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.2.tgz#ed1bed1fb8d79f1c6fd5fb1c99e990fbf9ddf178" - integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w== +pg-pool@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" + integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== -pg-protocol@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" - integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== +pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== pg-query-stream@^4.2.3: version "4.2.4" @@ -5946,16 +6155,16 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@8.7.3: - version "8.7.3" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.3.tgz#8a5bdd664ca4fda4db7997ec634c6e5455b27c44" - integrity sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw== +pg@8.10.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24" + integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" pg-connection-string "^2.5.0" - pg-pool "^3.5.1" - pg-protocol "^1.5.0" + pg-pool "^3.6.0" + pg-protocol "^1.6.0" pg-types "^2.1.0" pgpass "1.x" @@ -6044,11 +6253,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -6062,6 +6266,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -6172,12 +6381,12 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -rechoir@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" - integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: - resolve "^1.9.0" + resolve "^1.20.0" redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" @@ -6273,7 +6482,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.20.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -6400,6 +6609,15 @@ secp256k1@^4.0.2: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +secp256k1@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" + integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^5.0.0" + node-gyp-build "^4.2.0" + semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -6523,10 +6741,12 @@ stream-buffers@^3.0.2: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== -stream-transform@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-3.2.1.tgz#4c8cbdd3e4fa7254c770ef34a962cec68349fcb0" - integrity sha512-ApK+WTJ5bCOf0A2tlec1qhvr8bGEBM/sgXXB7mysdCYgZJO5DZeaV3h3G+g0HnAQ372P5IhiGqnW29zoLOfTzQ== +stream-transform@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-2.1.3.tgz#a1c3ecd72ddbf500aa8d342b0b9df38f5aa598e3" + integrity sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ== + dependencies: + mixme "^0.5.1" streamsearch@^1.1.0: version "1.1.0" @@ -6668,7 +6888,7 @@ tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tarn@^3.0.1: +tarn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== @@ -6838,6 +7058,11 @@ tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@^2.4.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" + integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== + tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" @@ -6899,20 +7124,15 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" - integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -u3@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/u3/-/u3-0.1.1.tgz#5f52044f42ee76cd8de33148829e14528494b73b" - integrity sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w== - -undici@5.14.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.14.0.tgz#1169d0cdee06a4ffdd30810f6228d57998884d00" - integrity sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ== +undici@5.21.0: + version "5.21.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.21.0.tgz#b00dfc381f202565ab7f52023222ab862bb2494f" + integrity sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA== dependencies: busboy "^1.6.0" @@ -6921,6 +7141,13 @@ undici@^4.12.2: resolved "https://registry.yarnpkg.com/undici/-/undici-4.16.0.tgz#469bb87b3b918818d3d7843d91a1d08da357d5ff" integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw== +undici@^5.19.1: + version "5.28.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.1.tgz#1052d37bd1a2e8cf3e188d7caebff833fdc06fa7" + integrity sha512-xcIIvj1LOQH9zAL54iWFkuDEaIVEjLrru7qRpa3GrEEHk6OBhb/LycuUY2m7VCcTuDeLziXCxobQVyKExyGeIA== + dependencies: + "@fastify/busboy" "^2.0.0" + undici@^5.8.0: version "5.12.0" resolved "https://registry.yarnpkg.com/undici/-/undici-5.12.0.tgz#c758ffa704fbcd40d506e4948860ccaf4099f531" @@ -7027,6 +7254,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + uzip-module@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/uzip-module/-/uzip-module-1.0.3.tgz#6bbabe2a3efea5d5a4a47479f523a571de3427ce" @@ -7090,6 +7322,83 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warp-arbundles@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/warp-arbundles/-/warp-arbundles-1.0.0.tgz#167f112d15ba0da698c22c5268a40a01a503b3d9" + integrity sha512-MC+NvottS+D/gdwP8tPZfqH1ob7sD5pFYhM/E0ZqP6KyRRAOdr7+WyuwB93265w8M+4OFTnQx8bgpEIqTTYPgw== + dependencies: + arweave "^1.13.7" + base64url "^3.0.1" + buffer "^6.0.3" + warp-isomorphic "^1.0.4" + +warp-arbundles@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/warp-arbundles/-/warp-arbundles-1.0.1.tgz#c816ee44aacd90e0214b1b7db8028c6d5b468770" + integrity sha512-bWyF8PoS5D2M8yk7YAT/DFi4pzoPxfXaS1o9eEelcD5SU42PW/wVxwzrvn6pLaHFIK5Zys15V2eGd+8X1Ty62Q== + dependencies: + arweave "^1.13.7" + base64url "^3.0.1" + buffer "^6.0.3" + warp-isomorphic "^1.0.4" + +warp-arbundles@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/warp-arbundles/-/warp-arbundles-1.0.4.tgz#10c0cd662ab41b0dabad9159c7110f43425cc5cc" + integrity sha512-KeRac/EJ7VOK+v5+PSMh2SrzpCKOAFnJICLlqZWt6qPkDCzVwcrNE5wFxOlEk5U170ewMDAB3e86UHUblevXpw== + dependencies: + arweave "^1.13.7" + base64url "^3.0.1" + buffer "^6.0.3" + warp-isomorphic "^1.0.7" + +"warp-contracts-new@npm:warp-contracts": + version "1.4.14" + resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.14.tgz#0a14428229736f568cdd54723dfa903dfe86ccfc" + integrity sha512-5tJzD1biarpXLJRxneEB7thWCqRdZXYhPr77XJxY9oveLANryibYJSEtUbrycHcLurSAQ+Vyd2qd5JHq8TAC4A== + dependencies: + archiver "^5.3.0" + arweave "1.13.7" + async-mutex "^0.4.0" + bignumber.js "9.1.1" + events "3.3.0" + fast-copy "^3.0.0" + level "^8.0.0" + memory-level "^1.0.0" + safe-stable-stringify "2.4.1" + stream-buffers "^3.0.2" + unzipit "^1.4.0" + warp-arbundles "1.0.1" + warp-isomorphic "1.0.4" + warp-wasm-metering "1.0.1" + +"warp-contracts-old@npm:warp-contracts@1.4.12": + version "1.4.12" + resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.12.tgz#b1681fc0b81e9128c272d7ce8f417319ce5fb969" + integrity sha512-DjmxN4U26jzYhlumTj1QgxH1GPmjN2oAkTXDGBvXSpdLYvf0n3aGJLBVYddfH9P/IQlxcQJ6KaXS4Ihlq9FrAw== + dependencies: + archiver "^5.3.0" + arweave "1.13.7" + async-mutex "^0.4.0" + bignumber.js "9.1.1" + events "3.3.0" + fast-copy "^3.0.0" + level "^8.0.0" + memory-level "^1.0.0" + safe-stable-stringify "2.4.1" + stream-buffers "^3.0.2" + unzipit "^1.4.0" + warp-isomorphic "1.0.4" + warp-wasm-metering "1.0.1" + +warp-contracts-plugin-signature@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/warp-contracts-plugin-signature/-/warp-contracts-plugin-signature-1.0.16.tgz#7ba9ad9651ae20e679e76daaffe6836686ebf00d" + integrity sha512-sYrL/bCj7iqjUk7nPSnNpdkcvRPA0yQlNqBN0iyUQ9GQuyskwLDEeQUfe+T1oDwHaznqqABqBtItB+3TsUULLw== + dependencies: + arbundles "^0.9.8" + arweave "^1.13.1" + warp-contracts-pubsub@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/warp-contracts-pubsub/-/warp-contracts-pubsub-1.0.3.tgz#5d2636b3da95c7b2a31b51668b70b9c3fb3685a6" @@ -7127,26 +7436,49 @@ warp-contracts@1.2.20: unzipit "^1.4.0" vm2 "3.9.11" -warp-contracts@1.2.48: - version "1.2.48" - resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.2.48.tgz#2a325e80093eb54ed59af495e21c3b2c98b796a9" - integrity sha512-TBZJ/VZdr+9mFjH2gjf55kTeHc9/tXQ3bsym41+ujq6m1kIreyhL1I0G7bn6qSHpAbOIZKIjP5GC5+pp9pm8Ew== +warp-contracts@1.4.26-beta.0: + version "1.4.26-beta.0" + resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.26-beta.0.tgz#2784c0bd45669f6a3c6281ea1daeeb6ece5dfc6f" + integrity sha512-B5us0r45aqfQoHiQQVg/q9xlVmceyKK7ZyZaJYef0eozdcrBZ576KkdZxKSGGaO+BnDZjkVxh1e/I/sV67/Yng== dependencies: - "@assemblyscript/loader" "^0.19.23" - "@idena/vrf-js" "^1.0.1" archiver "^5.3.0" - arweave "1.11.8" - elliptic "^6.5.4" + arweave "1.13.7" + async-mutex "^0.4.0" + bignumber.js "9.1.1" events "3.3.0" fast-copy "^3.0.0" level "^8.0.0" memory-level "^1.0.0" - redstone-isomorphic "1.1.8" safe-stable-stringify "2.4.1" stream-buffers "^3.0.2" unzipit "^1.4.0" - vm2 "3.9.11" - warp-wasm-metering "1.0.0" + warp-arbundles "^1.0.4" + warp-isomorphic "^1.0.7" + warp-wasm-metering "1.0.1" + +warp-isomorphic@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/warp-isomorphic/-/warp-isomorphic-1.0.0.tgz#dccccfc046bc6ac77919f8be1024ced1385c70ea" + integrity sha512-E+9+brlrnZoNpNvpz8foIZiCk9fIVukRBZYEy/yefM+oAG+zNgDPb/xNaZyUJWC8rSZxE1DqrhRl0JxjtLVltA== + dependencies: + buffer "^6.0.3" + undici "^5.8.0" + +warp-isomorphic@1.0.4, warp-isomorphic@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/warp-isomorphic/-/warp-isomorphic-1.0.4.tgz#1017eba260e0f0228b33b94b9e36a1afe54e09d8" + integrity sha512-W77IoLjq/eu5bY1uRrlmVt5lLDoIHeZ0ozJ/j67FTnxvZRXu887biEnom1nx8q1UgOKyJh8eQYFQaE2FLlKhFg== + dependencies: + buffer "^6.0.3" + undici "^5.8.0" + +warp-isomorphic@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/warp-isomorphic/-/warp-isomorphic-1.0.7.tgz#abf1ee7bce44bec7c6b97547859e614876869aa7" + integrity sha512-fXHbUXwdYqPm9fRPz8mjv5ndPco09aMQuTe4kXfymzOq8V6F3DLsg9cIafxvjms9/mc6eijzkLBJ63yjEENEjA== + dependencies: + buffer "^6.0.3" + undici "^5.19.1" warp-signature@1.0.4: version "1.0.4" @@ -7160,22 +7492,22 @@ warp-signature@1.0.4: safe-stable-stringify "^2.4.1" warp-contracts "1.2.20" -warp-wasm-json-toolkit@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/warp-wasm-json-toolkit/-/warp-wasm-json-toolkit-1.0.0.tgz#f08b555b64fac2b102450e6a5e94f4bf71405508" - integrity sha512-pFJpP3djS6GvDu5ohgBjSjkYQrKs1Toi7hsvjW5FIJO1Q/msclbeoUY3Poiyx0Hz6oRVfuO2MZkmCreDnNaUTg== +warp-wasm-json-toolkit@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/warp-wasm-json-toolkit/-/warp-wasm-json-toolkit-1.0.2.tgz#16ca399e5b20da804c01ff0d00979341b689a0e7" + integrity sha512-T6pKJz9mO0ZFYiu4jB2v8j8t8Cw21n/+uFh0QKbc/7cJSssGd3I26sV/VXjoDbGuG7bGzK9BewlFd+ukvxABOA== dependencies: buffer-pipe "0.0.3" leb128 "0.0.4" - redstone-isomorphic "1.1.0" + warp-isomorphic "1.0.0" -warp-wasm-metering@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/warp-wasm-metering/-/warp-wasm-metering-1.0.0.tgz#ff2ded687885af22c8fc6560409a582e127c3af3" - integrity sha512-lOlVz7BDTnTMuvhrtIbVtozGJANoVgVn6gD3dTEFyIMehC250CKHmsqenX1jy5OM2DNL/VuFNdCTLHCGaPg64w== +warp-wasm-metering@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/warp-wasm-metering/-/warp-wasm-metering-1.0.1.tgz#1496b0b9a936985cf21a910e909b87630faa1c43" + integrity sha512-s2NtOPTGIDPWeDKyrY5UiUUf3oOjbjwGF9sgmRR3nqXzjxdgppvuf+6VYquzYM3xRMXIOq+AWMK2H/D/Yv+4tg== dependencies: leb128 "^0.0.4" - warp-wasm-json-toolkit "1.0.0" + warp-wasm-json-toolkit "1.0.2" wcwidth@^1.0.1: version "1.0.1"