-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
489 additions
and
0 deletions.
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
packages/backend/migrations/20231016141155_create_ilp_peers_table.js
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,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') | ||
} |
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
26 changes: 26 additions & 0 deletions
26
packages/backend/src/payment-method/ilp/ilp-peer/errors.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,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' | ||
} |
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,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
211
packages/backend/src/payment-method/ilp/ilp-peer/service.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,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) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.