• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

handshake-org / hsd / 11933012791

20 Nov 2024 12:04PM UTC coverage: 71.254% (+1.2%) from 70.08%
11933012791

push

github

nodech
Merge PR #888 from 'nodech/wallet-pagination'

8055 of 13154 branches covered (61.24%)

Branch coverage included in aggregate %.

820 of 883 new or added lines in 16 files covered. (92.87%)

7 existing lines in 6 files now uncovered.

25716 of 34241 relevant lines covered (75.1%)

34509.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.8
/lib/wallet/nodeclient.js
1
/*!
2
 * nodeclient.js - node client for hsd
3
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
4
 * https://github.com/handshake-org/hsd
5
 */
6

7
'use strict';
8

9
const assert = require('bsert');
1✔
10
const blacklist = require('bsock/lib/blacklist');
1✔
11
const AsyncEmitter = require('bevent');
1✔
12

13
/** @typedef {import('../node/node')} Node */
14

15
/**
16
 * Node Client
17
 * @alias module:node.NodeClient
18
 */
19

20
class NodeClient extends AsyncEmitter {
21
  /** @type {Node} */
22
  node;
23

24
  /**
25
   * Create a node client.
26
   * @constructor
27
   * @param {Node} node
28
   */
29

30
  constructor(node) {
31
    super();
89✔
32

33
    this.node = node;
89✔
34
    this.network = node.network;
89✔
35
    this.filter = null;
89✔
36
    this.opened = false;
89✔
37
    this.hooks = new Map();
89✔
38

39
    this.init();
89✔
40
  }
41

42
  /**
43
   * Initialize the client.
44
   */
45

46
  init() {
47
    this.node.chain.on('connect', async (entry, block) => {
89✔
48
      if (!this.opened)
7,009✔
49
        return;
1✔
50

51
      await this.emitAsync('block connect', entry, block.txs);
7,008✔
52
    });
53

54
    this.node.chain.on('disconnect', async (entry, block) => {
89✔
55
      if (!this.opened)
107!
56
        return;
×
57

58
      await this.emitAsync('block disconnect', entry);
107✔
59
    });
60

61
    this.node.on('tx', (tx) => {
89✔
62
      if (!this.opened)
1,905✔
63
        return;
1✔
64

65
      this.emit('tx', tx);
1,904✔
66
    });
67

68
    this.node.on('reset', (tip) => {
89✔
69
      if (!this.opened)
7!
70
        return;
×
71

72
      this.emit('chain reset', tip);
7✔
73
    });
74
  }
75

76
  /**
77
   * Open the client.
78
   * @returns {Promise}
79
   */
80

81
  async open(options) {
82
    assert(!this.opened, 'NodeClient is already open.');
90✔
83
    this.opened = true;
90✔
84
    setImmediate(() => this.emit('connect'));
90✔
85
  }
86

87
  /**
88
   * Close the client.
89
   * @returns {Promise}
90
   */
91

92
  async close() {
93
    assert(this.opened, 'NodeClient is not open.');
90✔
94
    this.opened = false;
90✔
95
    setImmediate(() => this.emit('disconnect'));
90✔
96
  }
97

98
  /**
99
   * Add a listener.
100
   * @param {String} type
101
   * @param {Function} handler
102
   */
103

104
  bind(type, handler) {
105
    return this.on(type, handler);
356✔
106
  }
107

108
  /**
109
   * Add a hook.
110
   * @param {String} event
111
   * @param {Function} handler
112
   */
113

114
  hook(event, handler) {
115
    assert(typeof event === 'string', 'Event must be a string.');
267✔
116
    assert(typeof handler === 'function', 'Handler must be a function.');
267✔
117
    assert(!this.hooks.has(event), 'Hook already bound.');
267✔
118
    assert(!Object.prototype.hasOwnProperty.call(blacklist, event),
267✔
119
      'Blacklisted event.');
120
    this.hooks.set(event, handler);
267✔
121
  }
122

123
  /**
124
   * Remove a hook.
125
   * @param {String} event
126
   */
127

128
  unhook(event) {
129
    assert(typeof event === 'string', 'Event must be a string.');
×
130
    assert(!Object.prototype.hasOwnProperty.call(blacklist, event),
×
131
      'Blacklisted event.');
132
    this.hooks.delete(event);
×
133
  }
134

135
  /**
136
   * Call a hook.
137
   * @param {String} event
138
   * @param {...Object} args
139
   * @returns {Promise}
140
   */
141

142
  handleCall(event, ...args) {
143
    const hook = this.hooks.get(event);
2,015✔
144

145
    if (!hook)
2,015!
146
      throw new Error('No hook available.');
×
147

148
    return hook(...args);
2,015✔
149
  }
150

151
  /**
152
   * Get chain tip.
153
   * @returns {Promise}
154
   */
155

156
  async getTip() {
157
    return this.node.chain.tip;
×
158
  }
159

160
  /**
161
   * Get chain entry.
162
   * @param {Hash} hash
163
   * @returns {Promise}
164
   */
165

166
  async getEntry(hash) {
167
    const entry = await this.node.chain.getEntry(hash);
91✔
168

169
    if (!entry)
91✔
170
      return null;
1✔
171

172
    if (!await this.node.chain.isMainChain(entry))
90✔
173
      return null;
1✔
174

175
    return entry;
89✔
176
  }
177

178
  /**
179
   * Send a transaction. Do not wait for promise.
180
   * @param {TX} tx
181
   * @returns {Promise}
182
   */
183

184
  async send(tx) {
185
    this.node.relay(tx);
1,788✔
186
  }
187

188
  /**
189
   * Send a claim. Do not wait for promise.
190
   * @param {Claim} claim
191
   * @returns {Promise}
192
   */
193

194
  async sendClaim(claim) {
195
    this.node.relayClaim(claim);
8✔
196
  }
197

198
  /**
199
   * Set bloom filter.
200
   * @param {Bloom} filter
201
   * @returns {Promise}
202
   */
203

204
  async setFilter(filter) {
205
    this.filter = filter;
90✔
206
    this.node.pool.setFilter(filter);
90✔
207
  }
208

209
  /**
210
   * Add data to filter.
211
   * @param {Buffer} data
212
   * @returns {Promise}
213
   */
214

215
  async addFilter(data) {
216
    // `data` is ignored because pool.spvFilter === walletDB.filter
217
    // and therefore is already updated.
218
    // Argument is kept here to be consistent with API in
219
    // wallet/client.js (client/node.js) and wallet/nullclient.js
220
    this.node.pool.queueFilterLoad();
151,820✔
221
  }
222

223
  /**
224
   * Reset filter.
225
   * @returns {Promise}
226
   */
227

228
  async resetFilter() {
229
    this.node.pool.queueFilterLoad();
×
230
  }
231

232
  /**
233
   * Esimate smart fee.
234
   * @param {Number?} blocks
235
   * @returns {Promise}
236
   */
237

238
  async estimateFee(blocks) {
239
    if (!this.node.fees)
1,939!
240
      return this.network.feeRate;
×
241

242
    return this.node.fees.estimateFee(blocks);
1,939✔
243
  }
244

245
  /**
246
   * Get hash range.
247
   * @param {Number} start
248
   * @param {Number} end
249
   * @returns {Promise}
250
   */
251

252
  async getHashes(start = -1, end = -1) {
×
UNCOV
253
    return this.node.chain.getHashes(start, end);
×
254
  }
255

256
  /**
257
   * Get entries range.
258
   * @param {Number} start
259
   * @param {Number} end
260
   * @returns {Promise<ChainEntry[]>}
261
   */
262

263
  async getEntries(start = -1, end = -1) {
158✔
264
    return this.node.chain.getEntries(start, end);
79✔
265
  }
266

267
  /**
268
   * Rescan for any missed transactions.
269
   * @param {Number|Hash} start - Start block.
270
   * @returns {Promise}
271
   */
272

273
  async rescan(start) {
274
    if (this.node.spv)
×
275
      return this.node.chain.reset(start);
×
276

277
    return this.node.chain.scan(start, this.filter, (entry, txs) => {
×
278
      return this.handleCall('block rescan', entry, txs);
×
279
    });
280
  }
281

282
  /**
283
   * Rescan interactive for any missed transactions.
284
   * @param {Number|Hash} start - Start block.
285
   * @param {Boolean} [fullLock=false]
286
   * @returns {Promise}
287
   */
288

289
  async rescanInteractive(start, fullLock = true) {
×
290
    if (this.node.spv)
324✔
291
      return this.node.chain.reset(start);
6✔
292

293
    const iter = async (entry, txs) => {
318✔
294
      return await this.handleCall('block rescan interactive', entry, txs);
2,014✔
295
    };
296

297
    try {
318✔
298
      return await this.node.scanInteractive(
318✔
299
        start,
300
        this.filter,
301
        iter,
302
        fullLock
303
      );
304
    } catch (e) {
305
      await this.handleCall('block rescan interactive abort', e.message);
1✔
306
      throw e;
1✔
307
    }
308
  }
309

310
  /**
311
   * Get name state.
312
   * @param {Buffer} nameHash
313
   * @returns {Object}
314
   */
315

316
  async getNameStatus(nameHash) {
317
    return this.node.getNameStatus(nameHash);
413✔
318
  }
319

320
  /**
321
   * Get UTXO.
322
   * @param {Hash} hash
323
   * @param {Number} index
324
   * @returns {Object}
325
   */
326

327
  async getCoin(hash, index) {
328
    return this.node.getCoin(hash, index);
3✔
329
  }
330

331
  /**
332
   * Get block header.
333
   * @param {Hash|Number} block
334
   * @returns {Promise<ChainEntry>}
335
   */
336

337
  async getBlockHeader(block) {
NEW
338
    if (typeof block === 'string')
×
NEW
339
      block = Buffer.from(block, 'hex');
×
340

NEW
341
    const entry = await this.node.chain.getEntry(block);
×
342

NEW
343
    if (!entry)
×
NEW
344
      return null;
×
345

NEW
346
    return entry.toJSON();
×
347
  }
348
}
349

350
/*
351
 * Expose
352
 */
353

354
module.exports = NodeClient;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc