Skip to content

Commit 09418e2

Browse files
authored
Merge pull request #1446 from hirosystems/beta
release v6.2.1
2 parents fa1b78e + 2c52d76 commit 09418e2

File tree

13 files changed

+376
-93
lines changed

13 files changed

+376
-93
lines changed

.env

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ MAINNET_SEND_MANY_CONTRACT_ID=SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.send-man
138138
# IMGIX_DOMAIN=https://<your domain>.imgix.net
139139
# IMGIX_TOKEN=<your token>
140140

141+
# Web Socket ping interval to determine client availability, in seconds.
142+
# STACKS_API_WS_PING_INTERVAL=5
143+
144+
# Web Socket ping timeout, in seconds. Clients will be dropped if they do not respond with a pong
145+
# after this time has elapsed.
146+
# STACKS_API_WS_PING_TIMEOUT=5
147+
148+
# Web Socket message timeout, in seconds. Clients will be dropped if they do not acknowledge a
149+
# message after this time has elapsed.
150+
# STACKS_API_WS_MESSAGE_TIMEOUT=5
151+
152+
# Web Socket update queue timeout, in seconds. When an update is scheduled (new block, tx update,
153+
# etc.), we will allow this number of seconds to elapse to allow all subscribed clients to receive
154+
# new data.
155+
# STACKS_API_WS_UPDATE_QUEUE_TIMEOUT=5
156+
141157
# Specify max number of STX address to store in an in-memory LRU cache (CPU optimization).
142158
# Defaults to 50,000, which should result in around 25 megabytes of additional memory usage.
143159
# STACKS_ADDRESS_CACHE_SIZE=10000

CHANGELOG.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1-
## [6.2.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v6.1.1...v6.2.0) (2022-11-15)
1+
## [6.2.0-beta.6](https://github.com/hirosystems/stacks-blockchain-api/compare/v6.2.0-beta.5...v6.2.0-beta.6) (2022-11-18)
22

33

4-
### Features
4+
### Bug Fixes
55

