Skip to content

Commit

Permalink
feat(db): add Order and Trade models
Browse files Browse the repository at this point in the history
This commit creates new database models for orders and trades. The
`Order` model is used to persist all data related to a specific order,
whereas the `Trade` model will be used for persisting data about
completed trades. Some order-specific properties from `SwapDeal` have
moved to the `Order` model. Rather than repeat that data for each
`SwapDeal` instance that swaps the same order, which results in data
duplication and creates the possibility for inconsistencies, swaps
for the same order now point to the same `Order` instance.

This also attempts to add all known associations between models for
easy join queries via the Sequelize API going forward.

Closes #621.
  • Loading branch information
sangaman committed Oct 31, 2018
1 parent fa4e032 commit 9fc7aea
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 11 deletions.
16 changes: 13 additions & 3 deletions lib/db/DB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type Models = {
SwapDeal: Sequelize.Model<db.SwapDealInstance, db.SwapDealAttributes>;
Pair: Sequelize.Model<db.PairInstance, db.PairAttributes>;
ReputationEvent: Sequelize.Model<db.ReputationEventInstance, db.ReputationEventAttributes>;
Order: Sequelize.Model<db.OrderInstance, db.OrderAttributes>;
Trade: Sequelize.Model<db.TradeInstance, db.TradeAttributes>;
};

/** A class representing a connection to a SQL database. */
Expand Down Expand Up @@ -45,16 +47,24 @@ class DB {
this.logger.error('unable to connect to the database', err);
throw err;
}
const { Node, Currency, Pair, ReputationEvent, SwapDeal } = this.models;
const { Node, Currency, Pair, ReputationEvent, SwapDeal, Order, Trade } = this.models;
// sync schemas with the database in phases, according to FKs dependencies
await Promise.all([
Node.sync(),
Currency.sync(),
ReputationEvent.sync(),
SwapDeal.sync(),
]);
// Pair is dependent on Currency, ReputationEvent is dependent on Node
await Promise.all([
Pair.sync(),
ReputationEvent.sync(),
]);
// Order is dependent on Pair
await Promise.all([
Order.sync(),
]);
await Promise.all([
Trade.sync(),
SwapDeal.sync(),
]);

if (newDb && initDb) {
Expand Down
13 changes: 13 additions & 0 deletions lib/db/models/Currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,18 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)

const Currency = sequelize.define<db.CurrencyInstance, db.CurrencyAttributes>('Currency', attributes, options);

Currency.associate = (models: Sequelize.Models) => {
models.Currency.hasMany(models.Pair, {
as: 'quoteCurrencies',
foreignKey: 'quoteCurrency',
constraints: true,
});
models.Currency.hasMany(models.Pair, {
as: 'baseCurrencies',
foreignKey: 'baseCurrency',
constraints: true,
});
};

return Currency;
};
7 changes: 7 additions & 0 deletions lib/db/models/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,12 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)

const Node = sequelize.define<db.NodeInstance, db.NodeAttributes>('Node', attributes, options);

Node.associate = (models: Sequelize.Models) => {
models.Node.hasMany(models.ReputationEvent, {
foreignKey: 'nodeId',
constraints: true,
});
};

return Node;
};
49 changes: 49 additions & 0 deletions lib/db/models/Order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Sequelize from 'sequelize';
import { db } from '../../types';

