feat: balances (#476)

This commit is contained in:
Anthony Potdevin 2022-08-07 14:27:05 -05:00 committed by GitHub
parent 8d45e297e4
commit 29fb50ab7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 601 additions and 268 deletions

View file

@ -231,9 +231,20 @@ type ClosedChannel {
transaction_vout: Float!
}
enum ConfigFields {
BACKUPS
CHANNELS_PUSH
HEALTHCHECKS
ONCHAIN_PUSH
PRIVATE_CHANNELS_PUSH
}
type ConfigState {
backup_state: Boolean!
channels_push_enabled: Boolean!
healthcheck_ping_state: Boolean!
onchain_push_enabled: Boolean!
private_channels_push_enabled: Boolean!
}
type CreateBoltzReverseSwapType {
@ -429,8 +440,7 @@ type Mutation {
removeTwofaSecret(token: String!): Boolean!
sendMessage(maxFee: Float, message: String!, messageType: String, publicKey: String!, tokens: Float): Float!
sendToAddress(address: String!, fee: Float, sendAll: Boolean, target: Float, tokens: Float): ChainAddressSend!
toggleAutoBackups: Boolean!
toggleHealthPings: Boolean!
toggleConfig(field: ConfigFields!): Boolean!
updateFees(base_fee_tokens: Float, cltv_delta: Float, fee_rate: Float, max_htlc_mtokens: String, min_htlc_mtokens: String, transaction_id: String, transaction_vout: Float): Boolean!
updateMultipleFees(channels: [UpdateRoutingFeesParams!]!): Boolean!
updateTwofaSecret(secret: String!, token: String!): Boolean!

View file

@ -1,61 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ToggleAutoBackupsMutationVariables = Types.Exact<{
[key: string]: never;
}>;
export type ToggleAutoBackupsMutation = {
__typename?: 'Mutation';
toggleAutoBackups: boolean;
};
export const ToggleAutoBackupsDocument = gql`
mutation ToggleAutoBackups {
toggleAutoBackups
}
`;
export type ToggleAutoBackupsMutationFn = Apollo.MutationFunction<
ToggleAutoBackupsMutation,
ToggleAutoBackupsMutationVariables
>;
/**
* __useToggleAutoBackupsMutation__
*
* To run a mutation, you first call `useToggleAutoBackupsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useToggleAutoBackupsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [toggleAutoBackupsMutation, { data, loading, error }] = useToggleAutoBackupsMutation({
* variables: {
* },
* });
*/
export function useToggleAutoBackupsMutation(
baseOptions?: Apollo.MutationHookOptions<
ToggleAutoBackupsMutation,
ToggleAutoBackupsMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
ToggleAutoBackupsMutation,
ToggleAutoBackupsMutationVariables
>(ToggleAutoBackupsDocument, options);
}
export type ToggleAutoBackupsMutationHookResult = ReturnType<
typeof useToggleAutoBackupsMutation
>;
export type ToggleAutoBackupsMutationResult =
Apollo.MutationResult<ToggleAutoBackupsMutation>;
export type ToggleAutoBackupsMutationOptions = Apollo.BaseMutationOptions<
ToggleAutoBackupsMutation,
ToggleAutoBackupsMutationVariables
>;

View file

@ -0,0 +1,62 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ToggleConfigMutationVariables = Types.Exact<{
field: Types.ConfigFields;
}>;
export type ToggleConfigMutation = {
__typename?: 'Mutation';
toggleConfig: boolean;
};
export const ToggleConfigDocument = gql`
mutation ToggleConfig($field: ConfigFields!) {
toggleConfig(field: $field)
}
`;
export type ToggleConfigMutationFn = Apollo.MutationFunction<
ToggleConfigMutation,
ToggleConfigMutationVariables
>;
/**
* __useToggleConfigMutation__
*
* To run a mutation, you first call `useToggleConfigMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useToggleConfigMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [toggleConfigMutation, { data, loading, error }] = useToggleConfigMutation({
* variables: {
* field: // value for 'field'
* },
* });
*/
export function useToggleConfigMutation(
baseOptions?: Apollo.MutationHookOptions<
ToggleConfigMutation,
ToggleConfigMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
ToggleConfigMutation,
ToggleConfigMutationVariables
>(ToggleConfigDocument, options);
}
export type ToggleConfigMutationHookResult = ReturnType<
typeof useToggleConfigMutation
>;
export type ToggleConfigMutationResult =
Apollo.MutationResult<ToggleConfigMutation>;
export type ToggleConfigMutationOptions = Apollo.BaseMutationOptions<
ToggleConfigMutation,
ToggleConfigMutationVariables
>;

View file

@ -1,61 +0,0 @@
import * as Types from '../../types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ToggleHealthPingsMutationVariables = Types.Exact<{
[key: string]: never;
}>;
export type ToggleHealthPingsMutation = {
__typename?: 'Mutation';
toggleHealthPings: boolean;
};
export const ToggleHealthPingsDocument = gql`
mutation ToggleHealthPings {
toggleHealthPings
}
`;
export type ToggleHealthPingsMutationFn = Apollo.MutationFunction<
ToggleHealthPingsMutation,
ToggleHealthPingsMutationVariables
>;
/**
* __useToggleHealthPingsMutation__
*
* To run a mutation, you first call `useToggleHealthPingsMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useToggleHealthPingsMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [toggleHealthPingsMutation, { data, loading, error }] = useToggleHealthPingsMutation({
* variables: {
* },
* });
*/
export function useToggleHealthPingsMutation(
baseOptions?: Apollo.MutationHookOptions<
ToggleHealthPingsMutation,
ToggleHealthPingsMutationVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
ToggleHealthPingsMutation,
ToggleHealthPingsMutationVariables
>(ToggleHealthPingsDocument, options);
}
export type ToggleHealthPingsMutationHookResult = ReturnType<
typeof useToggleHealthPingsMutation
>;
export type ToggleHealthPingsMutationResult =
Apollo.MutationResult<ToggleHealthPingsMutation>;
export type ToggleHealthPingsMutationOptions = Apollo.BaseMutationOptions<
ToggleHealthPingsMutation,
ToggleHealthPingsMutationVariables
>;

View file

@ -1,7 +0,0 @@
import { gql } from '@apollo/client';
export const TOGGLE_AUTO_BACKUPS = gql`
mutation ToggleAutoBackups {
toggleAutoBackups
}
`;

View file

@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const TOGGLE_CONFIG = gql`
mutation ToggleConfig($field: ConfigFields!) {
toggleConfig(field: $field)
}
`;

View file

@ -1,7 +0,0 @@
import { gql } from '@apollo/client';
export const TOGGLE_HEALTH_PINGS = gql`
mutation ToggleHealthPings {
toggleHealthPings
}
`;

View file

@ -13,6 +13,9 @@ export type GetConfigStateQuery = {
__typename?: 'ConfigState';
backup_state: boolean;
healthcheck_ping_state: boolean;
onchain_push_enabled: boolean;
channels_push_enabled: boolean;
private_channels_push_enabled: boolean;
};
};
@ -21,6 +24,9 @@ export const GetConfigStateDocument = gql`
getConfigState {
backup_state
healthcheck_ping_state
onchain_push_enabled
channels_push_enabled
private_channels_push_enabled
}
}
`;

View file

@ -5,6 +5,9 @@ export const GET_CONFIG_STATE = gql`
getConfigState {
backup_state
healthcheck_ping_state
onchain_push_enabled
channels_push_enabled
private_channels_push_enabled
}
}
`;

View file

@ -275,10 +275,21 @@ export type ClosedChannel = {
transaction_vout: Scalars['Float'];
};
export enum ConfigFields {
Backups = 'BACKUPS',
ChannelsPush = 'CHANNELS_PUSH',
Healthchecks = 'HEALTHCHECKS',
OnchainPush = 'ONCHAIN_PUSH',
PrivateChannelsPush = 'PRIVATE_CHANNELS_PUSH',
}
export type ConfigState = {
__typename?: 'ConfigState';
backup_state: Scalars['Boolean'];
channels_push_enabled: Scalars['Boolean'];
healthcheck_ping_state: Scalars['Boolean'];
onchain_push_enabled: Scalars['Boolean'];
private_channels_push_enabled: Scalars['Boolean'];
};
export type CreateBoltzReverseSwapType = {
@ -493,8 +504,7 @@ export type Mutation = {
removeTwofaSecret: Scalars['Boolean'];
sendMessage: Scalars['Float'];
sendToAddress: ChainAddressSend;
toggleAutoBackups: Scalars['Boolean'];
toggleHealthPings: Scalars['Boolean'];
toggleConfig: Scalars['Boolean'];
updateFees: Scalars['Boolean'];
updateMultipleFees: Scalars['Boolean'];
updateTwofaSecret: Scalars['Boolean'];
@ -651,6 +661,10 @@ export type MutationSendToAddressArgs = {
tokens?: InputMaybe<Scalars['Float']>;
};
export type MutationToggleConfigArgs = {
field: ConfigFields;
};
export type MutationUpdateFeesArgs = {
base_fee_tokens?: InputMaybe<Scalars['Float']>;
cltv_delta?: InputMaybe<Scalars['Float']>;

View file

@ -9,11 +9,13 @@ import {
SubTitle,
} from '../../components/generic/Styled';
import styled from 'styled-components';
import { useToggleAutoBackupsMutation } from '../../graphql/mutations/__generated__/toggleAutoBackups.generated';
import { getErrorContent } from '../../utils/error';
import { toast } from 'react-toastify';
import { useGetConfigStateQuery } from '../../graphql/queries/__generated__/getConfigState.generated';
import { useToggleHealthPingsMutation } from '../../graphql/mutations/__generated__/toggleHealthPings.generated';
import { useToggleConfigMutation } from '../../graphql/mutations/__generated__/toggleConfig.generated';
import { ConfigFields } from '../../graphql/types';
import { VFC } from 'react';
import { LoadingCard } from '../../components/loading/LoadingCard';
const NoWrapText = styled.div`
white-space: nowrap;
@ -22,68 +24,31 @@ const NoWrapText = styled.div`
const InputTitle = styled(NoWrapText)``;
const AutoBackups = () => {
const { data, loading } = useGetConfigStateQuery({
onError: err => toast.error(getErrorContent(err)),
});
const [toggle, { loading: toggleLoading }] = useToggleAutoBackupsMutation({
const ConfigFieldToggle: VFC<{
title: string;
enabled: boolean;
field: ConfigFields;
}> = ({ title, enabled, field }) => {
const [toggle, { loading }] = useToggleConfigMutation({
refetchQueries: ['GetConfigState'],
onError: err => toast.error(getErrorContent(err)),
});
const enabled = data?.getConfigState.backup_state || false;
return (
<SingleLine>
<InputTitle>Auto Backups</InputTitle>
<MultiButton loading={loading || toggleLoading} width="103px">
<InputTitle>{title}</InputTitle>
<MultiButton loading={loading} width="103px">
<SingleButton
disabled={loading || toggleLoading}
disabled={loading}
selected={enabled}
onClick={toggle}
onClick={() => toggle({ variables: { field } })}
>
Yes
</SingleButton>
<SingleButton
disabled={loading || toggleLoading}
disabled={loading}
selected={!enabled}
onClick={toggle}
>
No
</SingleButton>
</MultiButton>
</SingleLine>
);
};
const HealthPings = () => {
const { data, loading } = useGetConfigStateQuery({
onError: err => toast.error(getErrorContent(err)),
});
const [toggle, { loading: toggleLoading }] = useToggleHealthPingsMutation({
refetchQueries: ['GetConfigState'],
onError: err => toast.error(getErrorContent(err)),
});
const enabled = data?.getConfigState.healthcheck_ping_state || false;
return (
<SingleLine>
<InputTitle>Healthcheck Pings</InputTitle>
<MultiButton loading={loading || toggleLoading} width="103px">
<SingleButton
disabled={loading || toggleLoading}
selected={enabled}
onClick={toggle}
>
Yes
</SingleButton>
<SingleButton
disabled={loading || toggleLoading}
selected={!enabled}
onClick={toggle}
onClick={() => toggle({ variables: { field } })}
>
No
</SingleButton>
@ -93,12 +58,55 @@ const HealthPings = () => {
};
export const AmbossSettings = () => {
const { data, loading } = useGetConfigStateQuery({
onError: err => toast.error(getErrorContent(err)),
});
if (loading) {
return <LoadingCard title="Amboss" />;
}
if (!data?.getConfigState) {
return null;
}
const {
backup_state,
channels_push_enabled,
healthcheck_ping_state,
onchain_push_enabled,
private_channels_push_enabled,
} = data.getConfigState;
return (
<CardWithTitle>
<SubTitle>Amboss</SubTitle>
<Card>
<AutoBackups />
<HealthPings />
<ConfigFieldToggle
field={ConfigFields.Backups}
enabled={backup_state}
title={'Auto Backups'}
/>
<ConfigFieldToggle
field={ConfigFields.Healthchecks}
enabled={healthcheck_ping_state}
title={'Healthcheck Pings'}
/>
<ConfigFieldToggle
field={ConfigFields.OnchainPush}
enabled={onchain_push_enabled}
title={'Onchain Push'}
/>
<ConfigFieldToggle
field={ConfigFields.ChannelsPush}
enabled={channels_push_enabled}
title={'Channels Push'}
/>
<ConfigFieldToggle
field={ConfigFields.PrivateChannelsPush}
enabled={private_channels_push_enabled}
title={'Private Channel Push'}
/>
</Card>
</CardWithTitle>
);

View file

@ -47,6 +47,7 @@ type SubscriptionsConfig = {
type AmbossConfig = {
disableHealthCheckPings: boolean;
disableBalancePushes: boolean;
};
type ConfigType = {
@ -132,6 +133,7 @@ export default (): ConfigType => {
const amboss = {
disableHealthCheckPings: process.env.DISABLE_HEALTHCHECK_PINGS === 'true',
disableBalancePushes: process.env.DISABLE_BALANCE_PUSHES === 'true',
};
const config: ConfigType = {

View file

@ -90,3 +90,9 @@ export const pingHealthCheckMutation = gql`
healthCheck(signature: $signature, timestamp: $timestamp)
}
`;
export const pushBalancesMutation = gql`
mutation PushBalances($input: BalancePushInput!) {
pushBalances(input: $input)
}
`;

View file

@ -7,10 +7,15 @@ import { getNetwork } from 'src/server/utils/network';
import { Logger } from 'winston';
import { AccountsService } from '../../accounts/accounts.service';
import { FetchService } from '../../fetch/fetch.service';
import { pingHealthCheckMutation, saveBackupMutation } from './amboss.gql';
import {
pingHealthCheckMutation,
pushBalancesMutation,
saveBackupMutation,
} from './amboss.gql';
import { auto, map, each } from 'async';
import { NodeService } from '../../node/node.service';
import { UserConfigService } from '../userConfig/userConfig.service';
import { getSHA256Hash } from 'src/server/utils/crypto';
const ONE_MINUTE = 60 * 1000;
@ -63,10 +68,37 @@ export class AmbossService {
}
}
async pushBalancesToAmboss(
timestamp: string,
signature: string,
onchainBalance: string,
channels: { balance: string; capacity: string; chan_id: string }[]
) {
const { data, error } = await this.fetchService.graphqlFetchWithProxy(
this.ambossUrl,
pushBalancesMutation,
{ input: { signature, timestamp, channels, onchainBalance } }
);
if (!data?.pushBalances || error) {
this.logger.error('Error pushing balances to Amboss', {
error,
data,
});
throw new Error('Error pushing balances to Amboss');
}
}
@Interval(ONE_MINUTE)
async ping() {
const isProduction = this.configService.get('isProduction');
const disabled = this.configService.get('amboss.disableHealthCheckPings');
if (!isProduction) {
this.logger.silly('Health check pings are only sent in production');
return;
}
if (disabled) {
this.logger.silly('Healthchecks are disabled in the server.');
return;
@ -150,17 +182,7 @@ export class AmbossService {
pingAmboss: [
'checkAvailable',
async ({ checkAvailable }) => {
const isProduction = this.configService.get('isProduction');
await each(checkAvailable, async node => {
if (!isProduction) {
this.logger.silly(
'Health check pings are only sent in production',
{ node: node.name }
);
return;
}
if (node.network !== 'btc') {
this.logger.silly(
'Health check pings are only sent for mainnet',
@ -193,4 +215,208 @@ export class AmbossService {
this.logger.error(error.message);
});
}
@Interval(ONE_MINUTE)
async pushBalances() {
const isProduction = this.configService.get('isProduction');
const disabled = this.configService.get('amboss.disableBalancePushes');
if (!isProduction) {
this.logger.silly('Balance pushes are only sent in production');
return;
}
if (disabled) {
this.logger.silly('Balance pushes are disabled in the server.');
return;
}
const {
onchainPushEnabled,
channelPushEnabled,
privateChannelPushEnabled,
} = this.userConfigService.getConfig();
if (
!channelPushEnabled &&
!privateChannelPushEnabled &&
!onchainPushEnabled
) {
this.logger.silly('Balance pushes are disabled.');
return;
}
await auto({
// Get Authenticated LND objects for each node
getNodes: async () => {
const accounts = this.accountsService.getAllAccounts();
const validAccounts = [];
for (const key in accounts) {
if (accounts.hasOwnProperty(key)) {
const account = accounts[key];
if (!account.encrypted) {
validAccounts.push({ id: account.hash, lnd: account.lnd });
}
}
}
return validAccounts;
},
// Try to connect to nodes
checkNodes: [
'getNodes',
async ({ getNodes }) => {
return map(getNodes, async ({ lnd, id }) => {
try {
const info = await getWalletInfo({ lnd });
const network = getNetwork(info?.chains?.[0] || '');
const sliced = info.public_key.slice(0, 10);
const name = `${info.alias}(${sliced})[${network}]`;
return {
id,
name,
pubkey: info.public_key,
lnd,
network,
};
} catch (err) {
this.logger.error('Error connecting to node', {
id,
err,
});
}
});
},
],
// Check which nodes are available and remove duplicates
checkAvailable: [
'checkNodes',
async ({ checkNodes }: { checkNodes: NodeType[] }) => {
const unique = checkNodes.filter(Boolean);
if (!unique.length) {
throw new Error('No node available for balance pushes');
}
const names = unique.map(a => a.name);
this.logger.silly(
`Connected to ${names.join(', ')} for balance pushes`
);
return unique;
},
],
pingAmboss: [
'checkAvailable',
async ({ checkAvailable }) => {
await each(checkAvailable, async node => {
if (node.network !== 'btc') {
this.logger.silly('Balance pushes are only sent for mainnet', {
node: node.name,
});
return;
}
let onchain;
let message = '';
if (onchainPushEnabled) {
const { chain_balance } = await this.nodeService.getChainBalance(
node.id
);
const { pending_chain_balance } =
await this.nodeService.getPendingChainBalance(node.id);
onchain = (chain_balance + pending_chain_balance).toString();
message += onchain;
}
const allChannels = [];
if (channelPushEnabled) {
const channels = await this.nodeService.getChannels(node.id, {
is_public: true,
});
if (!channels.channels.length) return;
const mapped = channels.channels.map(c => ({
chan_id: c.id,
balance: c.local_balance + '',
capacity: c.capacity + '',
}));
allChannels.push(...mapped);
}
if (privateChannelPushEnabled) {
const privateChannels = await this.nodeService.getChannels(
node.id,
{ is_private: true }
);
if (!privateChannels.channels.length) return;
const mapped = privateChannels.channels.map(c => ({
chan_id: c.id,
balance: c.local_balance + '',
capacity: c.capacity + '',
}));
allChannels.push(...mapped);
}
if (allChannels.length) {
const infoString = allChannels.reduce((p, c) => {
return p + `${c.chan_id}${c.balance}${c.capacity || ''}`;
}, '');
message += getSHA256Hash(infoString);
}
const timestamp = new Date().toISOString();
const finalMessage = timestamp + message;
const { signature } = await this.nodeService.signMessage(
node.id,
finalMessage
);
this.logger.info('Push Info', {
onchainBalance: !!onchain,
amountOfChannels: allChannels.length,
finalMessage,
signature,
});
await this.pushBalancesToAmboss(
timestamp,
signature,
onchain,
allChannels
);
});
},
],
})
.then(result => {
const nodes = result.checkAvailable.length;
this.logger.silly(
`Finished balance pushes for ${nodes} node${
nodes.length > 1 ? 's' : ''
}.`
);
})
.catch(error => {
this.logger.error(error.message);
});
}
}

View file

@ -1,10 +1,19 @@
import { Inject } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql';
import {
Args,
Mutation,
Query,
registerEnumType,
ResolveField,
Resolver,
} from '@nestjs/graphql';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { UserConfigService } from './userConfig.service';
import { ConfigState } from './userConfig.types';
import { ConfigFields, ConfigState } from './userConfig.types';
registerEnumType(ConfigFields, { name: 'ConfigFields' });
@Resolver(ConfigState)
export class UserConfigStateResolver {
@ -51,6 +60,63 @@ export class UserConfigStateResolver {
return healthCheckPingEnabled;
}
@ResolveField()
onchain_push_enabled() {
const { onchainPushEnabled } = this.userConfigService.getConfig();
const disabled = this.configService.get('amboss.disableBalancePushes');
if (disabled) {
if (onchainPushEnabled) {
this.logger.warn(
'Balance pushes are enabled in the config file but disabled in the env file.'
);
}
return false;
}
return onchainPushEnabled;
}
@ResolveField()
channels_push_enabled() {
const { channelPushEnabled } = this.userConfigService.getConfig();
const disabled = this.configService.get('amboss.disableBalancePushes');
if (disabled) {
if (channelPushEnabled) {
this.logger.warn(
'Balance pushes are enabled in the config file but disabled in the env file.'
);
}
return false;
}
return channelPushEnabled;
}
@ResolveField()
private_channels_push_enabled() {
const { privateChannelPushEnabled } = this.userConfigService.getConfig();
const disabled = this.configService.get('amboss.disableBalancePushes');
if (disabled) {
if (privateChannelPushEnabled) {
this.logger.warn(
'Balance pushes are enabled in the config file but disabled in the env file.'
);
}
return false;
}
return privateChannelPushEnabled;
}
}
@Resolver()
@ -66,28 +132,57 @@ export class UserConfigResolver {
}
@Mutation(() => Boolean)
async toggleAutoBackups() {
const disabled = this.configService.get('subscriptions.disableBackups');
async toggleConfig(
@Args('field', { type: () => ConfigFields }) field: ConfigFields
) {
switch (field) {
case ConfigFields.BACKUPS: {
const disabled = this.configService.get('subscriptions.disableBackups');
if (disabled) {
throw new Error('Auto backups is disabled in the server.');
if (disabled) {
throw new Error('Auto backups are disabled in the server.');
}
this.userConfigService.toggleAutoBackups();
break;
}
case ConfigFields.HEALTHCHECKS: {
const disabled = this.configService.get(
'amboss.disableHealthCheckPings'
);
if (disabled) {
throw new Error('Healthcheck pings are disabled in the server.');
}
this.userConfigService.toggleHealthCheckPing();
break;
}
case ConfigFields.ONCHAIN_PUSH:
case ConfigFields.CHANNELS_PUSH:
case ConfigFields.PRIVATE_CHANNELS_PUSH: {
const disabled = this.configService.get('amboss.disableBalancePushes');
if (disabled) {
throw new Error('Balance pushes are disabled in the server.');
}
switch (field) {
case ConfigFields.ONCHAIN_PUSH:
this.userConfigService.toggleOnChainPush();
break;
case ConfigFields.CHANNELS_PUSH:
this.userConfigService.toggleChannelPush();
break;
case ConfigFields.PRIVATE_CHANNELS_PUSH:
this.userConfigService.togglePrivateChannelPush();
break;
}
break;
}
}
this.userConfigService.toggleAutoBackups();
return true;
}
@Mutation(() => Boolean)
async toggleHealthPings() {
const disabled = this.configService.get('amboss.disableHealthCheckPings');
if (disabled) {
throw new Error('Healthcheck pings is disabled in the server.');
}
this.userConfigService.toggleHealthCheckPing();
return true;
}
}

View file

@ -27,59 +27,66 @@ export class UserConfigService implements OnModuleInit {
this.logger.info(
`No account config file found at path ${accountConfigPath}`
);
return { backupsEnabled: false, healthCheckPingEnabled: false };
return {
backupsEnabled: false,
healthCheckPingEnabled: false,
onchainPushEnabled: false,
channelPushEnabled: false,
privateChannelPushEnabled: false,
};
}
return {
backupsEnabled: !!yaml.backupsEnabled,
healthCheckPingEnabled: !!yaml.healthCheckPingEnabled,
onchainPushEnabled: !!yaml.onchainPushEnabled,
channelPushEnabled: !!yaml.channelPushEnabled,
privateChannelPushEnabled: !!yaml.privateChannelPushEnabled,
};
}
toggleValue(field: string): void {
const accountConfigPath = this.configService.get('accountConfigPath');
if (!accountConfigPath) {
this.logger.verbose('No config file path provided');
throw new Error('Error enabling auto backups');
}
const accountConfig = this.filesService.parseYaml(accountConfigPath);
if (!accountConfig) {
this.logger.info(`No config file found at path ${accountConfigPath}`);
throw new Error('Error enabling auto backups');
}
const currentStatus = accountConfig[field];
const configCopy = {
...accountConfig,
[field]: !currentStatus,
};
this.config = configCopy;
this.filesService.saveHashedYaml(configCopy, accountConfigPath);
}
togglePrivateChannelPush(): void {
this.toggleValue('privateChannelPushEnabled');
}
toggleChannelPush(): void {
this.toggleValue('channelPushEnabled');
}
toggleOnChainPush(): void {
this.toggleValue('onchainPushEnabled');
}
toggleHealthCheckPing(): void {
const accountConfigPath = this.configService.get('accountConfigPath');
if (!accountConfigPath) {
this.logger.verbose('No config file path provided');
throw new Error('Error enabling auto backups');
}
const accountConfig = this.filesService.parseYaml(accountConfigPath);
if (!accountConfig) {
this.logger.info(`No config file found at path ${accountConfigPath}`);
throw new Error('Error enabling auto backups');
}
const currentStatus = accountConfig.healthCheckPingEnabled;
const configCopy = {
...accountConfig,
healthCheckPingEnabled: !currentStatus,
};
this.config = configCopy;
this.filesService.saveHashedYaml(configCopy, accountConfigPath);
this.toggleValue('healthCheckPingEnabled');
}
toggleAutoBackups(): void {
const accountConfigPath = this.configService.get('accountConfigPath');
if (!accountConfigPath) {
this.logger.verbose('No config file path provided');
throw new Error('Error enabling auto backups');
}
const accountConfig = this.filesService.parseYaml(accountConfigPath);
if (!accountConfig) {
this.logger.info(`No config file found at path ${accountConfigPath}`);
throw new Error('Error enabling auto backups');
}
const currentStatus = accountConfig.backupsEnabled;
const configCopy = { ...accountConfig, backupsEnabled: !currentStatus };
this.config = configCopy;
this.filesService.saveHashedYaml(configCopy, accountConfigPath);
this.toggleValue('backupsEnabled');
}
}

View file

@ -1,9 +1,23 @@
import { Field, ObjectType } from '@nestjs/graphql';
export enum ConfigFields {
BACKUPS = 'BACKUPS',
HEALTHCHECKS = 'HEALTHCHECKS',
ONCHAIN_PUSH = 'ONCHAIN_PUSH',
CHANNELS_PUSH = 'CHANNELS_PUSH',
PRIVATE_CHANNELS_PUSH = 'PRIVATE_CHANNELS_PUSH',
}
@ObjectType()
export class ConfigState {
@Field()
backup_state: boolean;
@Field()
healthcheck_ping_state: boolean;
@Field()
onchain_push_enabled: boolean;
@Field()
channels_push_enabled: boolean;
@Field()
private_channels_push_enabled: boolean;
}

View file

@ -6,6 +6,9 @@ import { Agent } from 'https';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { DocumentNode, GraphQLError, print } from 'graphql';
type Variables = {
[key: string]: string | number | string[] | boolean | any[] | Variables;
};
@Injectable()
export class FetchService {
agent: Agent | null = null;
@ -31,7 +34,7 @@ export class FetchService {
async graphqlFetchWithProxy(
url: string,
query: DocumentNode,
variables?: { [key: string]: string | number | string[] | boolean },
variables?: Variables,
headers?: { [key: string]: string | number | string[] | boolean }
): Promise<{
data: any;

View file

@ -47,10 +47,16 @@ export type AccountConfigType = {
defaultNetwork: string | null;
backupsEnabled: boolean | null;
healthCheckPingEnabled: boolean | null;
onchainPushEnabled: boolean | null;
channelPushEnabled: boolean | null;
privateChannelPushEnabled: boolean | null;
accounts: AccountType[];
};
export type ConfigType = {
backupsEnabled: boolean;
healthCheckPingEnabled: boolean;
onchainPushEnabled: boolean;
channelPushEnabled: boolean;
privateChannelPushEnabled: boolean;
};