Skip to content
This repository has been archived by the owner on Aug 28, 2019. It is now read-only.

Commit

Permalink
feat: upload LND multi channel backups
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Apr 28, 2019
1 parent 892f84d commit 256ceed
Show file tree
Hide file tree
Showing 9 changed files with 697 additions and 176 deletions.
1 change: 1 addition & 0 deletions lib/BoltzMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class BoltzMiddleware {
this.logger,
this.config.dbpath,
this.config.backup,
this.boltzClient,
new Report(
this.service.swapRepository,
this.service.reverseSwapRepository,
Expand Down
58 changes: 41 additions & 17 deletions lib/backup/BackupScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { scheduleJob } from 'node-schedule';
import { Storage, Bucket } from '@google-cloud/storage';
import Logger from '../Logger';
import Report from '../report/Report';
import BoltzClient from '../boltz/BoltzClient';

type BackupConfig = {
email: string;
Expand All @@ -24,6 +25,7 @@ class BackupScheduler {
private logger: Logger,
private dbpath: string,
private config: BackupConfig,
private boltz: BoltzClient,
private report: Report) {

if (
Expand All @@ -44,19 +46,35 @@ class BackupScheduler {

this.bucket = storage.bucket(config.bucketname);

this.subscribeChannelBackups();
this.logger.info('Started channel backup subscription');

this.logger.verbose(`Scheduling database backups: ${this.config.interval}`);
scheduleJob(this.config.interval, async (date) => {
await this.uploadDatabases(date);
await this.uploadReport();
});
}

private static getDate = (date: Date) => {
return `${date.getFullYear()}${BackupScheduler.addLeadingZeros(date.getMonth())}${BackupScheduler.addLeadingZeros(date.getDate())}` +
`-${BackupScheduler.addLeadingZeros(date.getHours())}${BackupScheduler.addLeadingZeros(date.getMinutes())}`;
}

/**
* Adds a leading 0 to the provided number if it is smalled than 10
*/
private static addLeadingZeros = (number: number) => {
return `${number}`.padStart(2, '0');
}

public uploadDatabases = async (date: Date) => {
if (!this.bucket) {
throw 'Backups are disabled because of incomplete configuration';
}

const dateString = this.getDate(date);
this.logger.silly(`Doing database backup at: ${dateString}`);
const dateString = BackupScheduler.getDate(date);
this.logger.silly(`Backing up databases at: ${dateString}`);

await this.uploadFile(this.dbpath, dateString, true);

Expand All @@ -70,37 +88,43 @@ class BackupScheduler {
return;
}

const file = this.bucket.file('report.csv');
const data = await this.report.generate();

await file.save(data);

this.logger.debug('Uploaded report');
await this.uploadString('report.csv', data);
}

private uploadFile = async (fileName: string, date: string, isMiddleware: boolean) => {
try {
const destination = `${isMiddleware ? 'middleware' : 'backend'}/database-${date}.db`;

await this.bucket!.upload(fileName, {
destination: `${isMiddleware ? 'middleware' : 'backend'}/database-${date}.db`,
destination,
});

this.logger.debug(`Uploaded file ${fileName}`);
this.logger.silly(`Uploaded file ${fileName} to: ${destination}`);
} catch (error) {
this.logger.warn(`Could not upload file: ${error}`);
throw error;
}
}

private getDate = (date: Date) => {
return `${date.getFullYear()}${this.addLeadingZeros(date.getMonth())}${this.addLeadingZeros(date.getDay())}` +
`-${this.addLeadingZeros(date.getHours())}${this.addLeadingZeros(date.getMinutes())}`;
private uploadString = async (fileName: string, data: string) => {
try {
const file = this.bucket!.file(fileName);
await file.save(data);

this.logger.silly(`Uploaded data into file: ${fileName}`);
} catch (error) {
this.logger.warn(`Could not upload data to file: ${error}`);
throw error;
}
}

/**
* Adds a leading 0 to the provided number if it is smalled than 10
*/
private addLeadingZeros = (number: number) => {
return `${number}`.padStart(2, '0');
private subscribeChannelBackups = () => {
this.boltz.on('channel.backup', async (currency: string, channelBackup: string, date?: Date) => {
const dateString = BackupScheduler.getDate(date || new Date());

await this.uploadString(`lnd/${currency}/multiChannelBackup-${dateString}`, channelBackup);
});
}
}

Expand Down
40 changes: 33 additions & 7 deletions lib/boltz/BoltzClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ interface BoltzClient {

on(event: 'refund', listener: (lockupTransactionHash: string) => void): this;
emit(event: 'refund', lockupTransactionHash: string): boolean;

on(event: 'channel.backup', listener: (currency: string, channelBackup: string) => void): this;
emit(event: 'channel.backup', currency: string, channelBackup: string): boolean;
}

class BoltzClient extends BaseClient {
Expand All @@ -57,9 +60,10 @@ class BoltzClient extends BaseClient {
private boltz!: GrpcClient | BoltzMethodIndex;
private meta!: grpc.Metadata;

private transactionSubscription?: ClientReadableStream<boltzrpc.SubscribeTransactionsResponse>;
private invoicesSubscription?: ClientReadableStream<boltzrpc.SubscribeInvoicesResponse>;
private refundsSubscription?: ClientReadableStream<boltzrpc.SubscribeRefundsResponse>;
private invoicesSubscription?: ClientReadableStream<boltzrpc.SubscribeInvoicesResponse>;
private transactionSubscription?: ClientReadableStream<boltzrpc.SubscribeTransactionsResponse>;
private channelBackupSubscription?: ClientReadableStream<boltzrpc.ChannelBackup>;

private isReconnecting = false;

Expand Down Expand Up @@ -221,7 +225,7 @@ class BoltzClient extends BaseClient {
/**
* Subscribes to a stream of confirmed transactions to addresses that were specified with "ListenOnAddress"
*/
public subscribeTransactions = () => {
private subscribeTransactions = () => {
if (this.transactionSubscription) {
this.transactionSubscription.cancel();
}
Expand All @@ -242,7 +246,7 @@ class BoltzClient extends BaseClient {
/**
* Subscribes to a stream of settled invoices and those paid by Boltz
*/
public subscribeInvoices = () => {
private subscribeInvoices = () => {
if (this.invoicesSubscription) {
this.invoicesSubscription.cancel();
}
Expand Down Expand Up @@ -282,7 +286,7 @@ class BoltzClient extends BaseClient {
/**
* Subscribes to a stream of lockup transactions that Boltz refunds
*/
public subscribeRefunds = () => {
private subscribeRefunds = () => {
if (this.refundsSubscription) {
this.refundsSubscription.cancel();
}
Expand All @@ -301,6 +305,27 @@ class BoltzClient extends BaseClient {
});
}

/**
* Subscribes to a stream of channel backups
*/
private subscribeChannelBackups = () => {
if (this.channelBackupSubscription) {
this.channelBackupSubscription.cancel();
}

this.channelBackupSubscription = this.boltz.subscribeChannelBackups(new boltzrpc.SubscribeChannelBackupsRequest, this.meta)
.on('data', (response: boltzrpc.ChannelBackup) => {
this.logger.debug(`New ${response.getCurrency()} channel backup`);
this.emit('channel.backup', response.getCurrency(), response.getMultiChannelBackup());
})
.on('error', async (error) => {
this.emit('status.updated', ConnectionStatus.Disconnected);

this.logger.error(`Channel backup subscription errored: ${stringify(error)}`);
await this.startReconnectTimer();
});
}

private startReconnectTimer = async () => {
if (!this.isReconnecting) {
this.isReconnecting = true;
Expand All @@ -323,9 +348,10 @@ class BoltzClient extends BaseClient {

this.isReconnecting = false;

this.subscribeTransactions();
this.subscribeInvoices();
this.subscribeRefunds();
this.subscribeInvoices();
this.subscribeTransactions();
this.subscribeChannelBackups();
} catch (error) {
this.logger.error(`Could not connect to Boltz: ${error.message}`);
this.logger.verbose(`Retrying in ${this.RECONNECT_INTERVAL} ms`);
Expand Down
Loading

0 comments on commit 256ceed

Please sign in to comment.