Skip to content

Commit

Permalink
feat(api): avoid store unnecessary feed information on db
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommytrg committed Jan 26, 2022
1 parent 37b30a5 commit 3f64061
Show file tree
Hide file tree
Showing 10 changed files with 800 additions and 868 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: "2"
services:
database:
image: mongo:4
container_name: database
container_name: data-feeds-explorer-db
ports:
- $MONGO_PORT:27017
environment:
Expand Down
34 changes: 19 additions & 15 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import { MongoManager } from './database'
import { FeedRepository } from './repository/Feed'
import { ResultRequestRepository } from './repository/ResultRequest'
import { createServer } from './server'
import { FeedInfo, FeedInfoConfig, Repositories } from './types'
import {
FeedInfo,
FeedInfoConfig,
Repositories,
RouterDataFeedsConfig
} from './types'
import { Web3Middleware } from './web3Middleware/index'
import { normalizeConfig } from '../src/utils/index'
import { normalizeConfig } from './utils/index'
import dataFeedsRouterConfig from './dataFeedsRouter.json'

async function main () {
const mongoManager = new MongoManager()
const db = await mongoManager.start()
const dataFeeds = readDataFeeds()

const repositories: Repositories = {
feedRepository: new FeedRepository(db, dataFeeds),
feedRepository: new FeedRepository(dataFeeds),
resultRequestRepository: new ResultRequestRepository(db, dataFeeds)
}

Expand All @@ -36,19 +42,15 @@ async function main () {
})
}

