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

handshake-org / hsd / 11895505322

18 Nov 2024 03:21PM UTC coverage: 71.265% (+1.2%) from 70.033%
11895505322

Pull #888

github

web-flow
Merge a914b01ee into 1b331eedb
Pull Request #888: Wallet TX Count and time indexing

8052 of 13150 branches covered (61.23%)

Branch coverage included in aggregate %.

826 of 885 new or added lines in 16 files covered. (93.33%)

2063 existing lines in 60 files now uncovered.

25720 of 34239 relevant lines covered (75.12%)

34512.26 hits per line

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

67.23
/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,016✔
144

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

148
    return hook(...args);
2,016✔
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
   * Get median time past.
180
   * @param {Hash} blockhash
181
   * @returns {Promise<Number>}
182
   */
183

184
  async getMedianTime(blockhash) {
NEW
185
    const entry = await this.node.chain.getEntry(blockhash);
×
186

NEW
187
    if (!entry)
×
NEW
188
      return null;
×
189

NEW
190
    return this.node.chain.getMedianTime(entry);
×
191
  }
192

193
  /**
194
   * Send a transaction. Do not wait for promise.
195
   * @param {TX} tx
196
   * @returns {Promise}
197
   */
198

199
  async send(tx) {
200
    this.node.relay(tx);
1,788✔
201
  }
202

203
  /**
204
   * Send a claim. Do not wait for promise.
205
   * @param {Claim} claim
206
   * @returns {Promise}
207
   */
208

209
  async sendClaim(claim) {
210
    this.node.relayClaim(claim);
8✔
211
  }
212

213
  /**
214
   * Set bloom filter.
215
   * @param {Bloom} filter
216
   * @returns {Promise}
217
   */
218

219
  async setFilter(filter) {
220
    this.filter = filter;
90✔
221
    this.node.pool.setFilter(filter);
90✔
222
  }
223

224
  /**
225
   * Add data to filter.
226
   * @param {Buffer} data
227
   * @returns {Promise}
228
   */
229

230
  async addFilter(data) {
231
    // `data` is ignored because pool.spvFilter === walletDB.filter
232
    // and therefore is already updated.
233
    // Argument is kept here to be consistent with API in
234
    // wallet/client.js (client/node.js) and wallet/nullclient.js
235
    this.node.pool.queueFilterLoad();
151,820✔
236
  }
237

238
  /**
239
   * Reset filter.
240
   * @returns {Promise}
241
   */
242

243
  async resetFilter() {
244
    this.node.pool.queueFilterLoad();
×
245
  }
246

247
  /**
248
   * Esimate smart fee.
249
   * @param {Number?} blocks
250
   * @returns {Promise}
251
   */
252

253
  async estimateFee(blocks) {
254
    if (!this.node.fees)
1,939!
255
      return this.network.feeRate;
×
256

257
    return this.node.fees.estimateFee(blocks);
1,939✔
258
  }
259

260
  /**
261
   * Get hash range.
262
   * @param {Number} start
263
   * @param {Number} end
264
   * @returns {Promise}
265
   */
266

267
  async getHashes(start = -1, end = -1) {
×
UNCOV
268
    return this.node.chain.getHashes(start, end);
×
269
  }
270

271
  /**
272
   * Get entries range.
273
   * @param {Number} start
274
   * @param {Number} end
275
   * @returns {Promise<ChainEntry[]>}
276
   */
277

278
  async getEntries(start = -1, end = -1) {
158✔
279
    return this.node.chain.getEntries(start, end);
79✔
280
  }
281

282
  /**
283
   * Rescan for any missed transactions.
284
   * @param {Number|Hash} start - Start block.
285
   * @returns {Promise}
286
   */
287

288
  async rescan(start) {
289
    if (this.node.spv)
×
290
      return this.node.chain.reset(start);
×
291

292
    return this.node.chain.scan(start, this.filter, (entry, txs) => {
×
293
      return this.handleCall('block rescan', entry, txs);
×
294
    });
295
  }
296

297
  /**
298
   * Rescan interactive for any missed transactions.
299
   * @param {Number|Hash} start - Start block.
300
   * @param {Boolean} [fullLock=false]
301
   * @returns {Promise}
302
   */
303

304
  async rescanInteractive(start, fullLock = true) {
×
305
    if (this.node.spv)
324✔
306
      return this.node.chain.reset(start);
6✔
307

308
    const iter = async (entry, txs) => {
318✔
309
      return await this.handleCall('block rescan interactive', entry, txs);
2,015✔
310
    };
311

312
    try {
318✔
313
      return await this.node.scanInteractive(
318✔
314
        start,
315
        this.filter,
316
        iter,
317
        fullLock
318
      );
319
    } catch (e) {
320
      await this.handleCall('block rescan interactive abort', e.message);
1✔
321
      throw e;
1✔
322
    }
323
  }
324

325
  /**
326
   * Get name state.
327
   * @param {Buffer} nameHash
328
   * @returns {Object}
329
   */
330

331
  async getNameStatus(nameHash) {
332
    return this.node.getNameStatus(nameHash);
413✔
333
  }
334

335
  /**
336
   * Get UTXO.
337
   * @param {Hash} hash
338
   * @param {Number} index
339
   * @returns {Object}
340
   */
341

342
  async getCoin(hash, index) {
343
    return this.node.getCoin(hash, index);
3✔
344
  }
345

346
  /**
347
   * Get block header.
348
   * @param {Hash|Number} block
349
   * @returns {Promise<ChainEntry>}
350
   */
351

352
  async getBlockHeader(block) {
NEW
353
    if (typeof block === 'string')
×
NEW
354
      block = Buffer.from(block, 'hex');
×
355

NEW
356
    const entry = await this.node.chain.getEntry(block);
×
357

NEW
358
    if (!entry)
×
NEW
359
      return null;
×
360

NEW
361
    return entry;
×
362
  }
363
}
364

365
/*
366
 * Expose
367
 */
368

369
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