Skip to content

Commit 9470026

Browse files
committed
feat(backend): simplify localenv and seeding logic to accommodate multihop
1 parent d2a92e2 commit 9470026

File tree

7 files changed

+169
-23
lines changed

7 files changed

+169
-23
lines changed

localenv/cloud-nine-wallet/seed.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ peers:
1818
- global-to-cloud-nine
1919
outgoing: cloud-nine-to-global
2020
routes:
21-
- "test.global-bank"
21+
# This route MUST remain unchanged when NOT in multihop mode so that happy-life packets aren't routed through global-bank
22+
- "test"
23+
- initialLiquidity: '10000000'
24+
peerUrl: http://happy-life-bank-backend:3002
25+
peerIlpAddress: test.happy-life-bank
26+
liquidityThreshold: 1000000
27+
tokens:
28+
incoming:
29+
- happy-to-cloud-nine
30+
outgoing: cloud-nine-to-happy
31+
routes:
2232
- "test.happy-life-bank"
2333
accounts:
2434
- name: 'Grace Franklin'

localenv/cloud-ten-wallet/seed.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@ assets:
1818
peeringAsset: 'USD'
1919
peers:
2020
- initialLiquidity: '10000000'
21-
peerUrl: http://global-bank-backend:3002
22-
peerIlpAddress: test.global-bank
21+
peerUrl: http://happy-life-bank-backend:3002
22+
peerIlpAddress: test.happy-life-bank
2323
liquidityThreshold: 1000000
2424
tokens:
25-
incoming:
26-
- global-to-cloud-ten
27-
outgoing: cloud-ten-to-global
28-
routes:
29-
- "test.global-bank"
25+
incoming:
26+
- happy-to-cloud-ten
27+
outgoing: cloud-ten-to-happy
28+
routes:
3029
- "test.happy-life-bank"
3130
accounts:
3231
- name: 'Frace Granklin'

localenv/global-bank/seed.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@ peers:
1616
outgoing: global-to-cloud-nine
1717
routes:
1818
- "test.cloud-nine-wallet"
19-
- initialLiquidity: '100000000000000'
20-
peerUrl: http://cloud-nine-wallet-backend:3002
21-
peerIlpAddress: test.cloud-nine-wallet.bc293b79-8609-47bd-b914-6438b470aff8
22-
liquidityThreshold: 1000000
23-
maxPacketAmount: 10000
24-
tokens:
25-
incoming:
26-
- cloud-ten-to-global
27-
outgoing: global-to-cloud-ten
28-
routes:
29-
- "test.cloud-nine-wallet.bc293b79-8609-47bd-b914-6438b470aff8"
3019
- initialLiquidity: '100000000000000'
3120
peerUrl: http://happy-life-bank-backend:3002
3221
peerIlpAddress: test.happy-life-bank

localenv/happy-life-bank/seed.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ assets:
1717
liquidityThreshold: 100000
1818
peeringAsset: 'USD'
1919
peers:
20+
- initialLiquidity: '100000000000000'
21+
peerUrl: http://cloud-nine-wallet-backend:3002
22+
peerIlpAddress: test.cloud-nine-wallet
23+
liquidityThreshold: 1000000
24+
maxPacketAmount: 10000
25+
tokens:
26+
incoming:
27+
- cloud-nine-to-happy
28+
outgoing: happy-to-cloud-nine
29+
routes:
30+
- "test.cloud-nine-wallet"
31+
- initialLiquidity: '100000000000000'
32+
peerUrl: http://cloud-nine-wallet-backend:3002
33+
peerIlpAddress: test.cloud-nine-wallet.bc293b79-8609-47bd-b914-6438b470aff8
34+
liquidityThreshold: 1000000
35+
maxPacketAmount: 10000
36+
tokens:
37+
incoming:
38+
- cloud-ten-to-happy
39+
outgoing: happy-to-cloud-ten
40+
routes:
41+
- "test.cloud-nine-wallet.bc293b79-8609-47bd-b914-6438b470aff8"
2042
- initialLiquidity: '100000000000000'
2143
peerUrl: http://global-bank-backend:3002
2244
peerIlpAddress: test.global-bank

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
"clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
2020
"build": "tsc --build",
2121
"localenv:compose:psql": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml",
22-
"localenv:compose": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
23-
"localenv:compose:multitenancy": "docker compose -f ./localenv/cloud-ten-wallet/docker-compose.yml -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
22+
"localenv:compose": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
23+
"localenv:compose:multihop": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
24+
"localenv:compose:multitenancy": "docker compose -f ./localenv/cloud-ten-wallet/docker-compose.yml -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
2425
"localenv:compose:psql:telemetry": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/telemetry/docker-compose.yml",
2526
"localenv:compose:telemetry": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml -f ./localenv/telemetry/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle",
2627
"localenv:compose:adminauth": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/global-bank/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/admin-auth/docker-compose.yml",

