p2p-fret 1.0.1 → 1.0.2
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/dist/src/service/fret-service.d.ts +57 -8
- package/dist/src/service/fret-service.js +95 -25
- package/dist/src/service/libp2p-fret-service.d.ts +15 -0
- package/dist/src/service/libp2p-fret-service.js +19 -0
- package/package.json +1 -1
- package/src/rpc/protocols.ts +808 -808
- package/src/rpc/validate.ts +474 -474
- package/src/service/fret-service.ts +3904 -3825
- package/src/service/libp2p-fret-service.ts +21 -0
- package/src/store/digitree-store.ts +699 -699
|
@@ -253,8 +253,8 @@ export declare class FretService implements IFretService, Startable {
|
|
|
253
253
|
* Score a direct interaction with `id`.
|
|
254
254
|
*
|
|
255
255
|
* **Scoring never creates.** A peer we hold no entry for is not scored — bookkeeping *about* a
|
|
256
|
-
* peer must not be able to re-admit it, or removing a peer would not stick: `
|
|
257
|
-
* a
|
|
256
|
+
* peer must not be able to re-admit it, or removing a peer would not stick: `enforceCapacity`
|
|
257
|
+
* evicts a connected peer, its connection closes later, and the `peer:disconnect` listener's
|
|
258
258
|
* `applyFailure` would resurrect it as an unclassified stranger the classification pass then
|
|
259
259
|
* spends probes on. That is why the helper takes no coordinate: with the entry required to
|
|
260
260
|
* exist, `entry.coord` *is* the value every caller used to compute, so "create this peer at
|
|
@@ -320,8 +320,8 @@ export declare class FretService implements IFretService, Startable {
|
|
|
320
320
|
* Record a failed interaction with `id`.
|
|
321
321
|
*
|
|
322
322
|
* **Scoring never creates** — see {@link applyTouch}. This is the helper the rule exists for:
|
|
323
|
-
* the `peer:disconnect` listener calls it for a peer
|
|
324
|
-
* old create-on-miss arm brought that peer straight back.
|
|
323
|
+
* the `peer:disconnect` listener calls it for a peer capacity eviction may already have
|
|
324
|
+
* removed, and the old create-on-miss arm brought that peer straight back.
|
|
325
325
|
*/
|
|
326
326
|
private applyFailure;
|
|
327
327
|
/** This peer's stored ring coordinate, hashing its id when we hold no entry for it yet. */
|
|
@@ -766,6 +766,22 @@ export declare class FretService implements IFretService, Startable {
|
|
|
766
766
|
private computeReplacements;
|
|
767
767
|
private sendLeaveToNeighbors;
|
|
768
768
|
private handleLeave;
|
|
769
|
+
/**
|
|
770
|
+
* A peer told us it is leaving: mark it `dead` — the immediate counterpart of a completed run
|
|
771
|
+
* of failed contacts — rather than removing it.
|
|
772
|
+
*
|
|
773
|
+
* Removal did not stick. libp2p keeps the departed peer's peerStore record, negotiated
|
|
774
|
+
* protocols included, so the next tick's `seedFromPeerStore` (or a `peer:update`) re-created
|
|
775
|
+
* the entry and labelled it `member` again — a live ring member until three probes failed, and
|
|
776
|
+
* forever if it had no address to probe. `dead` survives that re-seed because `upsert`
|
|
777
|
+
* preserves `state` and classification touches only `membership`, and it already carries every
|
|
778
|
+
* other consequence a departure needs: out of every ring view, unprotected at eviction, skipped
|
|
779
|
+
* by the maintenance fan-outs, and restored by any proof of life if the peer comes back.
|
|
780
|
+
*
|
|
781
|
+
* The contact-failure run is left alone: nothing reads it while the peer is dead, and proof of
|
|
782
|
+
* life clears it together with the label.
|
|
783
|
+
*/
|
|
784
|
+
private markDeparted;
|
|
769
785
|
/**
|
|
770
786
|
* Record a leave notice's suggested replacements as untrusted local hints — and nothing more.
|
|
771
787
|
*
|
|
@@ -901,12 +917,45 @@ export declare class FretService implements IFretService, Startable {
|
|
|
901
917
|
*/
|
|
902
918
|
private stabilizeOnce;
|
|
903
919
|
/**
|
|
904
|
-
* The
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
920
|
+
* The S/P window a tick verifies — the live members nearest self, `max(2, m)` per side — split
|
|
921
|
+
* by whether we can contact them at all.
|
|
922
|
+
*
|
|
923
|
+
* `targets` are the dialable ones a tick pings and snapshot-fetches: at most 4, in **ring
|
|
924
|
+
* order** (closest first, sides interleaved) — deliberately not rotated like the other
|
|
925
|
+
* candidate lists. These are the peers ring correctness depends on most, so a truncated tick
|
|
926
|
+
* should skip the outermost and never the immediate successor or predecessor; it
|
|
927
|
+
* self-corrects next tick. `uncontactable` is the rest of the window, uncapped: the 4 bounds
|
|
928
|
+
* RPCs, and none is spent on these (see {@link strikeUncontactable}).
|
|
929
|
+
*
|
|
930
|
+
* Walked through `ringNeighborsBothSides` rather than the public `getNeighbors`, whose `both`
|
|
931
|
+
* direction is successor-biased (see the NOTE there): at m = 8 it returned self plus seven
|
|
932
|
+
* successors on any ring of more than nine live members, so no predecessor was ever verified.
|
|
908
933
|
*/
|
|
909
934
|
private nearProbeTargets;
|
|
935
|
+
/**
|
|
936
|
+
* Count one failed contact against each live member of the S/P window that no pass could
|
|
937
|
+
* reach: not connected, and no address libp2p could dial.
|
|
938
|
+
*
|
|
939
|
+
* Without this such a peer never reaches `dead`, because a strike is booked only when an
|
|
940
|
+
* outbound RPC fails and every pass that sends one filters on `isDialable` first. A phone that
|
|
941
|
+
* dials out and listens nowhere is exactly that peer once its connection drops — so it stayed a
|
|
942
|
+
* live member, and a cohort member, indefinitely.
|
|
943
|
+
*
|
|
944
|
+
* The observation is the tick, not the disconnect, so "an idle `peer:disconnect` is not a
|
|
945
|
+
* strike" stays true: `applyContactStrike`'s spacing guard makes each passive tick (1.5 s) one
|
|
946
|
+
* independent strike, the peer is `dead` after `deadAfterFailures` ticks, and one that
|
|
947
|
+
* reconnects within a tick or two (`peer:connect` is proof of life) is never killed. Once dead
|
|
948
|
+
* it leaves the live-member window, so the strikes stop by themselves.
|
|
949
|
+
*
|
|
950
|
+
* NOTE: only the S/P window is swept, so on a ring of more than 2·max(2, m) + 1 live members an
|
|
951
|
+
* address-less member outside our window is never struck by us and stays a live member in our
|
|
952
|
+
* cohorts. If cohorts assembled over such rings start waiting on departed phones, widen this to
|
|
953
|
+
* every live member — still local work, one store walk.
|
|
954
|
+
*
|
|
955
|
+
* Local work with no RPC, so the tick budget does not apply; but our own cancellation is still
|
|
956
|
+
* not evidence, so nothing is booked once the run signal has fired.
|
|
957
|
+
*/
|
|
958
|
+
private strikeUncontactable;
|
|
910
959
|
/**
|
|
911
960
|
* Ping `id`, then fetch and merge its neighbor snapshot — one pooled task per near peer.
|
|
912
961
|
*
|
|
@@ -533,8 +533,8 @@ export class FretService {
|
|
|
533
533
|
* Score a direct interaction with `id`.
|
|
534
534
|
*
|
|
535
535
|
* **Scoring never creates.** A peer we hold no entry for is not scored — bookkeeping *about* a
|
|
536
|
-
* peer must not be able to re-admit it, or removing a peer would not stick: `
|
|
537
|
-
* a
|
|
536
|
+
* peer must not be able to re-admit it, or removing a peer would not stick: `enforceCapacity`
|
|
537
|
+
* evicts a connected peer, its connection closes later, and the `peer:disconnect` listener's
|
|
538
538
|
* `applyFailure` would resurrect it as an unclassified stranger the classification pass then
|
|
539
539
|
* spends probes on. That is why the helper takes no coordinate: with the entry required to
|
|
540
540
|
* exist, `entry.coord` *is* the value every caller used to compute, so "create this peer at
|
|
@@ -647,8 +647,8 @@ export class FretService {
|
|
|
647
647
|
* Record a failed interaction with `id`.
|
|
648
648
|
*
|
|
649
649
|
* **Scoring never creates** — see {@link applyTouch}. This is the helper the rule exists for:
|
|
650
|
-
* the `peer:disconnect` listener calls it for a peer
|
|
651
|
-
* old create-on-miss arm brought that peer straight back.
|
|
650
|
+
* the `peer:disconnect` listener calls it for a peer capacity eviction may already have
|
|
651
|
+
* removed, and the old create-on-miss arm brought that peer straight back.
|
|
652
652
|
*/
|
|
653
653
|
async applyFailure(id) {
|
|
654
654
|
const entry = this.store.getById(id);
|
|
@@ -1266,9 +1266,9 @@ export class FretService {
|
|
|
1266
1266
|
}, this.protocols.PROTOCOL_MAYBE_ACT, this.maxBytesMaybeAct(), () => { this.diag.rejected.malformed++; }, streamCaps),
|
|
1267
1267
|
// NOTE: leave is deliberately the one inbound handler with no `noteInboundRpc`
|
|
1268
1268
|
// hook (compare neighbors / maybeAct / ping above and below). That hook applies
|
|
1269
|
-
// the `rpc-inbound` membership signal to the sender, which here
|
|
1270
|
-
// peer — it would
|
|
1271
|
-
// `handleLeave`
|
|
1269
|
+
// the `rpc-inbound` membership signal and proof of life to the sender, which here
|
|
1270
|
+
// is the *departing* peer — it would restore it as a live `member` immediately after
|
|
1271
|
+
// `handleLeave` marked it dead. The asymmetry is the point; do not "fix" it while
|
|
1272
1272
|
// tidying these signatures.
|
|
1273
1273
|
registerLeave(this.node, async (notice) => this.handleLeave(notice), this.protocols.PROTOCOL_LEAVE, () => { this.diag.rejected.identityMismatch++; }, () => { this.diag.rejected.malformed++; }, streamCaps),
|
|
1274
1274
|
registerPing(this.node, this.protocols.PROTOCOL_PING, () => this.handlePingRequest(), (from) => this.detach(this.noteInboundRpc(from), 'noteInboundRpc(ping)'), streamCaps),
|
|
@@ -1899,8 +1899,10 @@ export class FretService {
|
|
|
1899
1899
|
try {
|
|
1900
1900
|
let coord = null;
|
|
1901
1901
|
const entry = this.store.getById(peerId);
|
|
1902
|
-
if (entry)
|
|
1902
|
+
if (entry) {
|
|
1903
1903
|
coord = entry.coord;
|
|
1904
|
+
this.markDeparted(peerId);
|
|
1905
|
+
}
|
|
1904
1906
|
else {
|
|
1905
1907
|
try {
|
|
1906
1908
|
coord = await hashPeerId(peerIdFromString(peerId));
|
|
@@ -1909,8 +1911,6 @@ export class FretService {
|
|
|
1909
1911
|
log.error('handleLeave: could not hash departing peer id %s - %e', peerId, e);
|
|
1910
1912
|
}
|
|
1911
1913
|
}
|
|
1912
|
-
// remove leaving peer from the store
|
|
1913
|
-
this.store.remove(peerId);
|
|
1914
1914
|
if (!coord)
|
|
1915
1915
|
return;
|
|
1916
1916
|
await this.recordLeaveReplacements(notice.replacements, peerId);
|
|
@@ -1925,6 +1925,28 @@ export class FretService {
|
|
|
1925
1925
|
log.error('handleLeave failed for %s - %e', peerId, err);
|
|
1926
1926
|
}
|
|
1927
1927
|
}
|
|
1928
|
+
/**
|
|
1929
|
+
* A peer told us it is leaving: mark it `dead` — the immediate counterpart of a completed run
|
|
1930
|
+
* of failed contacts — rather than removing it.
|
|
1931
|
+
*
|
|
1932
|
+
* Removal did not stick. libp2p keeps the departed peer's peerStore record, negotiated
|
|
1933
|
+
* protocols included, so the next tick's `seedFromPeerStore` (or a `peer:update`) re-created
|
|
1934
|
+
* the entry and labelled it `member` again — a live ring member until three probes failed, and
|
|
1935
|
+
* forever if it had no address to probe. `dead` survives that re-seed because `upsert`
|
|
1936
|
+
* preserves `state` and classification touches only `membership`, and it already carries every
|
|
1937
|
+
* other consequence a departure needs: out of every ring view, unprotected at eviction, skipped
|
|
1938
|
+
* by the maintenance fan-outs, and restored by any proof of life if the peer comes back.
|
|
1939
|
+
*
|
|
1940
|
+
* The contact-failure run is left alone: nothing reads it while the peer is dead, and proof of
|
|
1941
|
+
* life clears it together with the label.
|
|
1942
|
+
*/
|
|
1943
|
+
markDeparted(id) {
|
|
1944
|
+
// Unreachable for a leave (the transport identity check rejects a notice from self), but a
|
|
1945
|
+
// dead self has no path back short of a restart — the same guard `applyContactStrike` keeps.
|
|
1946
|
+
if (id === this.selfIdStr)
|
|
1947
|
+
return;
|
|
1948
|
+
this.store.update(id, { state: 'dead' });
|
|
1949
|
+
}
|
|
1928
1950
|
/**
|
|
1929
1951
|
* Record a leave notice's suggested replacements as untrusted local hints — and nothing more.
|
|
1930
1952
|
*
|
|
@@ -1950,8 +1972,9 @@ export class FretService {
|
|
|
1950
1972
|
// Skipping self is a correctness guard, not tidiness: were self ever absent from the
|
|
1951
1973
|
// store, upserting it here would recreate it as `unknown` and drop self out of every
|
|
1952
1974
|
// member-only ring view (see *Network-scoped admission* in `docs/fret.md`). Skipping
|
|
1953
|
-
// the departed peer stops
|
|
1954
|
-
// inside one list collapse, so 12 copies of an id
|
|
1975
|
+
// the departed peer stops a notice naming its own sender from creating an entry for a
|
|
1976
|
+
// peer we held none for. Duplicates inside one list collapse, so 12 copies of an id
|
|
1977
|
+
// cost one hash and one upsert.
|
|
1955
1978
|
if (id === selfStr || id === departedId || seen.has(id))
|
|
1956
1979
|
continue;
|
|
1957
1980
|
seen.add(id);
|
|
@@ -2351,7 +2374,10 @@ export class FretService {
|
|
|
2351
2374
|
*/
|
|
2352
2375
|
async stabilizeOnce() {
|
|
2353
2376
|
this.sweepBoundedMaps();
|
|
2354
|
-
const near = await this.nearProbeTargets();
|
|
2377
|
+
const { targets: near, uncontactable } = await this.nearProbeTargets();
|
|
2378
|
+
// Before phase 1, and awaited: these peers are disjoint from every pooled candidate set
|
|
2379
|
+
// (live members, but undialable), so nothing in the pool races these strikes.
|
|
2380
|
+
await this.strikeUncontactable(uncontactable);
|
|
2355
2381
|
// `runSignal` is undefined before the first start(); `deadline` accepts that. `cancel()` in
|
|
2356
2382
|
// the finally is mandatory — see `deadline`.
|
|
2357
2383
|
const budget = deadline(FretService.STABILIZE_TICK_BUDGET_MS, this.runSignal);
|
|
@@ -2383,11 +2409,11 @@ export class FretService {
|
|
|
2383
2409
|
// tick still enforces.
|
|
2384
2410
|
// NOTE: a throw *earlier* in the tick skips this, and the seeds no longer trim for
|
|
2385
2411
|
// themselves, so that tick's inserts stay untrimmed until the next one. The reachable
|
|
2386
|
-
// candidates are the
|
|
2387
|
-
//
|
|
2388
|
-
// try/catch swallows anything that does, and the overshoot
|
|
2389
|
-
// deterministic throw is ever found on that path, move the
|
|
2390
|
-
// method's `finally` rather than adding a guard here.
|
|
2412
|
+
// candidates are the three calls above the try (`sweepBoundedMaps`, `nearProbeTargets`,
|
|
2413
|
+
// `strikeUncontactable`), all local and none able to throw on any input a tick can
|
|
2414
|
+
// present; the loop's own try/catch swallows anything that does, and the overshoot
|
|
2415
|
+
// self-heals next tick. If a deterministic throw is ever found on that path, move the
|
|
2416
|
+
// enforcement into this method's `finally` rather than adding a guard here.
|
|
2391
2417
|
await this.enforceCapacity();
|
|
2392
2418
|
if (announced.length > 0)
|
|
2393
2419
|
this.detach(this.announceToNewPeers(announced), 'announceToNewPeers');
|
|
@@ -2408,15 +2434,59 @@ export class FretService {
|
|
|
2408
2434
|
}
|
|
2409
2435
|
}
|
|
2410
2436
|
/**
|
|
2411
|
-
* The
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2414
|
-
*
|
|
2437
|
+
* The S/P window a tick verifies — the live members nearest self, `max(2, m)` per side — split
|
|
2438
|
+
* by whether we can contact them at all.
|
|
2439
|
+
*
|
|
2440
|
+
* `targets` are the dialable ones a tick pings and snapshot-fetches: at most 4, in **ring
|
|
2441
|
+
* order** (closest first, sides interleaved) — deliberately not rotated like the other
|
|
2442
|
+
* candidate lists. These are the peers ring correctness depends on most, so a truncated tick
|
|
2443
|
+
* should skip the outermost and never the immediate successor or predecessor; it
|
|
2444
|
+
* self-corrects next tick. `uncontactable` is the rest of the window, uncapped: the 4 bounds
|
|
2445
|
+
* RPCs, and none is spent on these (see {@link strikeUncontactable}).
|
|
2446
|
+
*
|
|
2447
|
+
* Walked through `ringNeighborsBothSides` rather than the public `getNeighbors`, whose `both`
|
|
2448
|
+
* direction is successor-biased (see the NOTE there): at m = 8 it returned self plus seven
|
|
2449
|
+
* successors on any ring of more than nine live members, so no predecessor was ever verified.
|
|
2415
2450
|
*/
|
|
2416
2451
|
async nearProbeTargets() {
|
|
2417
|
-
const
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2452
|
+
const window = ringNeighborsBothSides(this.store, await this.selfCoord(), Math.max(2, this.cfg.m), this.selfIdStr, { filter: isLiveMember });
|
|
2453
|
+
const targets = [];
|
|
2454
|
+
const uncontactable = [];
|
|
2455
|
+
for (const id of window)
|
|
2456
|
+
(this.isDialable(id) ? targets : uncontactable).push(id);
|
|
2457
|
+
return { targets: targets.slice(0, 4), uncontactable };
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Count one failed contact against each live member of the S/P window that no pass could
|
|
2461
|
+
* reach: not connected, and no address libp2p could dial.
|
|
2462
|
+
*
|
|
2463
|
+
* Without this such a peer never reaches `dead`, because a strike is booked only when an
|
|
2464
|
+
* outbound RPC fails and every pass that sends one filters on `isDialable` first. A phone that
|
|
2465
|
+
* dials out and listens nowhere is exactly that peer once its connection drops — so it stayed a
|
|
2466
|
+
* live member, and a cohort member, indefinitely.
|
|
2467
|
+
*
|
|
2468
|
+
* The observation is the tick, not the disconnect, so "an idle `peer:disconnect` is not a
|
|
2469
|
+
* strike" stays true: `applyContactStrike`'s spacing guard makes each passive tick (1.5 s) one
|
|
2470
|
+
* independent strike, the peer is `dead` after `deadAfterFailures` ticks, and one that
|
|
2471
|
+
* reconnects within a tick or two (`peer:connect` is proof of life) is never killed. Once dead
|
|
2472
|
+
* it leaves the live-member window, so the strikes stop by themselves.
|
|
2473
|
+
*
|
|
2474
|
+
* NOTE: only the S/P window is swept, so on a ring of more than 2·max(2, m) + 1 live members an
|
|
2475
|
+
* address-less member outside our window is never struck by us and stays a live member in our
|
|
2476
|
+
* cohorts. If cohorts assembled over such rings start waiting on departed phones, widen this to
|
|
2477
|
+
* every live member — still local work, one store walk.
|
|
2478
|
+
*
|
|
2479
|
+
* Local work with no RPC, so the tick budget does not apply; but our own cancellation is still
|
|
2480
|
+
* not evidence, so nothing is booked once the run signal has fired.
|
|
2481
|
+
*/
|
|
2482
|
+
async strikeUncontactable(ids) {
|
|
2483
|
+
const signal = this.runSignal;
|
|
2484
|
+
for (const id of ids) {
|
|
2485
|
+
if (this.wasCancelled(signal))
|
|
2486
|
+
return;
|
|
2487
|
+
log('near window: %s has no connection and no address - counted as a failed contact', id);
|
|
2488
|
+
await this.applyContactFailure(id);
|
|
2489
|
+
}
|
|
2420
2490
|
}
|
|
2421
2491
|
/**
|
|
2422
2492
|
* Ping `id`, then fetch and merge its neighbor snapshot — one pooled task per near peer.
|
|
@@ -80,7 +80,22 @@ export declare class Libp2pFretService implements Startable, FretService {
|
|
|
80
80
|
/** The same instance libp2p reaches via `peerDiscoverySymbol`, for applications that want to listen directly. */
|
|
81
81
|
getPeerDiscovery(): PeerDiscovery;
|
|
82
82
|
start(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Runs the whole shutdown here, ahead of {@link stop}, because this is the last point at which
|
|
85
|
+
* the leave notices can still go out. libp2p's `stop()` runs every component's `beforeStop()`
|
|
86
|
+
* first, then every component's `stop()` under one `Promise.all` — and the connection manager's
|
|
87
|
+
* `stop()` closes every connection inside that same `Promise.all`. A leave fan-out started from
|
|
88
|
+
* our `stop()` therefore races the closing connections and loses: a peer that listens nowhere
|
|
89
|
+
* can reach its neighbours only over connections it already holds, so its notices were sent to
|
|
90
|
+
* nobody. The registrar is still live here, so unhandling works too.
|
|
91
|
+
*/
|
|
92
|
+
beforeStop(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* A no-op after {@link beforeStop} under libp2p (both halves are idempotent), and the whole
|
|
95
|
+
* shutdown for a host that drives this facade by hand without a `beforeStop` call.
|
|
96
|
+
*/
|
|
83
97
|
stop(): Promise<void>;
|
|
98
|
+
private shutdown;
|
|
84
99
|
routeAct(msg: RouteAndMaybeActV1): Promise<NearAnchorV1 | {
|
|
85
100
|
commitCertificate: string;
|
|
86
101
|
}>;
|
|
@@ -121,7 +121,26 @@ export class Libp2pFretService {
|
|
|
121
121
|
// explicit start/stop stay ours.
|
|
122
122
|
await this.discovery.start();
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Runs the whole shutdown here, ahead of {@link stop}, because this is the last point at which
|
|
126
|
+
* the leave notices can still go out. libp2p's `stop()` runs every component's `beforeStop()`
|
|
127
|
+
* first, then every component's `stop()` under one `Promise.all` — and the connection manager's
|
|
128
|
+
* `stop()` closes every connection inside that same `Promise.all`. A leave fan-out started from
|
|
129
|
+
* our `stop()` therefore races the closing connections and loses: a peer that listens nowhere
|
|
130
|
+
* can reach its neighbours only over connections it already holds, so its notices were sent to
|
|
131
|
+
* nobody. The registrar is still live here, so unhandling works too.
|
|
132
|
+
*/
|
|
133
|
+
async beforeStop() {
|
|
134
|
+
await this.shutdown();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A no-op after {@link beforeStop} under libp2p (both halves are idempotent), and the whole
|
|
138
|
+
* shutdown for a host that drives this facade by hand without a `beforeStop` call.
|
|
139
|
+
*/
|
|
124
140
|
async stop() {
|
|
141
|
+
await this.shutdown();
|
|
142
|
+
}
|
|
143
|
+
async shutdown() {
|
|
125
144
|
await this.discovery.stop();
|
|
126
145
|
await this.inner?.stop();
|
|
127
146
|
}
|