trac-msb 0.2.26 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +2 -2
- package/cli/commandHandler.js +317 -20
- package/cli/handlers.js +9 -1
- package/cli/interactive.js +4 -3
- package/cli/tokenSupplyAudit.js +90 -0
- package/msb.mjs +1 -0
- package/package.json +15 -6
- package/rpc/create_server.js +12 -3
- package/rpc/handlers.js +103 -0
- package/rpc/routes/v1.js +8 -0
- package/src/codecs/apply/applyOperationCodec.js +334 -0
- package/src/codecs/apply/applyOperations.generated.cjs +6380 -0
- package/src/codecs/consensus/v1/consensusV1.generated.cjs +3508 -0
- package/src/codecs/consensus/v1/consensusV1OperationCodec.js +161 -0
- package/src/codecs/consensus/v1/vdfConfigCodec.js +83 -0
- package/src/{utils/protobuf → codecs/network/v1}/networkV1.generated.cjs +664 -393
- package/src/codecs/network/v1/networkV1OperationCodec.js +25 -0
- package/src/codecs/protobufRuntime.cjs +95 -0
- package/src/codecs/state/escrowEntryCodec.js +45 -0
- package/src/codecs/state/state.generated.cjs +659 -0
- package/src/config/args.js +0 -1
- package/src/config/config.js +41 -7
- package/src/config/env.js +33 -9
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionError.js +55 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionOperations.js +118 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelector.js +1042 -0
- package/src/core/consensus/epochAppenderSelection/createEpochAppenderSelector.js +23 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionLedgerReader.js +274 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionState.js +488 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionStorage.js +93 -0
- package/src/core/consensus/quorum.js +11 -0
- package/src/core/consensus/services/EpochCoordinationRound.js +839 -0
- package/src/core/consensus/services/EpochCoordinatorOperations.js +211 -0
- package/src/core/consensus/services/EpochCoordinatorService.js +335 -0
- package/src/core/consensus/services/EpochRoundListeners.js +116 -0
- package/src/core/consensus/services/EpochStateMachine.js +141 -0
- package/src/core/consensus/services/VDFBare.js +291 -0
- package/src/core/consensus/services/VDFNode.js +111 -0
- package/src/core/consensus/services/VDFProgressWatchdog.js +79 -0
- package/src/core/consensus/services/VDFService.js +87 -0
- package/src/core/consensus/services/VDFServiceManager.js +147 -0
- package/src/core/consensus/services/createVDFService.js +16 -0
- package/src/core/consensus/services/vdf-guardian.js +32 -0
- package/src/core/consensus/services/vdf-worker.js +90 -0
- package/src/core/network/Network.js +327 -238
- package/src/core/network/protocols/consensus/IndexerMessages.js +72 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1ConnectionPolicy.js +57 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1MessageRouter.js +147 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1Protocol.js +72 -0
- package/src/core/network/protocols/consensus/v1/IndexerPendingRequestService.js +108 -0
- package/src/core/network/protocols/consensus/v1/V1ConsensusProtocolError.js +29 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochAppendOperationHandler.js +68 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochProofProposalOperationHandler.js +226 -0
- package/src/core/network/protocols/consensus/v1/validators/ConsensusValidationSchema.js +192 -0
- package/src/core/network/protocols/consensus/v1/validators/V1BaseConsensusOperation.js +428 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendReply.js +47 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendRequest.js +17 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalApproval.js +116 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalRequest.js +152 -0
- package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js +145 -0
- package/src/core/network/protocols/shared/ConnectionOperationHandler.js +27 -0
- package/src/core/network/protocols/{NetworkMessages.js → validators/ValidatorMessages.js} +24 -14
- package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js} +22 -25
- package/src/core/network/protocols/{legacy/NetworkMessageRouter.js → validators/legacy/ValidatorLegacyMessageRouter.js} +6 -6
- package/src/core/network/protocols/{LegacyProtocol.js → validators/legacy/ValidatorLegacyProtocol.js} +9 -7
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/BaseStateOperationHandler.js +1 -1
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyGetRequestHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyRoleOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacySubnetworkOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyTransferOperationHandler.js +5 -5
- package/src/core/network/protocols/{ProtocolInterface.js → validators/shared/ValidatorProtocolInterface.js} +5 -5
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialBootstrapDeploymentValidator.js +1 -1
- package/src/core/network/protocols/validators/shared/validators/PartialHtlcValidator.js +325 -0
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialOperationValidator.js +44 -21
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialRoleAccessValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransactionValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransferValidator.js +4 -4
- package/src/core/network/protocols/{v1 → validators/v1}/V1ProtocolError.js +1 -1
- package/src/core/network/{services/PendingRequestService.js → protocols/validators/v1/ValidatorPendingRequestService.js} +19 -82
- package/src/core/network/protocols/{v1/NetworkMessageRouter.js → validators/v1/ValidatorV1MessageRouter.js} +9 -9
- package/src/core/network/protocols/{V1Protocol.js → validators/v1/ValidatorV1Protocol.js} +10 -8
- package/src/core/network/protocols/{connectionPolicies.js → validators/v1/connectionPolicies.js} +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BaseOperationHandler.js +6 -23
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BroadcastTransactionOperationHandler.js +14 -9
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1LivenessOperationHandler.js +5 -4
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +3 -3
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +44 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BaseOperation.js +19 -15
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionRequest.js +2 -2
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionResponse.js +34 -18
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1ValidationSchema.js +3 -3
- package/src/core/network/services/IndexerConnectionManager.js +88 -0
- package/src/core/network/services/TransactionPoolService.js +11 -18
- package/src/core/network/services/TransactionRateLimiterService.js +1 -1
- package/src/core/network/services/ValidatorConnectionManager.js +163 -0
- package/src/core/network/services/{MessageOrchestrator.js → ValidatorMessageOrchestrator.js} +34 -42
- package/src/core/network/services/ValidatorObserverService.js +21 -36
- package/src/core/network/shared/BasePendingRequestService.js +67 -0
- package/src/core/network/shared/PeerConnectionManager.js +185 -0
- package/src/core/state/State.js +1789 -173
- package/src/core/state/StateEventQueue.js +69 -0
- package/src/core/state/utils/address.js +10 -6
- package/src/core/state/utils/epochProof.js +109 -0
- package/src/core/state/utils/escrowEntry.js +178 -0
- package/src/core/state/validators/StateValidationSchema.js +757 -0
- package/src/core/state/validators/stateValidationRules.js +312 -0
- package/src/index.js +255 -65
- package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
- package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
- package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
- package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
- package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
- package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
- package/src/messages/state/ApplyStateMessageDirector.js +261 -2
- package/src/utils/StateMachine.js +125 -0
- package/src/utils/applyOperations.js +45 -2
- package/src/utils/buffer.js +239 -59
- package/src/utils/consensusConfig.js +76 -0
- package/src/utils/constants.js +174 -73
- package/src/utils/helpers.js +0 -25
- package/src/utils/htlcLock.js +122 -0
- package/src/utils/logger.js +10 -4
- package/src/utils/migrationUtils.js +2 -1
- package/src/utils/normalizers.js +113 -0
- package/src/utils/scheduler/SchedulableService.js +107 -0
- package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
- package/src/core/network/services/ConnectionManager.js +0 -406
- package/src/utils/check.js +0 -525
- package/src/utils/protobuf/applyOperations.cjs +0 -1373
- package/src/utils/protobuf/operationHelpers.js +0 -81
- /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
- /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
- /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessResponse.js +0 -0
|
@@ -4,61 +4,71 @@ import w from 'protomux-wakeup';
|
|
|
4
4
|
import b4a from 'b4a';
|
|
5
5
|
import TransactionPoolService from './services/TransactionPoolService.js';
|
|
6
6
|
import ValidatorObserverService from './services/ValidatorObserverService.js';
|
|
7
|
-
import
|
|
7
|
+
import ValidatorMessages from './protocols/validators/ValidatorMessages.js';
|
|
8
8
|
import { sleep } from '../../utils/helpers.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
CONNECTION_STATUS
|
|
13
|
-
} from '../../utils/constants.js';
|
|
14
|
-
import ConnectionManager from './services/ConnectionManager.js';
|
|
15
|
-
import MessageOrchestrator from './services/MessageOrchestrator.js';
|
|
9
|
+
import { TRAC_NAMESPACE, CONNECTION_STATUS } from '../../utils/constants.js';
|
|
10
|
+
import ValidatorConnectionManager from './services/ValidatorConnectionManager.js';
|
|
11
|
+
import ValidatorMessageOrchestrator from './services/ValidatorMessageOrchestrator.js';
|
|
16
12
|
import TransactionRateLimiterService from './services/TransactionRateLimiterService.js';
|
|
17
|
-
import
|
|
13
|
+
import ValidatorPendingRequestService from './protocols/validators/v1/ValidatorPendingRequestService.js';
|
|
18
14
|
import TransactionCommitService from "./services/TransactionCommitService.js";
|
|
19
|
-
import
|
|
15
|
+
import EpochCoordinatorService from '../consensus/services/EpochCoordinatorService.js';
|
|
16
|
+
import IndexerConnectionManager from './services/IndexerConnectionManager.js';
|
|
20
17
|
import { Logger } from '../../utils/logger.js';
|
|
21
18
|
import { WalletProvider } from 'trac-wallet';
|
|
19
|
+
import { CustomEventType } from '../../utils/constants.js';
|
|
20
|
+
import tracCryptoApi from 'trac-crypto-api'
|
|
21
|
+
import IndexerMessages from './protocols/consensus/IndexerMessages.js';
|
|
22
|
+
import IndexerPendingRequestService from './protocols/consensus/v1/IndexerPendingRequestService.js';
|
|
23
|
+
import { createEpochAppenderSelector } from '../consensus/epochAppenderSelection/createEpochAppenderSelector.js';
|
|
22
24
|
|
|
23
25
|
const wakeup = new w();
|
|
24
26
|
|
|
25
27
|
class Network extends ReadyResource {
|
|
26
|
-
#swarm
|
|
27
|
-
#networkMessages;
|
|
28
|
+
#swarm;
|
|
28
29
|
#transactionPoolService;
|
|
29
30
|
#validatorObserverService;
|
|
30
31
|
#validatorConnectionManager;
|
|
31
32
|
#validatorMessageOrchestrator;
|
|
32
33
|
#config;
|
|
33
34
|
#pendingConnections;
|
|
34
|
-
#connectTimeoutMs;
|
|
35
|
-
#maxPendingConnections;
|
|
36
35
|
#rateLimiter;
|
|
37
|
-
#
|
|
36
|
+
#validatorPendingRequestService;
|
|
38
37
|
#transactionCommitService;
|
|
39
38
|
#wallet;
|
|
40
|
-
#
|
|
39
|
+
#epochCoordinatorService;
|
|
41
40
|
#logger;
|
|
41
|
+
#state;
|
|
42
|
+
#store;
|
|
43
|
+
#indexerPendingRequestService;
|
|
44
|
+
#validatorMessages;
|
|
45
|
+
#indexerMessages;
|
|
46
|
+
#indexerConnectionManager;
|
|
47
|
+
#consensusPeerBanRequestedListener;
|
|
48
|
+
#epochAppenderSelector;
|
|
42
49
|
#closing = false;
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
52
|
* @param {State} state
|
|
53
|
+
* @param {object} store
|
|
46
54
|
* @param {Config} config
|
|
47
|
-
* @param {
|
|
55
|
+
* @param {object} address
|
|
48
56
|
**/
|
|
49
|
-
constructor(state, config,
|
|
57
|
+
constructor(state, store, config, wallet = null) {
|
|
50
58
|
super();
|
|
59
|
+
|
|
51
60
|
this.#config = config
|
|
52
|
-
this.#
|
|
53
|
-
this.#
|
|
61
|
+
this.#state = state
|
|
62
|
+
this.#store = store
|
|
63
|
+
this.#wallet = wallet
|
|
54
64
|
this.#pendingConnections = new Map();
|
|
55
65
|
this.#transactionCommitService = new TransactionCommitService(this.#config);
|
|
56
|
-
this.#transactionPoolService = new TransactionPoolService(state, address, this.#transactionCommitService ,this.#config);
|
|
57
|
-
this.#validatorObserverService = new ValidatorObserverService(this, state, address, this.#config);
|
|
58
|
-
this.#
|
|
59
|
-
this.#
|
|
60
|
-
this.#pendingRequestsService = new PendingRequestService(this.#config);
|
|
66
|
+
this.#transactionPoolService = new TransactionPoolService(state, wallet?.address, this.#transactionCommitService ,this.#config);
|
|
67
|
+
this.#validatorObserverService = new ValidatorObserverService(this, state, wallet?.address, this.#config);
|
|
68
|
+
this.#validatorPendingRequestService = new ValidatorPendingRequestService(this.#config);
|
|
69
|
+
this.#indexerPendingRequestService = new IndexerPendingRequestService(this.#config);
|
|
61
70
|
this.#logger = new Logger(this.#config);
|
|
71
|
+
this.#consensusPeerBanRequestedListener = this.#handleConsensusPeerBanRequested.bind(this);
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
get swarm() {
|
|
@@ -81,221 +91,235 @@ class Network extends ReadyResource {
|
|
|
81
91
|
return this.#validatorMessageOrchestrator;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
this.#
|
|
86
|
-
|
|
94
|
+
get indexerConnectionManager() {
|
|
95
|
+
return this.#indexerConnectionManager;
|
|
96
|
+
}
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
async _open() {
|
|
99
|
+
this.#logger.info('Network initialization...');
|
|
89
100
|
|
|
101
|
+
this.#assertCanReplicate();
|
|
102
|
+
const { wallet: wrappedWallet, keyPair } = await this.#getOrGenerateWallet();
|
|
103
|
+
this.#assertCanReplicate();
|
|
104
|
+
this.#wallet = wrappedWallet
|
|
105
|
+
|
|
106
|
+
// Recover durable votes before starting network resources.
|
|
107
|
+
this.#epochAppenderSelector = createEpochAppenderSelector(
|
|
108
|
+
this.#state,
|
|
109
|
+
this.#wallet,
|
|
110
|
+
this.#config,
|
|
111
|
+
() => this.#indexerConnectionManager,
|
|
112
|
+
this.#logger,
|
|
113
|
+
this.#indexerPendingRequestService
|
|
114
|
+
);
|
|
115
|
+
await this.#epochAppenderSelector.open();
|
|
116
|
+
this.#assertCanReplicate();
|
|
90
117
|
this.transactionPoolService.start();
|
|
118
|
+
|
|
119
|
+
this.#swarm = new Hyperswarm({
|
|
120
|
+
keyPair,
|
|
121
|
+
bootstrap: this.#config.dhtBootstrap,
|
|
122
|
+
maxPeers: this.#config.maxPeers,
|
|
123
|
+
maxParallel: this.#config.maxParallel,
|
|
124
|
+
maxServerConnections: this.#config.maxServerConnections,
|
|
125
|
+
maxClientConnections: this.#config.maxClientConnections
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
this.#rateLimiter = new TransactionRateLimiterService(this.#swarm, this.#config);
|
|
129
|
+
this.#validatorMessages = new ValidatorMessages(
|
|
130
|
+
this.#state,
|
|
131
|
+
this.#wallet,
|
|
132
|
+
this.#rateLimiter,
|
|
133
|
+
this.#transactionPoolService,
|
|
134
|
+
this.#validatorPendingRequestService,
|
|
135
|
+
this.#transactionCommitService,
|
|
136
|
+
this.#config
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this.#validatorConnectionManager = new ValidatorConnectionManager(this.#config.maxValidators, this.#config, this.#logger, this.#validatorMessages);
|
|
140
|
+
await this.#validatorConnectionManager.ready();
|
|
141
|
+
|
|
142
|
+
this.#validatorMessageOrchestrator = new ValidatorMessageOrchestrator(this.#validatorConnectionManager, this.#state, this.#config, this.#wallet);
|
|
143
|
+
|
|
144
|
+
this.#indexerMessages = new IndexerMessages(
|
|
145
|
+
this.#state, this.#wallet, this.#config, this.#indexerPendingRequestService,
|
|
146
|
+
this.#handleConsensusSessionClosed.bind(this), this.#epochAppenderSelector,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const indexerCount = await this.#state.indexerCount();
|
|
150
|
+
this.#indexerConnectionManager = new IndexerConnectionManager(
|
|
151
|
+
indexerCount,
|
|
152
|
+
this.#config,
|
|
153
|
+
this.#logger,
|
|
154
|
+
this.#indexerMessages,
|
|
155
|
+
this.#state,
|
|
156
|
+
this,
|
|
157
|
+
this.#wallet,
|
|
158
|
+
);
|
|
159
|
+
await this.#indexerConnectionManager.ready();
|
|
160
|
+
|
|
161
|
+
this.#epochCoordinatorService = new EpochCoordinatorService(
|
|
162
|
+
this.#state, this.#wallet, this.#config, this.#indexerConnectionManager, this.#epochAppenderSelector,
|
|
163
|
+
);
|
|
164
|
+
await this.#epochCoordinatorService.ready();
|
|
165
|
+
this.#assertCanReplicate();
|
|
166
|
+
|
|
167
|
+
this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
168
|
+
this.#listeners();
|
|
169
|
+
|
|
170
|
+
this.#swarm.join(this.#config.channel, { server: true, client: true });
|
|
171
|
+
this.#swarm.flush();
|
|
91
172
|
this.validatorObserverService.start();
|
|
173
|
+
|
|
174
|
+
if (this.#state.isIndexer()) {
|
|
175
|
+
if (await this.#state.getCurrentEpoch() !== null) {
|
|
176
|
+
this.#epochCoordinatorService.start();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
92
179
|
}
|
|
93
180
|
|
|
94
181
|
async _close() {
|
|
95
182
|
this.#logger.info('Network: closing gracefully...');
|
|
96
183
|
this.#closing = true;
|
|
97
|
-
|
|
184
|
+
this.#state.off(CustomEventType.CONSENSUS_PEER_BAN_REQUESTED, this.#consensusPeerBanRequestedListener);
|
|
185
|
+
await this.#epochCoordinatorService.close();
|
|
186
|
+
await this.#epochAppenderSelector.close();
|
|
187
|
+
await this.transactionPoolService.stop();
|
|
98
188
|
await sleep(100);
|
|
99
|
-
await this.#validatorObserverService.
|
|
100
|
-
await sleep(5_000);
|
|
101
|
-
if (this.#validatorHealthCheckService) {
|
|
102
|
-
await this.#validatorHealthCheckService.close();
|
|
103
|
-
}
|
|
189
|
+
await this.#validatorObserverService.stop();
|
|
104
190
|
|
|
105
|
-
this.cleanupNetworkListeners();
|
|
106
191
|
this.cleanupPendingConnections();
|
|
107
|
-
this.#
|
|
192
|
+
this.#validatorPendingRequestService.close();
|
|
108
193
|
this.#transactionCommitService.close();
|
|
109
|
-
|
|
194
|
+
this.#indexerPendingRequestService.close();
|
|
195
|
+
await this.#indexerConnectionManager.close();
|
|
196
|
+
await this.#validatorConnectionManager.close()
|
|
110
197
|
const swarm = this.#swarm;
|
|
111
|
-
if (swarm
|
|
112
|
-
|
|
113
|
-
swarm.removeAllListeners('connection');
|
|
114
|
-
}
|
|
198
|
+
if (swarm) {
|
|
199
|
+
swarm.removeAllListeners('connection');
|
|
115
200
|
await swarm.destroy();
|
|
116
|
-
if (this.#swarm === swarm)
|
|
117
|
-
this.#swarm = null;
|
|
118
|
-
}
|
|
201
|
+
if (this.#swarm === swarm) this.#swarm = null;
|
|
119
202
|
}
|
|
120
203
|
}
|
|
121
204
|
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
this.#pendingConnections.delete(publicKey);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
this.on(EventType.VALIDATOR_CONNECTION_READY, async ({ publicKey, type, connection }) => {
|
|
129
|
-
this.#logger.debug(`Network Event: VALIDATOR_CONNECTION_READY | PublicKey: ${publicKey} | Type: ${type}`);
|
|
130
|
-
const { timeoutId } = this.#pendingConnections.get(publicKey);
|
|
131
|
-
|
|
132
|
-
if (!timeoutId) return;
|
|
133
|
-
|
|
134
|
-
clearTimeout(timeoutId);
|
|
135
|
-
this.#pendingConnections.delete(publicKey);
|
|
205
|
+
#handleConsensusSessionClosed(connection) {
|
|
206
|
+
this.#indexerConnectionManager.remove(connection.remotePublicKey, connection);
|
|
207
|
+
}
|
|
136
208
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
209
|
+
#prepareConnection(connection) {
|
|
210
|
+
this.#assertCanReplicate();
|
|
211
|
+
connection.protocolSessions = {};
|
|
212
|
+
const stream = this.#store.replicate(connection);
|
|
213
|
+
wakeup.addStream(stream);
|
|
214
|
+
}
|
|
143
215
|
|
|
144
|
-
|
|
216
|
+
#listeners() {
|
|
217
|
+
this.#state.on(CustomEventType.CONSENSUS_PEER_BAN_REQUESTED, this.#consensusPeerBanRequestedListener);
|
|
218
|
+
this.#state.on(CustomEventType.IS_INDEXER, async (publicKey) => {
|
|
219
|
+
const publicKeyHex = this.#normalizePublicKey(publicKey);
|
|
220
|
+
this.#validatorConnectionManager.remove(publicKeyHex);
|
|
221
|
+
const indexerCount = await this.#state.indexerCount();
|
|
222
|
+
this.#indexerConnectionManager.setMax(indexerCount - 1);
|
|
223
|
+
});
|
|
145
224
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
} catch (err) {
|
|
150
|
-
this.#logger.debug(`health check support unknown for peer with publicKey ${publicKey}: ${err?.message ?? err}`);
|
|
151
|
-
}
|
|
225
|
+
this.#state.on(CustomEventType.UNWRITABLE, (publicKey) => {
|
|
226
|
+
this.disconnectValidatorPeer(publicKey, 'peer became unwritable');
|
|
227
|
+
});
|
|
152
228
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
229
|
+
this.#state.on(CustomEventType.IS_INDEXER, async bufferAddress => {
|
|
230
|
+
const address = tracCryptoApi.address.encode(this.#config.addressPrefix, bufferAddress);
|
|
231
|
+
if (address === this.#wallet.address) {
|
|
232
|
+
if (await this.#state.getCurrentEpoch() !== null) {
|
|
233
|
+
this.#epochCoordinatorService.start();
|
|
157
234
|
}
|
|
158
235
|
}
|
|
159
|
-
|
|
160
236
|
});
|
|
161
|
-
}
|
|
162
237
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async replicate(
|
|
176
|
-
state,
|
|
177
|
-
store,
|
|
178
|
-
wallet,
|
|
179
|
-
) {
|
|
180
|
-
this.#assertCanReplicate();
|
|
238
|
+
this.#state.on(CustomEventType.IS_NON_INDEXER, async (bufferAddress) => {
|
|
239
|
+
const address = tracCryptoApi.address.encode(this.#config.addressPrefix, bufferAddress);
|
|
240
|
+
if (address === this.#wallet.address) {
|
|
241
|
+
await this.#epochCoordinatorService.stop();
|
|
242
|
+
} else {
|
|
243
|
+
this.#indexerConnectionManager.remove(bufferAddress);
|
|
244
|
+
const indexerCount = await this.#state.indexerCount();
|
|
245
|
+
this.#indexerConnectionManager.setMax(indexerCount - 1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
181
248
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
249
|
+
this.#state.on(CustomEventType.GENESIS_EPOCH_CREATED, () => {
|
|
250
|
+
if (this.#state.isIndexer()) {
|
|
251
|
+
this.#epochCoordinatorService.start();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
185
254
|
|
|
186
|
-
|
|
187
|
-
this.#
|
|
255
|
+
this.#swarm.prependListener('connection', async (connection) => {
|
|
256
|
+
if (this.#closing || this.closed) {
|
|
257
|
+
this.#destroyConnection(connection);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// A handshake accepted before a ban can finish after the ban was applied.
|
|
261
|
+
if (this.#isPeerBanned(connection.remotePublicKey)) {
|
|
262
|
+
connection.destroy();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
188
265
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
266
|
+
/*
|
|
267
|
+
Here is the issue:
|
|
268
|
+
|
|
269
|
+
The current session is supposed to be attached as soon as possible (mostly to respond to probe since there is no connection ready signal on this level)
|
|
270
|
+
Since the connection was started from the other side, this havent gone through "qualification" which happens on tryConnect.
|
|
271
|
+
Because of that, we need to assume the current connection is that of a validator (who responds to probe) and later override it if necessary.
|
|
272
|
+
This is leaky for two reasons: first we need to keep a reference to messages and disclose the connection structure in this class.
|
|
273
|
+
second is that the protocol itself doesnt fit the connection life-cycle (this is a bigger problem that also touched on DHT factory structure being "swallowed by swarm")
|
|
274
|
+
*/
|
|
275
|
+
const publicKey = b4a.toString(connection.remotePublicKey, 'hex');
|
|
276
|
+
|
|
277
|
+
connection.on('close', () => {
|
|
278
|
+
this.#rejectAllPendingRequests(publicKey, new Error('Connection closed before response'));
|
|
279
|
+
this.#swarm?.leavePeer(connection.remotePublicKey);
|
|
280
|
+
this.#validatorConnectionManager.remove(publicKey, connection);
|
|
281
|
+
this.#closeProtocolSessions(connection);
|
|
196
282
|
});
|
|
197
283
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
this.#validatorHealthCheckService = new ValidatorHealthCheckService(this.#config);
|
|
209
|
-
await this.#validatorHealthCheckService.ready();
|
|
210
|
-
this.#assertCanReplicate();
|
|
211
|
-
|
|
212
|
-
this.#validatorConnectionManager.subscribeToHealthChecks(this.#validatorHealthCheckService);
|
|
213
|
-
|
|
214
|
-
this.#logger.info(`Channel: ${b4a.toString(this.#config.channel)}`);
|
|
215
|
-
|
|
216
|
-
this.#swarm.on('connection', async (connection) => {
|
|
217
|
-
if (this.#closing) {
|
|
218
|
-
this.#destroyConnection(connection);
|
|
284
|
+
connection.on('error', (error) => {
|
|
285
|
+
this.#rejectAllPendingRequests(publicKey, error ?? new Error('Connection error before response'));
|
|
286
|
+
if (
|
|
287
|
+
error && error.message && (
|
|
288
|
+
error.message.includes('connection reset by peer') ||
|
|
289
|
+
error.message.includes('Duplicate connection') ||
|
|
290
|
+
error.message.includes('connection timed out'))
|
|
291
|
+
) {
|
|
292
|
+
// TODO: decide if we want to handle this error in a specific way. It generates a lot of logs.
|
|
219
293
|
return;
|
|
220
294
|
}
|
|
295
|
+
this.#logger.error(error?.message ?? 'Unknown network connection error');
|
|
296
|
+
});
|
|
221
297
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
// after close has begun; store.replicate would otherwise throw.
|
|
231
|
-
if (this.#closing || this.#swarm === null) {
|
|
232
|
-
this.#destroyConnection(connection);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ATTENTION: Must be called AFTER the protomux init above
|
|
237
|
-
const stream = store.replicate(connection);
|
|
238
|
-
wakeup.addStream(stream);
|
|
239
|
-
} catch (error) {
|
|
240
|
-
const publicKey = connection.remotePublicKey
|
|
241
|
-
? b4a.toString(connection.remotePublicKey, 'hex')
|
|
242
|
-
: 'unknown';
|
|
243
|
-
this.#pendingRequestsService.rejectPendingRequestsForPeer(
|
|
244
|
-
publicKey,
|
|
245
|
-
error ?? new Error('Connection setup failed')
|
|
246
|
-
);
|
|
298
|
+
try {
|
|
299
|
+
this.#prepareConnection(connection);
|
|
300
|
+
this.#validatorMessages.prepareConnection(connection);
|
|
301
|
+
this.#indexerMessages.prepareConnection(connection);
|
|
302
|
+
await this.#promotePendingConnection(publicKey, connection);
|
|
303
|
+
if (this.#closing || this.closed) {
|
|
304
|
+
this.#validatorConnectionManager.remove(publicKey, connection);
|
|
305
|
+
this.#indexerConnectionManager.remove(publicKey, connection);
|
|
247
306
|
this.#destroyConnection(connection);
|
|
248
|
-
if (!this.#closing) {
|
|
249
|
-
this.#logger.error(error?.message ?? 'Unknown network connection setup error');
|
|
250
|
-
}
|
|
251
|
-
return;
|
|
252
307
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
308
|
+
} catch (error) {
|
|
309
|
+
this.#rejectAllPendingRequests(publicKey, error ?? new Error('Connection setup failed'));
|
|
310
|
+
this.#destroyConnection(connection);
|
|
311
|
+
if (!this.#closing) {
|
|
312
|
+
this.#logger.error(error?.message ?? 'Unknown network connection setup error');
|
|
258
313
|
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
259
317
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
new Error('Connection closed before response')
|
|
264
|
-
);
|
|
265
|
-
this.#swarm?.leavePeer(connection.remotePublicKey);
|
|
266
|
-
// only act on the connection this event belongs to
|
|
267
|
-
if (this.#validatorConnectionManager.isCurrent(publicKey, connection)) {
|
|
268
|
-
this.#validatorConnectionManager.remove(publicKey);
|
|
269
|
-
}
|
|
270
|
-
if (connection.protocolSession) {
|
|
271
|
-
try {
|
|
272
|
-
connection.protocolSession.close();
|
|
273
|
-
} catch {}
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
connection.on('error', (error) => {
|
|
278
|
-
this.#pendingRequestsService.rejectPendingRequestsForPeer(
|
|
279
|
-
publicKey,
|
|
280
|
-
error ?? new Error('Connection error before response')
|
|
281
|
-
);
|
|
282
|
-
if (
|
|
283
|
-
error && error.message && (
|
|
284
|
-
error.message.includes('connection reset by peer') ||
|
|
285
|
-
error.message.includes('Duplicate connection') ||
|
|
286
|
-
error.message.includes('connection timed out'))
|
|
287
|
-
) {
|
|
288
|
-
// TODO: decide if we want to handle this error in a specific way. It generates a lot of logs.
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
this.#logger.error(error?.message ?? 'Unknown network connection error');
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
this.#swarm.join(this.#config.channel, { server: true, client: true });
|
|
297
|
-
this.#swarm.flush();
|
|
318
|
+
cleanupPendingConnections() {
|
|
319
|
+
for (const { timeoutId } of this.#pendingConnections.values()) {
|
|
320
|
+
clearTimeout(timeoutId);
|
|
298
321
|
}
|
|
322
|
+
this.#pendingConnections.clear();
|
|
299
323
|
}
|
|
300
324
|
|
|
301
325
|
isConnectionPending(publicKey) {
|
|
@@ -306,6 +330,42 @@ class Network extends ReadyResource {
|
|
|
306
330
|
return this.#pendingConnections.size;
|
|
307
331
|
}
|
|
308
332
|
|
|
333
|
+
/** Executes a ban requested by the consensus connection policy. */
|
|
334
|
+
#handleConsensusPeerBanRequested({ connection, error } = {}) {
|
|
335
|
+
if (this.closing !== null || this.closed) return;
|
|
336
|
+
|
|
337
|
+
const publicKey = this.#normalizePublicKey(connection?.remotePublicKey);
|
|
338
|
+
if (!publicKey) return;
|
|
339
|
+
|
|
340
|
+
// Hyperswarm retains the ban for this swarm's lifetime, but does not close the socket.
|
|
341
|
+
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
342
|
+
peerInfo?.ban(true);
|
|
343
|
+
const connections = new Set([connection]);
|
|
344
|
+
for (const activeConnection of this.#swarm.connections) {
|
|
345
|
+
if (this.#normalizePublicKey(activeConnection.remotePublicKey) === publicKey) {
|
|
346
|
+
connections.add(activeConnection);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const pending = this.#pendingConnections.get(publicKey);
|
|
350
|
+
if (pending) {
|
|
351
|
+
clearTimeout(pending.timeoutId);
|
|
352
|
+
this.#pendingConnections.delete(publicKey);
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
this.#rejectAllPendingRequests(publicKey, error);
|
|
356
|
+
this.#validatorConnectionManager.remove(publicKey);
|
|
357
|
+
this.#indexerConnectionManager.remove(publicKey);
|
|
358
|
+
} finally {
|
|
359
|
+
for (const activeConnection of connections) activeConnection.destroy();
|
|
360
|
+
}
|
|
361
|
+
this.#logger.info(`Network: ${peerInfo?.banned ? 'banned' : 'disconnected'} consensus peer ${publicKey}: ${error.message}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#isPeerBanned(publicKey) {
|
|
365
|
+
const key = this.#normalizePublicKey(publicKey);
|
|
366
|
+
return this.#swarm.peers.get(key)?.banned === true;
|
|
367
|
+
}
|
|
368
|
+
|
|
309
369
|
disconnectValidatorPeer(publicKey, reason = 'validator peer invalidated') {
|
|
310
370
|
const publicKeyHex = this.#normalizePublicKey(publicKey);
|
|
311
371
|
if (!publicKeyHex) return false;
|
|
@@ -322,62 +382,85 @@ class Network extends ReadyResource {
|
|
|
322
382
|
|
|
323
383
|
if (isTrackedValidator) {
|
|
324
384
|
this.#logger.debug(`Network.disconnectValidatorPeer: detaching tracked validator ${publicKeyHex}. Reason: ${reason}`);
|
|
325
|
-
this.#validatorConnectionManager.remove(publicKeyHex
|
|
385
|
+
this.#validatorConnectionManager.remove(publicKeyHex);
|
|
326
386
|
}
|
|
327
387
|
|
|
328
388
|
return hadPendingValidatorConnection || isTrackedValidator;
|
|
329
389
|
}
|
|
330
390
|
|
|
331
|
-
async #getOrGenerateWallet(
|
|
332
|
-
if (
|
|
333
|
-
const keyPair =
|
|
334
|
-
|
|
335
|
-
return { keyPair, wallet }
|
|
391
|
+
async #getOrGenerateWallet() {
|
|
392
|
+
if (this.#config.enableWallet) {
|
|
393
|
+
const keyPair = { publicKey: this.#wallet.publicKey, secretKey: this.#wallet.secretKey }
|
|
394
|
+
return { keyPair, wallet: this.#wallet }
|
|
336
395
|
} else {
|
|
337
|
-
|
|
396
|
+
// This creates an ephemeral private key via holepunch facilities
|
|
397
|
+
const keyPair = await this.#store.createKeyPair(TRAC_NAMESPACE);
|
|
398
|
+
const wallet = await new WalletProvider(this.#config).fromSecretKey(keyPair.secretKey)
|
|
338
399
|
return { keyPair, wallet }
|
|
339
400
|
}
|
|
340
401
|
}
|
|
341
402
|
|
|
342
|
-
async tryConnect(publicKey, type
|
|
343
|
-
|
|
344
|
-
if (this.#
|
|
403
|
+
async tryConnect(publicKey, type) {
|
|
404
|
+
this.#assertCanReplicate();
|
|
405
|
+
if (!this.#swarm) throw new Error('Network swarm is not initialized');
|
|
406
|
+
if (this.#isPeerBanned(publicKey)) return CONNECTION_STATUS.IGNORED;
|
|
407
|
+
if (this.#pendingConnections.has(publicKey) || this.#pendingConnections.size >= this.#config.maxPendingConnections) {
|
|
345
408
|
this.#logger.debug(`Network.tryConnect: Connection to peer: ${publicKey} as type: ${type} is already pending or max pending connections reached.`);
|
|
346
409
|
return CONNECTION_STATUS.IGNORED;
|
|
347
410
|
}
|
|
348
411
|
|
|
349
412
|
const timeoutId = setTimeout(() => {
|
|
350
|
-
|
|
351
|
-
this.
|
|
352
|
-
|
|
413
|
+
// timeout is here to manage the scenario which we will join peer and estabilish a connection (a bit more overhead for validators)
|
|
414
|
+
if (this.#pendingConnections.has(publicKey)) {
|
|
415
|
+
this.#pendingConnections.delete(publicKey)
|
|
416
|
+
}
|
|
417
|
+
}, this.#config.connectTimeoutMs);
|
|
418
|
+
|
|
353
419
|
this.#pendingConnections.set(publicKey, { type, timeoutId });
|
|
354
420
|
|
|
355
421
|
const target = b4a.from(publicKey, 'hex');
|
|
356
422
|
// A retained peer may have lost explicit retries after leavePeer().
|
|
357
423
|
if (!this.#swarm.peers.get(publicKey)?.explicit) {
|
|
358
424
|
this.#swarm.joinPeer(target);
|
|
359
|
-
}
|
|
425
|
+
}
|
|
360
426
|
|
|
361
427
|
const peerInfo = this.#swarm.peers.get(publicKey);
|
|
362
|
-
if (peerInfo)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
428
|
+
if (!peerInfo) return CONNECTION_STATUS.PENDING;
|
|
429
|
+
|
|
430
|
+
const connection = this.#swarm._allConnections.get(peerInfo.publicKey);
|
|
431
|
+
// _allConnections also contains transports whose handshake is still in progress.
|
|
432
|
+
if (!connection || !this.#swarm.connections.has(connection)) return CONNECTION_STATUS.PENDING;
|
|
433
|
+
|
|
434
|
+
const isConnectionReady = (type === 'validator' && !this.#validatorPendingRequestService.isProbePending(connection.remotePublicKey.toString('hex'))) || type === 'indexer'
|
|
435
|
+
if (isConnectionReady) {
|
|
436
|
+
await this.#promotePendingConnection(publicKey, connection);
|
|
437
|
+
if (this.#closing || this.closed) return CONNECTION_STATUS.IGNORED;
|
|
438
|
+
if (this.#isPeerBanned(publicKey)) return CONNECTION_STATUS.IGNORED;
|
|
439
|
+
if (type === 'indexer' && !this.#indexerConnectionManager.connected(publicKey)) {
|
|
440
|
+
return CONNECTION_STATUS.IGNORED;
|
|
371
441
|
}
|
|
372
|
-
|
|
442
|
+
return CONNECTION_STATUS.CONNECTED;
|
|
443
|
+
}
|
|
373
444
|
|
|
374
445
|
return CONNECTION_STATUS.PENDING;
|
|
375
446
|
}
|
|
376
447
|
|
|
377
|
-
async #
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
448
|
+
async #promotePendingConnection(publicKey, connection) {
|
|
449
|
+
const pending = this.#pendingConnections.get(publicKey);
|
|
450
|
+
if (pending) {
|
|
451
|
+
clearTimeout(pending.timeoutId);
|
|
452
|
+
this.#pendingConnections.delete(publicKey);
|
|
453
|
+
|
|
454
|
+
if (pending.type === 'indexer') {
|
|
455
|
+
await this.#indexerConnectionManager.add(connection.remotePublicKey, connection);
|
|
456
|
+
} else if (pending.type === 'validator') {
|
|
457
|
+
await this.#validatorConnectionManager.add(publicKey, connection);
|
|
458
|
+
// A pending probe may settle after its peer has been banned.
|
|
459
|
+
if (this.#closing || this.closed || this.#isPeerBanned(publicKey)) {
|
|
460
|
+
this.#validatorConnectionManager.remove(publicKey, connection);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
381
464
|
}
|
|
382
465
|
|
|
383
466
|
#normalizePublicKey(publicKey) {
|
|
@@ -386,6 +469,11 @@ class Network extends ReadyResource {
|
|
|
386
469
|
return null;
|
|
387
470
|
}
|
|
388
471
|
|
|
472
|
+
#rejectAllPendingRequests(publicKey, error) {
|
|
473
|
+
this.#validatorPendingRequestService.rejectPendingRequestsForPeer(publicKey, error);
|
|
474
|
+
this.#indexerPendingRequestService.rejectPendingRequestsForPeer(publicKey, error);
|
|
475
|
+
}
|
|
476
|
+
|
|
389
477
|
#clearPendingValidatorConnection(publicKeyHex) {
|
|
390
478
|
if (!this.#pendingConnections.has(publicKeyHex)) return false;
|
|
391
479
|
|
|
@@ -403,18 +491,19 @@ class Network extends ReadyResource {
|
|
|
403
491
|
}
|
|
404
492
|
}
|
|
405
493
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
if (connection.protocolSession) {
|
|
494
|
+
#closeProtocolSessions(connection) {
|
|
495
|
+
for (const session of Object.values(connection.protocolSessions ?? {})) {
|
|
409
496
|
try {
|
|
410
|
-
|
|
497
|
+
session?.close();
|
|
411
498
|
} catch {}
|
|
412
499
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#destroyConnection(connection) {
|
|
503
|
+
if (!connection) return;
|
|
504
|
+
this.#closeProtocolSessions(connection);
|
|
505
|
+
if (typeof connection.destroy === 'function') connection.destroy();
|
|
506
|
+
else if (typeof connection.end === 'function') connection.end();
|
|
418
507
|
}
|
|
419
508
|
}
|
|
420
509
|
|