1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const log_1 = require("../common/log");
|
4 | const crypto_1 = require("crypto");
|
5 | const IlpPacket = require("ilp-packet");
|
6 | const bignumber_js_1 = require("bignumber.js");
|
7 | const STATIC_DATA_OFFSET = 25;
|
8 | const DEFAULT_CLEANUP_INTERVAL = 30000;
|
9 | const DEFAULT_PACKET_LIFETIME = 30000;
|
10 | class DeduplicateMiddleware {
|
11 | constructor(opts, { getInfo }) {
|
12 | this.packetCache = new Map();
|
13 | this.getInfo = getInfo;
|
14 | }
|
15 | async applyToPipelines(pipelines, accountId) {
|
16 | const log = log_1.create(`deduplicate-middleware[${accountId}]`);
|
17 | const accountInfo = this.getInfo(accountId);
|
18 | if (!accountInfo) {
|
19 | throw new Error('account info unavailable. accountId=' + accountId);
|
20 | }
|
21 | const { cleanupInterval, packetLifetime } = accountInfo.deduplicate || {
|
22 | cleanupInterval: DEFAULT_CLEANUP_INTERVAL,
|
23 | packetLifetime: DEFAULT_PACKET_LIFETIME
|
24 | };
|
25 | let interval;
|
26 | pipelines.startup.insertLast({
|
27 | name: 'deduplicate',
|
28 | method: async (dummy, next) => {
|
29 | interval = setInterval(() => this.cleanupCache(packetLifetime), cleanupInterval);
|
30 | return next(dummy);
|
31 | }
|
32 | });
|
33 | pipelines.teardown.insertLast({
|
34 | name: 'deduplicate',
|
35 | method: async (dummy, next) => {
|
36 | clearInterval(interval);
|
37 | return next(dummy);
|
38 | }
|
39 | });
|
40 | pipelines.outgoingData.insertLast({
|
41 | name: 'deduplicate',
|
42 | method: async (data, next) => {
|
43 | if (data[0] === IlpPacket.Type.TYPE_ILP_PREPARE) {
|
44 | const { contents } = IlpPacket.deserializeEnvelope(data);
|
45 | const index = crypto_1.createHash('sha256')
|
46 | .update(contents.slice(STATIC_DATA_OFFSET))
|
47 | .digest()
|
48 | .slice(0, 16)
|
49 | .toString('base64');
|
50 | const { amount, expiresAt } = IlpPacket.deserializeIlpPrepare(data);
|
51 | const cachedPacket = this.packetCache.get(index);
|
52 | if (cachedPacket) {
|
53 | if (new bignumber_js_1.default(cachedPacket.amount).gte(amount) && cachedPacket.expiresAt >= expiresAt) {
|
54 | log.warn('deduplicate packet cache hit. accountId=%s elapsed=%s amount=%s', accountId, cachedPacket.expiresAt.getTime() - Date.now(), amount);
|
55 | return cachedPacket.promise;
|
56 | }
|
57 | }
|
58 | const promise = next(data);
|
59 | this.packetCache.set(index, {
|
60 | amount,
|
61 | expiresAt,
|
62 | promise
|
63 | });
|
64 | return promise;
|
65 | }
|
66 | return next(data);
|
67 | }
|
68 | });
|
69 | }
|
70 | cleanupCache(packetLifetime) {
|
71 | const now = Date.now();
|
72 | for (const index of this.packetCache.keys()) {
|
73 | const cachedPacket = this.packetCache.get(index);
|
74 | if (!cachedPacket)
|
75 | continue;
|
76 | const packetExpiry = cachedPacket.expiresAt.getTime() + packetLifetime;
|
77 | if (packetExpiry < now) {
|
78 | this.packetCache.delete(index);
|
79 | }
|
80 | }
|
81 | }
|
82 | }
|
83 | exports.default = DeduplicateMiddleware;
|
84 |
|
\ | No newline at end of file |