export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) => {
const attributes: db.SequelizeAttributes<db.OrderAttributes> = {
id: { type: DataTypes.STRING, primaryKey: true, allowNull: false },
nodeId: { type: DataTypes.INTEGER },
localId: { type: DataTypes.STRING },
initialQuantity: { type: DataTypes.DECIMAL(8), allowNull: true },
pairId: { type: DataTypes.STRING, allowNull: false },
price: { type: DataTypes.DECIMAL(8), allowNull: false },
isBuy: { type: DataTypes.BOOLEAN, allowNull: false },
createdAt: { type: DataTypes.BIGINT, allowNull: false },
};

const options: Sequelize.DefineOptions<db.OrderInstance> = {
tableName: 'orders',
timestamps: false,
};

const Order = sequelize.define<db.OrderInstance, db.OrderAttributes>('Order', attributes, options);

Order.associate = (models: Sequelize.Models) => {
models.Order.belongsTo(models.Node, {
foreignKey: 'nodeId',
constraints: true,
});
models.Order.belongsTo(models.Pair, {
foreignKey: 'pairId',
constraints: false,
});
models.Order.hasMany(models.Trade, {
as: 'makerTrades',
foreignKey: 'makerOrderId',
constraints: true,
});
models.Order.hasMany(models.Trade, {
as: 'takerTrades',
foreignKey: 'takerOrderId',
constraints: true,
});
models.Order.hasMany(models.SwapDeal, {
foreignKey: 'orderId',
constraints: true,
});
};

return Order;
};
4 changes: 4 additions & 0 deletions lib/db/models/Pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)

Pair.associate = (models: Sequelize.Models) => {
models.Pair.belongsTo(models.Currency, {
as: 'baseCurrencyInstance',
constraints: true,
foreignKey: 'baseCurrency',
});

models.Pair.belongsTo(models.Currency, {
as: 'takerCurrencyInstance',
constraints: true,
foreignKey: 'quoteCurrency',
});

Expand Down
1 change: 1 addition & 0 deletions lib/db/models/ReputationEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)
ReputationEvent.associate = (models: Sequelize.Models) => {
models.ReputationEvent.belongsTo(models.Node, {
foreignKey: 'nodeId',
constraints: true,
});
};

Expand Down
15 changes: 12 additions & 3 deletions lib/db/models/SwapDeal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)
errorReason: { type: DataTypes.STRING, allowNull: true },
r_hash: { type: DataTypes.STRING, allowNull: false, unique: true },
r_preimage: { type: DataTypes.STRING, allowNull: true },
peerPubKey: { type: DataTypes.STRING, allowNull: false },
nodeId: { type: DataTypes.STRING, allowNull: false },
orderId: { type: DataTypes.STRING, allowNull: false },
localId: { type: DataTypes.STRING, allowNull: false },
proposedQuantity: { type: DataTypes.DECIMAL(8), allowNull: false },
quantity: { type: DataTypes.DECIMAL(8), allowNull: true },
pairId: { type: DataTypes.STRING, allowNull: false },
takerAmount: { type: DataTypes.BIGINT , allowNull: false },
takerCurrency: { type: DataTypes.STRING , allowNull: false },
takerPubKey: { type: DataTypes.STRING , allowNull: true },
price: { type: DataTypes.DECIMAL(8), allowNull: false },
takerCltvDelta: { type: DataTypes.SMALLINT, allowNull: false },
makerCltvDelta: { type: DataTypes.SMALLINT, allowNull: true },
makerAmount: { type: DataTypes.BIGINT, allowNull: false },
Expand All @@ -35,5 +33,16 @@ export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes)

const SwapDeal = sequelize.define<db.SwapDealInstance, db.SwapDealAttributes>('SwapDeal', attributes, options);

SwapDeal.associate = (models: Sequelize.Models) => {
models.SwapDeal.belongsTo(models.Order, {
foreignKey: 'orderId',
constraints: true,
});
models.SwapDeal.belongsTo(models.Node, {
foreignKey: 'nodeId',
constraints: true,
});
};

return SwapDeal;
};
33 changes: 33 additions & 0 deletions lib/db/models/Trade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Sequelize from 'sequelize';
import { db } from '../../types';

