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

handshake-org / hsd / 16217128391

11 Jul 2025 09:50AM UTC coverage: 71.664% (+0.002%) from 71.662%
16217128391

push

github

nodech
Merge PR #939 from 'nodech/batch-sort'

8218 of 13319 branches covered (61.7%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

26152 of 34641 relevant lines covered (75.49%)

34138.69 hits per line

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

69.03
/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('bfilter').BloomFilter} BloomFilter */
14
/** @typedef {import('../types').Hash} Hash */
15
/** @typedef {import('../primitives/tx')} TX */
16
/** @typedef {import('../primitives/claim')} Claim */
17
/** @typedef {import('../covenants/namestate')} NameState */
18
/** @typedef {import('../blockchain/chainentry')} ChainEntry */
19
/** @typedef {import('../node/fullnode')} FullNode */
20
/** @typedef {import('../node/spvnode')} SPVNode */
21
/** @typedef {FullNode|SPVNode} Node */
22

23
/**
24
 * Node Client
25
 * @alias module:node.NodeClient
26
 */
27

28
class NodeClient extends AsyncEmitter {
29
  /**
30
   * Create a node client.
31
   * @constructor
32
   * @param {Node} node
33
   */
34

35
  constructor(node) {
36
    super();
91✔
37

38
    /** @type {Node} */
39
    this.node = node;
91✔
40
    this.network = node.network;
91✔
41
    this.filter = null;
91✔
42
    this.opened = false;
91✔
43
    this.hooks = new Map();
91✔
44

45
    this.init();
91✔
46
  }
47

48
  /**
49
   * Initialize the client.
50
   */
51

52
  init() {
53
    this.node.chain.on('connect', async (entry, block) => {
91✔
54
      if (!this.opened)
7,019✔
55
        return;
1✔
56

57
      await this.emitAsync('block connect', entry, block.txs);
7,018✔
58
    });
59

60
    this.node.chain.on('disconnect', async (entry, block) => {
91✔
61
      if (!this.opened)
107!
62
        return;
×
63

64
      await this.emitAsync('block disconnect', entry);
107✔
65
    });
66

67
    this.node.on('tx', (tx) => {
91✔
68
      if (!this.opened)
1,916!
UNCOV
69
        return;
×
70

71
      this.emit('tx', tx);
1,916✔
72
    });
73

74
    this.node.on('reset', (tip) => {
91✔
75
      if (!this.opened)
7!
76
        return;
×
77

78
      this.emit('chain reset', tip);
7✔
79
    });
80
  }
81

82
  /**
83
   * Open the client.
84
   * @returns {Promise<void>}
85
   */
86

87
  async open() {
88
    assert(!this.opened, 'NodeClient is already open.');
92✔
89
    this.opened = true;
92✔
90
    setImmediate(() => this.emit('connect'));
92✔
91
  }
92

93
  /**
94
   * Close the client.
95
   * @returns {Promise<void>}
96
   */
97

98
  async close() {
99
    assert(this.opened, 'NodeClient is not open.');
92✔
100
    this.opened = false;
92✔
101
    setImmediate(() => this.emit('disconnect'));
92✔
102
  }
103

104
  /**
105
   * Add a listener.
106
   * @param {String} type
107
   * @param {Function} handler
108
   */
109

110
  bind(type, handler) {
111
    return this.on(type, handler);
364✔
112
  }
113

114
  /**
115
   * Add a hook.
116
   * @param {String} event
117
   * @param {Function} handler
118
   */
119

120
  hook(event, handler) {
121
    assert(typeof event === 'string', 'Event must be a string.');
273✔
122
    assert(typeof handler === 'function', 'Handler must be a function.');
273✔
123
    assert(!this.hooks.has(event), 'Hook already bound.');
273✔
124
    assert(!Object.prototype.hasOwnProperty.call(blacklist, event),
273✔
125
      'Blacklisted event.');
126
    this.hooks.set(event, handler);
273✔
127
  }
128

129
  /**
130
   * Remove a hook.
131
   * @param {String} event
132
   */
133

134
  unhook(event) {
135
    assert(typeof event === 'string', 'Event must be a string.');
×
136
    assert(!Object.prototype.hasOwnProperty.call(blacklist, event),
×
137
      'Blacklisted event.');
138
    this.hooks.delete(event);
×
139
  }
140

141
  /**
142
   * Call a hook.
143
   * @param {String} event
144
   * @param {...Object} args
145
   * @returns {Promise}
146
   */
147

148
  handleCall(event, ...args) {
149
    const hook = this.hooks.get(event);
2,017✔
150

151
    if (!hook)
2,017!
152
      throw new Error('No hook available.');
×
153

154
    return hook(...args);
2,017✔
155
  }
156

157
  /**
158
   * Get chain tip.
159
   * @returns {Promise<ChainEntry>}
160
   */
161

162
  async getTip() {
163
    return this.node.chain.tip;
×
164
  }
165

166
  /**
167
   * Get chain entry.
168
   * @param {Hash} hash
169
   * @returns {Promise<ChainEntry?>}
170
   */
171

172
  async getEntry(hash) {
173
    const entry = await this.node.chain.getEntry(hash);
93✔
174

175
    if (!entry)
93✔
176
      return null;
1✔
177

178
    if (!await this.node.chain.isMainChain(entry))
92✔
179
      return null;
1✔
180

181
    return entry;
91✔
182
  }
183

184
  /**
185
   * Send a transaction. Do not wait for promise.
186
   * @param {TX} tx
187
   * @returns {Promise<void>}
188
   */
189

190
  async send(tx) {
191
    this.node.relay(tx);
1,800✔
192
  }
193

194
  /**
195
   * Send a claim. Do not wait for promise.
196
   * @param {Claim} claim
197
   * @returns {Promise<void>}
198
   */
199

200
  async sendClaim(claim) {
201
    this.node.relayClaim(claim);
8✔
202
  }
203

204
  /**
205
   * Set bloom filter.
206
   * @param {BloomFilter} filter
207
   * @returns {Promise<void>}
208
   */
209

210
  async setFilter(filter) {
211
    this.filter = filter;
92✔
212
    this.node.pool.setFilter(filter);
92✔
213
  }
214

215
  /**
216
   * Add data to filter.
217
   * @param {Buffer} data
218
   * @returns {Promise<void>}
219
   */
220

221
  async addFilter(data) {
222
    // `data` is ignored because pool.spvFilter === walletDB.filter
223
    // and therefore is already updated.
224
    // Argument is kept here to be consistent with API in
225
    // wallet/client.js (client/node.js) and wallet/nullclient.js
226
    this.node.pool.queueFilterLoad();
153,023✔
227
  }
228

229
  /**
230
   * Reset filter.
231
   * @returns {Promise<void>}
232
   */
233

234
  async resetFilter() {
235
    this.node.pool.queueFilterLoad();
×
236
  }
237

238
  /**
239
   * Esimate smart fee.
240
   * @param {Number?} blocks
241
   * @returns {Promise<Number>}
242
   */
243

244
  async estimateFee(blocks) {
245
    if (!this.node.fees)
1,951!
246
      return this.network.feeRate;
×
247

248
    return this.node.fees.estimateFee(blocks);
1,951✔
249
  }
250

251
  /**
252
   * Get hash range.
253
   * @param {Number} start
254
   * @param {Number} end
255
   * @returns {Promise<Hash[]>}
256
   */
257

258
  async getHashes(start = -1, end = -1) {
×
259
    return this.node.chain.getHashes(start, end);
×
260
  }
261

262
  /**
263
   * Get entries range.
264
   * @param {Number} start
265
   * @param {Number} end
266
   * @returns {Promise<ChainEntry[]>}
267
   */
268

269
  async getEntries(start = -1, end = -1) {
162✔
270
    return this.node.chain.getEntries(start, end);
81✔
271
  }
272

273
  /**
274
   * Rescan for any missed transactions.
275
   * @param {Number|Hash} start - Start block.
276
   * @returns {Promise<void>}
277
   */
278

279
  async rescan(start) {
280
    if (this.node.spv)
×
281
      return this.node.chain.reset(start);
×
282

283
    return this.node.chain.scan(start, this.filter, (entry, txs) => {
×
284
      return this.handleCall('block rescan', entry, txs);
×
285
    });
286
  }
287

288
  /**
289
   * Rescan interactive for any missed transactions.
290
   * @param {Number|Hash} start - Start block.
291
   * @param {Boolean} [fullLock=false]
292
   * @returns {Promise<void>}
293
   */
294

295
  async rescanInteractive(start, fullLock = true) {
×
296
    if (this.node.spv)
326✔
297
      return this.node.chain.reset(start);
6✔
298

299
    const iter = async (entry, txs) => {
320✔
300
      return await this.handleCall('block rescan interactive', entry, txs);
2,016✔
301
    };
302

303
    try {
320✔
304
      return await this.node.scanInteractive(
320✔
305
        start,
306
        this.filter,
307
        iter,
308
        fullLock
309
      );
310
    } catch (e) {
311
      await this.handleCall('block rescan interactive abort', e.message);
1✔
312
      throw e;
1✔
313
    }
314
  }
315

316
  /**
317
   * Get name state.
318
   * @param {Buffer} nameHash
319
   * @returns {Promise<NameState>}
320
   */
321

322
  async getNameStatus(nameHash) {
323
    return this.node.getNameStatus(nameHash);
413✔
324
  }
325

326
  /**
327
   * Get UTXO.
328
   * @param {Hash} hash
329
   * @param {Number} index
330
   * @returns {Object}
331
   */
332

333
  async getCoin(hash, index) {
334
    return this.node.getCoin(hash, index);
3✔
335
  }
336

337
  /**
338
   * Get block header.
339
   * @param {Hash|Number} block
340
   * @returns {Promise<ChainEntry>}
341
   */
342

343
  async getBlockHeader(block) {
344
    if (typeof block === 'string')
×
345
      block = Buffer.from(block, 'hex');
×
346

347
    const entry = await this.node.chain.getEntry(block);
×
348

349
    if (!entry)
×
350
      return null;
×
351

352
    return entry.toJSON();
×
353
  }
354
}
355

356
/*
357
 * Expose
358
 */
359

360
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