Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paro/rfq indexer updates suggs #3243

Merged
merged 14 commits into from
Oct 7, 2024
26 changes: 16 additions & 10 deletions packages/rfq-indexer/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,40 @@ To make requests, use: https://triumphant-magic-production.up.railway.app , and

## API Calls

1. GET /api/hello
- Description: A simple hello world endpoint
- Example: `curl http://localhost:3001/api/hello`

2. GET /api/pending-transactions-missing-relay
All API calls can be viewed in Swagger:

[Swagger Documentation](http://localhost:3001/api-docs)

1. GET /api/pending-transactions-missing-relay
- Description: Retrieves pending transactions that are missing relay events
- Example:
```
curl http://localhost:3001/api/pending-transactions-missing-relay
curl http://localhost:3001/api/pending-transactions/missing-relay
```

3. GET /api/pending-transactions-missing-proof
2. GET /api/pending-transactions-missing-proof
- Description: Retrieves pending transactions that are missing proof events
- Example:
```
curl http://localhost:3001/api/pending-transactions-missing-proof
curl http://localhost:3001/api/pending-transactions/missing-proof
```

4. GET /api/pending-transactions-missing-claim
3. GET /api/pending-transactions-missing-claim
- Description: Retrieves pending transactions that are missing claim events
- Example:
```
curl http://localhost:3001/api/pending-transactions-missing-claim
curl http://localhost:3001/api/pending-transactions/missing-claim
```

5. GraphQL endpoint: /graphql
4. GraphQL endpoint: /graphql
- Description: Provides a GraphQL interface for querying indexed data, the user is surfaced an interface to query the data via GraphiQL

## Env Vars

- **NODE_ENV**: Set to `"development"` for localhost testing.
- **DATABASE_URL**: PostgreSQL connection URL for the ponder index.

## Important Scripts

- `yarn dev:local`: Runs the API in development mode using local environment variables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const conflictingProofsController = async (
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('proofs', () => qProofs({activeOnly: true}))
.with('combined', (qb) =>
qb
.selectFrom('deposits')
Expand Down Expand Up @@ -41,10 +41,10 @@ export const conflictingProofsController = async (
if (conflictingProofs && conflictingProofs.length > 0) {
res.json(conflictingProofs)
} else {
res.status(200).json({ message: 'No conflicting proofs found' })
res.status(200).json({ message: 'No active conflicting proofs found' })
}
} catch (error) {
console.error('Error fetching conflicting proofs:', error)
console.error('Error fetching active conflicting proofs:', error)
res.status(500).json({ message: 'Internal server error' })
}
}
27 changes: 27 additions & 0 deletions packages/rfq-indexer/api/src/controllers/disputesController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response } from 'express'

import { db } from '../db'
import { qDisputes } from '../queries'
import { nest_results } from '../utils/nestResults'

export const disputesController = async (req: Request, res: Response) => {
try {
const query = db
.with('disputes', () => qDisputes({activeOnly: true}))
.selectFrom('disputes')
.selectAll()
.orderBy('blockTimestamp_dispute', 'desc')

const results = await query.execute()
const disputes = nest_results(results)

if (disputes && disputes.length > 0) {
res.json(disputes)
} else {
res.status(200).json({ message: 'No active disputes found' })
}
} catch (error) {
console.error('Error fetching active disputes:', error)
res.status(500).json({ message: 'Internal server error' })
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Request, Response } from 'express'

import { db } from '../db'
import { qDeposits, qRelays, qProofs, qClaims, qRefunds } from '../queries'
import {
qDeposits,
qRelays,
qProofs,
qClaims,
qRefunds,
qDisputes,
} from '../queries'
import { nest_results } from '../utils/nestResults'

const sevenDaysAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60

export const pendingTransactionsMissingClaimController = async (
req: Request,
res: Response
Expand All @@ -12,7 +21,7 @@ export const pendingTransactionsMissingClaimController = async (
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('proofs', () => qProofs({activeOnly: true}))
.with('claims', () => qClaims())
.with('combined', (qb) =>
qb
Expand Down Expand Up @@ -45,7 +54,6 @@ export const pendingTransactionsMissingClaimController = async (
}
}


export const pendingTransactionsMissingProofController = async (
req: Request,
res: Response
Expand All @@ -54,7 +62,7 @@ export const pendingTransactionsMissingProofController = async (
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('proofs', () => qProofs({activeOnly: true}))
.with('combined', (qb) =>
qb
.selectFrom('deposits')
Expand Down Expand Up @@ -111,6 +119,52 @@ export const pendingTransactionsMissingRelayController = async (
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_deposit', 'desc')
.where('blockTimestamp_deposit', '>', sevenDaysAgo)

const results = await query.execute()
const nestedResults = nest_results(results)

if (nestedResults && nestedResults.length > 0) {
res.json(nestedResults)
} else {
res
.status(404)
.json({ message: 'No pending transactions missing relay found' })
}
} catch (error) {
console.error('Error fetching pending transactions missing relay:', error)
res.status(500).json({ message: 'Internal server error' })
}
Comment on lines +135 to +137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update error messages to reflect the correct controller context

In the pendingTransactionsMissingRelayExceedDeadlineController, both the console error message and the 404 response message are the same as those in pendingTransactionsMissingRelayController. This could cause confusion during debugging or when clients receive responses.

Update the error messages to accurately reflect the context of the new controller.

Apply this diff to correct the error messages:

// In the catch block of pendingTransactionsMissingRelayExceedDeadlineController

- console.error('Error fetching pending transactions missing relay:', error)
+ console.error('Error fetching pending transactions exceeding relay deadline:', error)

// In the 404 response of pendingTransactionsMissingRelayExceedDeadlineController

- res
-   .status(404)
-   .json({ message: 'No pending transactions missing relay found' })
+ res
+   .status(404)
+   .json({ message: 'No pending transactions missing relay exceeding deadline found' })

Also applies to: 165-167

}
Comment on lines +122 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor controllers to eliminate code duplication

The controllers pendingTransactionsMissingRelayController and pendingTransactionsMissingRelayExceedDeadlineController share similar logic, differing only in the time filter for blockTimestamp_deposit. Consider refactoring to reduce duplication by creating a reusable function or helper that accepts the time condition as a parameter.

Here's how you might refactor the code:

  1. Extract a helper function:
const getPendingTransactionsMissingRelay = async (
  timeOperator: '>' | '<=',
  timestamp: number
) => {
  return await db
    .with('deposits', () => qDeposits())
    .with('relays', () => qRelays())
    .with('refunds', () => qRefunds())
    .with(
      'combined',
      (qb) =>
        qb
          .selectFrom('deposits')
          .selectAll('deposits')
          .leftJoin('relays', 'transactionId_deposit', 'transactionId_relay')
          .leftJoin('refunds', 'transactionId_deposit', 'transactionId_refund')
          .where('transactionId_relay', 'is', null) // not relayed
          .where('transactionId_refund', 'is', null) // not refunded
    )
    .selectFrom('combined')
    .selectAll()
    .orderBy('blockTimestamp_deposit', 'desc')
    .where('blockTimestamp_deposit', timeOperator, timestamp)
    .execute()
}
  1. Update the controllers to use the helper function:
export const pendingTransactionsMissingRelayController = async (
  req: Request,
  res: Response
) => {
  try {
    const results = await getPendingTransactionsMissingRelay('>', sevenDaysAgo)
    const nestedResults = nest_results(results)

    if (nestedResults && nestedResults.length > 0) {
      res.json(nestedResults)
    } else {
      res
        .status(404)
        .json({ message: 'No pending transactions missing relay found' })
    }
  } catch (error) {
    console.error('Error fetching pending transactions missing relay:', error)
    res.status(500).json({ message: 'Internal server error' })
  }
}

export const pendingTransactionsMissingRelayExceedDeadlineController = async (
  req: Request,
  res: Response
) => {
  try {
    const results = await getPendingTransactionsMissingRelay('<=', sevenDaysAgo)
    const nestedResults = nest_results(results)

    if (nestedResults && nestedResults.length > 0) {
      res.json(nestedResults)
    } else {
      res
        .status(404)
        .json({
          message:
            'No pending transactions missing relay exceeding deadline found',
        })
    }
  } catch (error) {
    console.error(
      'Error fetching pending transactions exceeding relay deadline:',
      error
    )
    res.status(500).json({ message: 'Internal server error' })
  }
}

This refactoring enhances maintainability by centralizing shared logic.

Also applies to: 140-167


export const pendingTransactionsMissingRelayExceedDeadlineController = async (
req: Request,
res: Response
) => {
try {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('refunds', () => qRefunds())
.with(
'combined',
(qb) =>
qb
.selectFrom('deposits')
.selectAll('deposits')
.leftJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.leftJoin(
'refunds',
'transactionId_deposit',
'transactionId_refund'
)
.where('transactionId_relay', 'is', null) // is not relayed
.where('transactionId_refund', 'is', null) // is not refunded
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_deposit', 'desc')
.where('blockTimestamp_deposit', '<=', sevenDaysAgo)

const results = await query.execute()
const nestedResults = nest_results(results)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'

import { db } from '../db'
import { qDeposits, qRelays, qProofs, qClaims, qRefunds } from '../queries'
import { qDeposits, qRelays, qProofs, qClaims, qRefunds, qDisputes } from '../queries'
import { nest_results } from '../utils/nestResults'

export const getTransactionById = async (req: Request, res: Response) => {
Expand All @@ -13,19 +13,22 @@ export const getTransactionById = async (req: Request, res: Response) => {
qDeposits().where('transactionId', '=', transactionId as string)
)
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('proofs', () => qProofs({activeOnly: false})) // display proofs even if they have been invalidated/replaced by a dispute
.with('disputes', () => qDisputes({activeOnly: true})) // do not show disputes that have been invalidated/replaced by a proof
.with('claims', () => qClaims())
.with('refunds', () => qRefunds())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.leftJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.leftJoin('proofs', 'transactionId_deposit', 'transactionId_proof')
.leftJoin('disputes', 'transactionId_deposit', 'transactionId_dispute')
.leftJoin('claims', 'transactionId_deposit', 'transactionId_claim')
.leftJoin('refunds', 'transactionId_deposit', 'transactionId_refund')
.selectAll('deposits')
.selectAll('relays')
.selectAll('proofs')
.selectAll('disputes')
.selectAll('claims')
.selectAll('refunds')
)
Expand Down
2 changes: 2 additions & 0 deletions packages/rfq-indexer/api/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
BridgeProofProvidedEvents,
BridgeDepositRefundedEvents,
BridgeDepositClaimedEvents,
BridgeProofDisputedEvents,
} from '../types'

const { DATABASE_URL } = process.env
Expand All @@ -21,6 +22,7 @@ export interface Database {
BridgeProofProvidedEvents: BridgeProofProvidedEvents
BridgeDepositRefundedEvents: BridgeDepositRefundedEvents
BridgeDepositClaimedEvents: BridgeDepositClaimedEvents
BridgeProofDisputedEvents: BridgeProofDisputedEvents
}

export const db = new Kysely<Database>({ dialect })
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,21 @@ type ConflictingProof {
BridgeProof: Proof!
}

type DisputedRelay {
Bridge: Transaction!
BridgeProof: Proof!
}

type Query {
pendingTransactionsMissingRelay: [PendingTransactionMissingRelay!]!
pendingTransactionsMissingRelayExceedDeadline: [PendingTransactionMissingRelay!]!
pendingTransactionsMissingProof: [PendingTransactionMissingProof!]!
pendingTransactionsMissingClaim: [PendingTransactionMissingClaim!]!
transactionById(transactionId: String!): [CompleteTransaction!]!
recentInvalidRelays: [InvalidRelay!]!
refundedAndRelayedTransactions: [RefundedAndRelayedTransaction!]!
conflictingProofs: [ConflictingProof!]!
disputedRelays: [DisputedRelay!]!
}


50 changes: 50 additions & 0 deletions packages/rfq-indexer/api/src/graphql/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ const qRefunds = () => {
])
}

// typical fields to return for a BridgeProofDisputed event when it is joined to a BridgeRequest
const qDisputes = () => {
return db
.selectFrom('BridgeProofDisputedEvents')
.select([
'BridgeProofDisputedEvents.transactionId as transactionId_dispute',
'BridgeProofDisputedEvents.blockNumber as blockNumber_dispute',
'BridgeProofDisputedEvents.blockTimestamp as blockTimestamp_dispute',
'BridgeProofDisputedEvents.transactionHash as transactionHash_dispute',
'BridgeProofDisputedEvents.originChainId as originChainId_dispute',
'BridgeProofDisputedEvents.originChain as originChain_dispute',
])
}

// using the suffix of a field, move it into a nested sub-object. This is a cleaner final resultset
// example: transactionHash_deposit:0xyz would get moved into BridgeRequest{transactionHash:0xyz}
//
Expand Down Expand Up @@ -220,6 +234,19 @@ const resolvers = {
'BridgeDepositClaimedEvents.originChain',
])
)
.unionAll(
db
.selectFrom('BridgeProofDisputedEvents')
.select([
'BridgeProofDisputedEvents.id',
'BridgeProofDisputedEvents.transactionId',
'BridgeProofDisputedEvents.blockNumber',
'BridgeProofDisputedEvents.blockTimestamp',
'BridgeProofDisputedEvents.transactionHash',
'BridgeProofDisputedEvents.originChainId',
'BridgeProofDisputedEvents.originChain',
])
)

if (filter) {
if (filter.transactionId) {
Expand Down Expand Up @@ -466,6 +493,29 @@ const resolvers = {

return nest_results(await query.execute())
},
disputedRelays: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('disputes', () => qDisputes())
.with('combined', (qb) =>
qb
.selectFrom('proofs')
.leftJoin(
'disputes',
'transactionId_proof',
'transactionId_dispute'
)
.selectAll('proofs')
.selectAll('disputes')
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_proof', 'desc')

return nest_results(await query.execute())
},
},
BridgeEvent: {
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
Expand Down
20 changes: 15 additions & 5 deletions packages/rfq-indexer/api/src/graphql/types/events.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ scalar BigInt
destChain: String!
sendChainGas: Boolean!
}

type BridgeRelayedEvent {
id: String!
transactionId: String!
Expand All @@ -39,7 +39,7 @@ scalar BigInt
destChainId: Int!
destChain: String!
}

type BridgeProofProvidedEvent {
id: String!
transactionId: String!
Expand All @@ -50,7 +50,7 @@ scalar BigInt
originChain: String!
relayer: String!
}

type BridgeDepositRefundedEvent {
id: String!
transactionId: String!
Expand All @@ -64,7 +64,7 @@ scalar BigInt
amount: BigInt!
amountFormatted: String!
}

type BridgeDepositClaimedEvent {
id: String!
transactionId: String!
Expand All @@ -78,4 +78,14 @@ scalar BigInt
token: String!
amount: BigInt!
amountFormatted: String!
}
}

type BridgeProofDisputedEvent {
id: String!
transactionId: String!
blockNumber: BigInt!
blockTimestamp: Int!
transactionHash: String!
originChainId: Int!
originChain: String!
}
Loading
Loading