Skip to content

Commit

Permalink
Implement transactions by snapshot endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mwadon committed Aug 18, 2020
1 parent e130133 commit f2384a4
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 35 deletions.
65 changes: 38 additions & 27 deletions api/elastic.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ApiResponse, Client} from '@elastic/elasticsearch'
import {chain, left, right, TaskEither, tryCatch} from 'fp-ts/lib/TaskEither'
import {chain, left, map, right, TaskEither, tryCatch} from 'fp-ts/lib/TaskEither'
import {ApplicationError, StatusCodes} from './http'
import {CheckpointBlock, Snapshot, Transaction} from './model'
import {CheckpointBlock, Snapshot, Sort, SortOrder, Transaction, WithTimestamp} from './model'
import {pipe} from 'fp-ts/lib/pipeable'
import {TransportRequestPromise} from '@elastic/elasticsearch/lib/Transport'

Expand All @@ -16,30 +16,24 @@ export const getClient = (): Client => {
return new Client({node: process.env.ELASTIC_SEARCH})
}

const getByHashQuery = (index: string, hash: string) => (es: Client) =>
const getByFieldQuery = <T extends WithTimestamp>(
index: string,
field: keyof T,
value: string,
size: number = 1,
sort: Sort<T> = {field: 'timestamp', order: SortOrder.Desc}
) => (es: Client) =>
es.search({
index,
body: {
size: 1,
query: {
match: {
hash: {
query: hash,
},
}
}
}
})

const getByHeightQuery = (index: string, height: string) => (es: Client) =>
es.search({
index,
body: {
size: 1,
size,
sort: [
{[sort.field]: sort.order}
],
query: {
match: {
height: {
query: height,
[field]: {
query: value,
},
}
}
Expand All @@ -66,18 +60,35 @@ const isHeight = (term: string): boolean => /^\d+$/.test(term)
export const getSnapshot = (es: Client) => (term: string): TaskEither<ApplicationError, Snapshot> => {
const esSearch = (isLatest(term)
? getLatestQuery(ESIndex.Snapshots)
: (isHeight(term) ? getByHeightQuery : getByHashQuery)(ESIndex.Snapshots, term))(es)
: getByFieldQuery<Snapshot>(ESIndex.Snapshots, isHeight(term) ? 'height' : 'hash', term))(es)

return execute(esSearch)
return findOne(esSearch)
}

export const getCheckpointBlock = (es: Client) => (term: string): TaskEither<ApplicationError, CheckpointBlock> =>
execute(getByHashQuery(ESIndex.CheckpointBlocks, term)(es))
findOne(getByFieldQuery<CheckpointBlock>(ESIndex.CheckpointBlocks, 'hash', term)(es))

export const getTransaction = (es: Client) => (term: string): TaskEither<ApplicationError, Transaction> =>
execute(getByHashQuery(ESIndex.Transactions, term)(es))
findOne(getByFieldQuery<Transaction>(ESIndex.Transactions, 'hash', term)(es))

export const getTransactionBySnapshot = (es: Client) => (term: string): TaskEither<ApplicationError, Transaction[]> => {
if (isHeight(term)) {
return pipe(
getSnapshot(es)(term),
chain(snapshot => getTransactionBySnapshot(es)(snapshot.hash))
)
}

return findAll(getByFieldQuery<Transaction>(ESIndex.Transactions, 'snapshotHash', term, -1)(es))
}

const findOne = (search: TransportRequestPromise<ApiResponse>) =>
pipe(
findAll(search),
map(s => s[0])
)

const execute = (search: TransportRequestPromise<ApiResponse>) => pipe(
const findAll = (search: TransportRequestPromise<ApiResponse>) => pipe(
tryCatch<ApplicationError, any>(
() => search.then(r => {
return r.body.hits.hits
Expand All @@ -90,7 +101,7 @@ const execute = (search: TransportRequestPromise<ApiResponse>) => pipe(
),
chain(hits => {
if (hits.length > 0) {
return right(hits[0]._source)
return right(hits.map(h => h._source))
}

return left(
Expand Down
3 changes: 2 additions & 1 deletion api/handler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {getClient} from './elastic'
import {getCheckpointBlocksHandler, getSnapshotHandler, getTransactionsHandler} from './service'
import {getCheckpointBlocksHandler, getSnapshotHandler, getTransactionsBySnapshotHandler, getTransactionsHandler} from './service'

const client = getClient()

export const snapshots = async event => getSnapshotHandler(event, client)()
export const checkpointBlocks = async event => getCheckpointBlocksHandler(event, client)()
export const transactions = async event => getTransactionsHandler(event, client)()
export const transactionsBySnapshot = async event => getTransactionsBySnapshotHandler(event, client)()
24 changes: 18 additions & 6 deletions api/model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
export type WithTimestamp = {
timestamp: string
}

export enum SortOrder {
Desc = 'desc',
Asc = 'asc'
}

export type Sort<T> = {
field: keyof T
order: SortOrder
}

export type Snapshot = {
checkpointBlocks: string[]
hash: string
height: number
timestamp: string
}
} & WithTimestamp

export type CheckpointBlock = {
hash: string
Expand All @@ -15,8 +28,7 @@ export type CheckpointBlock = {
snapshotHash: string
soeHash: string
parentSOEHashes: string[]
timestamp: string
}
} & WithTimestamp

export type Transaction = {
hash: string
Expand All @@ -31,8 +43,8 @@ export type Transaction = {
}
snapshotHash: string
checkpointBlock: string
transactionOriginal: unknown
}
transactionOriginal: any
} & WithTimestamp

type Height = {
min: number,
Expand Down
9 changes: 9 additions & 0 deletions api/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ functions:
parameters:
paths:
term: true
transactionsBySnapshot:
handler: handler.transactionsBySnapshot
events:
- http:
path: snapshot/{term}/transaction
method: GET
parameters:
paths:
term: true

plugins:
- serverless-plugin-typescript
14 changes: 13 additions & 1 deletion api/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {ApplicationError, errorResponse, StatusCodes, successResponse} from './h
import {APIGatewayEvent} from 'aws-lambda'
import {chain, fold, taskEither, map} from 'fp-ts/lib/TaskEither'
import {pipe} from 'fp-ts/lib/pipeable'
import {getSnapshot, getCheckpointBlock, getTransaction} from './elastic'
import {getSnapshot, getCheckpointBlock, getTransaction, getTransactionBySnapshot} from './elastic'
import {validateListCheckpointBlocksEvent, validateListSnapshotsEvent, validateListTransactionsEvent} from './validation'

export const getSnapshotHandler = (event: APIGatewayEvent, es: Client) =>
Expand Down Expand Up @@ -40,4 +40,16 @@ export const getTransactionsHandler = (event: APIGatewayEvent, es: Client) =>
reason => taskEither.of(errorResponse(reason)),
value => taskEither.of(successResponse(StatusCodes.OK)(value))
)
)

export const getTransactionsBySnapshotHandler = (event: APIGatewayEvent, es: Client) =>
pipe(
taskEither.of<ApplicationError, APIGatewayEvent>(event),
chain(validateListTransactionsEvent),
map(event => event.pathParameters!.term),
chain(getTransactionBySnapshot(es)),
fold(
reason => taskEither.of(errorResponse(reason)),
value => taskEither.of(successResponse(StatusCodes.OK)(value))
)
)

0 comments on commit f2384a4

Please sign in to comment.