6-
* add ENV configs for DB close and API shutdown timeouts ([#1366](https://github.com/hirosystems/stacks-blockchain-api/issues/1366)) ([444f008](https://github.com/hirosystems/stacks-blockchain-api/commit/444f008fe2f188148ce14c519373a053a3fc8c89))
6+
* get rosetta latest block from chain_tip view ([#1445](https://github.com/hirosystems/stacks-blockchain-api/issues/1445)) ([ad386d3](https://github.com/hirosystems/stacks-blockchain-api/commit/ad386d30d18afcf22aba51f0c898f306eaaf5fdf))
7+
8+
## [6.2.0-beta.5](https://github.com/hirosystems/stacks-blockchain-api/compare/v6.2.0-beta.4...v6.2.0-beta.5) (2022-11-15)
79

810

911
### Bug Fixes
1012

13+
* handle postgres dns lookup error ([#1433](https://github.com/hirosystems/stacks-blockchain-api/issues/1433)) ([e00efd4](https://github.com/hirosystems/stacks-blockchain-api/commit/e00efd4e64a3f0072ffa103e9ec011e5a080e7ed))
14+
* handle websocket messages with a priority queue ([#1427](https://github.com/hirosystems/stacks-blockchain-api/issues/1427)) ([f0cb01c](https://github.com/hirosystems/stacks-blockchain-api/commit/f0cb01c0541496959b924b29b5962caf63099432))
15+
* revert to 404 error code on bns name errors ([#1440](https://github.com/hirosystems/stacks-blockchain-api/issues/1440)) ([cdc039c](https://github.com/hirosystems/stacks-blockchain-api/commit/cdc039cea88749103a48cfc66d55d3ba14b3c2a3))
16+
17+
## [6.2.0](https://github.com/hirosystems/stacks-blockchain-api/compare/v6.1.1...v6.2.0) (2022-11-15)
18+
19+
20+
### Features
21+
22+
* add ENV configs for DB close and API shutdown timeouts ([#1366](https://github.com/hirosystems/stacks-blockchain-api/issues/1366)) ([444f008](https://github.com/hirosystems/stacks-blockchain-api/commit/444f008fe2f188148ce14c519373a053a3fc8c89))
1123
* add memos to send-many-memo rosetta STX transfer operations ([#1389](https://github.com/hirosystems/stacks-blockchain-api/issues/1389)) ([0a552b8](https://github.com/hirosystems/stacks-blockchain-api/commit/0a552b8d8c193f64199e63b0956b1c070ce2c530))
1224
* catch cache controller db errors ([#1368](https://github.com/hirosystems/stacks-blockchain-api/issues/1368)) ([f15df41](https://github.com/hirosystems/stacks-blockchain-api/commit/f15df41fa98a171b5e20289240c391a847fd1460))
1325
* disable faucet endpoints on mainnet ([#1425](https://github.com/hirosystems/stacks-blockchain-api/issues/1425)) ([b79b9b4](https://github.com/hirosystems/stacks-blockchain-api/commit/b79b9b43d5bce5d65f5bc322589704e40de1ad55))
@@ -16,6 +28,7 @@
1628
* sql transaction consistency ([#1410](https://github.com/hirosystems/stacks-blockchain-api/issues/1410)) ([01e26d9](https://github.com/hirosystems/stacks-blockchain-api/commit/01e26d9c89472c8e07ee9d44372d3de86ee0fdb0))
1729
* update testnet send-many-memo contract id ENV ([#1420](https://github.com/hirosystems/stacks-blockchain-api/issues/1420)) ([45ea24d](https://github.com/hirosystems/stacks-blockchain-api/commit/45ea24d9a2df96d582aaae70e433b0717a0e47cf))
1830

31+
1932
## [6.2.0-beta.4](https://github.com/hirosystems/stacks-blockchain-api/compare/v6.2.0-beta.3...v6.2.0-beta.4) (2022-11-08)
2033

2134

docs/socket-io/index.d.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,38 @@ export type Topic =
2424
| NftAssetEventTopic
2525
| NftCollectionEventTopic;
2626

27+
// Allows timeout callbacks for messages. See
28+
// https://socket.io/docs/v4/typescript/#emitting-with-a-timeout
29+
type WithTimeoutAck<isSender extends boolean, args extends any[]> = isSender extends true ? [Error, ...args] : args;
30+
2731
export interface ClientToServerMessages {
2832
subscribe: (topic: Topic | Topic[], callback: (error: string | null) => void) => void;
2933
unsubscribe: (...topic: Topic[]) => void;
3034
}
3135

32-
export interface ServerToClientMessages {
33-
block: (block: Block) => void;
34-
microblock: (microblock: Microblock) => void;
35-
mempool: (transaction: MempoolTransaction) => void;
36-
transaction: (transaction: Transaction | MempoolTransaction) => void;
36+
export interface ServerToClientMessages<isSender extends boolean = false> {
37+
block: (block: Block, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
38+
microblock: (microblock: Microblock, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
39+
mempool: (transaction: MempoolTransaction, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
40+
transaction: (transaction: Transaction | MempoolTransaction, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
3741

3842
// @ts-ignore scheduled for support in TS v4.3 https://github.com/microsoft/TypeScript/pull/26797
39-
[key: 'nft-event']: (event: NftEvent) => void;
40-
'nft-event': (event: NftEvent) => void;
43+
[key: 'nft-event']: (event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
44+
'nft-event': (event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
4145

4246
// @ts-ignore scheduled for support in TS v4.3 https://github.com/microsoft/TypeScript/pull/26797
43-
[key: NftAssetEventTopic]: (assetIdentifier: string, value: string, event: NftEvent) => void;
44-
'nft-asset-event': (assetIdentifier: string, value: string, event: NftEvent) => void;
47+
[key: NftAssetEventTopic]: (assetIdentifier: string, value: string, event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
48+
'nft-asset-event': (assetIdentifier: string, value: string, event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
4549

4650
// @ts-ignore scheduled for support in TS v4.3 https://github.com/microsoft/TypeScript/pull/26797
47-
[key: NftCollectionEventTopic]: (assetIdentifier: string, event: NftEvent) => void;
48-
'nft-collection-event': (assetIdentifier: string, event: NftEvent) => void;
51+
[key: NftCollectionEventTopic]: (assetIdentifier: string, event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
52+
'nft-collection-event': (assetIdentifier: string, event: NftEvent, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
4953

5054
// @ts-ignore scheduled for support in TS v4.3 https://github.com/microsoft/TypeScript/pull/26797
51-
[key: AddressTransactionTopic]: (address: string, stxBalance: AddressTransactionWithTransfers) => void;
52-
'address-transaction': (address: string, tx: AddressTransactionWithTransfers) => void;
55+
[key: AddressTransactionTopic]: (address: string, stxBalance: AddressTransactionWithTransfers, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
56+
'address-transaction': (address: string, tx: AddressTransactionWithTransfers, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
5357

5458
// @ts-ignore scheduled for support in TS v4.3 https://github.com/microsoft/TypeScript/pull/26797
55-
[key: AddressStxBalanceTopic]: (address: string, stxBalance: AddressStxBalanceResponse) => void;
56-
'address-stx-balance': (address: string, stxBalance: AddressStxBalanceResponse) => void;
59+
[key: AddressStxBalanceTopic]: (address: string, stxBalance: AddressStxBalanceResponse, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
60+
'address-stx-balance': (address: string, stxBalance: AddressStxBalanceResponse, callback: (...args: WithTimeoutAck<isSender, [string]>) => void) => void;
5761
}

src/api/routes/bns/names.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export function createBnsNamesRouter(db: PgStore, chainId: ChainID): express.Rou
161161
if (error instanceof NameRedirectError) {
162162
res.redirect(error.message);
163163
} else {
164-
res.status(400).json(error);
164+
res.status(404).json(error);
165165
}
166166
});
167167
})

src/api/routes/ws/channels/socket-io-channel.ts

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ import {
1717
WebSocketPayload,
1818
WebSocketTopics,
1919
} from '../web-socket-channel';
20+
import {
21+
getWsMessageTimeoutMs,
22+
getWsPingIntervalMs,
23+
getWsPingTimeoutMs,
24+
} from '../web-socket-transmitter';
2025

2126
/**
2227
* SocketIO channel for sending real time API updates.
2328
*/
2429
export class SocketIOChannel extends WebSocketChannel {
25-
private io?: SocketIOServer<ClientToServerMessages, ServerToClientMessages>;
30+
private io?: SocketIOServer<ClientToServerMessages, ServerToClientMessages<true>>;
2631
private adapter?: Adapter;
2732

2833
constructor(server: http.Server) {
@@ -33,9 +38,14 @@ export class SocketIOChannel extends WebSocketChannel {
3338
}
3439

3540
connect(): void {
36-
const io = new SocketIOServer<ClientToServerMessages, ServerToClientMessages>(this.server, {
37-
cors: { origin: '*' },
38-
});
41+
const io = new SocketIOServer<ClientToServerMessages, ServerToClientMessages<true>>(
42+
this.server,
43+
{
44+
cors: { origin: '*' },
45+
pingInterval: getWsPingIntervalMs(),
46+
pingTimeout: getWsPingTimeoutMs(),
47+
}
48+
);
3949
this.io = io;
4050

4151
io.on('connection', async socket => {
@@ -153,74 +163,136 @@ export class SocketIOChannel extends WebSocketChannel {
153163
return false;
154164
}
155165

166+
private async getTopicSockets(room: Topic) {
167+
if (!this.io) {
168+
return;
169+
}
170+
const sockets = [];
171+
const socketIds = await this.io.to(room).allSockets();
172+
for (const id of socketIds) {
173+
const socket = this.io.sockets.sockets.get(id);
174+
if (socket) {
175+
sockets.push(socket);
176+
}
177+
}
178+
return sockets;
179+
}
180+
156181
send<P extends keyof WebSocketPayload>(
157182
payload: P,
158183
...args: ListenerType<WebSocketPayload[P]>
159184
): void {
160185
if (!this.io) {
161186
return;
162187
}
188+
// If a client takes more than this number of ms to respond to an event `emit`, it will be
189+
// disconnected.
190+
const timeout = getWsMessageTimeoutMs();
163191
switch (payload) {
164192
case 'block': {
165193
const [block] = args as ListenerType<WebSocketPayload['block']>;
166194
this.prometheus?.sendEvent('block');
167-
this.io.to('block').emit('block', block);
195+
void this.getTopicSockets('block').then(sockets =>
196+
sockets?.forEach(socket =>
197+
socket.timeout(timeout).emit('block', block, _ => socket.disconnect(true))
198+
)
199+
);
168200
break;
169201
}
170202
case 'microblock': {
171203
const [microblock] = args as ListenerType<WebSocketPayload['microblock']>;
172204
this.prometheus?.sendEvent('microblock');
173-
this.io.to('microblock').emit('microblock', microblock);
205+
void this.getTopicSockets('microblock').then(sockets =>
206+
sockets?.forEach(socket =>
207+
socket.timeout(timeout).emit('microblock', microblock, _ => socket.disconnect(true))
208+
)
209+
);
174210
break;
175211
}
176212
case 'mempoolTransaction': {
177213
const [tx] = args as ListenerType<WebSocketPayload['mempoolTransaction']>;
178214
this.prometheus?.sendEvent('mempool');
179-
this.io.to('mempool').emit('mempool', tx);
215+
void this.getTopicSockets('mempool').then(sockets =>
216+
sockets?.forEach(socket =>
217+
socket.timeout(timeout).emit('mempool', tx, _ => socket.disconnect(true))
218+
)
219+
);
180220
break;
181221
}
182222
case 'transaction': {
183223
const [tx] = args as ListenerType<WebSocketPayload['transaction']>;
184224
this.prometheus?.sendEvent('transaction');
185-
this.io.to(`transaction:${tx.tx_id}`).emit('transaction', tx);
225+
void this.getTopicSockets(`transaction:${tx.tx_id}`).then(sockets =>
226+
sockets?.forEach(socket =>
227+
socket.timeout(timeout).emit('transaction', tx, _ => socket.disconnect(true))
228+
)
229+
);
186230
break;
187231
}
188232
case 'nftEvent': {
189233
const [event] = args as ListenerType<WebSocketPayload['nftEvent']>;
190234
this.prometheus?.sendEvent('nft-event');
191-
this.io.to('nft-event').emit('nft-event', event);
235+
void this.getTopicSockets(`nft-event`).then(sockets =>
236+
sockets?.forEach(socket =>
237+
socket.timeout(timeout).emit('nft-event', event, _ => socket.disconnect(true))
238+
)
239+
);
192240
break;
193241
}
194242
case 'nftAssetEvent': {
195243
const [assetIdentifier, value, event] = args as ListenerType<
196244
WebSocketPayload['nftAssetEvent']
197245
>;
198246
this.prometheus?.sendEvent('nft-asset-event');
199-
this.io.to('nft-event').emit('nft-asset-event', assetIdentifier, value, event);
247+
void this.getTopicSockets(`nft-event`).then(sockets =>
248+
sockets?.forEach(socket =>
249+
socket
250+
.timeout(timeout)
251+
.emit('nft-asset-event', assetIdentifier, value, event, _ => socket.disconnect(true))
252+
)
253+
);
200254
break;
201255
}
202256
case 'nftCollectionEvent': {
203257
const [assetIdentifier, event] = args as ListenerType<
204258
WebSocketPayload['nftCollectionEvent']
205259
>;
206260
this.prometheus?.sendEvent('nft-collection-event');
207-
this.io.to('nft-event').emit('nft-collection-event', assetIdentifier, event);
261+
void this.getTopicSockets(`nft-event`).then(sockets =>
262+
sockets?.forEach(socket =>
263+
socket
264+
.timeout(timeout)
265+
.emit('nft-collection-event', assetIdentifier, event, _ => socket.disconnect(true))
266+
)
267+
);
208268
break;
209269
}
210270
case 'principalTransaction': {
211271
const [principal, tx] = args as ListenerType<WebSocketPayload['principalTransaction']>;
212272
const topic: AddressTransactionTopic = `address-transaction:${principal}`;
213273
this.prometheus?.sendEvent('address-transaction');
214-
this.io.to(topic).emit('address-transaction', principal, tx);
215-
this.io.to(topic).emit(topic, principal, tx);
274+
void this.getTopicSockets(topic).then(sockets =>
275+
sockets?.forEach(socket => {
276+
socket
277+
.timeout(timeout)
278+
.emit('address-transaction', principal, tx, _ => socket.disconnect(true));
279+
socket.timeout(timeout).emit(topic, principal, tx, _ => socket.disconnect(true));
280+
})
281+
);
216282
break;
217283
}
218284
case 'principalStxBalance': {
219285
const [principal, balance] = args as ListenerType<WebSocketPayload['principalStxBalance']>;
220286
const topic: AddressStxBalanceTopic = `address-stx-balance:${principal}`;
221287
this.prometheus?.sendEvent('address-stx-balance');
222-
this.io.to(topic).emit('address-stx-balance', principal, balance);
223-
this.io.to(topic).emit(topic, principal, balance);
288+
void this.getTopicSockets(topic).then(sockets =>
289+
sockets?.forEach(socket => {
290+
socket
291+
.timeout(timeout)
292+
.emit('address-stx-balance', principal, balance, _ => socket.disconnect(true));
293+
socket.timeout(timeout).emit(topic, principal, balance, _ => socket.disconnect(true));
294+
})
295+
);
224296
break;
225297
}
226298
}

0 commit comments

Comments
 (0)