Skip to content

Commit

Permalink
add db integration (#74)
Browse files Browse the repository at this point in the history
* update

* update docs

* fix test

* fix fee receiver addr

* fix test

* add rescue endpoint

* update doc

* bump

* save router info to db on shouldRoute

* update

* fix docker build

* bump v1.9.0-3

* update doc

* polish

* fix ci

* dump docker logs on failure

* test

* test

* update

* update

* update

* test magic

* update

* update

* try self hosted machine

* test

* test

* saveRouterInfo api

* magic

* test WIP

* polish

* update

* fix flow

* update tests
  • Loading branch information
shunjizhan authored Oct 28, 2024
1 parent b0adda0 commit b597f69
Show file tree
Hide file tree
Showing 26 changed files with 435 additions and 32 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ ACALA_NODE_URL=wss://acala-rpc.aca-api.network
ACALA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044 # test account, no balance on mainnet

PORT=3111
TESTNET_MODE=0
TESTNET_MODE=0

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
4 changes: 3 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ ACALA_NODE_URL=ws://localhost:8000
ACALA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044

PORT=3111
TESTNET_MODE=0
TESTNET_MODE=0

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
17 changes: 14 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ jobs:
- name: setup test env
run: cp .env.test .env

- name: run tests
run: yarn test:ci
- name: setup test infra (db + node + eth rpc)
run: yarn start:test-infra -d

- name: generate db client
run: yarn db:gen

- name: migrate db
run: yarn db:migrate:dev

- name: run tests with coverage
run: yarn test:coverage

- name: upload coverage reports
uses: codecov/codecov-action@v4
Expand All @@ -36,4 +45,6 @@ jobs:
# files: ./coverage/lcov.info
fail_ci_if_error: true


- name: dump docker logs on failure
if: failure()
uses: jwalton/gh-docker-logs@v2
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ WORKDIR /home/node/app

COPY package.json yarn.lock ./

RUN yarn
RUN yarn install --frozen-lockfile

COPY . .

RUN yarn build
RUN yarn db:gen

CMD node dist/index.js
62 changes: 56 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,13 +637,13 @@ GET /shouldRouteSwapAndLp?recipient=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6&s
- swap small amount of token and airdrop ACA to recipient
- swap `swapAmount` token to LDOT, then add LP, refund the remaining token recipient
- stake Lp to euphrates for the recipient
- returns the txhash
- returns the txhash and how many records are removed
```
POST /routeSwapAndLp
data: {
poolId: string; // euphrates pool id
recipient: string; // dest evm address
token: string; // token to route
token: string; // token to route
swapAmount: string; // how many token to swap before adding liquidity
minShareAmount?: string; // add liquidity min share amount (default: 0)
}
Expand All @@ -661,7 +661,10 @@ data: {
=> tx hash
{
data: '0xe1c82c53796d82d87d2e31e289b3cc8ff18e304b8ac95f2bd7548a1706bb8655'
data: {
txHash: '0xe1c82c53796d82d87d2e31e289b3cc8ff18e304b8ac95f2bd7548a1706bb8655',
removed: 1
}
}
/* ---------- when error ---------- */
Expand All @@ -677,7 +680,7 @@ POST /rescueSwapAndLp
data: {
poolId: string; // euphrates pool id
recipient: string; // dest evm address
token: string; // token to route
token: string; // token to route
swapAmount: string; // how many token to swap before adding liquidity
minShareAmount?: string; // add liquidity min share amount (default: 0)
}
Expand All @@ -702,6 +705,51 @@ data: {
// similar to /routeXcm
```

### `/saveRouterInfo`
save router info to db, returns the record

```
POST /saveRouterInfo
data: {
routerAddr: string;
recipient: string;
params: string; // stringified JSON router params
}
```

example
```
POST /saveRouterInfo
data: {
routerAddr: "0xA1C5a8d7389a428C1A7dE77782B7Cc4A6F02bAbD",
recipient: "0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6",
params: "{\"recipient\":\"0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6\",\"poolId\":\"7\",\"swapAmount\":100000000,\"minShareAmount\":999999}"
}
=>
{"data":{"id":1,"timestamp":"2024-10-28T07:38:56.060Z","params":"{\"recipient\":\"0x0085560b24769dac4ed057f1b2ae40746aa9aab6\",\"poolId\":\"7\",\"swapAmount\":100000000,\"minShareAmount\":999999}","factoryAddr":"0x0420974d035457c6f05d4417cc605e6c239d73ab","feeAddr":"0x5fc7261e168f6a8c1053f2208c7db4bcbef133b3","recipient":"0x0085560b24769dac4ed057f1b2ae40746aa9aab6","routerAddr":"0xa1c5a8d7389a428c1a7de77782b7cc4a6f02babd"}}
```

### `/routerInfo`
get router info

```
GET /routerInfo
{
routerAddr?: string; // router address
recipient?: string; // recipient address
}
```

example
```
GET /routerInfo?routerAddr=0xA1C5a8d7389a428C1A7dE77782B7Cc4A6F02bAbD
GET /routerInfo?recipient=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6
=> (same result)
{"data":[{"id":1,"timestamp":"2024-10-28T07:38:56.060Z","params":"{\"recipient\":\"0x0085560b24769dac4ed057f1b2ae40746aa9aab6\",\"poolId\":\"7\",\"swapAmount\":100000000,\"minShareAmount\":999999}","factoryAddr":"0x0420974d035457c6f05d4417cc605e6c239d73ab","feeAddr":"0x5fc7261e168f6a8c1053f2208c7db4bcbef133b3","recipient":"0x0085560b24769dac4ed057f1b2ae40746aa9aab6","routerAddr":"0xa1c5a8d7389a428c1a7de77782b7cc4a6f02babd"}]}
```


## Routing Process
A complete working flow can be found in [routing e2e tests](./src/__tests__/route.test.ts).
Expand Down Expand Up @@ -744,8 +792,9 @@ yarn vite preview --outDir ./coverage/
```

### run tests with separate relayer (no coverage report)
first start a local relayer
first start a local relayer with db
```
yarn start:db
yarn dev
```

Expand All @@ -755,4 +804,5 @@ yarn test
```

## Production Config
`cp .env.example .env` and replace the test keys with real private keys for relayers
- setup env: `cp .env.example .env` and fill in the blanks
- setup db: `docker run --env-file .env acala/relayer:latest yarn db:migrate:prod`
17 changes: 16 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
version: '3.8'

services:
db:
image: postgres:12-alpine
container_name: db
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 5s
retries: 100

node:
container_name: node
image: oven/bun:1.1.26
Expand Down Expand Up @@ -46,7 +59,7 @@ services:
- ENDPOINT_URL=ws://node:8000
healthcheck:
test: |
wget -q -O- --post-data='{"id": 1, "jsonrpc": "2.0", "method": "eth_chainId", "params": []}' --header='Content-Type: application/json' http://localhost:8545 || exit 1
wget -q -O- --post-data='{"id": 1, "jsonrpc": "2.0", "method": "eth_chainId", "params": []}' --header='Content-Type: application/json' http://localhost:8545 || exit 1
interval: 2s
timeout: 5s
retries: 100
Expand All @@ -57,4 +70,6 @@ services:
depends_on:
eth-rpc:
condition: service_healthy
db:
condition: service_healthy
command: echo "test stack ready 🚀"
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@
"repository": "https://github.com/AcalaNetwork/wormhole-relayer/",
"scripts": {
"build": "tsc",
"clean": "rm -rf coverage .nyc_output dist/",
"dev": "ts-node-dev --respawn src/index.ts | pino-pretty --singleLine --colorize --ignore time,hostname,ip",

"start": "node dist/index.js",
"start:db": "docker compose up -- db",
"start:coverage": "pm2 start --name relayer 'nyc node -r ts-node/register src/index.ts' && ./scripts/health-check.sh",
"stop:coverage": "pm2 stop relayer && pm2 delete relayer",
"start:test-infra": "docker compose up",
"stop:coverage": "pm2 stop relayer && pm2 delete relayer",

"db:init": "prisma init",
"db:gen": "prisma generate",
"db:migrate:dev": "prisma migrate dev",
"db:migrate:prod": "prisma migrate deploy",

"test": "vitest src/__tests__/*.test.ts --singleThread --run",
"test:coverage": "yarn start:coverage && yarn test && yarn stop:coverage",
"test:ci": "yarn start:test-infra -d; yarn test:coverage",
"clean": "rm -rf coverage .nyc_output dist/"
"test:ci": "yarn start:test-infra -d; yarn test:coverage"
},
"dependencies": {
"@acala-network/api": "~6.0.4",
Expand All @@ -30,6 +38,7 @@
"@improbable-eng/grpc-web": "^0.14.0",
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"@polkadot/api": "^10.9.1",
"@prisma/client": "^5.19.1",
"axios": "^0.26.1",
"bech32": "^2.0.0",
"cors": "^2.8.5",
Expand All @@ -39,6 +48,7 @@
"express": "^4.17.1",
"js-base64": "^3.6.1",
"pino": "^8.11.0",
"prisma": "^5.19.1",
"protobufjs": "^6.11.2",
"rxjs": "^7.3.0",
"yup": "^1.0.2"
Expand Down
15 changes: 15 additions & 0 deletions prisma/migrations/20241028072124_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "RouterInfo" (
"id" SERIAL NOT NULL,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"params" TEXT NOT NULL,
"factoryAddr" TEXT NOT NULL,
"feeAddr" TEXT NOT NULL,
"recipient" TEXT NOT NULL,
"routerAddr" TEXT NOT NULL,

CONSTRAINT "RouterInfo_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "RouterInfo_routerAddr_key" ON "RouterInfo"("routerAddr");
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
18 changes: 18 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model RouterInfo {
id Int @id @default(autoincrement())
timestamp DateTime @default(now())
params String // stringified JSON
factoryAddr String
feeAddr String
recipient String
routerAddr String @unique
}
34 changes: 32 additions & 2 deletions src/__tests__/swapAndLp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
expectError,
provider,
relayer,
sudoTransferToken,
transferToken,
user,
} from './testUtils';
Expand Down Expand Up @@ -153,6 +154,25 @@ describe('route and rescue', () => {
({ routerAddr } = shouldRouteRes.data);
console.log({ routerAddr });

// save and get router info
await api.saveRouterInfo({
routerAddr,
recipient: user.address,
params: JSON.stringify(routeArgs),
});

let [info0, info1] = await Promise.all([
api.routerInfo({ routerAddr }),
api.routerInfo({ recipient: user.address }),
]);

const expectedInfo = {
routerAddr: routerAddr.toLowerCase(),
recipient: user.address.toLowerCase(),
};
expect(info0.data[0]).to.contain(expectedInfo);
expect(info1.data[0]).to.contain(expectedInfo);

// make sure user has enough token to transfer to router
const bal = await fetchTokenBalances();
const trasnferAmount = BigNumber.from(swapAmount).mul(2);
Expand All @@ -176,8 +196,18 @@ describe('route and rescue', () => {
...routeArgs,
token: JITOSOL_ADDR,
});
const txHash = routeRes.data;
console.log(`route finished! txHash: ${txHash}`);
const { txHash, removed } = routeRes.data;
console.log(`route finished! txHash: ${txHash}, removed: ${removed}`);

// record should be removed
expect(removed).to.eq(1);
[info0, info1] = await Promise.all([
api.routerInfo({ routerAddr }),
api.routerInfo({ recipient: user.address }),
]);

expect(info0.data).to.deep.eq([]);
expect(info1.data).to.deep.eq([]);

const bal1 = await fetchTokenBalances();

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const sudoTransferToken = async (

const { data } = await token.populateTransaction.transfer(toAddr, amount);
const api = await ApiPromise.create({
provider: new WsProvider('ws://localhost:8000'),
provider: new WsProvider('ws://0.0.0.0:8000'),
});

const tx = api.tx.evm.call(tokenAddr, data!, 0, 1000000, 100000, []);
Expand Down
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export * from './homa';
export * from './euphrates';
export * from './health';
export * from './swapAndRoute';
export * from './dropAndBootstrap';
export * from './swapAndLp';
export * from './routerInfo';
19 changes: 19 additions & 0 deletions src/api/routerInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CHAIN_ID_ACALA } from '@certusone/wormhole-sdk';

import { RouterInfoQuery, RouterInfoUpdate, getChainConfig } from '../utils';
import { db } from '../db';

export const getRouterInfo = async (params: RouterInfoQuery) => {
return await db.getRouterInfo(params);
};

export const saveRouterInfo = async (params: RouterInfoUpdate) => {
// only supports pool 7 on Acala for now
const { feeAddr, dropAndSwapStakeFactoryAddr } = await getChainConfig(CHAIN_ID_ACALA);

return await db.upsertRouterInfo({
...params,
feeAddr: feeAddr!,
factoryAddr: dropAndSwapStakeFactoryAddr!,
});
};
Loading

0 comments on commit b597f69

Please sign in to comment.