Skip to content

Commit

Permalink
Merge pull request #67 from dappforce/deploy/transaction-history
Browse files Browse the repository at this point in the history
Add tx history endpoints
  • Loading branch information
samchuk-vlad authored Jan 16, 2024
2 parents f360f5f + 6395c39 commit c4efad0
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 14 deletions.
13 changes: 11 additions & 2 deletions deployment/features/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ spec:
app: subid-back-<BRANCH>
spec:
imagePullSecrets:
- name: dockerhub
- name: dockerhub
containers:
- name: subid-back-<BRANCH>
image: <IMAGE>
image: <IMAGE>
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3001
Expand All @@ -37,6 +37,15 @@ spec:
value: redis-master.default
- name: REDIS_PORT
value: '6379'

- name: AGGREGATOR_REDIS_HOST
value: redis-aggregation-datasource-subquery-master.default
- name: AGGREGATOR_REDIS_PASSWORD
value: 2KK6RWECApEZJjmd
- name: AGGREGATOR_REDIS_PREFIX
value: aggregator_queue_datasource-subquery
- name: AGGREGATOR_REDIS_PORT
value: '6379'
envFrom:
- secretRef:
name: subid-back-secret
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"devDependencies": {
"@subsocial/config": "0.7.7",
"@types/bn.js": "^5.1.0",
"@types/bull": "^4.10.0",
"@types/connect-timeout": "^0.0.34",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.3",
Expand All @@ -45,6 +46,7 @@
"@polkawallet/bridge": "0.1.3",
"@subsocial/api": "0.8.14",
"axios": "^0.26.0",
"bull": "^4.11.4",
"connect-timeout": "^1.9.0",
"cors": "^2.8.5",
"dayjs": "^1.10.4",
Expand Down
23 changes: 18 additions & 5 deletions src/constant/graphQlClients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,30 @@ import { RelayChain } from '../services/crowdloan/types'

export const subsocialGraphQlClient = new GraphQLClient(SUBSOCIAL_GRAPHQL_CLIENT)

export const soonsocialGraphQlClient = new GraphQLClient('https://squid.subsquid.io/soonsocial/graphql')
export const soonsocialGraphQlClient = new GraphQLClient(
'https://squid.subsquid.io/soonsocial/graphql'
)

export const contributionsClientByRelay: Record<RelayChain, { client: GraphQLClient; addressPrefix: number }> = {
export const txAggregatorGraphQlClient = new GraphQLClient(
'https://tx-aggregation.subsocial.network/graphql'
)

export const contributionsClientByRelay: Record<
RelayChain,
{ client: GraphQLClient; addressPrefix: number }
> = {
kusama: {
client: new GraphQLClient('https://squid.subsquid.io/kusama-explorer/graphql', { timeout: 4000 }),
client: new GraphQLClient('https://squid.subsquid.io/kusama-explorer/graphql', {
timeout: 4000
}),
addressPrefix: 2
},
polkadot: {
client: new GraphQLClient('https://squid.subsquid.io/polkadot-explorer/graphql', { timeout: 4000 }),
client: new GraphQLClient('https://squid.subsquid.io/polkadot-explorer/graphql', {
timeout: 4000
}),
addressPrefix: 0
}
}

export const quartzClient = new GraphQLClient(QUARTZ_GRAPHQL_CLIENT)
export const quartzClient = new GraphQLClient(QUARTZ_GRAPHQL_CLIENT)
4 changes: 3 additions & 1 deletion src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import createFeesRouter from './fees'
import { Connections } from '../connections'
import createCreatorStakingRouter from './creatorStaking'
import { isDef } from '@subsocial/utils';
import createTxHistoryRouter from './txHistory'

export const createRoutes = (apis: Connections) => {
const router = Router()
Expand Down Expand Up @@ -79,7 +80,7 @@ export const createRoutes = (apis: Connections) => {
const result = balances.map((balance: any) => {
const { status, value } = balance

return status === 'fulfilled' && value
return status === 'fulfilled' ? value : undefined
})

res.send(result.filter(isDef))
Expand Down Expand Up @@ -151,6 +152,7 @@ export const createRoutes = (apis: Connections) => {
router.use('/staking/collator', asyncErrorHandler(createCollatorStakingRouter(apis.mixedApis)))
router.use('/staking/validator', asyncErrorHandler(createValidatorStakingRouter(apis)))
router.use('/staking/creator', asyncErrorHandler(createCreatorStakingRouter(apis)))
router.use('/tx', asyncErrorHandler(createTxHistoryRouter()))

router.use('/fees', asyncErrorHandler(createFeesRouter(apis.mixedApis)))

Expand Down
34 changes: 34 additions & 0 deletions src/routes/txHistory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Router } from 'express'
import { getAccountTxHistory, getAccountTxHistoryWithQueue } from '../../services/txHistory'

const createTxHistoryRouter = () => {
const router = Router()

router.get('/history/queue', async function (req, res) {
const { address, pageSize, offset } = req.query
const txs = await getAccountTxHistoryWithQueue({
address: address as string,
pageSize: parseInt(pageSize as string),
offset: parseInt(offset as string),
})

res.send(txs)
})

router.get('/history', async function (req, res) {
const { address, pageSize, offset, networks, events } = req.query
const txs = await getAccountTxHistory({
address: address as string,
pageSize: parseInt(pageSize as string),
offset: parseInt(offset as string),
networks: networks as string[],
events: events as string[],
})

res.send(txs)
})

return router
}

export default createTxHistoryRouter
4 changes: 2 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express'
import cors from 'cors'
import timeout from 'connect-timeout'
import { reqTimeoutSecs, allowedOrigins, port } from './constant/env'
import { reqTimeoutSecs, port, allowedOrigins } from './constant/env'
import { newLogger } from '@subsocial/utils'

import { createRoutes } from './routes'
Expand Down Expand Up @@ -36,7 +36,7 @@ export const startHttpServer = (apis: Connections) => {
})
)

// For localhost testing
// For localhost testing
// app.use(
// cors((req, callback) => {
// const origin = req.method === 'GET' ? '*' : '*'
Expand Down
9 changes: 6 additions & 3 deletions src/services/identity/getIdentityFromSquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { identitiesInfoCache } from '.'
import { toGenericAccountId } from '../utils'
import { u8aToHex } from '@polkadot/util'
import { getIdentityFromChain } from './getIdentityFromChain'
import { ApiPromise } from '@polkadot/api';
import { ApiPromise } from '@polkadot/api'
import { newLogger } from '@subsocial/utils'

const log = newLogger('Identity')

const GET_IDENTITY = gql`
query GetIdentity($ids: [String!]) {
Expand Down Expand Up @@ -94,8 +97,8 @@ export const tryToGetIdentityFromSquid = async (
) => {
try {
await getIdentityFromSquid(accounts, chain)
} catch {
console.error('Failed to get identity from squid, trying to get from chain')
} catch (e) {
log.warn('Failed to get identity from squid, trying to get from chain', e)
await getIdentityFromChain(api, accounts, chain)
}
}
110 changes: 110 additions & 0 deletions src/services/txHistory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { gql } from 'graphql-request'
import { u8aToHex } from '@polkadot/util'
import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto'
import { getOrCreateQueue } from './queue'
import { txAggregatorGraphQlClient } from '../../constant/graphQlClients'

const ADD_QUEUE_JOB_NAME = 'REFRESH_TX_HISTORY_FOR_ACCOUNT_ON_DEMAND'

const buildGetAccountTxHistoryQuery = (networks?: string[], events?: string[]) => {
const networkFilterValues = networks ? ', blockchainTag: $networks' : ''
const networkFilterParams = networks ? ', $networks: [BlockchainTag!]' : ''

const eventFilterValues = events ? ', txKind: $txKind' : ''
const eventFilterParams = events ? ', $txKind: [TransactionKind!]' : ''

return gql`
query getAccountTxHistory($address: String!, $pageSize: Int!, $offset: Int! ${networkFilterParams}${eventFilterParams}) {
accountTxHistory(
args: { where: { publicKey: $address ${networkFilterValues}${eventFilterValues} }, pageSize: $pageSize, offset: $offset }
) {
data {
id
txKind
blockchainTag
amount
senderOrTargetPublicKey
timestamp
success
transaction {
transferNative {
extrinsicHash
}
}
}
}
}
`
}

type GetAccountTransactionsWithQueue = {
address: string
pageSize: number
offset: number
}

type GetAccountTransactions = GetAccountTransactionsWithQueue & {
networks?: string[]
events?: string[]
}

export const getAccountTxHistory = async ({
address,
pageSize,
offset,
networks,
events
}: GetAccountTransactions) => {
const networkFilterValues = networks
? { networks: networks.map((network) => network.toUpperCase()) }
: {}
const eventsFilterValues = events ? { txKind: events.map((event) => event.toUpperCase()) } : {}

const query = buildGetAccountTxHistoryQuery(networks, events)

const txs = await txAggregatorGraphQlClient.request(query, {
address,
pageSize,
offset,
...networkFilterValues,
...eventsFilterValues
})

return txs?.accountTxHistory?.data
}

export const getAccountTxHistoryWithQueue = async (props: GetAccountTransactionsWithQueue) => {
const txs = await getAccountTxHistory(props)

const address = props.address

const jobId = `${address}-${ADD_QUEUE_JOB_NAME}`
const queue = getOrCreateQueue()
const jobByAddress = await queue.getJob(jobId)

let actualData = false

if (jobByAddress) {
const jobState = await jobByAddress.getState()

if (jobState === 'completed') {
jobByAddress.remove()

actualData = true
}
} else {
const taskPayload = {
publicKey: isEthereumAddress(address) ? address : u8aToHex(decodeAddress(address))
}

queue.add(ADD_QUEUE_JOB_NAME, taskPayload, {
attempts: 5,
jobId,
removeOnComplete: false,
removeOnFail: true,
priority: 1
})
}

return { txs, actualData }
}
30 changes: 30 additions & 0 deletions src/services/txHistory/queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Queue from 'bull'

let queue = null

export const getOrCreateQueue = () => {
if (!queue) {
const host = process.env.AGGREGATOR_REDIS_HOST
const password = process.env.AGGREGATOR_REDIS_PASSWORD
const port = process.env.AGGREGATOR_REDIS_PORT as unknown as number

const aggregatorRedisConfig = {
host,
password,
port
}

queue = new Queue('ACCOUNT_AGGREGATION_FLOW', {
redis: aggregatorRedisConfig,
prefix: process.env.AGGREGATOR_REDIS_PREFIX,
settings: {
lockDuration: 20000, // Check for stalled jobs each 2 min
lockRenewTime: 10000,
stalledInterval: 20 * 60 * 1000,
maxStalledCount: 1
}
})
}

return queue
}
Loading

0 comments on commit c4efad0

Please sign in to comment.