Skip to content

Commit

Permalink
feat: add ilp-peers
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurapov committed Oct 17, 2023
1 parent 29ec5fd commit 416afe8
Show file tree
Hide file tree
Showing 7 changed files with 489 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { v4: uuid } = require('uuid')

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema
.createTable('ilpPeers', function (table) {
table.uuid('id').notNullable().primary()

table.uuid('peerId').notNullable().index().unique()
table.foreign('peerId').references('peers.id')

table.bigInteger('maxPacketAmount').nullable()

table.string('staticIlpAddress').notNullable().index()

table.timestamp('createdAt').defaultTo(knex.fn.now())
table.timestamp('updatedAt').defaultTo(knex.fn.now())
})
.then(() =>
knex('peers').select(
'id',
'staticIlpAddress',
'maxPacketAmount',
'createdAt',
'updatedAt'
)
)
.then((rows) => {
if (rows.length > 0) {
return knex('ilpPeers').insert(
rows.map((r) => ({
id: uuid(),
peerId: r.id,
staticIlpAddress: r.staticIlpAddress,
maxPacketAmount: r.maxPacketAmount,
createdAt: r.createdAt,
updatedAt: r.updatedAt
}))
)
}
})
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.dropTableIfExists('ilpPeers')
}
2 changes: 2 additions & 0 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import { Rafiki as ConnectorApp } from './payment-method/ilp/connector/core'
import { AxiosInstance } from 'axios'
import { PaymentMethodHandlerService } from './payment-method/handler/service'
import { IlpPaymentService } from './payment-method/ilp/service'
import { IlpPeerService } from './payment-method/ilp/ilp-peer/service'

export interface AppContextData {
logger: Logger
Expand Down Expand Up @@ -229,6 +230,7 @@ export interface AppServices {
tigerbeetle: Promise<TigerbeetleClient>
paymentMethodHandlerService: Promise<PaymentMethodHandlerService>
ilpPaymentService: Promise<IlpPaymentService>
ilpPeerService: Promise<IlpPeerService>
}

export type AppContainer = IocContract<AppServices>
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { createAutoPeeringRoutes } from './payment-method/ilp/auto-peering/route
import axios from 'axios'
import { createIlpPaymentService } from './payment-method/ilp/service'
import { createPaymentMethodHandlerService } from './payment-method/handler/service'
import { createIlpPeerService } from './payment-method/ilp/ilp-peer/service'

BigInt.prototype.toJSON = function () {
return this.toString()
Expand Down Expand Up @@ -452,6 +453,13 @@ export function initIocContainer(
})
})

container.singleton('ilpPeerService', async (deps) => {
return createIlpPeerService({
knex: await deps.use('knex'),
logger: await deps.use('logger')
})
})

return container
}

Expand Down
26 changes: 26 additions & 0 deletions packages/backend/src/payment-method/ilp/ilp-peer/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export enum IlpPeerError {
DuplicateIlpPeer = 'DuplicateIlpPeer',
UnknownPeer = 'UnknownPeer',
InvalidStaticIlpAddress = 'InvalidStaticIlpAddress'
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export const isIlpPeerError = (o: any): o is IlpPeerError =>
Object.values(IlpPeerError).includes(o)

export const errorToCode: {
[key in IlpPeerError]: number
} = {
[IlpPeerError.DuplicateIlpPeer]: 409,
[IlpPeerError.InvalidStaticIlpAddress]: 400,
[IlpPeerError.UnknownPeer]: 404
}

export const errorToMessage: {
[key in IlpPeerError]: string
} = {
[IlpPeerError.DuplicateIlpPeer]:
'duplicate peer found for same ILP address and asset',
[IlpPeerError.InvalidStaticIlpAddress]: 'invalid ILP address',
[IlpPeerError.UnknownPeer]: 'unknown peer'
}
26 changes: 26 additions & 0 deletions packages/backend/src/payment-method/ilp/ilp-peer/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Model } from 'objection'
import { BaseModel } from '../../../shared/baseModel'
import { Peer } from '../peer/model'

export class IlpPeer extends BaseModel {
public static get tableName(): string {
return 'ilpPeers'
}

static relationMappings = {
peer: {
relation: Model.HasOneRelation,
modelClass: Peer,
join: {
from: 'ilpPeers.peerId',
to: 'peers.id'
}
}
}

public peerId!: string
public peer!: Peer

public maxPacketAmount?: bigint
public staticIlpAddress!: string
}
211 changes: 211 additions & 0 deletions packages/backend/src/payment-method/ilp/ilp-peer/service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import assert from 'assert'
import { v4 as uuid } from 'uuid'

import { createTestApp, TestContainer } from '../../../tests/app'
import { Config } from '../../../config/app'
import { IocContract } from '@adonisjs/fold'
import { initIocContainer } from '../../..'
import { AppServices } from '../../../app'
import { createPeer } from '../../../tests/peer'
import { truncateTables } from '../../../tests/tableManager'
import { CreateArgs, IlpPeerService, UpdateArgs } from './service'
import { Peer } from '../peer/model'
import { IlpPeerError, isIlpPeerError } from './errors'
import { createAsset } from '../../../tests/asset'