export default (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) => {
const attributes: db.SequelizeAttributes<db.TradeAttributes> = {
makerOrderId: { type: DataTypes.STRING, primaryKey: true },
takerOrderId: { type: DataTypes.STRING, primaryKey: true },
quantity: { type: DataTypes.DECIMAL(8), allowNull: false },
};

const options: Sequelize.DefineOptions<db.TradeInstance> = {
tableName: 'trades',
timestamps: true,
updatedAt: false,
};

const Trade = sequelize.define<db.TradeInstance, db.TradeAttributes>('Trade', attributes, options);

Trade.associate = (models: Sequelize.Models) => {
models.Trade.belongsTo(models.Order, {
as: 'makerOrder',
foreignKey: 'makerOrderId',
constraints: true,
});
models.Trade.belongsTo(models.Order, {
as: 'takerOrder',
foreignKey: 'takerOrderId',
constraints: true,
});
};

return Trade;
};
10 changes: 8 additions & 2 deletions lib/swaps/SwapRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ class SwapRepository {
});
}

public addSwapDeal = (swapDeal: db.SwapDealFactory): Bluebird<db.SwapDealInstance> => {
return this.models.SwapDeal.create(<db.SwapDealAttributes>swapDeal);
public addSwapDeal = async (swapDeal: db.SwapDealFactory): Promise<db.SwapDealInstance> => {
const node = await this.models.Node.findOne({
where: {
nodePubKey: swapDeal.peerPubKey,
},
});
const attributes = { ...swapDeal, peerNodeId: node!.id } as db.SwapDealAttributes;
return this.models.SwapDeal.create(attributes);
}
}
export default SwapRepository;
31 changes: 28 additions & 3 deletions lib/types/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Sequelize, { DataTypeAbstract, DefineAttributeColumnOptions } from 'sequelize';
import { Address, NodeConnectionInfo } from './p2p';
import { SwapDeal } from '../swaps/types';
import { Currency, Pair } from './orders';
import { Currency, Pair, OwnOrder, Order } from './orders';
import { ReputationEvent } from './enums';

export type SequelizeAttributes<T extends { [key: string]: any }> = {
Expand All @@ -28,20 +28,45 @@ export type CurrencyAttributes = CurrencyFactory & {
export type CurrencyInstance = CurrencyAttributes & Sequelize.Instance<CurrencyAttributes>;

/* SwapDeal */
export type SwapDealFactory = Pick<SwapDeal, Exclude<keyof SwapDeal, 'makerToTakerRoutes'>>;
export type SwapDealFactory = Pick<SwapDeal, Exclude<keyof SwapDeal, 'makerToTakerRoutes' | 'price' | 'pairId'>>;

export type SwapDealAttributes = SwapDealFactory & {
export type SwapDealAttributes = Pick<SwapDealFactory, Exclude<keyof SwapDealFactory, 'peerPubKey'>> & {
makerCltvDelta: number;
r_preimage: string;
errorReason: string;
quantity: number;
takerPubKey: string;
executeTime: number;
completeTime: number;
/** The internal db node id of the counterparty peer for this swap deal. */
nodeId: number;
};

export type SwapDealInstance = SwapDealAttributes & Sequelize.Instance<SwapDealAttributes>;

export type OrderFactory = Pick<Order, Exclude<keyof Order, 'quantity' | 'hold'>>;

export type OrderAttributes = OrderFactory & {
/** The internal db node id of the peer that created this order. */
nodeId: number;
localId: string;
};

export type OrderInstance = OrderAttributes & Sequelize.Instance<OrderAttributes>;

export type TradeFactory = {
/** The order id of the maker order involved in this trade. */
makerOrderId: string,
/** The order id of the taker order involved in this trade. */
takerOrderId: string,
/** The quantity transacted in this trade. */
quantity: number,
};

export type TradeAttributes = TradeFactory;

export type TradeInstance = TradeAttributes & Sequelize.Instance<TradeAttributes>;

/* Node */
export type NodeFactory = NodeConnectionInfo;

Expand Down

0 comments on commit 9fc7aea

Please sign in to comment.