packages/mock-account-service-lib/src/requesters.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
type Asset,
1818
type TenantMutationResponse,
1919
type CreateTenantInput,
20-
TenantSettingKey
20+
TenantSettingKey,
21+
type UpdatePeerMutationResponse,
22+
type UpdatePeerInput,
23+
type DeletePeerMutationResponse
2124
} from './generated/graphql'
2225
import { v4 as uuid } from 'uuid'
2326

@@ -79,6 +82,8 @@ export function createRequesters(
7982
staticIlpAddress: string,
8083
assetId: string
8184
) => Promise<Peer | null>
85+
updatePeer: (input: UpdatePeerInput) => Promise<UpdatePeerMutationResponse>
86+
deletePeer: (peerId: string) => Promise<DeletePeerMutationResponse>
8287
} {
8388
return {
8489
createAsset: (code, scale, liquidityThreshold) =>
@@ -129,7 +134,10 @@ export function createRequesters(
129134
getAssetByCodeAndScale(apolloClient, code, scale),
130135
getWalletAddressByURL: (url) => getWalletAddressByURL(apolloClient, url),
131136
getPeerByAddressAndAsset: (staticIlpAddress, assetId) =>
132-
getPeerByAddressAndAsset(apolloClient, staticIlpAddress, assetId)
137+
getPeerByAddressAndAsset(apolloClient, staticIlpAddress, assetId),
138+
updatePeer: (input: UpdatePeerInput) =>
139+
updatePeer(apolloClient, logger, input),
140+
deletePeer: (peerId: string) => deletePeer(apolloClient, logger, peerId)
133141
}
134142
}
135143

@@ -280,6 +288,62 @@ export async function createPeer(
280288
})
281289
}
282290