describe('Ilp Peer Service', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let ilpPeerService: IlpPeerService
let peer: Peer

const randomIlpPeer = (override?: Partial<CreateArgs>): CreateArgs => ({
peerId: peer.id,
maxPacketAmount: BigInt(100),
staticIlpAddress: 'test.' + uuid(),
...override
})

beforeAll(async (): Promise<void> => {
deps = initIocContainer(Config)
appContainer = await createTestApp(deps)
ilpPeerService = await deps.use('ilpPeerService')
})

beforeEach(async (): Promise<void> => {
peer = await createPeer(deps)
})

afterEach(async (): Promise<void> => {
await truncateTables(appContainer.knex)
})

afterAll(async (): Promise<void> => {
await appContainer.shutdown()
})

describe('Create/Get Peer', (): void => {
test('A peer can be created with all settings', async (): Promise<void> => {
const args = randomIlpPeer()
const ilpPeer = await ilpPeerService.create(args)

assert.ok(!isIlpPeerError(ilpPeer))
expect(ilpPeer).toMatchObject({
peer,
maxPacketAmount: args.maxPacketAmount,
staticIlpAddress: args.staticIlpAddress
})
const retrievedPeer = await ilpPeerService.get(ilpPeer.id)
expect(retrievedPeer).toEqual(ilpPeer)
})

test('Cannot create an ILP peer for unknown peer', async (): Promise<void> => {
const args = randomIlpPeer()
await expect(
ilpPeerService.create({
...args,
peerId: uuid()
})
).resolves.toEqual(IlpPeerError.UnknownPeer)
})

test('Cannot create a peer with duplicate ILP address and peer', async (): Promise<void> => {
const args = randomIlpPeer()
const ilpPeer = await ilpPeerService.create(args)
assert.ok(!isIlpPeerError(ilpPeer))

await expect(ilpPeerService.create(args)).resolves.toEqual(
IlpPeerError.DuplicateIlpPeer
)
})
})

describe('Update Peer', (): void => {
test.each`
staticIlpAddress | maxPacketAmount | description
${undefined} | ${1000n} | ${'with just maxPacketAmount'}
${`test.${uuid()}`} | ${undefined} | ${'with just staticIlpAddress'}
${`test.${uuid()}`} | ${1000n} | ${'with maxPacketAmount and staticIlpAddress'}
`(
'Can update a peer $description',
async ({ staticIlpAddress, maxPacketAmount }): Promise<void> => {
const args = randomIlpPeer()
const originalIlpPeer = await ilpPeerService.create(args)
assert.ok(!isIlpPeerError(originalIlpPeer))

const updateArgs: UpdateArgs = {
id: originalIlpPeer.id,
maxPacketAmount,
staticIlpAddress
}

const expectedPeer = {
peerId: args.peerId,
maxPacketAmount:
updateArgs.maxPacketAmount || originalIlpPeer.maxPacketAmount,
staticIlpAddress:
updateArgs.staticIlpAddress || originalIlpPeer.staticIlpAddress
}
await expect(ilpPeerService.update(updateArgs)).resolves.toMatchObject(
expectedPeer
)
await expect(
ilpPeerService.get(originalIlpPeer.id)
).resolves.toMatchObject(expectedPeer)
}
)

test('Cannot update nonexistent peer', async (): Promise<void> => {
const updateArgs: UpdateArgs = {
id: uuid(),
maxPacketAmount: BigInt(2)
}

await expect(ilpPeerService.update(updateArgs)).resolves.toEqual(
IlpPeerError.UnknownPeer
)
})

test('Returns error for invalid static ILP address', async (): Promise<void> => {
const args = randomIlpPeer()
const originalIlpPeer = await ilpPeerService.create(args)
assert.ok(!isIlpPeerError(originalIlpPeer))

const updateArgs: UpdateArgs = {
id: originalIlpPeer.id,
staticIlpAddress: 'test.hello!'
}
await expect(ilpPeerService.update(updateArgs)).resolves.toEqual(
IlpPeerError.InvalidStaticIlpAddress
)
await expect(ilpPeerService.get(originalIlpPeer.id)).resolves.toEqual(
originalIlpPeer
)
})
})

describe('Get Peer By ILP Address', (): void => {
test('Can retrieve peer by ILP address', async (): Promise<void> => {
const args = randomIlpPeer()
const ilpPeer = await ilpPeerService.create(args)

assert.ok(!isIlpPeerError(ilpPeer))
await expect(
ilpPeerService.getByDestinationAddress(ilpPeer.staticIlpAddress)
).resolves.toEqual(ilpPeer)

await expect(
ilpPeerService.getByDestinationAddress(
ilpPeer.staticIlpAddress + '.suffix'
)
).resolves.toEqual(ilpPeer)

await expect(
ilpPeerService.getByDestinationAddress(
ilpPeer.staticIlpAddress + 'suffix'
)
).resolves.toBeUndefined()
})

test('Returns undefined if no account exists with address', async (): Promise<void> => {
await expect(
ilpPeerService.getByDestinationAddress('test.nope')
).resolves.toBeUndefined()
})

test('Properly escapes Postgres pattern "_" wildcards in the static address', async (): Promise<void> => {
const args = randomIlpPeer()

await ilpPeerService.create({
...args,
staticIlpAddress: 'test.rafiki_with_wildcards'
})
await expect(
ilpPeerService.getByDestinationAddress('test.rafiki-with-wildcards')
).resolves.toBeUndefined()
})

test('returns peer by ILP address and asset', async (): Promise<void> => {
const staticIlpAddress = 'test.rafiki'
const args = randomIlpPeer({
staticIlpAddress
})

const ilpPeer = await ilpPeerService.create(args)

const secondAsset = await createAsset(deps)

const ilpPeerWithSecondAsset = await ilpPeerService.create({
staticIlpAddress,
peerId: (await createPeer(deps, { assetId: secondAsset.id })).id
})

await expect(
ilpPeerService.getByDestinationAddress('test.rafiki')
).resolves.toEqual(ilpPeer)
await expect(
ilpPeerService.getByDestinationAddress('test.rafiki', secondAsset.id)
).resolves.toEqual(ilpPeerWithSecondAsset)
})
})
})
Loading

0 comments on commit 416afe8

Please sign in to comment.