-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(status-api): Status API for Oracles (#1315)
* oracles status setup * Refactor name to OracleStatusController and update tests for status * Update logic for operational status to less than equals to 15 minutes * Update Oracle status logic to outage if > 45 mins * Update oracle status path * Corrected to get last published time from price feed instead * Refactor oracle status test * Update readme file with new /oracles status * Introduce OracleStatus for explicit API response * Updated mocked oracle function name * Refactored OracleStatus * Refactor controller test dir * Refactor controller test dir * Update oracle address to path param * Update oracle address to path param * Update OracleStatusController.test.ts Co-authored-by: Joel-David Wong <[email protected]>
- Loading branch information
1 parent
70ce784
commit 96f768e
Showing
7 changed files
with
166 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
87 changes: 87 additions & 0 deletions
87
apps/status-api/__tests__/controllers/OracleStatusController.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { StatusApiTesting } from '../../testing/StatusApiTesting' | ||
import { ApiPagedResponse, WhaleApiClient } from '@defichain/whale-api-client' | ||
import { Oracle, OraclePriceFeed } from '@defichain/whale-api-client/dist/api/Oracles' | ||
|
||
const apiTesting = StatusApiTesting.create() | ||
|
||
beforeAll(async () => { | ||
await apiTesting.start() | ||
jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getOracleByAddress') | ||
.mockReturnValue(getMockedOracle()) | ||
}) | ||
|
||
afterAll(async () => { | ||
await apiTesting.stop() | ||
}) | ||
|
||
describe('OracleStatusController - Status test', () => { | ||
it('/oracles/<address> - should get operational as last published < 45 mins ago', async () => { | ||
jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getPriceFeed') | ||
.mockReturnValueOnce(getMockedOraclePriceFeed('df1qm7f2cx8vs9lqn8v43034nvckz6dxxpqezfh6dw', 5)) | ||
|
||
const res = await apiTesting.app.inject({ | ||
method: 'GET', | ||
url: 'oracles/df1qm7f2cx8vs9lqn8v43034nvckz6dxxpqezfh6dw' | ||
}) | ||
expect(res.json()).toStrictEqual({ | ||
status: 'operational' | ||
}) | ||
expect(res.statusCode).toStrictEqual(200) | ||
}) | ||
|
||
it('/oracles/<address> - should get outage as last published >= 45 mins ago', async () => { | ||
jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getPriceFeed') | ||
.mockReturnValueOnce(getMockedOraclePriceFeed('df1qcpp3entq53tdyklm5v0lnvqer4verr4puxchq4', 46)) | ||
|
||
const res = await apiTesting.app.inject({ | ||
method: 'GET', | ||
url: 'oracles/df1qcpp3entq53tdyklm5v0lnvqer4verr4puxchq4' | ||
}) | ||
expect(res.json()).toStrictEqual({ | ||
status: 'outage' | ||
}) | ||
expect(res.statusCode).toStrictEqual(200) | ||
}) | ||
}) | ||
|
||
async function getMockedOraclePriceFeed (oracleAddress: string, minutesDiff: number): Promise<ApiPagedResponse<OraclePriceFeed>> { | ||
const blockMedianTime = Date.now() / 1000 - (minutesDiff * 60) | ||
|
||
return new ApiPagedResponse({ | ||
data: [{ | ||
block: { | ||
medianTime: blockMedianTime, | ||
hash: '', | ||
height: 0, | ||
time: 0 | ||
}, | ||
id: '', | ||
key: '', | ||
sort: '', | ||
token: '', | ||
currency: '', | ||
oracleId: '', | ||
txid: '', | ||
time: 0, | ||
amount: '' | ||
}] | ||
}, 'GET', `oracles/${oracleAddress}/AAPL-USD/feed`) | ||
} | ||
|
||
async function getMockedOracle (): Promise<Oracle> { | ||
return { | ||
id: '', | ||
block: { | ||
hash: '', | ||
height: 0, | ||
medianTime: 0, | ||
time: 0 | ||
}, | ||
ownerAddress: '', | ||
priceFeeds: [{ | ||
token: '', | ||
currency: '' | ||
}], | ||
weightage: 0 | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Controller, Get, Param } from '@nestjs/common' | ||
import { WhaleApiClient } from '@defichain/whale-api-client' | ||
import { OraclePriceFeed } from '@defichain/whale-api-client/dist/api/Oracles' | ||
import { SemaphoreCache } from '../../../whale/src/module.api/cache/semaphore.cache' | ||
|
||
type OracleStatus = 'outage' | 'operational' | ||
|
||
@Controller('oracles') | ||
export class OracleStatusController { | ||
constructor ( | ||
private readonly client: WhaleApiClient, | ||
protected readonly cache: SemaphoreCache | ||
) { | ||
} | ||
|
||
/** | ||
* To provide the status of each oracle given the address based on the last published time for any given token | ||
* | ||
* @param oracleAddress | ||
* @return {Promise<OracleStatus>} | ||
*/ | ||
@Get('/:address') | ||
async getOracleStatus ( | ||
@Param('address') oracleAddress: string | ||
): Promise<{ status: OracleStatus }> { | ||
const oraclePriceFeed: OraclePriceFeed = await this.cachedGet(`oracle-${oracleAddress}`, async () => { | ||
const oracle = await this.client.oracles.getOracleByAddress(oracleAddress) | ||
return (await this.client.oracles.getPriceFeed(oracle.id, oracle.priceFeeds[0].token, oracle.priceFeeds[0].currency, 1))[0] | ||
}, 5000) | ||
|
||
const nowEpoch = Date.now() | ||
const latestPublishedTime = oraclePriceFeed.block.medianTime * 1000 | ||
const timeDiff = nowEpoch - latestPublishedTime | ||
|
||
return { | ||
status: timeDiff <= (45 * 60 * 1000) ? 'operational' : 'outage' | ||
} | ||
} | ||
|
||
private async cachedGet<T> (field: string, fetch: () => Promise<T>, ttl: number): Promise<T> { | ||
const object = await this.cache.get(`OracleStatusController.${field}`, fetch, { ttl }) | ||
return requireValue(object, field) | ||
} | ||
} | ||
|
||
function requireValue<T> (value: T | undefined, name: string): T { | ||
if (value === undefined) { | ||
throw new Error(`failed to compute: ${name}`) | ||
} | ||
return value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters