Skip to content

Commit

Permalink
Merge branch '2893/multi-tenancy-v1' into 3114/tenanted-wallet-addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
koekiebox committed Dec 10, 2024
2 parents 6f16eaa + 8e04749 commit 0ad7ccc
Show file tree
Hide file tree
Showing 22 changed files with 172 additions and 100 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/node-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ jobs:
- backend
- frontend
steps:
- uses: actions/checkout@v4
- name: Fetch docker image from cache
uses: actions/cache/restore@v4
with:
Expand Down Expand Up @@ -326,6 +327,7 @@ jobs:
- backend
- frontend
steps:
- uses: actions/checkout@v4
- name: Fetch docker image from cache
uses: actions/cache/restore@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .grype.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore:
- vulnerability: GHSA-3xgq-45jj-v275
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@ build
**/styles/*.css
.docusaurus
.cache-loader
packages/documentation/**
.astro
1 change: 1 addition & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CVE-2024-21538 exp:2024-12-31
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"license": "Apache-2.0",
"repository": "https://github.com/interledger/rafiki",
"engines": {
"pnpm": "^8.15.4",
"pnpm": "^8.15.9",
"node": "20"
},
"packageManager": "[email protected].5",
"packageManager": "[email protected].9",
"scripts": {
"preinstall": "npx only-allow pnpm",
"lint": "eslint --max-warnings=0 --fix .",
Expand Down Expand Up @@ -78,7 +78,8 @@
"tar@<6.2.1": ">=6.2.1",
"braces@<3.0.3": ">=3.0.3",
"@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9",
"dset@<3.1.4": ">=3.1.4"
"dset@<3.1.4": ">=3.1.4",
"cross-spawn@>=7.0.0 <7.0.5": ">=7.0.5"
}
}
}
11 changes: 11 additions & 0 deletions packages/backend/src/payment-method/local/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ describe('LocalPaymentService', (): void => {
scale: 2
})

assetMap['USD_9'] = await createAsset(deps, {
code: 'USD_9',
scale: 9
})

assetMap['EUR'] = await createAsset(deps, {
code: 'EUR',
scale: 2
Expand All @@ -67,6 +72,10 @@ describe('LocalPaymentService', (): void => {
assetId: assetMap['USD'].id
})

walletAddressMap['USD_9'] = await createWalletAddress(deps, {
assetId: assetMap['USD_9'].id
})

walletAddressMap['EUR'] = await createWalletAddress(deps, {
assetId: assetMap['EUR'].id
})
Expand Down Expand Up @@ -261,6 +270,7 @@ describe('LocalPaymentService', (): void => {
${'EUR'} | ${100n} | ${'USD'} | ${100n} | ${1.0} | ${'cross currency, same rate'}
${'EUR'} | ${100n} | ${'USD'} | ${112n} | ${0.9} | ${'cross currency, exchange rate < 1'}
${'EUR'} | ${100n} | ${'USD'} | ${50n} | ${2.0} | ${'cross currency, exchange rate > 1'}
${'USD_9'} | ${100_000_000n} | ${'USD'} | ${10n} | ${1.0} | ${'local currency, different scale'}
`(
'$description',
async ({
Expand All @@ -270,6 +280,7 @@ describe('LocalPaymentService', (): void => {
expectedDebitAmount,
exchangeRate
}): Promise<void> => {
if (incomingAssetCode !== 'USD_9') return
let ratesScope

if (incomingAssetCode !== debitAssetCode) {
Expand Down
20 changes: 11 additions & 9 deletions packages/backend/src/rates/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { convertSource, Asset, convertDestination } from './util'

describe('Rates util', () => {
describe('convert', () => {
describe('convert same scales', () => {
describe('convertSource', () => {
describe('convertSource same scales', () => {
test.each`
exchangeRate | sourceAmount | assetScale | expectedResult | description
${1.5} | ${100n} | ${9} | ${{ amount: 150n, scaledExchangeRate: 1.5 }} | ${'exchange rate above 1'}
Expand Down Expand Up @@ -31,11 +31,12 @@ describe('Rates util', () => {
)
})

describe('convert different scales', () => {
describe('convertSource different scales', () => {
test.each`
exchangeRate | sourceAmount | sourceAssetScale | destinationAssetScale | expectedResult | description
${1.5} | ${100n} | ${9} | ${12} | ${{ amount: 150_000n, scaledExchangeRate: 1500 }} | ${'convert scale from low to high'}
${1.5} | ${100_000n} | ${12} | ${9} | ${{ amount: 150n, scaledExchangeRate: 0.0015 }} | ${'convert scale from high to low'}
exchangeRate | sourceAmount | sourceAssetScale | destinationAssetScale | expectedResult | description
${1.5} | ${100n} | ${9} | ${12} | ${{ amount: 150_000n, scaledExchangeRate: 1500 }} | ${'convert scale from low to high'}
${1.5} | ${100_000n} | ${12} | ${9} | ${{ amount: 150n, scaledExchangeRate: 0.0015 }} | ${'convert scale from high to low'}
${1} | ${100_000_000n} | ${9} | ${2} | ${{ amount: 10n, scaledExchangeRate: 0.0000001 }} | ${'exchange rate of 1 with different scale'}
`(
'$description',
async ({
Expand All @@ -57,7 +58,7 @@ describe('Rates util', () => {
)
})
})
describe('convert reverse', () => {
describe('convertDestination', () => {
describe('convert same scales', () => {
test.each`
exchangeRate | destinationAmount | assetScale | expectedResult | description
Expand Down Expand Up @@ -88,8 +89,9 @@ describe('Rates util', () => {
describe('convert different scales', () => {
test.each`
exchangeRate | destinationAmount | sourceAssetScale | destinationAssetScale | expectedResult | description
${2.0} | ${100n} | ${9} | ${12} | ${{ amount: 50_000n, scaledExchangeRate: 0.002 }} | ${'convert scale from low to high'}
${2.0} | ${100_000n} | ${12} | ${9} | ${{ amount: 50n, scaledExchangeRate: 2000 }} | ${'convert scale from high to low'}
${2.0} | ${100n} | ${12} | ${9} | ${{ amount: 50_000n, scaledExchangeRate: 0.002 }} | ${'convert scale from low to high'}
${2.0} | ${100_000n} | ${9} | ${12} | ${{ amount: 50n, scaledExchangeRate: 2000 }} | ${'convert scale from high to low'}
${1} | ${100_000_000n} | ${2} | ${9} | ${{ amount: 10n, scaledExchangeRate: 10000000 }} | ${'convert scale from high to low (same asset)'}
`(
'$description',
async ({
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/rates/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function convertSource(opts: ConvertSourceOptions): ConvertResults {
export function convertDestination(
opts: ConvertDestinationOptions
): ConvertResults {
const scaleDiff = opts.sourceAsset.scale - opts.destinationAsset.scale
const scaleDiff = opts.destinationAsset.scale - opts.sourceAsset.scale
const scaledExchangeRate = opts.exchangeRate * 10 ** scaleDiff

return {
Expand Down
59 changes: 51 additions & 8 deletions packages/backend/src/telemetry/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { Counter, Histogram } from '@opentelemetry/api'
import { privacy } from './privacy'
import { mockRatesApi } from '../tests/rates'
import { ConvertResults } from '../rates/util'

jest.mock('@opentelemetry/api', () => ({
...jest.requireActual('@opentelemetry/api'),
Expand Down Expand Up @@ -355,14 +356,17 @@ describe('Telemetry Service', () => {
expect(internalConvertSpy).not.toHaveBeenCalled()
})

it('should apply privacy', async () => {
it('should apply privacy by default', async () => {
const convertedAmount = 500n

jest
//"any" to access private ts class member variable
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(telemetryService as any, 'convertAmount')
.mockImplementation(() => Promise.resolve(convertedAmount))
.mockResolvedValueOnce({
scaledExchangeRate: 1,
amount: convertedAmount
} as ConvertResults)
const applyPrivacySpy = jest
.spyOn(privacy, 'applyPrivacy')
.mockReturnValue(123)
Expand All @@ -378,14 +382,49 @@ describe('Telemetry Service', () => {
value: 100n,
assetCode: 'USD',
assetScale: 2
}
},
undefined
)

expect(applyPrivacySpy).toHaveBeenCalledWith(Number(convertedAmount))
expect(incrementCounterSpy).toHaveBeenCalledWith(counterName, 123, {})
})

it('should not apply privacy if preservePrivacy is false', async () => {
const convertedAmount = 500n

jest
//"any" to access private ts class member variable
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(telemetryService as any, 'convertAmount')
.mockResolvedValueOnce({
scaledExchangeRate: 1,
amount: convertedAmount
} as ConvertResults)

const applyPrivacySpy = jest.spyOn(privacy, 'applyPrivacy')
const incrementCounterSpy = jest.spyOn(
telemetryService,
'incrementCounter'
)

const counterName = 'test_counter'
await telemetryService.incrementCounterWithTransactionAmount(
counterName,
{
value: 100n,
assetCode: 'USD',
assetScale: 2
},
undefined,
false
)

expect(applyPrivacySpy).not.toHaveBeenCalled()
expect(incrementCounterSpy).toHaveBeenCalledWith(
counterName,
123,
expect.any(Object)
Number(convertedAmount),
{}
)
})

Expand Down Expand Up @@ -421,7 +460,10 @@ describe('Telemetry Service', () => {
//"any" to access private ts class member variable
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(telemetryService as any, 'convertAmount')
.mockImplementation(() => Promise.resolve(10000n))
.mockResolvedValueOnce({
scaledExchangeRate: 1,
amount: 100n
} as ConvertResults)
const incrementCounterSpy = jest.spyOn(
telemetryService,
'incrementCounter'
Expand All @@ -437,14 +479,15 @@ describe('Telemetry Service', () => {
value: 100n,
assetCode: 'USD',
assetScale: 2
}
},
{ attribute: 'metric attribute' }
)

expect(convertSpy).toHaveBeenCalled()
expect(incrementCounterSpy).toHaveBeenCalledWith(
counterName,
obfuscatedAmount,
expect.any(Object)
{ attribute: 'metric attribute' }
)
})
})
Expand Down
11 changes: 5 additions & 6 deletions packages/backend/src/telemetry/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,13 @@ export class TelemetryServiceImpl implements TelemetryService {
return
}

const obfuscatedAmount = preservePrivacy
? privacy.applyPrivacy(Number(converted))
: Number(converted)
this.incrementCounter(name, obfuscatedAmount, attributes)
const finalAmount = preservePrivacy
? privacy.applyPrivacy(Number(converted.amount))
: Number(converted.amount)
this.incrementCounter(name, finalAmount, attributes)
} catch (e) {
this.deps.logger.error(e, `Unable to collect telemetry`)
this.deps.logger.error(e, 'Unable to collect telemetry')
}
return Promise.resolve()
}

public recordHistogram(
Expand Down
6 changes: 3 additions & 3 deletions packages/documentation/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ export default defineConfig({
],
plugins: [
starlightLinksValidator({
errorOnLocalLinks: false,
}),
],
errorOnLocalLinks: false
})
]
}),
GraphQL({
schema: '../backend/src/graphql/schema.graphql',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ You can either trigger the debugger by adding `debugger` statements in the code
#### Debugging with VS Code:

To debug with VS Code, add this configuration to your `.vscode/launch.json`:

```json
{
"name": "Attach to docker (cloud-nine-backend)",
Expand Down Expand Up @@ -220,7 +221,7 @@ The following are the most commonly used commands:
| pnpm localenv:compose up | Start (with TigerBeetle) |
| pnpm localenv:compose up -d | Start (with TigerBeetle) detached |
| pnpm localenv:compose down | Down (with TigerBeetle) |
| pnpm localenv:compose down --volumes | Down and kill volumes (with TigerBeetle)
| pnpm localenv:compose down --volumes | Down and kill volumes (with TigerBeetle) |
| pnpm localenv:compose down --volumes --rmi all | Down, kill volumes (with TigerBeetle) and images |
| pnpm localenv:compose:psql config | Show all merged config (with PostgreSQL) |
| pnpm localenv:compose build | Build all the containers (with TigerBeetle) |
Expand Down Expand Up @@ -292,4 +293,4 @@ test.rafiki.viXmy1OVHgvmQakNjX1C6kQMri92DzHeISEv-5VzTDuFhrpsrkDzsq5OO9Lfa9yed0L2

#### TigerBeetle container exists with code 137

There is a known <LinkOut href='https://docs.tigerbeetle.com/operating/docker/#exited-with-code-137'>issue</LinkOut> when running TigerBeetle in Docker. The container exits without logs and simply shows error code 137. To fix this, increase the Docker memory limit. If you run the local Docker playground on a Windows machine via the Windows Subsystem for Linux (WSL), you can increase the memory limit by <LinkOut href='https://learn.microsoft.com/en-us/windows/wsl/wsl-config#example-wslconfig-file'>configuring</LinkOut> your `.wslconfig` file.
There is a known <LinkOut href='https://docs.tigerbeetle.com/operating/docker/#exited-with-code-137'>issue</LinkOut> when running TigerBeetle in Docker. The container exits without logs and simply shows error code 137. To fix this, increase the Docker memory limit. If you run the local Docker playground on a Windows machine via the Windows Subsystem for Linux (WSL), you can increase the memory limit by <LinkOut href='https://learn.microsoft.com/en-us/windows/wsl/wsl-config#example-wslconfig-file'>configuring</LinkOut> your `.wslconfig` file.
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ Deploy a general purpose VM with the following minimum specifications:

Install the following software on the VM:

- <LinkOut href='https://docs.docker.com/engine/install/'>Docker Engine</LinkOut>
- <LinkOut href='https://docs.docker.com/compose/install/#scenario-two-install-the-compose-plugin'>Docker Compose</LinkOut>
- <LinkOut href='https://docs.docker.com/engine/install/'>
Docker Engine
</LinkOut>
- <LinkOut href='https://docs.docker.com/compose/install/#scenario-two-install-the-compose-plugin'>
Docker Compose
</LinkOut>

### Install Nginx and Certbot

Expand Down Expand Up @@ -263,7 +267,7 @@ Create nginx configuration files for every exposed domain:
| Open Payments resource server | DOMAIN | myrafiki.com | /etc/nginx/sites-available/open_payments_resource_server.config |
| ILP Connector | ilp.DOMAIN | ilp.myrafiki.com | /etc/nginx/sites-available/ilp.config |
| Open Payments auth server | auth.DOMAIN | auth.myrafiki.com | /etc/nginx/sites-available/open_payments_auth_server.config |
| Admin UI | admin.DOMAIN | admin.myrafiki.com | /etc/nginx/sites-available/admin.config |
| Admin UI | admin.DOMAIN | admin.myrafiki.com | /etc/nginx/sites-available/admin.config |

</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Each payment account belonging to your users (e.g., customers) must have at leas

- Your Rafiki instance must be set up for at least one asset before wallet addresses can be created as each wallet address must have an asset assigned to it.
- Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.

:::

## Create wallet addresses
Expand Down Expand Up @@ -84,12 +85,12 @@ We strongly recommend you store at least the `walletAddress.id` in your internal

<div class="overflow-table">

|Variable | Description |
|-------- | ----------- |
| `assetId` | The unique ID of the asset, assigned by Rafiki when the asset was created, that the wallet address's underlying payment account is denominated in |
| `publicName` | The public name associated with the wallet address |
| `url` | The wallet address's case-insensitive URL |
| `additionalProperties` | Optional [additional properties](/apis/graphql/backend/inputobjects/#additionalpropertyinput) associated with the wallet address |
| Variable | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `assetId` | The unique ID of the asset, assigned by Rafiki when the asset was created, that the wallet address's underlying payment account is denominated in |
| `publicName` | The public name associated with the wallet address |
| `url` | The wallet address's case-insensitive URL |
| `additionalProperties` | Optional [additional properties](/apis/graphql/backend/inputobjects/#additionalpropertyinput) associated with the wallet address |

</div>

Expand Down Expand Up @@ -194,11 +195,11 @@ Open Payments <LinkOut href="https://openpayments.dev/apis/wallet-address-server

<div class="overflow-table">

| Parameter | Required value | Description |
| --------- | -------------- | ----------- |
| `alg` | `EdDSA` | The algorithm used to generate the key pair |
| `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key |
| `crv` | `Ed25519` | The cryptographic curve used with the key |
| Parameter | Required value | Description |
| --------- | -------------- | ----------------------------------------------------------------------------- |
| `alg` | `EdDSA` | The algorithm used to generate the key pair |
| `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key |
| `crv` | `Ed25519` | The cryptographic curve used with the key |

</div>

Expand Down Expand Up @@ -285,4 +286,4 @@ mutation RevokeWalletAddressKey($input: RevokeWalletAddressKeyInput!) {
}
```

</CodeBlock>
</CodeBlock>
Loading

0 comments on commit 0ad7ccc

Please sign in to comment.