function readDataFeeds (): Array<FeedInfo> {
const dataFeeds: Array<FeedInfoConfig> = normalizeConfig(
JSON.parse(
fs.readFileSync(
path.resolve(
process.env.DATA_FEED_CONFIG_PATH || './dataFeedsRouter.json'
),
'utf-8'
)
)
)
export function readDataFeeds (): Array<FeedInfo> {
const dataFeeds: Array<Omit<
FeedInfoConfig,
'abi' | 'routerAbi'
>> = normalizeConfig(dataFeedsRouterConfig as RouterDataFeedsConfig)

// Throw and error if config file is not valid
validateDataFeeds(dataFeeds)

return dataFeeds.map(dataFeed => ({
...dataFeed,
routerAbi: JSON.parse(
Expand All @@ -72,7 +74,9 @@ function readDataFeeds (): Array<FeedInfo> {
}

// Throw an error if a field is missing in the data feed config file
function validateDataFeeds (dataFeeds: Array<FeedInfoConfig>) {
function validateDataFeeds (
dataFeeds: Array<Omit<FeedInfoConfig, 'abi' | 'routerAbi'>>
) {
const fields = [
'feedFullName',
'address',
Expand Down
189 changes: 53 additions & 136 deletions packages/api/src/repository/Feed.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,75 @@
import { AggregationCursor } from 'mongodb'
import {
FeedDbObjectNormalized,
FeedDbObject,
Collection,
Db,
FeedInfo,
WithoutId,
PaginatedFeedsObject
} from '../types'
import { containFalsyValues } from './containFalsyValues'
import { PaginatedFeedsObject, FeedInfo, Network } from '../types'

export class FeedRepository {
collection: Collection<FeedDbObject>
// list of fullNames to include in the search queries using feedFullName as an id for each data feed
dataFeedsFullNames: Array<string>
sortedDataFeeds: Array<FeedInfo>
// TODO: replace string with Network
dataFeedsByNetwork: Record<string, Array<FeedInfo>>

constructor (db: Db, dataFeeds: Array<FeedInfo>) {
this.collection = db.collection('feed')
this.dataFeedsFullNames = dataFeeds.map(dataFeed => dataFeed.feedFullName)
this.collection.createIndex({ feedFullName: 1 })
}

async getAll (): Promise<Array<FeedDbObjectNormalized>> {
return (
await this.collection
.find({
feedFullName: { $in: this.dataFeedsFullNames }
})
.sort('network')
.toArray()
).map(this.normalizeId)
}

async insert (
feed: WithoutId<FeedDbObject>
): Promise<FeedDbObjectNormalized | null> {
if (this.isValidFeed(feed)) {
const response = await this.collection.insertOne(feed)
return this.normalizeId(response.ops[0])
} else {
console.error('Error inserting feed: Validation Error', feed)
constructor (dataFeeds: Array<FeedInfo>) {
this.dataFeedsByNetwork = dataFeeds.reduce(
(acc: Record<string, Array<FeedInfo>>, feedInfo: FeedInfo) => ({
...acc,
[feedInfo.network]: acc[feedInfo.network]
? [...acc[feedInfo.network], feedInfo]
: [feedInfo]
}),
{}
)

return null
}
}
const sortedFeedsWithoutEth = dataFeeds
.filter(feed => !feed.network.includes(Network.EthereumMainnet))
.sort((a, b) => (b.network < a.network ? 1 : -1))

async get (feedFullName: string): Promise<FeedDbObjectNormalized> {
const response = await this.collection.findOne({ feedFullName })
return this.normalizeId(response)
this.sortedDataFeeds = [
...this.dataFeedsByNetwork[Network.EthereumMainnet],
...this.dataFeedsByNetwork[Network.EthereumGoerli],
...this.dataFeedsByNetwork[Network.EthereumRinkeby],
...sortedFeedsWithoutEth
]
}

async updateFeed (
feed: WithoutId<FeedDbObject>
): Promise<FeedDbObjectNormalized> {
const feedFound = await this.collection.findOne({
feedFullName: feed.feedFullName
})
if (feedFound) {
await this.collection.updateOne(
{ feedFullName: feed.feedFullName },
{ $set: feed }
)
}
return this.normalizeId(feedFound)
get (feedFullName: string): FeedInfo {
return this.sortedDataFeeds.find(feed => feed.feedFullName === feedFullName)
}

async getPaginatedFeeds (
// starts in 1
page: number,
size: number,
network: string
): Promise<PaginatedFeedsObject> {
const queryByNetwork = {
feedFullName: { $in: this.dataFeedsFullNames },
network
}
const queryAll = {
feedFullName: { $in: this.dataFeedsFullNames }
}
const match = network !== 'all' ? queryByNetwork : queryAll
const aggregation = (
await (await this.aggregateCollection(match, page, size)).toArray()
)[0]
const filteredFeeds =
network === 'all'
? this.sortedDataFeeds
: this.dataFeedsByNetwork[network]

const paginatedFeeds = filteredFeeds.slice((page - 1) * size, page * size)

return {
feeds: aggregation.feeds.map(this.normalizeId),
total: aggregation.total[0]?.count || 0
feeds: paginatedFeeds,
total: filteredFeeds.length
}
}

private async aggregateCollection (
match,
page,
size
): Promise<AggregationCursor> {
return await this.collection.aggregate([
{
$project: {
_id: 1,
address: 1,
blockExplorer: 1,
feedFullName: 1,
label: 1,
name: 1,
network: 1,
order: {
$cond: {
if: { $eq: ['$network', 'ethereum-mainnet'] },
then: 1,
else: {
$cond: {
if: { $eq: [{ $substr: ['$network', 0, 8] }, 'ethereum'] },
then: 2,
else: 3
}
}
}
}
}
},
{ $match: match },
{ $sort: { order: 1, network: 1 } },
{
$project: {
_id: 1,
address: 1,
blockExplorer: 1,
feedFullName: 1,
label: 1,
name: 1,
network: 1
}
},
{
$facet: {
feeds: [{ $skip: size * (page - 1) }, { $limit: size }],
total: [
{
$count: 'count'
}
]
}
}
])
}
updateFeedAddress (feedFullName: string, address: string): FeedInfo {
const hasSameFeedFullName = (feed: FeedInfo) =>
feed.feedFullName === feedFullName

private normalizeId (feed: FeedDbObject): FeedDbObjectNormalized | null {
if (feed && feed._id) {
return { ...feed, id: feed._id.toString() }
} else {
// this code should be unreachable: value from db always contains _id
return null
}
}
// Update address in sortedDataFeeds
const sortedDataFeedIndex = this.sortedDataFeeds.findIndex(
hasSameFeedFullName
)
const feed = this.sortedDataFeeds[sortedDataFeedIndex]
feed.address = address

// Update address in dataFeedsByNetwork
const dataFeedsByNetworkIndex = this.dataFeedsByNetwork[
feed.network
].findIndex(hasSameFeedFullName)
this.dataFeedsByNetwork[feed.network][
dataFeedsByNetworkIndex
].address = address

private isValidFeed (feed: Omit<FeedDbObject, '_id'>): boolean {
return !containFalsyValues(feed)
return feed
}
}
16 changes: 8 additions & 8 deletions packages/api/src/typeDefs.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { gql } from 'apollo-server'

const typeDefs = gql`
type Feed @entity {
id: String! @id
address: String! @column
blockExplorer: String! @column
type Feed {
id: String!
address: String!
blockExplorer: String!
color: String!
feedFullName: String! @column
label: String! @column
name: String! @column
network: String! @column
feedFullName: String!
label: String!
name: String!
network: String!
lastResult: String
deviation: String!
heartbeat: String!
Expand Down
27 changes: 20 additions & 7 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AbiItem } from 'web3-utils'
import { FeedDbObject, ResultRequestDbObject } from './generated/types'
import { ResultRequestDbObject } from './generated/types'
import { Contract } from 'web3-eth-contract'

import { FeedRepository } from './repository/Feed'
Expand Down Expand Up @@ -44,6 +44,7 @@ export type FeedInfoGeneric<ABI> = {
abi: ABI
routerAbi: ABI
address: string
routerAddress: string
network: Network
name: string
pollingPeriod: number
Expand All @@ -55,13 +56,14 @@ export type FeedInfoGeneric<ABI> = {
finality: string
}
export type FeedInfo = FeedInfoGeneric<Array<AbiItem>>

export type FeedInfoConfig = FeedInfoGeneric<string>

export type FeedDbObjectNormalized = FeedDbObject & { id: string }
export type PaginatedFeedsObject = {
feeds: Array<FeedDbObjectNormalized>
feeds: Array<FeedInfo>
total: number
}

export type ResultRequestDbObjectNormalized = ResultRequestDbObject & {
id: string
}
Expand All @@ -87,30 +89,41 @@ export type Contracts = {
feedContract: Contract
}

export type FeedInfoRouterConfig = {
export type FeedInfoRouterConfigMap = {
[key: string]: FeedParamsConfig
}

export type FeedParamsConfig = {
label: string
deviationPercentage: number
maxSecsBetweenUpdates: number
minSecsBetweenUpdates: number
}

export type FeedParsedParams = {
label: string
deviationPercentage: number
maxSecsBetweenUpdates: number
minSecsBetweenUpdates: number
key: string
}

export type FeedConfig = {
address: string
blockExplorer: string
color: string
name: string
pollingPeriod: number
feeds: Array<FeedInfoRouterConfig>
feeds: FeedInfoRouterConfigMap
}
export type NetworkConfig = {
[key: string]: FeedConfig

export type NetworkConfigMap = Record<string, FeedConfig>

export type RouterDataFeedsConfig = {
abi: string
chains: Record<string, { networks: Record<string, FeedConfig> }>
}

export type FeedInfosWithoutAbis = Array<
Omit<FeedInfoConfig, 'abi' | 'routerAbi'>
>
Loading

0 comments on commit 3f64061

Please sign in to comment.