@waku/core 0.0.27 → 0.0.28-070b625.0

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.
Files changed (44) hide show
  1. package/bundle/{base_protocol-LhsIWF3-.js → base_protocol-D0Zdzb-v.js} +2 -5
  2. package/bundle/{browser-BQyFvtq6.js → browser-DoQRY-an.js} +18 -13
  3. package/bundle/{index-8YyfzF9R.js → index-BJwgMx4y.js} +35 -62
  4. package/bundle/index.js +258 -342
  5. package/bundle/lib/base_protocol.js +3 -3
  6. package/bundle/lib/message/version_0.js +3 -3
  7. package/bundle/lib/predefined_bootstrap_nodes.js +17 -17
  8. package/bundle/{version_0-FXfzO8Km.js → version_0-C6o0DvNW.js} +566 -321
  9. package/dist/.tsbuildinfo +1 -1
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.js +3 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/base_protocol.d.ts +4 -5
  14. package/dist/lib/base_protocol.js +0 -3
  15. package/dist/lib/base_protocol.js.map +1 -1
  16. package/dist/lib/filter/index.js +4 -0
  17. package/dist/lib/filter/index.js.map +1 -1
  18. package/dist/lib/light_push/index.d.ts +12 -2
  19. package/dist/lib/light_push/index.js +79 -76
  20. package/dist/lib/light_push/index.js.map +1 -1
  21. package/dist/lib/message/version_0.js +1 -1
  22. package/dist/lib/message/version_0.js.map +1 -1
  23. package/dist/lib/metadata/index.d.ts +1 -1
  24. package/dist/lib/metadata/index.js +42 -14
  25. package/dist/lib/metadata/index.js.map +1 -1
  26. package/dist/lib/predefined_bootstrap_nodes.d.ts +11 -11
  27. package/dist/lib/predefined_bootstrap_nodes.js +16 -16
  28. package/dist/lib/predefined_bootstrap_nodes.js.map +1 -1
  29. package/dist/lib/store/history_rpc.js +1 -1
  30. package/dist/lib/store/history_rpc.js.map +1 -1
  31. package/dist/lib/store/index.d.ts +14 -6
  32. package/dist/lib/store/index.js +50 -232
  33. package/dist/lib/store/index.js.map +1 -1
  34. package/dist/lib/wait_for_remote_peer.js +4 -2
  35. package/dist/lib/wait_for_remote_peer.js.map +1 -1
  36. package/package.json +1 -129
  37. package/src/index.ts +3 -2
  38. package/src/lib/base_protocol.ts +4 -9
  39. package/src/lib/filter/index.ts +7 -0
  40. package/src/lib/light_push/index.ts +97 -118
  41. package/src/lib/metadata/index.ts +56 -26
  42. package/src/lib/predefined_bootstrap_nodes.ts +22 -22
  43. package/src/lib/store/index.ts +77 -339
  44. package/src/lib/wait_for_remote_peer.ts +14 -4
@@ -1,18 +1,16 @@
1
- import type { PeerId, Stream } from "@libp2p/interface";
1
+ import type { Peer, PeerId, Stream } from "@libp2p/interface";
2
2
  import {
3
+ Failure,
4
+ IBaseProtocolCore,
3
5
  IEncoder,
4
- ILightPush,
5
6
  IMessage,
6
7
  Libp2p,
7
8
  ProtocolCreateOptions,
8
- SendError,
9
- SendResult
9
+ ProtocolError,
10
+ ProtocolResult
10
11
  } from "@waku/interfaces";
11
12
  import { PushResponse } from "@waku/proto";
12
- import {
13
- ensurePubsubTopicIsConfigured,
14
- isMessageSizeUnderCap
15
- } from "@waku/utils";
13
+ import { isMessageSizeUnderCap } from "@waku/utils";
16
14
  import { Logger } from "@waku/utils";
17
15
  import all from "it-all";
18
16
  import * as lp from "it-length-prefixed";
@@ -28,20 +26,14 @@ const log = new Logger("light-push");
28
26
  export const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
29
27
  export { PushResponse };
30
28
 
31
- type PreparePushMessageResult =
32
- | {
33
- query: PushRpc;
34
- error: null;
35
- }
36
- | {
37
- query: null;
38
- error: SendError;
39
- };
29
+ type PreparePushMessageResult = ProtocolResult<"query", PushRpc>;
30
+
31
+ type CoreSendResult = ProtocolResult<"success", PeerId, "failure", Failure>;
40
32
 
41
33
  /**
42
34
  * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
43
35
  */
44
- class LightPush extends BaseProtocol implements ILightPush {
36
+ export class LightPushCore extends BaseProtocol implements IBaseProtocolCore {
45
37
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
46
38
  super(
47
39
  LightPushCodec,
@@ -54,18 +46,17 @@ class LightPush extends BaseProtocol implements ILightPush {
54
46
 
55
47
  private async preparePushMessage(
56
48
  encoder: IEncoder,
57
- message: IMessage,
58
- pubsubTopic: string
49
+ message: IMessage
59
50
  ): Promise<PreparePushMessageResult> {
60
51
  try {
61
52
  if (!message.payload || message.payload.length === 0) {
62
53
  log.error("Failed to send waku light push: payload is empty");
63
- return { query: null, error: SendError.EMPTY_PAYLOAD };
54
+ return { query: null, error: ProtocolError.EMPTY_PAYLOAD };
64
55
  }
65
56
 
66
57
  if (!(await isMessageSizeUnderCap(encoder, message))) {
67
58
  log.error("Failed to send waku light push: message is bigger than 1MB");
68
- return { query: null, error: SendError.SIZE_TOO_BIG };
59
+ return { query: null, error: ProtocolError.SIZE_TOO_BIG };
69
60
  }
70
61
 
71
62
  const protoMessage = await encoder.toProtoObj(message);
@@ -73,132 +64,120 @@ class LightPush extends BaseProtocol implements ILightPush {
73
64
  log.error("Failed to encode to protoMessage, aborting push");
74
65
  return {
75
66
  query: null,
76
- error: SendError.ENCODE_FAILED
67
+ error: ProtocolError.ENCODE_FAILED
77
68
  };
78
69
  }
79
70
 
80
- const query = PushRpc.createRequest(protoMessage, pubsubTopic);
71
+ const query = PushRpc.createRequest(protoMessage, encoder.pubsubTopic);
81
72
  return { query, error: null };
82
73
  } catch (error) {
83
74
  log.error("Failed to prepare push message", error);
84
75
 
85
76
  return {
86
77
  query: null,
87
- error: SendError.GENERIC_FAIL
78
+ error: ProtocolError.GENERIC_FAIL
88
79
  };
89
80
  }
90
81
  }
91
82
 
92
- async send(encoder: IEncoder, message: IMessage): Promise<SendResult> {
93
- const { pubsubTopic } = encoder;
94
- ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
95
-
96
- const recipients: PeerId[] = [];
97
-
83
+ async send(
84
+ encoder: IEncoder,
85
+ message: IMessage,
86
+ peer: Peer
87
+ ): Promise<CoreSendResult> {
98
88
  const { query, error: preparationError } = await this.preparePushMessage(
99
89
  encoder,
100
- message,
101
- pubsubTopic
90
+ message
102
91
  );
103
92
 
104
93
  if (preparationError || !query) {
105
94
  return {
106
- recipients,
107
- errors: [preparationError]
95
+ success: null,
96
+ failure: {
97
+ error: preparationError,
98
+ peerId: peer.id
99
+ }
108
100
  };
109
101
  }
110
102
 
111
- const peers = await this.getPeers({
112
- maxBootstrapPeers: 1,
113
- numPeers: this.numPeersToUse
114
- });
115
-
116
- if (!peers.length) {
103
+ let stream: Stream | undefined;
104
+ try {
105
+ stream = await this.getStream(peer);
106
+ } catch (err) {
107
+ log.error(
108
+ `Failed to get a stream for remote peer${peer.id.toString()}`,
109
+ err
110
+ );
117
111
  return {
118
- recipients,
119
- errors: [SendError.NO_PEER_AVAILABLE]
112
+ success: null,
113
+ failure: {
114
+ error: ProtocolError.REMOTE_PEER_FAULT,
115
+ peerId: peer.id
116
+ }
120
117
  };
121
118
  }
122
119
 
123
- const promises = peers.map(async (peer) => {
124
- let stream: Stream | undefined;
125
- try {
126
- stream = await this.getStream(peer);
127
- } catch (err) {
128
- log.error(
129
- `Failed to get a stream for remote peer${peer.id.toString()}`,
130
- err
131
- );
132
- return { recipients, error: SendError.REMOTE_PEER_FAULT };
133
- }
134
-
135
- let res: Uint8ArrayList[] | undefined;
136
- try {
137
- res = await pipe(
138
- [query.encode()],
139
- lp.encode,
140
- stream,
141
- lp.decode,
142
- async (source) => await all(source)
143
- );
144
- } catch (err) {
145
- log.error("Failed to send waku light push request", err);
146
- return { recipients, error: SendError.GENERIC_FAIL };
147
- }
148
-
149
- const bytes = new Uint8ArrayList();
150
- res.forEach((chunk) => {
151
- bytes.append(chunk);
152
- });
153
-
154
- let response: PushResponse | undefined;
155
- try {
156
- response = PushRpc.decode(bytes).response;
157
- } catch (err) {
158
- log.error("Failed to decode push reply", err);
159
- return { recipients, error: SendError.DECODE_FAILED };
160
- }
120
+ let res: Uint8ArrayList[] | undefined;
121
+ try {
122
+ res = await pipe(
123
+ [query.encode()],
124
+ lp.encode,
125
+ stream,
126
+ lp.decode,
127
+ async (source) => await all(source)
128
+ );
129
+ } catch (err) {
130
+ log.error("Failed to send waku light push request", err);
131
+ return {
132
+ success: null,
133
+ failure: {
134
+ error: ProtocolError.GENERIC_FAIL,
135
+ peerId: peer.id
136
+ }
137
+ };
138
+ }
161
139
 
162
- if (!response) {
163
- log.error("Remote peer fault: No response in PushRPC");
164
- return { recipients, error: SendError.REMOTE_PEER_FAULT };
165
- }
140
+ const bytes = new Uint8ArrayList();
141
+ res.forEach((chunk) => {
142
+ bytes.append(chunk);
143
+ });
166
144
 
167
- if (!response.isSuccess) {
168
- log.error("Remote peer rejected the message: ", response.info);
169
- return { recipients, error: SendError.REMOTE_PEER_REJECTED };
170
- }
145
+ let response: PushResponse | undefined;
146
+ try {
147
+ response = PushRpc.decode(bytes).response;
148
+ } catch (err) {
149
+ log.error("Failed to decode push reply", err);
150
+ return {
151
+ success: null,
152
+ failure: {
153
+ error: ProtocolError.DECODE_FAILED,
154
+ peerId: peer.id
155
+ }
156
+ };
157
+ }
171
158
 
172
- recipients.some((recipient) => recipient.equals(peer.id)) ||
173
- recipients.push(peer.id);
159
+ if (!response) {
160
+ log.error("Remote peer fault: No response in PushRPC");
161
+ return {
162
+ success: null,
163
+ failure: {
164
+ error: ProtocolError.REMOTE_PEER_FAULT,
165
+ peerId: peer.id
166
+ }
167
+ };
168
+ }
174
169
 
175
- return { recipients };
176
- });
170
+ if (!response.isSuccess) {
171
+ log.error("Remote peer rejected the message: ", response.info);
172
+ return {
173
+ success: null,
174
+ failure: {
175
+ error: ProtocolError.REMOTE_PEER_REJECTED,
176
+ peerId: peer.id
177
+ }
178
+ };
179
+ }
177
180
 
178
- const results = await Promise.allSettled(promises);
179
-
180
- // TODO: handle renewing faulty peers with new peers (https://github.com/waku-org/js-waku/issues/1463)
181
- const errors = results
182
- .filter(
183
- (
184
- result
185
- ): result is PromiseFulfilledResult<{
186
- recipients: PeerId[];
187
- error: SendError | undefined;
188
- }> => result.status === "fulfilled"
189
- )
190
- .map((result) => result.value.error)
191
- .filter((error) => error !== undefined) as SendError[];
192
-
193
- return {
194
- recipients,
195
- errors
196
- };
181
+ return { success: peer.id, failure: null };
197
182
  }
198
183
  }
199
-
200
- export function wakuLightPush(
201
- init: Partial<ProtocolCreateOptions> = {}
202
- ): (libp2p: Libp2p) => ILightPush {
203
- return (libp2p: Libp2p) => new LightPush(libp2p, init);
204
- }
@@ -1,10 +1,12 @@
1
1
  import type { PeerId } from "@libp2p/interface";
2
2
  import { IncomingStreamData } from "@libp2p/interface";
3
- import type {
4
- IMetadata,
5
- Libp2pComponents,
6
- PeerIdStr,
7
- ShardInfo
3
+ import {
4
+ type IMetadata,
5
+ type Libp2pComponents,
6
+ type PeerIdStr,
7
+ ProtocolError,
8
+ QueryResult,
9
+ type ShardInfo
8
10
  } from "@waku/interfaces";
9
11
  import { proto_metadata } from "@waku/proto";
10
12
  import { encodeRelayShard, Logger, shardInfoToPubsubTopics } from "@waku/utils";
@@ -21,7 +23,7 @@ export const MetadataCodec = "/vac/waku/metadata/1.0.0";
21
23
 
22
24
  class Metadata extends BaseProtocol implements IMetadata {
23
25
  private libp2pComponents: Libp2pComponents;
24
- handshakesConfirmed: Set<PeerIdStr> = new Set();
26
+ handshakesConfirmed: Map<PeerIdStr, ShardInfo> = new Map();
25
27
 
26
28
  constructor(
27
29
  public shardInfo: ShardInfo,
@@ -57,13 +59,13 @@ class Metadata extends BaseProtocol implements IMetadata {
57
59
  async (source) => await all(source)
58
60
  );
59
61
 
60
- const remoteShardInfoResponse =
61
- this.decodeMetadataResponse(encodedResponse);
62
+ const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse);
62
63
 
63
- await this.savePeerShardInfo(
64
- connection.remotePeer,
65
- remoteShardInfoResponse
66
- );
64
+ if (error) {
65
+ return;
66
+ }
67
+
68
+ await this.savePeerShardInfo(connection.remotePeer, shardInfo);
67
69
  } catch (error) {
68
70
  log.error("Error handling metadata request", error);
69
71
  }
@@ -72,12 +74,15 @@ class Metadata extends BaseProtocol implements IMetadata {
72
74
  /**
73
75
  * Make a metadata query to a peer
74
76
  */
75
- async query(peerId: PeerId): Promise<ShardInfo> {
77
+ async query(peerId: PeerId): Promise<QueryResult> {
76
78
  const request = proto_metadata.WakuMetadataRequest.encode(this.shardInfo);
77
79
 
78
80
  const peer = await this.peerStore.get(peerId);
79
81
  if (!peer) {
80
- throw new Error(`Peer ${peerId.toString()} not found`);
82
+ return {
83
+ shardInfo: null,
84
+ error: ProtocolError.NO_PEER_AVAILABLE
85
+ };
81
86
  }
82
87
 
83
88
  const stream = await this.getStream(peer);
@@ -90,22 +95,38 @@ class Metadata extends BaseProtocol implements IMetadata {
90
95
  async (source) => await all(source)
91
96
  );
92
97
 
93
- const decodedResponse = this.decodeMetadataResponse(encodedResponse);
98
+ const { error, shardInfo } = this.decodeMetadataResponse(encodedResponse);
99
+
100
+ if (error) {
101
+ return {
102
+ shardInfo: null,
103
+ error
104
+ };
105
+ }
94
106
 
95
- await this.savePeerShardInfo(peerId, decodedResponse);
107
+ await this.savePeerShardInfo(peerId, shardInfo);
96
108
 
97
- return decodedResponse;
109
+ return {
110
+ shardInfo,
111
+ error: null
112
+ };
98
113
  }
99
114
 
100
- public async confirmOrAttemptHandshake(peerId: PeerId): Promise<void> {
101
- if (this.handshakesConfirmed.has(peerId.toString())) return;
102
-
103
- await this.query(peerId);
115
+ public async confirmOrAttemptHandshake(peerId: PeerId): Promise<QueryResult> {
116
+ const shardInfo = this.handshakesConfirmed.get(peerId.toString());
117
+ if (shardInfo) {
118
+ return {
119
+ shardInfo,
120
+ error: null
121
+ };
122
+ }
104
123
 
105
- return;
124
+ return await this.query(peerId);
106
125
  }
107
126
 
108
- private decodeMetadataResponse(encodedResponse: Uint8ArrayList[]): ShardInfo {
127
+ private decodeMetadataResponse(
128
+ encodedResponse: Uint8ArrayList[]
129
+ ): QueryResult {
109
130
  const bytes = new Uint8ArrayList();
110
131
 
111
132
  encodedResponse.forEach((chunk) => {
@@ -115,9 +136,18 @@ class Metadata extends BaseProtocol implements IMetadata {
115
136
  bytes
116
137
  ) as ShardInfo;
117
138
 
118
- if (!response) log.error("Error decoding metadata response");
139
+ if (!response) {
140
+ log.error("Error decoding metadata response");
141
+ return {
142
+ shardInfo: null,
143
+ error: ProtocolError.DECODE_FAILED
144
+ };
145
+ }
119
146
 
120
- return response;
147
+ return {
148
+ shardInfo: response,
149
+ error: null
150
+ };
121
151
  }
122
152
 
123
153
  private async savePeerShardInfo(
@@ -131,7 +161,7 @@ class Metadata extends BaseProtocol implements IMetadata {
131
161
  }
132
162
  });
133
163
 
134
- this.handshakesConfirmed.add(peerId.toString());
164
+ this.handshakesConfirmed.set(peerId.toString(), shardInfo);
135
165
  }
136
166
  }
137
167
 
@@ -3,22 +3,22 @@ import { getPseudoRandomSubset } from "@waku/utils";
3
3
  export const DefaultWantedNumber = 1;
4
4
 
5
5
  export enum Fleet {
6
- Prod = "prod",
6
+ Sandbox = "sandbox",
7
7
  Test = "test"
8
8
  }
9
9
 
10
10
  /**
11
11
  * Return list of pre-defined (hardcoded) bootstrap nodes.
12
12
  *
13
- * Default behavior is to return nodes of the nwaku Status Prod fleet.
13
+ * Default behavior is to return nodes of the nwaku Status Sandbox fleet.
14
14
  *
15
- * @param fleet The fleet to be returned. Defaults to production fleet.
15
+ * @param fleet The fleet to be returned. Defaults to sandbox fleet.
16
16
  * @param wantedNumber The number of connections desired. Defaults to {@link DefaultWantedNumber}.
17
17
  *
18
18
  * @returns An array of multiaddresses.
19
19
  */
20
20
  export function getPredefinedBootstrapNodes(
21
- fleet: Fleet = Fleet.Prod,
21
+ fleet: Fleet = Fleet.Sandbox,
22
22
  wantedNumber: number = DefaultWantedNumber
23
23
  ): string[] {
24
24
  if (wantedNumber <= 0) {
@@ -27,14 +27,14 @@ export function getPredefinedBootstrapNodes(
27
27
 
28
28
  let nodes;
29
29
  switch (fleet) {
30
- case Fleet.Prod:
31
- nodes = fleets.fleets["wakuv2.prod"]["waku-websocket"];
30
+ case Fleet.Sandbox:
31
+ nodes = fleets.fleets["waku.sandbox"]["waku-websocket"];
32
32
  break;
33
33
  case Fleet.Test:
34
- nodes = fleets.fleets["wakuv2.test"]["waku-websocket"];
34
+ nodes = fleets.fleets["waku.test"]["waku-websocket"];
35
35
  break;
36
36
  default:
37
- nodes = fleets.fleets["wakuv2.prod"]["waku-websocket"];
37
+ nodes = fleets.fleets["waku.sandbox"]["waku-websocket"];
38
38
  }
39
39
 
40
40
  nodes = Object.values(nodes) as string[];
@@ -44,24 +44,24 @@ export function getPredefinedBootstrapNodes(
44
44
 
45
45
  export const fleets = {
46
46
  fleets: {
47
- "wakuv2.prod": {
47
+ "waku.sandbox": {
48
48
  "waku-websocket": {
49
- "node-01.ac-cn-hongkong-c.wakuv2.prod":
50
- "/dns4/node-01.ac-cn-hongkong-c.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
51
- "node-01.do-ams3.wakuv2.prod":
52
- "/dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e",
53
- "node-01.gc-us-central1-a.wakuv2.prod":
54
- "/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA"
49
+ "node-01.ac-cn-hongkong-c.waku.sandbox":
50
+ "/dns4/node-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV",
51
+ "node-01.do-ams3.waku.sandbox":
52
+ "/dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE",
53
+ "node-01.gc-us-central1-a.waku.sandbox":
54
+ "/dns4/node-01.gc-us-central1-a.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB"
55
55
  }
56
56
  },
57
- "wakuv2.test": {
57
+ "waku.test": {
58
58
  "waku-websocket": {
59
- "node-01.ac-cn-hongkong-c.wakuv2.test":
60
- "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
61
- "node-01.do-ams3.wakuv2.test":
62
- "/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
63
- "node-01.gc-us-central1-a.wakuv2.test":
64
- "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
59
+ "node-01.ac-cn-hongkong-c.waku.test":
60
+ "/dns4/node-01.ac-cn-hongkong-c.waku.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAkzHaTP5JsUwfR9NR8Rj9HC24puS6ocaU8wze4QrXr9iXp",
61
+ "node-01.do-ams3.waku.test":
62
+ "/dns4/node-01.do-ams3.waku.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAkykgaECHswi3YKJ5dMLbq2kPVCo89fcyTd38UcQD6ej5W",
63
+ "node-01.gc-us-central1-a.waku.test":
64
+ "/dns4/node-01.gc-us-central1-a.waku.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmDCp8XJ9z1ev18zuv8NHekAsjNyezAvmMfFEJkiharitG"
65
65
  }
66
66
  }
67
67
  }