Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(backend): add polymorphic ConnectorAccount interface #227

Merged
merged 2 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions packages/backend/src/accounting/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { StartedTestContainer } from 'testcontainers'
import { CreateAccountError as CreateTbAccountError } from 'tigerbeetle-node'
import { v4 as uuid } from 'uuid'

import { AccountingService, Account, Deposit, Withdrawal } from './service'
import {
AccountingService,
LiquidityAccount,
Deposit,
Withdrawal
} from './service'
import { CreateAccountError, TransferError, isTransferError } from './errors'
import { createTestApp, TestContainer } from '../tests/app'
import { resetGraphileDb } from '../tests/graphileDb'
Expand Down Expand Up @@ -75,26 +80,26 @@ describe('Accounting Service', (): void => {
}
)

describe('Create Account', (): void => {
test('Can create an account', async (): Promise<void> => {
const account: Account = {
describe('Create Liquidity Account', (): void => {
test('Can create a liquidity account', async (): Promise<void> => {
const account: LiquidityAccount = {
id: uuid(),
asset: {
id: uuid(),
unit: newUnit()
}
}
await expect(accountingService.createAccount(account)).resolves.toEqual(
account
)
await expect(
accountingService.createLiquidityAccount(account)
).resolves.toEqual(account)
await expect(accountingService.getBalance(account.id)).resolves.toEqual(
BigInt(0)
)
})

test('Create throws on invalid id', async (): Promise<void> => {
await expect(
accountingService.createAccount({
accountingService.createLiquidityAccount({
id: 'not a uuid',
asset: {
id: uuid(),
Expand All @@ -114,7 +119,7 @@ describe('Accounting Service', (): void => {
])

await expect(
accountingService.createAccount({
accountingService.createLiquidityAccount({
id: uuid(),
asset: {
id: uuid(),
Expand Down Expand Up @@ -209,7 +214,7 @@ describe('Accounting Service', (): void => {
${true} | ${'same asset'}
${false} | ${'cross-currency'}
`('$description', ({ sameAsset }): void => {
let sourceAccount: Account
let sourceAccount: LiquidityAccount
let destinationAccount: FactoryAccount
const startingSourceBalance = BigInt(10)
const startingDestinationLiquidity = BigInt(100)
Expand Down
22 changes: 11 additions & 11 deletions packages/backend/src/accounting/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { BaseService } from '../shared/baseService'
import { validateId } from '../shared/utils'

// Model classes that have a corresponding Tigerbeetle liquidity
// account SHOULD implement this Account interface and call
// createAccount for each model instance.
// account SHOULD implement this LiquidityAccount interface and call
// createLiquidityAccount for each model instance.
// The Tigerbeetle account id will be the model id.
// Such models include:
// ../asset/model
Expand All @@ -37,7 +37,7 @@ import { validateId } from '../shared/utils'
// ../peer/model
// Asset settlement Tigerbeetle accounts are the only exception.
// Their account id is the corresponding asset's unit value.
export interface Account {
export interface LiquidityAccount {
id: string
asset: {
id: string
Expand All @@ -47,7 +47,7 @@ export interface Account {

export interface Deposit {
id: string
account: Account
account: LiquidityAccount
amount: bigint
}

Expand All @@ -56,8 +56,8 @@ export interface Withdrawal extends Deposit {
}

export interface TransferOptions {
sourceAccount: Account
destinationAccount: Account
sourceAccount: LiquidityAccount
destinationAccount: LiquidityAccount
sourceAmount: bigint
destinationAmount?: bigint
timeout: bigint // nano-seconds
Expand All @@ -69,7 +69,7 @@ export interface Transaction {
}

export interface AccountingService {
createAccount(account: Account): Promise<Account>
createLiquidityAccount(account: LiquidityAccount): Promise<LiquidityAccount>
createSettlementAccount(unit: number): Promise<void>
getBalance(id: string): Promise<bigint | undefined>
getTotalSent(id: string): Promise<bigint | undefined>
Expand Down Expand Up @@ -100,7 +100,7 @@ export function createAccountingService({
tigerbeetle
}
return {
createAccount: (options) => createAccount(deps, options),
createLiquidityAccount: (options) => createLiquidityAccount(deps, options),
createSettlementAccount: (unit) => createSettlementAccount(deps, unit),
getBalance: (id) => getAccountBalance(deps, id),
getTotalSent: (id) => getAccountTotalSent(deps, id),
Expand All @@ -114,10 +114,10 @@ export function createAccountingService({
}
}

export async function createAccount(
export async function createLiquidityAccount(
deps: ServiceDependencies,
account: Account
): Promise<Account> {
account: LiquidityAccount
): Promise<LiquidityAccount> {
if (!validateId(account.id)) {
throw new Error('unable to create account, invalid id')
}
Expand Down
11 changes: 7 additions & 4 deletions packages/backend/src/asset/model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Account } from '../accounting/service'
import { LiquidityAccount } from '../accounting/service'
import { BaseModel } from '../shared/baseModel'

export class Asset extends BaseModel implements Account {
export class Asset extends BaseModel implements LiquidityAccount {
public static get tableName(): string {
return 'assets'
}
Expand All @@ -12,7 +12,10 @@ export class Asset extends BaseModel implements Account {
// TigerBeetle account 2 byte unit field representing account's asset
public readonly unit!: number

public get asset(): Asset {
return this
public get asset(): LiquidityAccount['asset'] {
return {
id: this.id,
unit: this.unit
}
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/asset/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async function getOrCreateAsset(
code,
scale
})
await deps.accountingService.createAccount(asset)
await deps.accountingService.createLiquidityAccount(asset)
await deps.accountingService.createSettlementAccount(asset.unit)

return asset
Expand Down
4 changes: 1 addition & 3 deletions packages/backend/src/connector/core/controllers/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ export function createStreamController(): ILPMiddleware {
const { logger, redis, streamServer } = ctx.services
const { request, response } = ctx

const { stream } = ctx.accounts.outgoing
if (
!stream ||
!stream.enabled ||
ctx.accounts.outgoing.http ||
!streamServer.decodePaymentTag(request.prepare.destination) // XXX mark this earlier in the middleware pipeline
) {
await next()
Expand Down
12 changes: 2 additions & 10 deletions packages/backend/src/connector/core/factories/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ export const IncomingAccountFactory = Factory.define<MockIncomingAccount>(

export const OutgoingAccountFactory = Factory.define<MockOutgoingAccount>(
'OutgoingAccountFactory'
).attrs({
...accountAttrs,
stream: {
enabled: true
}
})
).attrs(accountAttrs)

export const IncomingPeerFactory = Factory.define<MockIncomingAccount>(
'IncomingPeerFactory'
Expand Down Expand Up @@ -58,10 +53,7 @@ export const OutgoingPeerFactory = Factory.define<MockOutgoingAccount>(
authToken: Faker.datatype.string(32),
endpoint: Faker.internet.url()
}
}),
stream: {
enabled: false
}
})
})
.attr('staticIlpAddress', ['id'], (id: string) => {
return `test.${id}`
Expand Down
12 changes: 1 addition & 11 deletions packages/backend/src/connector/core/factories/rafiki-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,7 @@ export const RafikiServicesFactory = Factory.define<MockRafikiServices>(
get: async (id: string) => await accounting._getAccount(id)
}))
.attr('invoices', ['accounting'], (accounting: MockAccountingService) => ({
get: async (id: string) => {
const invoice = await accounting._getInvoice(id)
if (invoice) {
return {
...invoice,
account: {
asset: invoice.asset
}
}
}
},
get: async (id: string) => await accounting._getInvoice(id),
handlePayment: async (_id: string) => {
return undefined
}
Expand Down
39 changes: 4 additions & 35 deletions packages/backend/src/connector/core/middleware/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,15 @@ export function createAccountMiddleware(serverAddress: string): ILPMiddleware {
if (!invoice.active) {
throw new Errors.UnreachableError('destination account is disabled')
}
return {
id: invoice.id,
asset: invoice.account.asset,
stream: {
enabled: true
},
invoice: true
}
return invoice
}
// Open Payments SPSP fallback account
const spspAccount = await accounts.get(ctx.state.streamDestination)
if (spspAccount) {
return {
id: spspAccount.id,
asset: spspAccount.asset,
stream: {
enabled: true
}
}
}
return undefined
return await accounts.get(ctx.state.streamDestination)
}
const address = ctx.request.prepare.destination
const peer = await peers.getByDestinationAddress(address)
if (peer) {
return {
...peer,
stream: {
enabled: false
}
}
return peer
}
if (
address.startsWith(serverAddress + '.') &&
Expand All @@ -71,16 +49,7 @@ export function createAccountMiddleware(serverAddress: string): ILPMiddleware {
)
if (validateId(accountId)) {
// TODO: Look up direct ILP access account
// const account = await accounts.get(accountId)
// return account
// ? {
// // TODO: this is missing asset code and scale
// ...account,
// stream: {
// enabled: true
// }
// }
// : undefined
// return await accounts.get(accountId)
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/connector/core/middleware/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ export function createBalanceMiddleware(): ILPMiddleware {

if (response.fulfill) {
await trxOrError.commit()
if (accounts.outgoing.invoice) {
await services.invoices.handlePayment(accounts.outgoing.id)
}
// TODO: move handlePayment inside accountingServices's trxOrError.commit()
await services.invoices.handlePayment(accounts.outgoing.id)
} else {
await trxOrError.rollback()
}
Expand Down
21 changes: 12 additions & 9 deletions packages/backend/src/connector/core/rafiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,35 @@ import {
import { createTokenAuthMiddleware } from './middleware'
import { RatesService } from '../../rates/service'
import { TransferError } from '../../accounting/errors'
import { Account, Transaction } from '../../accounting/service'
import { LiquidityAccount, Transaction } from '../../accounting/service'
import { AssetOptions } from '../../asset/service'
import { AccountService } from '../../open_payments/account/service'
import { InvoiceService } from '../../open_payments/invoice/service'
import { PeerService } from '../../peer/service'

type RafikiAccount = Account & {
asset: AssetOptions
// Model classes that represent an Interledger sender, receiver, or
// connector SHOULD implement this ConnectorAccount interface.
// Such models include:
// ../../open_payments/account/model
// ../../open_payments/invoice/model
// ../../outgoing_payment/model
// ../../peer/model
export interface ConnectorAccount extends LiquidityAccount {
asset: LiquidityAccount['asset'] & AssetOptions
}

export type IncomingAccount = RafikiAccount & {
export interface IncomingAccount extends ConnectorAccount {
maxPacketAmount?: bigint
staticIlpAddress?: string
}

export type OutgoingAccount = RafikiAccount & {
export interface OutgoingAccount extends ConnectorAccount {
http?: {
outgoing: {
authToken: string
endpoint: string
}
}
stream?: {
enabled: boolean
}
invoice?: boolean
}

export interface TransferOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ describe('Account Middleware', () => {
await expect(middleware(ctx, next)).resolves.toBeUndefined()

expect(ctx.accounts.incoming).toEqual(incomingAccount)
expect(ctx.accounts.outgoing).toEqual({
id: outgoingAccount.id,
asset: outgoingAccount.asset,
stream: { enabled: true },
invoice: true
})
expect(ctx.accounts.outgoing).toEqual(outgoingAccount)
})

test('set the accounts according to state and streamDestination SPSP fallback', async () => {
Expand All @@ -99,11 +94,7 @@ describe('Account Middleware', () => {
await expect(middleware(ctx, next)).resolves.toBeUndefined()

expect(ctx.accounts.incoming).toEqual(incomingAccount)
expect(ctx.accounts.outgoing).toEqual({
id: outgoingAccount.id,
asset: outgoingAccount.asset,
stream: { enabled: true }
})
expect(ctx.accounts.outgoing).toEqual(outgoingAccount)
})

test('return an error when the destination account is disabled', async () => {
Expand Down
Loading