diff --git a/.prettierignore b/.prettierignore index 815a7f9fd..714ee041f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ typescript/ system-tests/ monitoring/ cross-chain/ +infrastructure/ diff --git a/infrastructure/tbtc-deposit-reveals/.gitignore b/infrastructure/tbtc-deposit-reveals/.gitignore new file mode 100644 index 000000000..477c64bb4 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/node_modules +*-lock.* +*.lock +*.log +.wrangler/ +/external + +/dist diff --git a/infrastructure/tbtc-deposit-reveals/.prettierignore b/infrastructure/tbtc-deposit-reveals/.prettierignore new file mode 100644 index 000000000..6f5de4a15 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/.prettierignore @@ -0,0 +1,2 @@ +external/ +.wrangler/ diff --git a/infrastructure/tbtc-deposit-reveals/.prettierrc.js b/infrastructure/tbtc-deposit-reveals/.prettierrc.js new file mode 100644 index 000000000..c8280797a --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("@keep-network/prettier-config-keep"), +} diff --git a/infrastructure/tbtc-deposit-reveals/README.md b/infrastructure/tbtc-deposit-reveals/README.md new file mode 100644 index 000000000..f7b3c22b2 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/README.md @@ -0,0 +1,44 @@ +# Deposit Reveals API + +A backend to store TBTC reveal and recovery information so that a +user does not have to. + +We're using [cloudflare workers](https://workers.cloudflare.com/) with typescript, +and a D1 database for persistence. + +[itty-router](https://github.com/kwhitley/itty-router) is used for routing. + +## Architecture + +We have a `routes.ts` file that hands off http requests to various functions within +the `controllers` folder. The controllers read from D1, giving us the +[model-view-controller pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) +pattern (the view is the front end). + +## Development + +- local development: `yarn run dev` +- query remote D1: `yarn run query: ` +- - example: `yarn run query:local 'select * from reveals;'` +- view pending remote migrations: `yarn run migrations::list` +- apply pending remote migrations: `yarn run migrations::apply` +- create a new migration: `yarn run migrations:create` +- reset to a bootstrapped local database: `yarn run reload_db:local` +- factory reset the local database: `yarn run reset_db:local` + +## Deployment + +We have two different environments: `staging` and `production`. +To deploy to an environment: + +``` +$ yarn run deploy --env +``` + +Make sure to select the "Thesis" account when prompted. +If deployment does not prompt you, log out and then back in: + +``` +$ npx wrangler logout +$ npx wrangler login +``` diff --git a/infrastructure/tbtc-deposit-reveals/migrations/0000_initial-tables.sql b/infrastructure/tbtc-deposit-reveals/migrations/0000_initial-tables.sql new file mode 100644 index 000000000..a075212d1 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/migrations/0000_initial-tables.sql @@ -0,0 +1,9 @@ +-- Migration number: 0000 2024-03-19T14:35:39.927Z + +CREATE TABLE IF NOT EXISTS reveals ( + address TEXT, + reveal_info JSON, + metadata JSON, + application TEXT, + inserted_at TIMESTAMP NOT NULL DEFAULT current_timestamp +); diff --git a/infrastructure/tbtc-deposit-reveals/package.json b/infrastructure/tbtc-deposit-reveals/package.json new file mode 100644 index 000000000..82ba6c1a2 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/package.json @@ -0,0 +1,31 @@ +{ + "name": "template-worker-typescript", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy src/index.ts", + "dev": "wrangler dev --local-upstream 'localhost:3000'", + "query:production": "wrangler d1 execute tbtc-deposit-reveals-production --env production", + "query:staging": "wrangler d1 execute tbtc-deposit-reveals-staging --env staging", + "query:local": "wrangler d1 execute tbtc-deposit-reveals-staging --local", + "migrations:create": "wrangler d1 migrations create tbtc-deposit-reveals-production", + "migrations:production:list": "wrangler d1 migrations list tbtc-deposit-reveals-production --env production", + "migrations:production:apply": "wrangler d1 migrations apply tbtc-deposit-reveals-production --env production", + "migrations:staging:list": "wrangler d1 migrations list tbtc-deposit-reveals-staging --env staging", + "migrations:staging:apply": "wrangler d1 migrations apply tbtc-deposit-reveals-staging --env staging", + "migrations:local:list": "wrangler d1 migrations list tbtc-deposit-reveals-staging --local", + "migrations:local:apply": "wrangler d1 migrations apply tbtc-deposit-reveals-staging --local", + "reload_db:local": "sh scripts/rebuild_db.sh", + "reset_db:local": "wrangler d1 execute tbtc-deposit-reveals-staging --local --file ./scripts/reset_db.sql" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20231016.0", + "@keep-network/prettier-config-keep": "github:keep-network/prettier-config-keep", + "typescript": "^5.0.4", + "wrangler": "^3.0.0" + }, + "dependencies": { + "@keep-network/tbtc-v2.ts": "^2.3.0", + "itty-router": "^4.2.2" + } +} diff --git a/infrastructure/tbtc-deposit-reveals/scripts/local_data.sql b/infrastructure/tbtc-deposit-reveals/scripts/local_data.sql new file mode 100644 index 000000000..71ee30a55 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/scripts/local_data.sql @@ -0,0 +1,8 @@ +INSERT INTO reveals (address, reveal_info, metadata, application) +VALUES + ( + '0xalice', + '{"depositor":{"identifierHex":"27343e0410acd8cf711d079c57811fe8c0666df2"},"blindingFactor":"32cd0d8907411467","walletPublicKeyHash":"03b74d6893ad46dfdd01b9e0e3b3385f4fce2d1e","refundPublicKeyHash":"be94fbd152b1c9f396a5e2dce4f536de0cddac1e","refundLocktime":"6ebe3b65","btcRecoveryAddress":"2NAcvrJF3oDpjC5nuqhJvLrsvEtXwQ1W65n"}', + '{}', + "mezo" + ); diff --git a/infrastructure/tbtc-deposit-reveals/scripts/rebuild_db.sh b/infrastructure/tbtc-deposit-reveals/scripts/rebuild_db.sh new file mode 100644 index 000000000..a2f890d7d --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/scripts/rebuild_db.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +yarn run query:local --file ./scripts/reset_db.sql +yes | yarn run migrations:local:apply +yarn run query:local --file ./scripts/local_data.sql diff --git a/infrastructure/tbtc-deposit-reveals/scripts/reset_db.sql b/infrastructure/tbtc-deposit-reveals/scripts/reset_db.sql new file mode 100644 index 000000000..6e4e26897 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/scripts/reset_db.sql @@ -0,0 +1,3 @@ +drop table if exists reveals; + +drop table if exists d1_migrations; diff --git a/infrastructure/tbtc-deposit-reveals/src/controllers/deposits.ts b/infrastructure/tbtc-deposit-reveals/src/controllers/deposits.ts new file mode 100644 index 000000000..bc859f61d --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/src/controllers/deposits.ts @@ -0,0 +1,90 @@ +import { IRequest, StatusError } from "itty-router" +import { Env } from "#/types" +import { DepositReceipt } from "@keep-network/tbtc-v2.ts/src/lib/contracts/bridge" + +type DepositQueryResult = { + address: string + reveal_info: string + metadata: string + application: string + inserted_at: number +} + +type Deposit = { + address: string + revealInfo: DepositReceipt + metadata: Object + application: string + insertedAt: number +} + +export async function getDepositsForAddress( + request: IRequest, + env: Env, +): Promise { + const { + params: { address }, + } = request + if (address === undefined) { + throw new StatusError( + 500, + "Unable to extract the address from the request.", + ) + } + const { results: reveals } = await env.DB.prepare( + ` + SELECT + address, + reveal_info, + metadata, + application, + CAST(strftime('%s', inserted_at) as INT) as inserted_at + FROM reveals + WHERE address = ?1 + `, + ) + .bind(address) + .all() + + return reveals.map((reveal) => { + return { + address: reveal.address, + revealInfo: JSON.parse(reveal.reveal_info), + metadata: JSON.parse(reveal.metadata), + application: reveal.application, + insertedAt: reveal.inserted_at, + } + }) +} + +export type SaveDepositRequest = { + address: string + revealInfo: DepositReceipt + metadata: Object + application: string +} + +export async function saveDeposit( + request: SaveDepositRequest & IRequest, + env: Env, +): Promise<{ success: boolean }> { + const saveDepositInfo: SaveDepositRequest = request.content + const { address, revealInfo, metadata, application } = saveDepositInfo + const result = await env.DB.prepare( + ` + INSERT INTO reveals + (address, reveal_info, metadata, application) + VALUES + (?1, ?2, ?3, ?4); + `, + ) + .bind( + address, + JSON.stringify(revealInfo), + JSON.stringify(metadata), + application, + ) + .run() + + return { success: result.success } +} diff --git a/infrastructure/tbtc-deposit-reveals/src/index.ts b/infrastructure/tbtc-deposit-reveals/src/index.ts new file mode 100644 index 000000000..5acc7e815 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/src/index.ts @@ -0,0 +1,29 @@ +import { error, json } from "itty-router" +import router, { RouterRequest, corsify } from "#/routes" +import { Env } from "#/types" + +export default { + async fetch(request: Request, env: Env, context: ExecutionContext) { + return router + .handle(request, env, context) + .then((response) => { + const jsonResponse = json(response) + // Reflect the changes made by the router to the request object. + const routerRequest = request as unknown as RouterRequest + + routerRequest.responseHeaders?.forEach( + (headerValue: string, headerName: string) => { + jsonResponse.headers.append(headerName, headerValue) + }, + ) + + return jsonResponse + }) + .catch((err) => { + console.error(err) + + return error(err) + }) + .then(corsify) + }, +} diff --git a/infrastructure/tbtc-deposit-reveals/src/routes.ts b/infrastructure/tbtc-deposit-reveals/src/routes.ts new file mode 100644 index 000000000..17f7c7d41 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/src/routes.ts @@ -0,0 +1,66 @@ +import { + Router, + IRequest, + createCors, + error, + withContent, + withCookies, + RouterType, + RouteHandler, +} from "itty-router" +import { Env } from "#/types" +import { + getDepositsForAddress, + saveDeposit, + SaveDepositRequest, +} from "./controllers/deposits" + +export const { preflight, corsify } = createCors({ + origins: (_: string) => true, + methods: ["GET", "POST", "DELETE"], + headers: { + "Access-Control-Allow-Credentials": true, + }, +}) + +const router = Router() + +/** + * Adds a property, `responseHeaders`, that can be used to modify the eventual + * response headers without returning a response. This allows middleware to set + * response headers. + */ +function withResponseHeaders(request: IRequest): void { + request.responseHeaders = new Headers() +} + +type TypedRoute< + RequestType = IRequest, + // This is a direct pull from itty router for easier typing on our end. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Args extends unknown[] = [env: Env, context: ExecutionContext], + RT = RouterType, +> = (path: string, ...handlers: RouteHandler[]) => RT + +export type RouterRequest = IRequest & { + cookies: Record + responseHeaders: Headers + content?: Record + sessionId?: string +} +// Capture the added properties created by our middlewares. +export type MiddlewaredRouter = RouterType, []> + +router + .all( + "*", + preflight, + withResponseHeaders, + withCookies, + withContent, + ) + .get("/deposits/:address", getDepositsForAddress) + .post("/deposits", saveDeposit) + .all("*", () => error(404, "No home route.")) + +export default router diff --git a/infrastructure/tbtc-deposit-reveals/src/types.ts b/infrastructure/tbtc-deposit-reveals/src/types.ts new file mode 100644 index 000000000..0b619e17d --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/src/types.ts @@ -0,0 +1,3 @@ +export type Env = { + DB: D1Database +} diff --git a/infrastructure/tbtc-deposit-reveals/tsconfig.json b/infrastructure/tbtc-deposit-reveals/tsconfig.json new file mode 100644 index 000000000..1a4447fa1 --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../typescript/tsconfig.json", + "compilerOptions": { + "moduleResolution": "node", + "types": ["@cloudflare/workers-types"], + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules"] +} diff --git a/infrastructure/tbtc-deposit-reveals/wrangler.toml b/infrastructure/tbtc-deposit-reveals/wrangler.toml new file mode 100644 index 000000000..0556e9fab --- /dev/null +++ b/infrastructure/tbtc-deposit-reveals/wrangler.toml @@ -0,0 +1,20 @@ +name = "tbtc-deposit-reveals" +main = "./src/index.ts" +compatibility_date = "2022-10-10" + +[[d1_databases]] +binding = "DB" +database_name = "tbtc-deposit-reveals-staging" +database_id = "144880b2-3392-4e24-afa2-453d6940b9f7" + +# Staging +[[env.staging.d1_databases]] +binding = "DB" +database_name = "tbtc-deposit-reveals-staging" +database_id = "144880b2-3392-4e24-afa2-453d6940b9f7" + +# Production +[[env.production.d1_databases]] +binding = "DB" +database_name = "tbtc-deposit-reveals-production" +database_id = "a859f79c-2a13-4e71-8602-c0a1098a3303"