From 5b766fb8fc1ae400744438de96a2e0c6129a64b1 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Thu, 27 Aug 2020 18:30:49 +0200 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=A8=20Add=20new=20collateralization?= =?UTF-8?q?=20config=20and=20admin=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/node/example.http | 31 +++++++- modules/node/src/admin/admin.controller.ts | 88 ++++++++++++++++++++- modules/node/src/channel/channel.service.ts | 7 +- modules/node/src/config/config.service.ts | 39 +++++++-- ops/start-indra.sh | 2 + 5 files changed, 157 insertions(+), 10 deletions(-) diff --git a/modules/node/example.http b/modules/node/example.http index 7764ef8e58..99303d4f09 100644 --- a/modules/node/example.http +++ b/modules/node/example.http @@ -1,3 +1,8 @@ +### ADMIN REQUESTS + +### +# Force uninstall deposit app (only works if node has deposit rights) + POST http://localhost:3000/api/admin/uninstall-deposit Content-Type: application/json x-auth-token: cxt1234 @@ -5,4 +10,28 @@ x-auth-token: cxt1234 { "multisigAddress": "0x93a8eAFC6436F3e238d962Cb429893ec22875705", "assetId": "0x4E72770760c011647D4873f60A3CF6cDeA896CD8" -} \ No newline at end of file +} + +### +# Set rebalance profile + +POST http://localhost:3000/api/admin/rebalance-profile +Content-Type: application/json +x-auth-token: cxt1234 + +{ + "multisigAddress": "0x93a8eAFC6436F3e238d962Cb429893ec22875705", + "rebalanceProfile": { + "assetId": "0x4E72770760c011647D4873f60A3CF6cDeA896CD8", + "collateralizeThreshold": "5", + "target": "15", + "reclaimThreshold": "0" + } +} + +### +# GET rebalance profile + +GET http://localhost:3000/api/admin/rebalance-profile/0x93a8eAFC6436F3e238d962Cb429893ec22875705/0x0000000000000000000000000000000000000000 +Content-Type: application/json +x-auth-token: cxt1234 \ No newline at end of file diff --git a/modules/node/src/admin/admin.controller.ts b/modules/node/src/admin/admin.controller.ts index 3271f4ac21..96d2747331 100644 --- a/modules/node/src/admin/admin.controller.ts +++ b/modules/node/src/admin/admin.controller.ts @@ -6,22 +6,36 @@ import { UnauthorizedException, NotFoundException, BadRequestException, + Get, + Param, } from "@nestjs/common"; import { ConfigService } from "../config/config.service"; import { AdminService } from "./admin.service"; +import { RebalanceProfile } from "@connext/types"; +import { ChannelService } from "../channel/channel.service"; +import { ChannelRepository } from "../channel/channel.repository"; +import { BigNumber } from "ethers"; export class UninstallDepositAppDto { multisigAddress!: string; assetId?: string; } +export class AddRebalanceProfileDto { + multisigAddress!: string; + rebalanceProfile!: RebalanceProfile; +} + @Controller("admin") export class AdminController { constructor( private readonly adminService: AdminService, private readonly configService: ConfigService, + private readonly channelService: ChannelService, + private readonly channelRepository: ChannelRepository, ) {} + @Post("uninstall-deposit") async uninstallDepositApp( @Body() { multisigAddress, assetId }: UninstallDepositAppDto, @@ -35,7 +49,79 @@ export class AdminController { return res; } catch (e) { if (e.message.includes("Channel does not exist for multisig")) { - throw new NotFoundException(); + throw new NotFoundException("Channel not found"); + } + throw new BadRequestException(e.message); + } + } + + @Post("rebalance-profile") + async addRebalanceProfile( + @Body() { multisigAddress, rebalanceProfile }: AddRebalanceProfileDto, + @Headers("x-auth-token") token: string, + ): Promise { + // not ideal to do this everywhere, can be refactored into a "guard" (see nest docs) + if (token !== this.configService.getAdminToken()) { + throw new UnauthorizedException(); + } + try { + const channel = await this.channelRepository.findByMultisigAddressOrThrow(multisigAddress); + const res = await this.channelService.addRebalanceProfileToChannel( + channel.userIdentifier, + channel.chainId, + { + ...rebalanceProfile, + collateralizeThreshold: BigNumber.from(rebalanceProfile.collateralizeThreshold), + target: BigNumber.from(rebalanceProfile.target), + reclaimThreshold: BigNumber.from(rebalanceProfile.reclaimThreshold), + }, + ); + return { + assetId: res.assetId, + collateralizeThreshold: res.collateralizeThreshold.toString(), + target: res.target.toString(), + reclaimThreshold: res.reclaimThreshold.toString(), + } as any; + } catch (e) { + if (e.message.includes("Channel does not exist for multisig")) { + throw new NotFoundException("Channel not found"); + } + throw new BadRequestException(e.message); + } + } + + @Get("rebalance-profile/:multisigAddress/:assetId") + async getRebalanceProfile( + @Param("multisigAddress") multisigAddress: string, + @Param("assetId") assetId: string, + @Headers("x-auth-token") token: string, + ): Promise { + // not ideal to do this everywhere, can be refactored into a "guard" (see nest docs) + if (token !== this.configService.getAdminToken()) { + throw new UnauthorizedException(); + } + try { + const channel = await this.channelRepository.findByMultisigAddressOrThrow(multisigAddress); + let res = await this.channelRepository.getRebalanceProfileForChannelAndAsset( + channel.userIdentifier, + channel.chainId, + assetId, + ); + if (!res) { + res = await this.configService.getDefaultRebalanceProfile(assetId); + } + if (!res) { + throw new NotFoundException("Rebalance profile not found"); + } + return { + assetId: res.assetId, + collateralizeThreshold: res.collateralizeThreshold.toString(), + target: res.target.toString(), + reclaimThreshold: res.reclaimThreshold.toString(), + } as any; + } catch (e) { + if (e.message.includes("Channel does not exist for multisig")) { + throw new NotFoundException("Channel not found"); } throw new BadRequestException(e.message); } diff --git a/modules/node/src/channel/channel.service.ts b/modules/node/src/channel/channel.service.ts index 842df0642f..b9bed9c39f 100644 --- a/modules/node/src/channel/channel.service.ts +++ b/modules/node/src/channel/channel.service.ts @@ -305,13 +305,16 @@ export class ChannelService { )}`, ); const { assetId, collateralizeThreshold, target, reclaimThreshold } = profile; - if (reclaimThreshold.lt(target) || collateralizeThreshold.gt(target)) { + if ( + (!reclaimThreshold.isZero() && reclaimThreshold.lt(target)) || + collateralizeThreshold.gt(target) + ) { throw new Error(`Rebalancing targets not properly configured: ${stringify(profile)}`); } // reclaim targets cannot be less than collateralize targets, otherwise we get into a loop of // collateralize/reclaim - if (reclaimThreshold.lt(collateralizeThreshold)) { + if (!reclaimThreshold.isZero() && reclaimThreshold.lt(collateralizeThreshold)) { throw new Error( `Reclaim targets cannot be less than collateralize targets: ${stringify(profile)}`, ); diff --git a/modules/node/src/config/config.service.ts b/modules/node/src/config/config.service.ts index b1493a6819..1ce7161991 100644 --- a/modules/node/src/config/config.service.ts +++ b/modules/node/src/config/config.service.ts @@ -297,22 +297,49 @@ export class ConfigService implements OnModuleInit { assetId: string = AddressZero, ): Promise { if (assetId === AddressZero) { + let defaultProfileEth = { + collateralizeThreshold: parseEther(`0.05`), + target: parseEther(`0.1`), + reclaimThreshold: Zero, + }; + try { + const parsed = JSON.parse(this.get("INDRA_DEFAULT_REBALANCE_PROFILE_ETH")); + if (parsed) { + defaultProfileEth = { + collateralizeThreshold: BigNumber.from(parsed.collateralizeThreshold), + target: BigNumber.from(parsed.target), + reclaimThreshold: BigNumber.from(parsed.reclaimThreshold), + }; + } + } catch (e) {} return { assetId: AddressZero, channels: [], id: 0, - collateralizeThreshold: parseEther(`0.05`), - target: parseEther(`0.1`), - reclaimThreshold: Zero, + ...defaultProfileEth, }; } + let defaultProfileToken = { + collateralizeThreshold: parseEther(`0.05`), + target: parseEther(`0.1`), + reclaimThreshold: Zero, + }; + try { + defaultProfileToken = JSON.parse(this.get("INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN")); + const parsed = JSON.parse(this.get("INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN")); + if (parsed) { + defaultProfileToken = { + collateralizeThreshold: BigNumber.from(parsed.collateralizeThreshold), + target: BigNumber.from(parsed.target), + reclaimThreshold: BigNumber.from(parsed.reclaimThreshold), + }; + } + } catch (e) {} return { assetId, channels: [], id: 0, - collateralizeThreshold: parseEther(`5`), - target: parseEther(`20`), - reclaimThreshold: Zero, + ...defaultProfileToken, }; } diff --git a/ops/start-indra.sh b/ops/start-indra.sh index 90bfa5938c..0df666201b 100644 --- a/ops/start-indra.sh +++ b/ops/start-indra.sh @@ -356,6 +356,8 @@ services: INDRA_ADMIN_TOKEN: '$INDRA_ADMIN_TOKEN' INDRA_CHAIN_PROVIDERS: '$INDRA_CHAIN_PROVIDERS' INDRA_CONTRACT_ADDRESSES: '$INDRA_CONTRACT_ADDRESSES' + INDRA_DEFAULT_REBALANCE_PROFILE_ETH: '$INDRA_DEFAULT_REBALANCE_PROFILE_ETH' + INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN: '$INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN' INDRA_MNEMONIC_FILE: '$INDRA_MNEMONIC_FILE' INDRA_LOG_LEVEL: '$INDRA_LOG_LEVEL' INDRA_NATS_JWT_SIGNER_PRIVATE_KEY: '$INDRA_NATS_JWT_SIGNER_PRIVATE_KEY' From cf878a348b17ce200ab2536497f31aef1507cfd3 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 10:44:16 +0200 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=A5=85=20Better=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/node/src/admin/admin.controller.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/node/src/admin/admin.controller.ts b/modules/node/src/admin/admin.controller.ts index 96d2747331..44d39ca299 100644 --- a/modules/node/src/admin/admin.controller.ts +++ b/modules/node/src/admin/admin.controller.ts @@ -5,9 +5,9 @@ import { Headers, UnauthorizedException, NotFoundException, - BadRequestException, Get, Param, + InternalServerErrorException, } from "@nestjs/common"; import { ConfigService } from "../config/config.service"; @@ -51,7 +51,7 @@ export class AdminController { if (e.message.includes("Channel does not exist for multisig")) { throw new NotFoundException("Channel not found"); } - throw new BadRequestException(e.message); + throw new InternalServerErrorException(e.message); } } @@ -86,7 +86,7 @@ export class AdminController { if (e.message.includes("Channel does not exist for multisig")) { throw new NotFoundException("Channel not found"); } - throw new BadRequestException(e.message); + throw new InternalServerErrorException(e.message); } } @@ -108,7 +108,7 @@ export class AdminController { assetId, ); if (!res) { - res = await this.configService.getDefaultRebalanceProfile(assetId); + res = this.configService.getDefaultRebalanceProfile(assetId); } if (!res) { throw new NotFoundException("Rebalance profile not found"); @@ -123,7 +123,7 @@ export class AdminController { if (e.message.includes("Channel does not exist for multisig")) { throw new NotFoundException("Channel not found"); } - throw new BadRequestException(e.message); + throw new InternalServerErrorException(e.message); } } } From 861b3fda207d0208d77c51cd6b3b706e5b644827 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 10:45:19 +0200 Subject: [PATCH 03/10] =?UTF-8?q?=E2=9C=A8=20Add=20optional=20amount=20for?= =?UTF-8?q?=20collateral=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/node/src/channel/channel.provider.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/node/src/channel/channel.provider.ts b/modules/node/src/channel/channel.provider.ts index 63e28231a5..e648ec9a8e 100644 --- a/modules/node/src/channel/channel.provider.ts +++ b/modules/node/src/channel/channel.provider.ts @@ -63,7 +63,7 @@ class ChannelMessaging extends AbstractMessagingProvider { async requestCollateral( userPublicIdentifier: string, chainId: number, - data: { assetId?: string }, + data: { assetId?: string; amount?: string }, ): Promise { // do not allow clients to specify an amount to collateralize with const channel = await this.channelRepository.findByUserPublicIdentifierAndChainOrThrow( @@ -136,16 +136,14 @@ class ChannelMessaging extends AbstractMessagingProvider { throw new Error(`Found channel, but no setup commitment. This should not happen.`); } // get active app set state commitments - const setStateCommitments = - await this.setStateCommitmentRepository.findAllActiveCommitmentsByMultisig( - channel.multisigAddress, - ); + const setStateCommitments = await this.setStateCommitmentRepository.findAllActiveCommitmentsByMultisig( + channel.multisigAddress, + ); // get active app conditional transaction commitments - const conditionalCommitments = - await this.conditionalTransactionCommitmentRepository.findAllActiveCommitmentsByMultisig( - channel.multisigAddress, - ); + const conditionalCommitments = await this.conditionalTransactionCommitmentRepository.findAllActiveCommitmentsByMultisig( + channel.multisigAddress, + ); return { channel, setupCommitment: convertSetupEntityToMinimalTransaction(setupCommitment), From fb19b6df5eccf34da9a3aa37c3963bee074ae8c1 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 10:45:37 +0200 Subject: [PATCH 04/10] Allow passed in target for collateral requests --- modules/node/src/channel/channel.service.ts | 28 +++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/modules/node/src/channel/channel.service.ts b/modules/node/src/channel/channel.service.ts index b9bed9c39f..40cb49cdfb 100644 --- a/modules/node/src/channel/channel.service.ts +++ b/modules/node/src/channel/channel.service.ts @@ -12,6 +12,7 @@ import { getSignerAddressFromPublicIdentifier, stringify, calculateExchangeWad, + maxBN, } from "@connext/utils"; import { Injectable, HttpService } from "@nestjs/common"; import { AxiosResponse } from "axios"; @@ -28,7 +29,7 @@ import { DEFAULT_DECIMALS } from "../constants"; import { Channel } from "./channel.entity"; import { ChannelRepository } from "./channel.repository"; -const { AddressZero } = constants; +const { AddressZero, Zero } = constants; const { getAddress, toUtf8Bytes, sha256 } = utils; export enum RebalanceType { @@ -111,6 +112,7 @@ export class ChannelService { multisigAddress: string, assetId: string = AddressZero, rebalanceType: RebalanceType, + requestedTarget: BigNumber = Zero, ): Promise< | { completed?: () => Promise; @@ -149,10 +151,10 @@ export class ChannelService { normalizedAssetId, ); - const { collateralizeThreshold, target, reclaimThreshold } = rebalancingTargets; + const { collateralizeThreshold, target: profileTarget, reclaimThreshold } = rebalancingTargets; if ( - (collateralizeThreshold.gt(target) || reclaimThreshold.lt(target)) && + (collateralizeThreshold.gt(profileTarget) || reclaimThreshold.lt(profileTarget)) && !reclaimThreshold.isZero() ) { throw new Error(`Rebalancing targets not properly configured: ${rebalancingTargets}`); @@ -174,15 +176,25 @@ export class ChannelService { if (rebalanceType === RebalanceType.COLLATERALIZE) { // If free balance is too low, collateralize up to upper bound - if (nodeFreeBalance.lt(collateralizeThreshold)) { + const configuredMax = this.configService.getMaxChannelCollateralizationForAsset(assetId); + if (configuredMax && requestedTarget?.gt(configuredMax)) { + throw new Error( + `Requested target ${requestedTarget.toString()} is greater than channel max ${configuredMax.toString()}`, + ); + } + + const targetToUse = maxBN([profileTarget, requestedTarget]); + const thresholdToUse = maxBN([collateralizeThreshold, requestedTarget]); + + if (nodeFreeBalance.lt(thresholdToUse)) { this.log.info( - `nodeFreeBalance ${nodeFreeBalance.toString()} < collateralizeThreshold ${collateralizeThreshold.toString()}, depositing`, + `nodeFreeBalance ${nodeFreeBalance.toString()} < thresholdToUse ${thresholdToUse.toString()}, depositing to target ${requestedTarget.toString()}`, ); - const amount = target.sub(nodeFreeBalance); + const amount = targetToUse.sub(nodeFreeBalance); rebalanceRes = (await this.depositService.deposit(channel, amount, normalizedAssetId))!; } else { this.log.info( - `Free balance ${nodeFreeBalance} is greater than or equal to lower collateralization bound: ${collateralizeThreshold.toString()}`, + `Free balance ${nodeFreeBalance} is greater than or equal to lower collateralization bound: ${thresholdToUse.toString()}`, ); } } else if (rebalanceType === RebalanceType.RECLAIM) { @@ -191,7 +203,7 @@ export class ChannelService { this.log.info( `nodeFreeBalance ${nodeFreeBalance.toString()} > reclaimThreshold ${reclaimThreshold.toString()}, withdrawing`, ); - const amount = nodeFreeBalance.sub(target); + const amount = nodeFreeBalance.sub(profileTarget); const transaction = await this.withdrawService.withdraw(channel, amount, normalizedAssetId); rebalanceRes.transaction = transaction; } else { From 914775798e44fc85ebe940708315f0c35e2aa46c Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 10:46:39 +0200 Subject: [PATCH 05/10] =?UTF-8?q?=F0=9F=94=A7=20Add=20environment=20variab?= =?UTF-8?q?le=20for=20max=20channel=20collateral=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/node/src/config/config.service.ts | 26 ++++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/node/src/config/config.service.ts b/modules/node/src/config/config.service.ts index 1ce7161991..b45783298d 100644 --- a/modules/node/src/config/config.service.ts +++ b/modules/node/src/config/config.service.ts @@ -29,6 +29,10 @@ type PostgresConfig = { username: string; }; +type MaxCollateralMap = { + [assetId: string]: T; +}; + @Injectable() export class ConfigService implements OnModuleInit { private readonly envConfig: { [key: string]: string }; @@ -293,9 +297,7 @@ export class ConfigService implements OnModuleInit { return parseInt(this.get(`INDRA_APP_CLEANUP_INTERVAL`) || "3600000"); } - async getDefaultRebalanceProfile( - assetId: string = AddressZero, - ): Promise { + getDefaultRebalanceProfile(assetId: string = AddressZero): RebalanceProfile | undefined { if (assetId === AddressZero) { let defaultProfileEth = { collateralizeThreshold: parseEther(`0.05`), @@ -320,8 +322,8 @@ export class ConfigService implements OnModuleInit { }; } let defaultProfileToken = { - collateralizeThreshold: parseEther(`0.05`), - target: parseEther(`0.1`), + collateralizeThreshold: parseEther(`5`), + target: parseEther(`20`), reclaimThreshold: Zero, }; try { @@ -343,9 +345,17 @@ export class ConfigService implements OnModuleInit { }; } - async getZeroRebalanceProfile( - assetId: string = AddressZero, - ): Promise { + getMaxChannelCollateralizationForAsset(assetId: string = AddressZero): BigNumber | undefined { + const collateralizationMap: MaxCollateralMap | {} = + this.get("INDRA_MAX_CHANNEL_COLLATERALIZATION") || {}; + + if (collateralizationMap[assetId]) { + return BigNumber.from(collateralizationMap[assetId]); + } + return undefined; + } + + getZeroRebalanceProfile(assetId: string = AddressZero): RebalanceProfile | undefined { if (assetId === AddressZero) { return { assetId: AddressZero, From 65ec24e00b3216911c12e02e8561b1061963d72d Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 10:59:49 +0200 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=8E=A8=20Reclaim=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/node/src/channel/channel.service.ts | 7 ++-- modules/node/src/config/config.service.ts | 14 +------ .../src/collateral/profiles.test.ts | 22 +++-------- .../src/collateral/reclaim.test.ts | 15 +++----- .../src/store/restoreState.test.ts | 6 +-- .../src/util/helpers/rebalanceProfile.ts | 37 +++++++++++-------- ops/start-indra.sh | 3 +- 7 files changed, 42 insertions(+), 62 deletions(-) diff --git a/modules/node/src/channel/channel.service.ts b/modules/node/src/channel/channel.service.ts index 40cb49cdfb..c4d7e2dcc4 100644 --- a/modules/node/src/channel/channel.service.ts +++ b/modules/node/src/channel/channel.service.ts @@ -176,10 +176,11 @@ export class ChannelService { if (rebalanceType === RebalanceType.COLLATERALIZE) { // If free balance is too low, collateralize up to upper bound - const configuredMax = this.configService.getMaxChannelCollateralizationForAsset(assetId); - if (configuredMax && requestedTarget?.gt(configuredMax)) { + + // make sure requested target is under reclaim threshold + if (requestedTarget?.gt(reclaimThreshold)) { throw new Error( - `Requested target ${requestedTarget.toString()} is greater than channel max ${configuredMax.toString()}`, + `Requested target ${requestedTarget.toString()} is greater than reclaim threshold ${reclaimThreshold.toString()}`, ); } diff --git a/modules/node/src/config/config.service.ts b/modules/node/src/config/config.service.ts index b45783298d..5a63aa992d 100644 --- a/modules/node/src/config/config.service.ts +++ b/modules/node/src/config/config.service.ts @@ -302,7 +302,7 @@ export class ConfigService implements OnModuleInit { let defaultProfileEth = { collateralizeThreshold: parseEther(`0.05`), target: parseEther(`0.1`), - reclaimThreshold: Zero, + reclaimThreshold: parseEther(`0.5`), }; try { const parsed = JSON.parse(this.get("INDRA_DEFAULT_REBALANCE_PROFILE_ETH")); @@ -324,7 +324,7 @@ export class ConfigService implements OnModuleInit { let defaultProfileToken = { collateralizeThreshold: parseEther(`5`), target: parseEther(`20`), - reclaimThreshold: Zero, + reclaimThreshold: parseEther(`100`), }; try { defaultProfileToken = JSON.parse(this.get("INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN")); @@ -345,16 +345,6 @@ export class ConfigService implements OnModuleInit { }; } - getMaxChannelCollateralizationForAsset(assetId: string = AddressZero): BigNumber | undefined { - const collateralizationMap: MaxCollateralMap | {} = - this.get("INDRA_MAX_CHANNEL_COLLATERALIZATION") || {}; - - if (collateralizationMap[assetId]) { - return BigNumber.from(collateralizationMap[assetId]); - } - return undefined; - } - getZeroRebalanceProfile(assetId: string = AddressZero): RebalanceProfile | undefined { if (assetId === AddressZero) { return { diff --git a/modules/test-runner/src/collateral/profiles.test.ts b/modules/test-runner/src/collateral/profiles.test.ts index fc67c0741c..47023bdfba 100644 --- a/modules/test-runner/src/collateral/profiles.test.ts +++ b/modules/test-runner/src/collateral/profiles.test.ts @@ -2,15 +2,8 @@ import { IConnextClient, RebalanceProfile } from "@connext/types"; import { toBN } from "@connext/utils"; import { constants } from "ethers"; import { before } from "mocha"; -import { Client } from "ts-nats"; -import { - addRebalanceProfile, - createClient, - expect, - getNatsClient, - getTestLoggers, -} from "../util"; +import { addRebalanceProfile, createClient, expect, getTestLoggers } from "../util"; const { AddressZero } = constants; @@ -18,12 +11,9 @@ const name = "Collateralization Profiles"; const { timeElapsed } = getTestLoggers(name); describe(name, () => { let client: IConnextClient; - let nats: Client; let start: number; - before(async () => { - nats = getNatsClient(); - }); + before(async () => {}); beforeEach(async () => { start = Date.now(); @@ -32,7 +22,7 @@ describe(name, () => { }); afterEach(async () => { - await client.off(); + client.off(); }); it("throws error if collateral targets are higher than reclaim", async () => { @@ -42,7 +32,7 @@ describe(name, () => { target: toBN("10"), reclaimThreshold: toBN("15"), }; - const profileResponse = await addRebalanceProfile(nats, client, REBALANCE_PROFILE, false); + const profileResponse = await addRebalanceProfile(client, REBALANCE_PROFILE, false); expect(profileResponse).to.match(/Rebalancing targets not properly configured/); }); @@ -53,7 +43,7 @@ describe(name, () => { target: toBN("1"), reclaimThreshold: toBN("9"), }; - const profileResponse = await addRebalanceProfile(nats, client, REBALANCE_PROFILE, false); + const profileResponse = await addRebalanceProfile(client, REBALANCE_PROFILE, false); expect(profileResponse).to.match(/Rebalancing targets not properly configured/); }); @@ -64,7 +54,7 @@ describe(name, () => { target: toBN("10"), reclaimThreshold: toBN("9"), }; - const profileResponse = await addRebalanceProfile(nats, client, REBALANCE_PROFILE, false); + const profileResponse = await addRebalanceProfile(client, REBALANCE_PROFILE, false); expect(profileResponse).to.match(/Rebalancing targets not properly configured/); }); }); diff --git a/modules/test-runner/src/collateral/reclaim.test.ts b/modules/test-runner/src/collateral/reclaim.test.ts index b97ff99d94..c595ce8055 100644 --- a/modules/test-runner/src/collateral/reclaim.test.ts +++ b/modules/test-runner/src/collateral/reclaim.test.ts @@ -3,7 +3,6 @@ import { IConnextClient, Contract, RebalanceProfile } from "@connext/types"; import { getRandomBytes32, toBN } from "@connext/utils"; import { BigNumber, constants } from "ethers"; import { before, describe } from "mocha"; -import { Client } from "ts-nats"; import { addRebalanceProfile, @@ -11,7 +10,6 @@ import { createClient, expect, fundChannel, - getNatsClient, getTestLoggers, } from "../util"; @@ -24,12 +22,9 @@ describe(name, () => { let clientB: IConnextClient; let tokenAddress: string; let nodeSignerAddress: string; - let nats: Client; let start: number; - before(async () => { - nats = getNatsClient(); - }); + before(async () => {}); beforeEach(async () => { start = Date.now(); @@ -45,8 +40,8 @@ describe(name, () => { }); afterEach(async () => { - await clientA.off(); - await clientB.off(); + clientA.off(); + clientB.off(); }); it("should reclaim ETH with async transfer", async () => { @@ -58,7 +53,7 @@ describe(name, () => { }; // set rebalancing profile to reclaim collateral - await addRebalanceProfile(nats, clientA, REBALANCE_PROFILE); + await addRebalanceProfile(clientA, REBALANCE_PROFILE); // deposit client await fundChannel( @@ -109,7 +104,7 @@ describe(name, () => { }; // set rebalancing profile to reclaim collateral - await addRebalanceProfile(nats, clientA, REBALANCE_PROFILE); + await addRebalanceProfile(clientA, REBALANCE_PROFILE); // deposit client await fundChannel( diff --git a/modules/test-runner/src/store/restoreState.test.ts b/modules/test-runner/src/store/restoreState.test.ts index 0111d85bad..a698ba12fb 100644 --- a/modules/test-runner/src/store/restoreState.test.ts +++ b/modules/test-runner/src/store/restoreState.test.ts @@ -17,7 +17,6 @@ import { ethProviderUrl, expect, fundChannel, - getNatsClient, getTestLoggers, TOKEN_AMOUNT, TOKEN_AMOUNT_SM, @@ -37,7 +36,6 @@ describe(name, () => { beforeEach(async () => { start = Date.now(); - const nats = getNatsClient(); signerA = getRandomChannelSigner(ethProviderUrl); store = getLocalStore(); clientA = await createClient({ signer: signerA, store, id: "A" }); @@ -50,12 +48,12 @@ describe(name, () => { reclaimThreshold: toBN("0"), }; // set rebalancing profile to reclaim collateral - await addRebalanceProfile(nats, clientA, REBALANCE_PROFILE); + await addRebalanceProfile(clientA, REBALANCE_PROFILE); timeElapsed("beforeEach complete", start); }); afterEach(async () => { - await clientA.off(); + clientA.off(); }); it("client can delete its store and restore from a remote backup", async () => { diff --git a/modules/test-runner/src/util/helpers/rebalanceProfile.ts b/modules/test-runner/src/util/helpers/rebalanceProfile.ts index 532254ab2c..28f9103af5 100644 --- a/modules/test-runner/src/util/helpers/rebalanceProfile.ts +++ b/modules/test-runner/src/util/helpers/rebalanceProfile.ts @@ -1,29 +1,34 @@ import { RebalanceProfile, IConnextClient } from "@connext/types"; -import { Client } from "ts-nats"; import { expect } from ".."; import { env } from "../env"; +import Axios from "axios"; export const addRebalanceProfile = async ( - nats: Client, client: IConnextClient, profile: RebalanceProfile, assertProfile: boolean = true, ) => { - const msg = await nats.request( - `admin.${client.publicIdentifier}.${client.chainId}.channel.add-profile`, - 5000, - JSON.stringify({ - id: Date.now(), - profile, - token: env.adminToken, - }), - ); + try { + const msg = await Axios.post( + `${env.nodeUrl}/admin/rebalance-profile`, + { + multisigAddress: client.multisigAddress, + rebalanceProfile: profile, + }, + { + headers: { + "x-auth-token": env.adminToken, + }, + }, + ); + if (assertProfile) { + const returnedProfile = await client.getRebalanceProfile(profile.assetId); + expect(returnedProfile).to.deep.include(profile); + } - if (assertProfile) { - const returnedProfile = await client.getRebalanceProfile(profile.assetId); - expect(returnedProfile).to.deep.include(profile); + return msg.data; + } catch (e) { + return e.response.data.message; } - - return msg.data; }; diff --git a/ops/start-indra.sh b/ops/start-indra.sh index 0df666201b..018e86f190 100644 --- a/ops/start-indra.sh +++ b/ops/start-indra.sh @@ -358,8 +358,9 @@ services: INDRA_CONTRACT_ADDRESSES: '$INDRA_CONTRACT_ADDRESSES' INDRA_DEFAULT_REBALANCE_PROFILE_ETH: '$INDRA_DEFAULT_REBALANCE_PROFILE_ETH' INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN: '$INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN' - INDRA_MNEMONIC_FILE: '$INDRA_MNEMONIC_FILE' INDRA_LOG_LEVEL: '$INDRA_LOG_LEVEL' + INDRA_MNEMONIC_FILE: '$INDRA_MNEMONIC_FILE' + INDRA_MAX_CHANNEL_COLLATERALIZATION: '$INDRA_MAX_CHANNEL_COLLATERALIZATION' INDRA_NATS_JWT_SIGNER_PRIVATE_KEY: '$INDRA_NATS_JWT_SIGNER_PRIVATE_KEY' INDRA_NATS_JWT_SIGNER_PUBLIC_KEY: '$INDRA_NATS_JWT_SIGNER_PUBLIC_KEY' INDRA_NATS_SERVERS: 'nats://nats:$nats_port' From 8a1e58d1e9e26876e13bbb3693d103f41d18dbc8 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 11:00:11 +0200 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=85=20Test=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/test-runner/src/collateral/request.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/test-runner/src/collateral/request.test.ts b/modules/test-runner/src/collateral/request.test.ts index 290a12d7a8..4558d7f9f4 100644 --- a/modules/test-runner/src/collateral/request.test.ts +++ b/modules/test-runner/src/collateral/request.test.ts @@ -22,7 +22,7 @@ describe(name, () => { }); afterEach(async () => { - await client.off(); + client.off(); }); it("should collateralize ETH", async () => { From 2a6f932fc6b00a13b94e9a6bb3df1178bba06252 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 11:18:22 +0200 Subject: [PATCH 08/10] =?UTF-8?q?=E2=9C=A8=20Collateral=20target=20into=20?= =?UTF-8?q?client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/client/src/connext.ts | 9 ++++++--- modules/client/src/node.ts | 8 ++++++-- modules/node/src/channel/channel.provider.ts | 4 +++- .../test-runner/src/collateral/request.test.ts | 15 ++++++++++++++- modules/types/src/api.ts | 7 +++++-- modules/types/src/client.ts | 4 ++-- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/modules/client/src/connext.ts b/modules/client/src/connext.ts index 79e923043b..768613d2b9 100644 --- a/modules/client/src/connext.ts +++ b/modules/client/src/connext.ts @@ -46,7 +46,7 @@ import { stringify, computeCancelDisputeHash, } from "@connext/utils"; -import { BigNumber, Contract, providers, constants, utils } from "ethers"; +import { BigNumber, Contract, providers, constants, utils, BigNumberish } from "ethers"; import { DepositController, @@ -192,8 +192,11 @@ export class ConnextClient implements IConnextClient { return this.node.getChannel(); }; - public requestCollateral = async (assetId: string): Promise => { - const requestCollateralResponse = await this.node.requestCollateral(assetId); + public requestCollateral = async ( + assetId: string, + amount?: BigNumberish, + ): Promise => { + const requestCollateralResponse = await this.node.requestCollateral(assetId, amount); if (!requestCollateralResponse) { return undefined; } diff --git a/modules/client/src/node.ts b/modules/client/src/node.ts index 98def92c8a..0e2fc0e254 100644 --- a/modules/client/src/node.ts +++ b/modules/client/src/node.ts @@ -17,7 +17,7 @@ import { } from "@connext/types"; import { bigNumberifyJson, isNode, logTime, stringify } from "@connext/utils"; import axios, { AxiosResponse } from "axios"; -import { utils, providers } from "ethers"; +import { utils, providers, BigNumberish } from "ethers"; import { v4 as uuid } from "uuid"; import { createCFChannelProvider } from "./channelProvider"; @@ -286,11 +286,15 @@ export class NodeApiClient implements INodeApiClient { ); } - public async requestCollateral(assetId: string): Promise { + public async requestCollateral( + assetId: string, + amount?: BigNumberish, + ): Promise { return this.send( `${this.userIdentifier}.${this.nodeIdentifier}.${this.chainId}.channel.request-collateral`, { assetId, + amount: amount?.toString(), }, ); } diff --git a/modules/node/src/channel/channel.provider.ts b/modules/node/src/channel/channel.provider.ts index e648ec9a8e..c4da2c39be 100644 --- a/modules/node/src/channel/channel.provider.ts +++ b/modules/node/src/channel/channel.provider.ts @@ -1,7 +1,7 @@ import { MethodResults, NodeResponses } from "@connext/types"; import { MessagingService } from "@connext/messaging"; import { FactoryProvider } from "@nestjs/common/interfaces"; -import { utils, constants } from "ethers"; +import { utils, constants, BigNumber } from "ethers"; import { AuthService } from "../auth/auth.service"; import { LoggerService } from "../logger/logger.service"; @@ -71,10 +71,12 @@ class ChannelMessaging extends AbstractMessagingProvider { chainId, ); try { + const requestedTarget = data.amount ? BigNumber.from(data.amount) : undefined; const response = await this.channelService.rebalance( channel.multisigAddress, getAddress(data.assetId || constants.AddressZero), RebalanceType.COLLATERALIZE, + requestedTarget, ); return ( response && { diff --git a/modules/test-runner/src/collateral/request.test.ts b/modules/test-runner/src/collateral/request.test.ts index 4558d7f9f4..842a7ed4cb 100644 --- a/modules/test-runner/src/collateral/request.test.ts +++ b/modules/test-runner/src/collateral/request.test.ts @@ -1,5 +1,5 @@ import { IConnextClient, EventNames } from "@connext/types"; -import { constants } from "ethers"; +import { constants, utils } from "ethers"; import { createClient, ETH_AMOUNT_MD, expect, getTestLoggers, TOKEN_AMOUNT } from "../util"; @@ -49,6 +49,19 @@ describe(name, () => { expect(freeBalance[nodeSignerAddress]).to.be.least(TOKEN_AMOUNT); }); + it.only("should collateralize tokens with a target", async () => { + const requestedTarget = utils.parseEther("50"); // 20 < requested < 100 + const response = (await client.requestCollateral(tokenAddress, requestedTarget))!; + expect(response).to.be.ok; + expect(response.completed).to.be.ok; + expect(response.transaction).to.be.ok; + expect(response.transaction.hash).to.be.ok; + expect(response.depositAppIdentityHash).to.be.ok; + const { freeBalance } = await response.completed(); + expect(freeBalance[client.signerAddress]).to.be.eq(Zero); + expect(freeBalance[nodeSignerAddress]).to.be.least(requestedTarget); + }); + it("should properly handle concurrent collateral requests", async () => { const appDef = client.config.contractAddresses[client.chainId].DepositApp; let depositAppCount = 0; diff --git a/modules/types/src/api.ts b/modules/types/src/api.ts index 1d86bb816e..4c06529ed6 100644 --- a/modules/types/src/api.ts +++ b/modules/types/src/api.ts @@ -1,5 +1,5 @@ import { AppRegistry } from "./app"; -import { providers } from "ethers"; +import { providers, BigNumberish } from "ethers"; import { Address, @@ -78,7 +78,10 @@ export interface INodeApiClient { paymentId: string, conditionType: ConditionalTransferTypes, ): Promise; - requestCollateral(assetId: Address): Promise; + requestCollateral( + assetId: Address, + amount?: BigNumberish, + ): Promise; fetchLinkedTransfer(paymentId: Bytes32): Promise; fetchSignedTransfer(paymentId: Bytes32): Promise; fetchGraphTransfer(paymentId: Bytes32): Promise; diff --git a/modules/types/src/client.ts b/modules/types/src/client.ts index 5682ccc036..e1b456494e 100644 --- a/modules/types/src/client.ts +++ b/modules/types/src/client.ts @@ -1,4 +1,4 @@ -import { providers } from "ethers"; +import { providers, BigNumberish } from "ethers"; import { AppRegistry, DefaultApp, AppInstanceJson } from "./app"; import { Address, Bytes32, DecString, PublicIdentifier } from "./basic"; @@ -117,7 +117,7 @@ export interface IConnextClient { subscribeToSwapRates(from: Address, to: Address, callback: any): Promise; getLatestSwapRate(from: Address, to: Address): Promise; unsubscribeToSwapRates(from: Address, to: Address): Promise; - requestCollateral(tokenAddress: Address): Promise; + requestCollateral(tokenAddress: Address, amount?: BigNumberish): Promise; getRebalanceProfile(assetId?: Address): Promise; getTransferHistory(): Promise; reclaimPendingAsyncTransfers(): Promise; From 667c601d7e29adfa8520aa9b1f7b1b96a37bd59a Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 16:23:19 +0200 Subject: [PATCH 09/10] Remove .only --- modules/test-runner/src/collateral/request.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/test-runner/src/collateral/request.test.ts b/modules/test-runner/src/collateral/request.test.ts index 842a7ed4cb..7ac14ca135 100644 --- a/modules/test-runner/src/collateral/request.test.ts +++ b/modules/test-runner/src/collateral/request.test.ts @@ -49,7 +49,7 @@ describe(name, () => { expect(freeBalance[nodeSignerAddress]).to.be.least(TOKEN_AMOUNT); }); - it.only("should collateralize tokens with a target", async () => { + it("should collateralize tokens with a target", async () => { const requestedTarget = utils.parseEther("50"); // 20 < requested < 100 const response = (await client.requestCollateral(tokenAddress, requestedTarget))!; expect(response).to.be.ok; From 2dfbd5409b3f771fe6d0d7c819e486fd1aa35c18 Mon Sep 17 00:00:00 2001 From: Rahul Sethuram Date: Fri, 28 Aug 2020 16:38:34 +0200 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=93=9D=20Some=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/reference/node.md | 29 +++++++++++++++++++++++++++++ ops/start-indra.sh | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/src/reference/node.md diff --git a/docs/src/reference/node.md b/docs/src/reference/node.md new file mode 100644 index 0000000000..fbbbce0d8a --- /dev/null +++ b/docs/src/reference/node.md @@ -0,0 +1,29 @@ +# Node + +## Configuration + +### Environment Variables + +The following environment variables are used by the node: + +| Variable Name | Type | Description | Example | +| --------------------------------------- | ----------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `INDRA_ADMIN_TOKEN` | String | Token for administrative functions. | cxt1234 | +| `INDRA_CHAIN_PROVIDERS` | JSON String | Mapping of chainId to ethProviderUrl | '{"1":"https://mainnet.infura.io/v3/TOKEN","4":"https://rinkeby.infura.io/v3/TOKEN"}' | +| `INDRA_CONTRACT_ADDRESSES` | JSON String | Contract information, keyed by chainId | '{ "1337": { "ChallengeRegistry": { "address": "0x8CdaF0CD259887258Bc13a92C0a6dA92698644C0", "creationCodeHash": "0x42eba77f58ecb5c1352e9a62df1eed73aa1a89890ff73be1939f884f62d88c46", "runtimeCodeHash": "0xc38bff65185807f2babc2ae1334b0bdcf5fe0192ae041e3033b2084c61f80950", "txHash": "0x89f705aefdffa59061d97488e4507a7af4a4751462e100b8ed3fb1f5cc2238af" }, ...}' | +| `INDRA_DEFAULT_REBALANCE_PROFILE_ETH` | JSON String | Rebalance Profile to use by default | '{"collateralizeThreshold":"500000000000","target":"1500000000000","reclaimThreshold":"10000000000000"}' | +| `INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN` | JSON String | Rebalance Profile to use by default (real units) | '{"collateralizeThreshold":"500000000000","target":"1500000000000","reclaimThreshold":"10000000000000"} | +| `INDRA_LOG_LEVEL` | Number | Log level - 1 = Error, 4 = Debug | 3 | +| `INDRA_MNEMONIC_FILE` | +| `INDRA_NATS_JWT_SIGNER_PRIVATE_KEY` | +| `INDRA_NATS_JWT_SIGNER_PUBLIC_KEY` | +| `INDRA_NATS_SERVERS` | +| `INDRA_NATS_WS_ENDPOINT` | +| `INDRA_PG_DATABASE` | +| `INDRA_PG_HOST` | +| `INDRA_PG_PASSWORD_FILE` | +| `INDRA_PG_PORT` | +| `INDRA_PG_USERNAME` | +| `INDRA_PORT` | +| `INDRA_REDIS_URL` | +| | diff --git a/ops/start-indra.sh b/ops/start-indra.sh index 018e86f190..6cc85eb308 100644 --- a/ops/start-indra.sh +++ b/ops/start-indra.sh @@ -360,7 +360,6 @@ services: INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN: '$INDRA_DEFAULT_REBALANCE_PROFILE_TOKEN' INDRA_LOG_LEVEL: '$INDRA_LOG_LEVEL' INDRA_MNEMONIC_FILE: '$INDRA_MNEMONIC_FILE' - INDRA_MAX_CHANNEL_COLLATERALIZATION: '$INDRA_MAX_CHANNEL_COLLATERALIZATION' INDRA_NATS_JWT_SIGNER_PRIVATE_KEY: '$INDRA_NATS_JWT_SIGNER_PRIVATE_KEY' INDRA_NATS_JWT_SIGNER_PUBLIC_KEY: '$INDRA_NATS_JWT_SIGNER_PUBLIC_KEY' INDRA_NATS_SERVERS: 'nats://nats:$nats_port'