Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommytrg committed Jan 17, 2024
1 parent ac14e2b commit f62ebab
Show file tree
Hide file tree
Showing 8 changed files with 1,510 additions and 28 deletions.
1,300 changes: 1,300 additions & 0 deletions packages/api/src/abi/WitnetPriceFeeds.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async function main () {
{ repositories, Web3: Web3 },
dataFeeds
)
web3Middleware.listen()
web3Middleware.listen2()

await createServer(repositories, svgCache, {
dataFeedsConfig: dataFeeds,
Expand Down
23 changes: 23 additions & 0 deletions packages/api/src/repository/ResultRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export class ResultRequestRepository {
ResultRequestDbObject | WithoutId<ResultRequestDbObject>
>

latestResults: Record<string, WithoutId<ResultRequestDbObject>>

constructor (db: Db, _dataFeeds: Array<FeedInfo>) {
this.collection = db.collection('result_request')
}
Expand Down Expand Up @@ -89,6 +91,9 @@ export class ResultRequestRepository {
if (this.isValidResultRequest(resultRequest)) {
const response = await this.collection.insertOne(resultRequest)

// store in cache
this.latestResults[resultRequest.feedFullName] = resultRequest

return this.normalizeId(response[0])
} else {
console.error(
Expand All @@ -99,6 +104,24 @@ export class ResultRequestRepository {
}
}

async insertIfLatest (
resultRequest: WithoutId<ResultRequestDbObject>
): Promise<ResultRequestDbObjectNormalized | null> {
const storedResult = this.latestResults[resultRequest.feedFullName] || (await this.getLastResult(resultRequest.feedFullName))
const timestampChanged = storedResult?.timestamp !== resultRequest.timestamp

if (timestampChanged) {
return await this.insert(resultRequest)
} else {
console.error(
'Error inserting result request: Validation Error',
resultRequest
)
return null
}
}


private normalizeId (
resultRequest: ResultRequestDbObject
): ResultRequestDbObjectNormalized | null {
Expand Down
10 changes: 5 additions & 5 deletions packages/api/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { ApolloServer } from '@apollo/server'
import typeDefs from './typeDefs'
import typeDefs from './typeDefs.js'
import { DIRECTIVES } from '@graphql-codegen/typescript-mongodb'
import resolvers from './resolvers'
import resolvers from './resolvers.js'
import {
ConfigByFullName,
Context,
FeedInfo,
Loaders,
NetworksConfig,
Repositories
} from './types'
} from './types.js'
import { startStandaloneServer } from '@apollo/server/standalone'
import { LoadersFactory } from './loaders'
import SvgCache from './svgCache'
import { LoadersFactory } from './loaders/index.js'
import SvgCache from './svgCache.js'

export async function createServer (
repositories: Repositories,
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export type FeedInfoGeneric<ABI> = {
finality: string
}

export type configurationFile = {

}

export type NetworksConfig = {
chain: string
label: string
Expand Down Expand Up @@ -180,6 +184,7 @@ export type ExtendedFeedConfig = {
export type Chain = {
name: string
hide?: boolean
legacy?: boolean,
networks: Record<string, FeedConfig>
}

Expand Down
117 changes: 117 additions & 0 deletions packages/api/src/web3Middleware/NetworkRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Web3 from "web3"
import WitnetPriceFeedsABI from './../abi/WitnetPriceFeeds.json'
import { AbiItem, Repositories } from "./../types"
import { toHex } from "web3-utils"
import { createFeedFullName } from "../utils"


enum ResultStatus {
Void = 0,
Awaiting = 1,
Ready = 2,
Error = 3,
AwaitingReady = 4,
AwaitingError = 5
}

type SupportedFeed = {
id: string,
caption: string
solver: string
}

type LatestPrice = {
value: string,
timestamp: string,
tallyHash: string,
status: ResultStatus
}

export type NetworkInfo = {
name: string,
provider: string,
address: string,
pollingPeriod: number,
maxSecsBetweenUpdates: number,
feeds?: Array<{ name: string, maxSecsBetweenUpdates: number}>
}
export type NetworkSnapshot = {
network: string,
feeds: Array<SupportedFeed & LatestPrice>
}

export class NetworkRouter {
public contract: any
public network: string
public pollingPeriod: number
public maxSecsBetweenUpdates: number
public feeds?: Array<{ name: string; maxSecsBetweenUpdates: number }>

public repositories: Repositories

constructor(repositories: Repositories, networkInfo: NetworkInfo) {
const { name, provider, address, pollingPeriod, maxSecsBetweenUpdates, feeds } = networkInfo

const web3 = new Web3(new Web3.providers.HttpProvider(provider, { timeout: 30000 }))
this.contract = new web3.eth.Contract( WitnetPriceFeedsABI as unknown as AbiItem, address)
this.network = name
this.pollingPeriod = pollingPeriod,
this.maxSecsBetweenUpdates = maxSecsBetweenUpdates
this.feeds = feeds
this.repositories = repositories
}

// Periodically fetch the price feed router contract and store it in mongodb
public listen() {
setInterval(async () => {
const snapshot = await this.getSnapshot()
console.log("Last contract snapshot", snapshot)
const insertPromises = snapshot.feeds.filter(feed => feed.status !== ResultStatus.Ready).map((feed) => ({
feedFullName: createFeedFullName(this.network, feed.caption, feed.caption.split("-").reverse()[0]),
drTxHash: toHex(feed.tallyHash).slice(2),
requestId: feed.id,
result: feed.value,
timestamp: feed.timestamp
}))
.map(this.repositories.resultRequestRepository.insertIfLatest)

Promise.all(insertPromises)
}, this.pollingPeriod)
}

async getSnapshot(): Promise<NetworkSnapshot> {
const supportedFeeds = await this.getSupportedFeeds()
const feedIds = supportedFeeds.map(feed => feed.id)
const latestPrices = await this.latestPrices(feedIds)

return {
network: this.network,
feeds: supportedFeeds.map((supportedFeed, index) => ({
...supportedFeed,
...latestPrices[index]
}))
}
}

// Wrap supportedFeeds contract method
private async getSupportedFeeds (): Promise<Array<SupportedFeed>> {
const supportedFeeds = await this.contract.methods.supportedFeeds().call()

return supportedFeeds._ids.map((_, index) => ({
id: supportedFeeds._ids[index],
caption: supportedFeeds._captions[index],
solver: supportedFeeds._solvers[index],
}))
}

// Wrap latestPrices contract method
private async latestPrices (ids: Array<string>): Promise<Array<LatestPrice>> {
const latestPrices = await this.contract.methods.latestPrices(ids).call()
return latestPrices.map(latestPrice => ({
value: latestPrice.value.toString(),
timestamp: latestPrice.timestamp.toString(),
tallyHash: latestPrice.tallyHash,
status: Number(latestPrice.status)
}))
}
}
51 changes: 29 additions & 22 deletions packages/api/src/web3Middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import {
ContractsState,
FeedInfo,
Repositories,
ResultRequestDbObject,
ContractInfo,
Contract
} from '../types'
import { isZeroAddress } from '../utils/index'
import { getProvider } from './provider'
import { NetworkInfo, NetworkRouter } from './NetworkRouter'

export class Web3Middleware {
public repositories: Repositories
private Web3: typeof Web3
public dataFeeds: Array<FeedInfo>
public lastStoredResult: Record<string, ResultRequestDbObject> = {}
public routerContractByNetwork: Record<string, Contract> = {}
public contractIdByFeedId: Record<string, string> = {}
// feedFullname -> address
public currentFeedAddresses: Record<string, string> = {}
public networkRouters: Array<NetworkRouter>

private intervals = []

Expand All @@ -34,7 +34,24 @@ export class Web3Middleware {
this.Web3 = dependencies.Web3
}

public async initializeAddresses (): Promise<Array<FeedInfo>> {
public async listen2 () {
// TODO: move this to dataFeeds information requested from github
const networks: Array<NetworkInfo> = [{
name:'ethereum.sepholia',
address: "0x9999999d139bdBFbF25923ba39F63bBFc7593400",
provider:"https://sepolia.infura.io/v3/3199c9172a13459caa4e25fd30827194",
pollingPeriod: 12000,
maxSecsBetweenUpdates: 86400,
// optional
feeds: [
{ name: '', maxSecsBetweenUpdates: 86400}
]
}]

networks.forEach(networkInfo => new NetworkRouter(this.repositories, networkInfo).listen())
}

private async initializeAddresses (): Promise<Array<FeedInfo>> {
const promises = this.dataFeeds.map(feed => this.recheckFeedAddress(feed))

const feeds = await Promise.all(promises)
Expand Down Expand Up @@ -253,26 +270,16 @@ export class Web3Middleware {
contracts,
feedFullName
)
const decodedDrTxHash = toHex(lastDrTxHash)
const lastStoredResult =
this.lastStoredResult[feedFullName] ||
(await this.repositories.resultRequestRepository.getLastResult(
await this.repositories.resultRequestRepository.insertIfLatest(
{
result: lastPrice,
timestamp: lastTimestamp,
requestId: requestId,
drTxHash: toHex(lastDrTxHash).slice(2),
feedFullName
))
const timestampChanged = lastStoredResult?.timestamp !== lastTimestamp
if (timestampChanged) {
const result = await this.repositories.resultRequestRepository.insert(
{
result: lastPrice,
timestamp: lastTimestamp,
requestId: requestId,
drTxHash: decodedDrTxHash.slice(2),
feedFullName
}
)
this.lastStoredResult[feedFullName] = result
resolve(true)
}
}
)
resolve(true)
} catch (error) {
console.error(
`Error reading contracts state for ${feedFullName}:`,
Expand Down
30 changes: 30 additions & 0 deletions packages/api/test/web3Middleware/NetworkRouter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Repositories } from '../../src/types.js'
import { NetworkRouter } from '../../src/web3Middleware/NetworkRouter'


describe('NetworkRouter', () => {
it('should fetch network contract', async () => {
// FIXME: create a proper mock
const repositories = { feedRepository: { }, resultRequestRepository: { } } as unknown as Repositories
const networkInfo = {
address:'0x9999999d139bdBFbF25923ba39F63bBFc7593400',
provider: 'https://rpc2.sepolia.org',
name: 'ethereum.sepholia',
pollingPeriod: 1,
maxSecsBetweenUpdates: 1
}
const router = new NetworkRouter(repositories, networkInfo)

const snapshot = await router.getSnapshot()

expect(snapshot.feeds[0].caption).toBeTruthy()
expect(snapshot.feeds[0].id).toBeTruthy()
expect(snapshot.feeds[0].solver).toBeTruthy()
expect(snapshot.feeds[0].status).toBeTruthy()
expect(snapshot.feeds[0].tallyHash).toBeTruthy()
expect(snapshot.feeds[0].timestamp).toBeTruthy()
expect(snapshot.feeds[0].value).toBeTruthy()

expect(snapshot.network).toBe('ethereum.sepholia')
})
})

0 comments on commit f62ebab

Please sign in to comment.