291+
export async function updatePeer(
292+
apolloClient: ApolloClient<NormalizedCacheObject>,
293+
logger: Logger,
294+
input: UpdatePeerInput
295+
): Promise<UpdatePeerMutationResponse> {
296+
const updatePeerMutation = gql`
297+
mutation UpdatePeer($input: UpdatePeerInput!) {
298+
updatePeer(input: $input) {
299+
peer {
300+
id
301+
}
302+
}
303+
}
304+
`
305+
306+
return apolloClient
307+
.mutate({
308+
mutation: updatePeerMutation,
309+
variables: { input }
310+
})
311+
.then(({ data }): UpdatePeerMutationResponse => {
312+
logger.debug(data)
313+
if (!data?.updatePeer) {
314+
throw new Error('Data was empty')
315+
}
316+
return data.updatePeer
317+
})
318+
}
319+
320+
export async function deletePeer(
321+
apolloClient: ApolloClient<NormalizedCacheObject>,
322+
logger: Logger,
323+
peerId: string
324+
): Promise<DeletePeerMutationResponse> {
325+
const mutation = gql`
326+
mutation DeletePeer($input: DeletePeerInput!) {
327+
deletePeer(input: $input) {
328+
success
329+
}
330+
}
331+
`
332+
333+
return apolloClient
334+
.mutate({
335+
mutation,
336+
variables: { input: { id: peerId } }
337+
})
338+
.then(({ data }): DeletePeerMutationResponse => {
339+
logger.debug(data)
340+
if (!data?.deletePeer) {
341+
throw new Error('Data was empty')
342+
}
343+
return data.deletePeer
344+
})
345+
}
346+
283347
export async function createAutoPeer(
284348
apolloClient: ApolloClient<NormalizedCacheObject>,
285349
logger: Logger,

packages/mock-account-service-lib/src/seed.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export async function setupFromSeed(
6262
depositAssetLiquidity,
6363
setFee,
6464
createPeer,
65+
updatePeer,
66+
deletePeer,
6567
depositPeerLiquidity,
6668
createAutoPeer,
6769
createWalletAddress,
@@ -108,6 +110,8 @@ export async function setupFromSeed(
108110
)
109111

110112
if (existingPeer && existingPeer.staticIlpAddress === peer.peerIlpAddress) {
113+
// Needed for refreshing routes if changed in seed (when going back and forth from multihop mode)
114+
await updatePeer({ id: existingPeer.id, routes: peer.routes || [] })
111115
continue
112116
}
113117

@@ -133,6 +137,38 @@ export async function setupFromSeed(
133137

134138
logger.debug('Finished seeding peers')
135139

140+
// Enforce multihop automatically: if the global-bank backend is reachable,
141+
// remove direct peers between cloud-nine/cloud-ten and happy-life so routing goes through global-bank.
142+
try {
143+
const globalPeerSeed = config.seed.peers.find((p) =>
144+
p.peerIlpAddress.startsWith('test.global-bank')
145+
)
146+
if (globalPeerSeed) {
147+
const adminHealthUrl = getBackendHealthUrl(
148+
globalPeerSeed.peerUrl
149+
)
150+
if (await isBackendReachable(adminHealthUrl)) {
151+
logger.debug('global-bank backend is reachable, enforcing multihop')
152+
const peersToBeDeleted: string[] = config.seed.peers
153+
.filter((p) => !p.peerIlpAddress.startsWith('test.global-bank'))
154+
.map((p) => p.peerIlpAddress)
155+
156+
for (const p of peersToBeDeleted) {
157+
const peer = await getPeerByAddressAndAsset(
158+
p,
159+
assets[peeringAsset].id
160+
)
161+
if (peer) {
162+
await deletePeer(peer.id)
163+
}
164+
}
165+
logger.debug('Multihop enforced: removed direct peers where present')
166+
}
167+
}
168+
} catch (e) {
169+
logger.debug('Multihop enforcement skipped', e)
170+
}
171+
136172
if (config.testnetAutoPeerUrl) {
137173
logger.debug('autopeering url: ', config.testnetAutoPeerUrl)
138174
const autoPeerResponse = await createAutoPeer(
@@ -215,3 +251,28 @@ export async function setupFromSeed(
215251
? { tenantId: createdTenant.id, apiSecret: createdTenant.apiSecret }
216252
: undefined
217253
}
254+
255+
function getBackendHealthUrl(connectorUrl: string): string {
256+
try {
257+
const url = new URL(connectorUrl)
258+
const port = url.port === '3002' ? '3001' : url.port
259+
url.port = port
260+
url.pathname = '/healthz'
261+
return url.toString()
262+
} catch {
263+
return connectorUrl
264+
}
265+
}
266+
267+
async function isBackendReachable(url: string): Promise<boolean> {
268+
const controller = new AbortController()
269+
const timeout = setTimeout(() => controller.abort(), 5000)
270+
try {
271+
const res = await fetch(url, { method: 'GET', signal: controller.signal })
272+
return res.ok
273+
} catch {
274+
return false
275+
} finally {
276+
clearTimeout(timeout)
277+
}
278+
}

0 commit comments

Comments
 (0)