1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const log_1 = require("../common/log");
|
4 | const ilp_protocol_ccp_1 = require("ilp-protocol-ccp");
|
5 | const MINIMUM_UPDATE_INTERVAL = 150;
|
6 | const MAX_EPOCHS_PER_UPDATE = 50;
|
7 | class CcpSender {
|
8 | constructor({ accountId, plugin, forwardingRoutingTable, getOwnAddress, getAccountRelation, routeExpiry, routeBroadcastInterval }) {
|
9 | this.mode = ilp_protocol_ccp_1.Mode.MODE_IDLE;
|
10 | this.lastKnownEpoch = 0;
|
11 | this.lastUpdate = 0;
|
12 | this.scheduleRouteUpdate = () => {
|
13 | if (this.sendRouteUpdateTimer) {
|
14 | clearTimeout(this.sendRouteUpdateTimer);
|
15 | this.sendRouteUpdateTimer = undefined;
|
16 | }
|
17 | if (this.mode !== ilp_protocol_ccp_1.Mode.MODE_SYNC) {
|
18 | return;
|
19 | }
|
20 | const lastUpdate = this.lastUpdate;
|
21 | const nextEpoch = this.lastKnownEpoch;
|
22 | let delay;
|
23 | if (nextEpoch < this.forwardingRoutingTable.currentEpoch) {
|
24 | delay = 0;
|
25 | }
|
26 | else {
|
27 | delay = this.routeBroadcastInterval - (Date.now() - lastUpdate);
|
28 | }
|
29 | delay = Math.max(MINIMUM_UPDATE_INTERVAL, delay);
|
30 | this.log.trace('scheduling next route update. accountId=%s delay=%s currentEpoch=%s peerHasEpoch=%s', this.accountId, delay, this.forwardingRoutingTable.currentEpoch, this.lastKnownEpoch);
|
31 | this.sendRouteUpdateTimer = setTimeout(() => {
|
32 | this.sendSingleRouteUpdate()
|
33 | .then(() => this.scheduleRouteUpdate())
|
34 | .catch((err) => {
|
35 | const errInfo = (err instanceof Object && err.stack) ? err.stack : err;
|
36 | this.log.debug('failed to broadcast route information to peer. peer=%s error=%s', this.accountId, errInfo);
|
37 | });
|
38 | }, delay);
|
39 | this.sendRouteUpdateTimer.unref();
|
40 | };
|
41 | this.plugin = plugin;
|
42 | this.forwardingRoutingTable = forwardingRoutingTable;
|
43 | this.log = log_1.create(`ccp-sender[${accountId}]`);
|
44 | this.accountId = accountId;
|
45 | this.getOwnAddress = getOwnAddress;
|
46 | this.getAccountRelation = getAccountRelation;
|
47 | this.routeExpiry = routeExpiry;
|
48 | this.routeBroadcastInterval = routeBroadcastInterval;
|
49 | }
|
50 | stop() {
|
51 | if (this.sendRouteUpdateTimer) {
|
52 | clearTimeout(this.sendRouteUpdateTimer);
|
53 | }
|
54 | }
|
55 | getAccountId() {
|
56 | return this.accountId;
|
57 | }
|
58 | getLastUpdate() {
|
59 | return this.lastUpdate;
|
60 | }
|
61 | getLastKnownEpoch() {
|
62 | return this.lastKnownEpoch;
|
63 | }
|
64 | getMode() {
|
65 | return this.mode;
|
66 | }
|
67 | getStatus() {
|
68 | return {
|
69 | epoch: this.lastKnownEpoch,
|
70 | mode: ilp_protocol_ccp_1.ModeReverseMap[this.mode]
|
71 | };
|
72 | }
|
73 | handleRouteControl({ mode, lastKnownRoutingTableId, lastKnownEpoch, features }) {
|
74 | if (this.mode !== mode) {
|
75 | this.log.trace('peer requested changing routing mode. oldMode=%s newMode=%s', ilp_protocol_ccp_1.ModeReverseMap[this.mode], ilp_protocol_ccp_1.ModeReverseMap[mode]);
|
76 | }
|
77 | this.mode = mode;
|
78 | if (lastKnownRoutingTableId !== this.forwardingRoutingTable.routingTableId) {
|
79 | this.log.trace('peer has old routing table id, resetting lastKnownEpoch to zero. theirTableId=%s correctTableId=%s', lastKnownRoutingTableId, this.forwardingRoutingTable.routingTableId);
|
80 | this.lastKnownEpoch = 0;
|
81 | }
|
82 | else {
|
83 | this.log.trace('peer epoch set. epoch=%s currentEpoch=%s', this.accountId, lastKnownEpoch, this.forwardingRoutingTable.currentEpoch);
|
84 | this.lastKnownEpoch = lastKnownEpoch;
|
85 | }
|
86 | if (this.mode === ilp_protocol_ccp_1.Mode.MODE_SYNC) {
|
87 | this.scheduleRouteUpdate();
|
88 | }
|
89 | else {
|
90 | if (this.sendRouteUpdateTimer) {
|
91 | clearTimeout(this.sendRouteUpdateTimer);
|
92 | this.sendRouteUpdateTimer = undefined;
|
93 | }
|
94 | }
|
95 | }
|
96 | async sendSingleRouteUpdate() {
|
97 | this.lastUpdate = Date.now();
|
98 | if (!this.plugin.isConnected()) {
|
99 | this.log.debug('cannot send routes, plugin not connected (yet).');
|
100 | return;
|
101 | }
|
102 | const nextRequestedEpoch = this.lastKnownEpoch;
|
103 | const allUpdates = this.forwardingRoutingTable.log
|
104 | .slice(nextRequestedEpoch, nextRequestedEpoch + MAX_EPOCHS_PER_UPDATE);
|
105 | const toEpoch = nextRequestedEpoch + allUpdates.length;
|
106 | const relation = this.getAccountRelation(this.accountId);
|
107 | function isRouteUpdate(update) {
|
108 | return !!update;
|
109 | }
|
110 | const updates = allUpdates
|
111 | .filter(isRouteUpdate)
|
112 | .map((update) => {
|
113 | if (!update.route)
|
114 | return update;
|
115 | if (update.route.nextHop === this.accountId ||
|
116 | (relation === 'parent' &&
|
117 | ['peer', 'parent'].indexOf(this.getAccountRelation(update.route.nextHop)) !== -1)) {
|
118 | return Object.assign({}, update, { route: undefined });
|
119 | }
|
120 | else {
|
121 | return update;
|
122 | }
|
123 | });
|
124 | const newRoutes = [];
|
125 | const withdrawnRoutes = [];
|
126 | for (const update of updates) {
|
127 | if (update.route) {
|
128 | newRoutes.push({
|
129 | prefix: update.prefix,
|
130 | nextHop: update.route.nextHop,
|
131 | path: update.route.path,
|
132 | auth: update.route.auth
|
133 | });
|
134 | }
|
135 | else {
|
136 | withdrawnRoutes.push({
|
137 | prefix: update.prefix,
|
138 | epoch: update.epoch
|
139 | });
|
140 | }
|
141 | }
|
142 | this.log.trace('broadcasting routes to peer. speaker=%s peer=%s fromEpoch=%s toEpoch=%s routeCount=%s unreachableCount=%s', this.getOwnAddress(), this.accountId, this.lastKnownEpoch, toEpoch, newRoutes.length, withdrawnRoutes.length);
|
143 | const routeUpdate = {
|
144 | speaker: this.getOwnAddress(),
|
145 | routingTableId: this.forwardingRoutingTable.routingTableId,
|
146 | holdDownTime: this.routeExpiry,
|
147 | currentEpochIndex: this.forwardingRoutingTable.currentEpoch,
|
148 | fromEpochIndex: this.lastKnownEpoch,
|
149 | toEpochIndex: toEpoch,
|
150 | newRoutes: newRoutes.map(r => (Object.assign({}, r, { nextHop: undefined, auth: r.auth, props: [] }))),
|
151 | withdrawnRoutes: withdrawnRoutes.map(r => r.prefix)
|
152 | };
|
153 | const previousNextRequestedEpoch = this.lastKnownEpoch;
|
154 | this.lastKnownEpoch = toEpoch;
|
155 | const timeout = this.routeBroadcastInterval;
|
156 | const timerPromise = new Promise((resolve, reject) => {
|
157 | const timer = setTimeout(() => reject(new Error('route update timed out.')), timeout);
|
158 | timer.unref();
|
159 | });
|
160 | try {
|
161 | await Promise.race([
|
162 | this.plugin.sendData(ilp_protocol_ccp_1.serializeCcpRouteUpdateRequest(routeUpdate)),
|
163 | timerPromise
|
164 | ]);
|
165 | }
|
166 | catch (err) {
|
167 | this.lastKnownEpoch = previousNextRequestedEpoch;
|
168 | throw err;
|
169 | }
|
170 | }
|
171 | }
|
172 | exports.default = CcpSender;
|
173 |
|
\ | No newline at end of file |