Skip to content

Commit 9a7a629

Browse files
refactor(SwapDeals) Store swapdeals in database (#569)
* refactor(SwapDeals) Store swapdeals in database
1 parent 5308002 commit 9a7a629

File tree

8 files changed

+129
-64
lines changed

8 files changed

+129
-64
lines changed

Diff for: lib/Xud.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ class Xud extends EventEmitter {
9191

9292
this.pool = new Pool(this.config.p2p, loggers.p2p, this.db.models);
9393

94-
this.swaps = new Swaps(loggers.swaps, this.pool, this.lndbtcClient, this.lndltcClient);
94+
this.swaps = new Swaps(loggers.swaps, this.db.models, this.pool, this.lndbtcClient, this.lndltcClient);
95+
initPromises.push(this.swaps.init());
9596

9697
this.orderBook = new OrderBook(loggers.orderbook, this.db.models, this.config.nomatching, this.pool, this.swaps);
9798
initPromises.push(this.orderBook.init());

Diff for: lib/db/DB.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SwapClients } from '../types/enums';
99
type Models = {
1010
Node: Sequelize.Model<db.NodeInstance, db.NodeAttributes>;
1111
Currency: Sequelize.Model<db.CurrencyInstance, db.CurrencyAttributes>;
12+
SwapDeal: Sequelize.Model<db.SwapDealInstance, db.SwapDealAttributes>;
1213
Pair: Sequelize.Model<db.PairInstance, db.PairAttributes>;
1314
ReputationEvent: Sequelize.Model<db.ReputationEventInstance, db.ReputationEventAttributes>;
1415
};
@@ -44,12 +45,13 @@ class DB {
4445
this.logger.error('unable to connect to the database', err);
4546
throw err;
4647
}
47-
const { Node, Currency, Pair, ReputationEvent } = this.models;
48+
const { Node, Currency, Pair, ReputationEvent, SwapDeal } = this.models;
4849
// sync schemas with the database in phases, according to FKs dependencies
4950
await Promise.all([
5051
Node.sync(),
5152
Currency.sync(),
5253
ReputationEvent.sync(),
54+
SwapDeal.sync(),
5355
]);
5456
await Promise.all([
5557
Pair.sync(),

Diff for: lib/db/models/SwapDeal.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Sequelize from 'sequelize';
2+
import { db } from '../../types';
3+
4+
export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) => {
5+
const attributes: db.SequelizeAttributes<db.SwapDealAttributes> = {
6+
role: { type: DataTypes.TINYINT, allowNull: false },
7+
state: { type: DataTypes.TINYINT, allowNull: false },
8+
phase: { type: DataTypes.TINYINT, allowNull: false },
9+
errorReason: { type: DataTypes.STRING, allowNull: true },
10+
r_hash: { type: DataTypes.STRING, allowNull: false, unique: true },
11+
r_preimage: { type: DataTypes.STRING, allowNull: true },
12+
peerPubKey: { type: DataTypes.STRING, allowNull: false },
13+
orderId: { type: DataTypes.STRING, allowNull: false },
14+
localId: { type: DataTypes.STRING, allowNull: false },
15+
proposedQuantity: { type: DataTypes.DECIMAL(8), allowNull: false },
16+
quantity: { type: DataTypes.DECIMAL(8), allowNull: true },
17+
pairId: { type: DataTypes.STRING, allowNull: false },
18+
takerAmount: { type: DataTypes.BIGINT , allowNull: false },
19+
takerCurrency: { type: DataTypes.STRING , allowNull: false },
20+
takerPubKey: { type: DataTypes.STRING , allowNull: true },
21+
price: { type: DataTypes.DECIMAL(8), allowNull: false },
22+
takerCltvDelta: { type: DataTypes.SMALLINT, allowNull: false },
23+
makerCltvDelta: { type: DataTypes.SMALLINT, allowNull: true },
24+
makerAmount: { type: DataTypes.BIGINT, allowNull: false },
25+
makerCurrency: { type: DataTypes.STRING, allowNull: false },
26+
createTime: { type: DataTypes.BIGINT, allowNull: false },
27+
executeTime: { type: DataTypes.BIGINT, allowNull: true },
28+
completeTime: { type: DataTypes.BIGINT, allowNull: true },
29+
};
30+
31+
const options: Sequelize.DefineOptions<db.SwapDealInstance> = {
32+
tableName: 'swapdeals',
33+
timestamps: false,
34+
};
35+
36+
const SwapDeal = sequelize.define<db.SwapDealInstance, db.SwapDealAttributes>('SwapDeal', attributes, options);
37+
38+
return SwapDeal;
39+
};

Diff for: lib/swaps/SwapRepository.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { db } from '../types';
2+
import Bluebird from 'bluebird';
3+
import { Models } from '../db/DB';
4+
5+
class SwapRepository {
6+
7+
constructor(private models: Models) {}
8+
9+
public getSwapDeals = (): Bluebird<db.SwapDealInstance[]> => {
10+
return this.models.SwapDeal.findAll();
11+
}
12+
13+
public getSwapDeal = async (r_hash: string): Promise<db.SwapDealInstance | null> => {
14+
return this.models.SwapDeal.findOne({
15+
where: {
16+
r_hash,
17+
},
18+
});
19+
}
20+
21+
public addSwapDeal = (swapDeal: db.SwapDealFactory): Bluebird<db.SwapDealInstance> => {
22+
return this.models.SwapDeal.create(<db.SwapDealAttributes>swapDeal);
23+
}
24+
}
25+
export default SwapRepository;

Diff for: lib/swaps/Swaps.ts

+35-59
Original file line numberDiff line numberDiff line change
@@ -7,58 +7,12 @@ import * as lndrpc from '../proto/lndrpc_pb';
77
import LndClient from '../lndclient/LndClient';
88
import Pool from '../p2p/Pool';
99
import { EventEmitter } from 'events';
10+
import SwapRepository from './SwapRepository';
1011
import { StampedOwnOrder, StampedPeerOrder, SwapResult } from '../types/orders';
1112
import assert from 'assert';
12-
13-
type SwapDeal = {
14-
/** The role of the local node in the swap. */
15-
role: SwapRole;
16-
/** The most updated deal phase */
17-
phase: SwapPhase;
18-
/**
19-
* The most updated deal state. State works together with phase to indicate where the
20-
* deal is in its life cycle and if the deal is active, errored, or completed.
21-
*/
22-
state: SwapState;
23-
/** The reason for being in the current state. */
24-
errorReason?: string;
25-
/** The xud node pub key of the counterparty to this swap deal. */
26-
peerPubKey: string;
27-
/** The global order id in the XU network for the order being executed. */
28-
orderId: string;
29-
/** The local id for the order being executed. */
30-
localOrderId: string;
31-
/** The quantity of the order to execute as proposed by the taker. */
32-
proposedQuantity: number;
33-
/** The accepted quantity of the order to execute as accepted by the maker. */
34-
quantity?: number;
35-
/** The trading pair of the order. The pairId together with the orderId are needed to find the deal in orderBook. */
36-
pairId: string;
37-
/** The number of satoshis (or equivalent) the taker is expecting to receive. */
38-
takerAmount: number;
39-
/** The currency the taker is expecting to receive. */
40-
takerCurrency: string;
41-
/** Taker's lnd pubkey on the taker currency's network. */
42-
takerPubKey?: string;
43-
/** The CLTV delta from the current height that should be used to set the timelock for the final hop when sending to taker. */
44-
takerCltvDelta: number;
45-
/** The number of satoshis (or equivalent) the maker is expecting to receive. */
46-
makerAmount: number;
47-
/** The currency the maker is expecting to receive. */
48-
makerCurrency: string;
49-
/** The CLTV delta from the current height that should be used to set the timelock for the final hop when sending to maker. */
50-
makerCltvDelta?: number;
51-
/** The price of the order that's being executed. */
52-
price: number;
53-
/** The hash of the preimage. */
54-
r_hash: string;
55-
r_preimage?: string;
56-
/** The routes the maker should use to send to the taker. */
57-
makerToTakerRoutes?: lndrpc.Route[];
58-
createTime: number;
59-
executeTime?: number;
60-
completeTime?: number
61-
};
13+
import { Models } from '../db/DB';
14+
import { SwapDealInstance } from 'lib/types/db';
15+
import { SwapDeal } from './types';
6216

6317
type OrderToAccept = {
6418
quantityToAccept: number;
@@ -76,13 +30,15 @@ interface Swaps {
7630
class Swaps extends EventEmitter {
7731
/** A map between r_hash and swap deals. */
7832
private deals = new Map<string, SwapDeal>();
33+
private usedHashes = new Set<string>();
34+
private repository: SwapRepository;
7935

8036
/** The number of satoshis in a bitcoin. */
8137
private static readonly SATOSHIS_PER_COIN = 100000000;
8238

83-
constructor(private logger: Logger, private pool: Pool, private lndBtcClient: LndClient, private lndLtcClient: LndClient) {
39+
constructor(private logger: Logger, private models: Models, private pool: Pool, private lndBtcClient: LndClient, private lndLtcClient: LndClient) {
8440
super();
85-
41+
this.repository = new SwapRepository(this.models);
8642
this.bind();
8743
}
8844

@@ -99,6 +55,14 @@ class Swaps extends EventEmitter {
9955
return { baseCurrencyAmount, quoteCurrencyAmount };
10056
}
10157

58+
public init = async () => {
59+
// Load Swaps from data base
60+
const result = await this.repository.getSwapDeals();
61+
result.forEach((deal: SwapDealInstance) => {
62+
this.usedHashes.add(deal.r_hash);
63+
});
64+
}
65+
10266
private bind() {
10367
this.pool.on('packet.swapResponse', this.handleSwapResponse);
10468
this.pool.on('packet.swapComplete', this.handleSwapComplete);
@@ -153,6 +117,17 @@ class Swaps extends EventEmitter {
153117
return;
154118
}
155119

120+
/**
121+
* Saves deal to database and deletes from memory.
122+
* @param deal The deal to persist.
123+
*/
124+
private persistDeal = async (deal: SwapDeal) => {
125+
if (this.usedHashes.has(deal.r_hash)) {
126+
await this.repository.addSwapDeal(deal);
127+
this.removeDeal(deal);
128+
}
129+
}
130+
156131
/**
157132
* Gets a deal by its r_hash value.
158133
* @param r_hash The r_hash value of the deal to get.
@@ -284,7 +259,7 @@ class Swaps extends EventEmitter {
284259
const deal: SwapDeal = {
285260
...swapRequestBody,
286261
peerPubKey: peer.nodePubKey!,
287-
localOrderId: taker.localId,
262+
localId: taker.localId,
288263
price: maker.price,
289264
phase: SwapPhase.SwapCreated,
290265
state: SwapState.Active,
@@ -328,7 +303,7 @@ class Swaps extends EventEmitter {
328303
takerPubKey,
329304
peerPubKey: peer.nodePubKey!,
330305
price: orderToAccept.price,
331-
localOrderId: orderToAccept.localId,
306+
localId: orderToAccept.localId,
332307
quantity: orderToAccept.quantityToAccept,
333308
phase: SwapPhase.SwapCreated,
334309
state: SwapState.Active,
@@ -581,7 +556,7 @@ class Swaps extends EventEmitter {
581556
}
582557

583558
const request = new lndrpc.SendToRouteRequest();
584-
request.setRoutesList(deal.makerToTakerRoutes!);
559+
request.setRoutesList(deal.makerToTakerRoutes ? deal.makerToTakerRoutes : []);
585560
request.setPaymentHashString(deal.r_hash);
586561

587562
try {
@@ -669,7 +644,7 @@ class Swaps extends EventEmitter {
669644
if (deal.phase === SwapPhase.AmountReceived) {
670645
const swapResult = {
671646
orderId: deal.orderId,
672-
localId: deal.localOrderId,
647+
localId: deal.localId,
673648
pairId: deal.pairId,
674649
quantity: deal.quantity!,
675650
amountReceived: deal.role === SwapRole.Maker ? deal.makerAmount : deal.takerAmount,
@@ -682,27 +657,28 @@ class Swaps extends EventEmitter {
682657
}
683658
}
684659

685-
private handleSwapComplete = (response: packets.SwapCompletePacket): void => {
660+
private handleSwapComplete = (response: packets.SwapCompletePacket) => {
686661
const { r_hash } = response.body!;
687662
const deal = this.getDeal(r_hash);
688663
if (!deal) {
689664
this.logger.error(`received swap complete for unknown deal r_hash ${r_hash}`);
690665
return;
691666
}
692667
this.setDealPhase(deal, SwapPhase.SwapCompleted);
668+
return this.persistDeal(deal);
693669
}
694670

695-
private handleSwapError = (error: packets.SwapErrorPacket): void => {
671+
private handleSwapError = (error: packets.SwapErrorPacket) => {
696672
const { r_hash, errorMessage } = error.body!;
697673
const deal = this.getDeal(r_hash);
698674
if (!deal) {
699675
this.logger.error(`received swap error for unknown deal r_hash ${r_hash}`);
700676
return;
701677
}
702678
this.setDealState(deal, SwapState.Error, errorMessage);
679+
return this.persistDeal(deal);
703680
}
704681

705682
}
706683

707684
export default Swaps;
708-
export { SwapDeal };

Diff for: lib/types/db.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Sequelize, { DataTypeAbstract, DefineAttributeColumnOptions } from 'sequelize';
22
import { Address, NodeConnectionInfo } from './p2p';
3+
import { SwapDeal } from '../swaps/types';
34
import { Currency, Pair } from './orders';
45
import { ReputationEvent } from './enums';
56

@@ -17,6 +18,7 @@ export type SequelizeAttributes<T extends { [key: string]: any }> = {
1718
* "xInstance" is the type definition of a fetched record as a Sequelize row instance, which contains some util properties.
1819
*/
1920

21+
/* Currency */
2022
export type CurrencyFactory = Currency;
2123

2224
export type CurrencyAttributes = CurrencyFactory & {
@@ -25,6 +27,22 @@ export type CurrencyAttributes = CurrencyFactory & {
2527

2628
export type CurrencyInstance = CurrencyAttributes & Sequelize.Instance<CurrencyAttributes>;
2729

30+
/* SwapDeal */
31+
export type SwapDealFactory = Pick<SwapDeal, Exclude<keyof SwapDeal, 'makerToTakerRoutes'>>;
32+
33+
export type SwapDealAttributes = SwapDealFactory & {
34+
makerCltvDelta: number;
35+
r_preimage: string;
36+
errorReason: string;
37+
quantity: number;
38+
takerPubKey: string;
39+
executeTime: number;
40+
completeTime: number;
41+
};
42+
43+
export type SwapDealInstance = SwapDealAttributes & Sequelize.Instance<SwapDealAttributes>;
44+
45+
/* Node */
2846
export type NodeFactory = NodeConnectionInfo;
2947

3048
export type NodeAttributes = NodeFactory & {
@@ -39,6 +57,7 @@ export type NodeInstance = NodeAttributes & Sequelize.Instance<NodeAttributes> &
3957
reputationScore: number;
4058
};
4159

60+
/* Pairs */
4261
export type PairFactory = Pair;
4362

4463
export type PairAttributes = PairFactory & {
@@ -47,6 +66,7 @@ export type PairAttributes = PairFactory & {
4766

4867
export type PairInstance = PairAttributes & Sequelize.Instance<PairAttributes>;
4968

69+
/* Reputation events */
5070
export type ReputationEventFactory = {
5171
event: ReputationEvent;
5272
nodeId: number;

Diff for: lib/types/orders.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { SwapClients, SwapRole } from './enums';
1+
import { SwapClients, SwapRole, SwapState, SwapPhase } from './enums';
2+
import * as lndrpc from '../proto/lndrpc_pb';
23

34
/** An order without a price that is intended to match against any available orders on the opposite side of the book for its trading pair. */
45
type MarketOrder = {

Diff for: test/unit/Swaps.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect } from 'chai';
2-
import Swaps, { SwapDeal } from '../../lib/swaps/Swaps';
2+
import Swaps from '../../lib/swaps/Swaps';
3+
import { SwapDeal } from '../../lib/swaps/types';
34
import Logger, { Level } from '../../lib/Logger';
45
import { SwapPhase, SwapState, SwapRole } from '../../lib/types/enums';
56

@@ -18,7 +19,7 @@ describe('Swaps', () => {
1819
state: SwapState.Active,
1920
peerPubKey: '03029c6a4d80c91da9e40529ec41c93b17cc9d7956b59c7d8334b0318d4a86aef8',
2021
orderId: 'f8a85c66-7e73-43cd-9ac4-176ff4cc28a8',
21-
localOrderId: '1',
22+
localId: '1',
2223
proposedQuantity: quantity,
2324
pairId: 'LTC/BTC',
2425
takerCurrency: 'LTC',

0 commit comments

Comments
 (0)