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

handshake-org / hsd / 15042451478

15 May 2025 10:16AM UTC coverage: 19.792% (-51.5%) from 71.269%
15042451478

Pull #928

github

web-flow
Merge fa1d30da4 into 5f11d622b
Pull Request #928: Wallet coinselection

1570 of 13236 branches covered (11.86%)

Branch coverage included in aggregate %.

217 of 388 new or added lines in 6 files covered. (55.93%)

17866 existing lines in 127 files now uncovered.

7855 of 34384 relevant lines covered (22.84%)

129.11 hits per line

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

20.78
/lib/node/fullnode.js
1
/*!
2
 * fullnode.js - full node 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 Chain = require('../blockchain/chain');
1✔
11
const Fees = require('../mempool/fees');
1✔
12
const Mempool = require('../mempool/mempool');
1✔
13
const Pool = require('../net/pool');
1✔
14
const Miner = require('../mining/miner');
1✔
15
const Node = require('./node');
1✔
16
const HTTP = require('./http');
1✔
17
const RPC = require('./rpc');
1✔
18
const blockstore = require('../blockstore');
1✔
19
const pkg = require('../pkg');
1✔
20
const {RootServer, RecursiveServer} = require('../dns/server');
1✔
21

22
/**
23
 * Full Node
24
 * Respresents a fullnode complete with a
25
 * chain, mempool, miner, etc.
26
 * @alias module:node.FullNode
27
 * @extends Node
28
 */
29

30
class FullNode extends Node {
31
  /**
32
   * Create a full node.
33
   * @constructor
34
   * @param {Object?} options
35
   */
36

37
  constructor(options) {
38
    super(pkg.core, pkg.cfg, 'debug.log', options);
25✔
39

40
    this.opened = false;
25✔
41

42
    // SPV flag.
43
    this.spv = false;
25✔
44

45
    // Instantiate block storage.
46
    this.blocks = blockstore.create({
25✔
47
      network: this.network,
48
      logger: this.logger,
49
      prefix: this.config.prefix,
50
      cacheSize: this.config.mb('block-cache-size'),
51
      memory: this.memory
52
    });
53

54
    // Instantiate blockchain.
55
    this.chain = new Chain({
25✔
56
      network: this.network,
57
      logger: this.logger,
58
      blocks: this.blocks,
59
      workers: this.workers,
60
      memory: this.config.bool('memory'),
61
      prefix: this.config.prefix,
62
      maxFiles: this.config.uint('max-files'),
63
      cacheSize: this.config.mb('cache-size'),
64
      prune: this.config.bool('prune'),
65
      checkpoints: this.config.bool('checkpoints'),
66
      entryCache: this.config.uint('entry-cache'),
67
      chainMigrate: this.config.uint('chain-migrate'),
68
      indexTX: this.config.bool('index-tx'),
69
      indexAddress: this.config.bool('index-address'),
70
      compactTreeOnInit: this.config.bool('compact-tree-on-init'),
71
      compactTreeInitInterval: this.config.uint('compact-tree-init-interval')
72
    });
73

74
    // Fee estimation.
75
    this.fees = new Fees(this.logger);
25✔
76
    this.fees.init();
25✔
77

78
    // Mempool needs access to the chain.
79
    this.mempool = new Mempool({
25✔
80
      network: this.network,
81
      logger: this.logger,
82
      workers: this.workers,
83
      chain: this.chain,
84
      fees: this.fees,
85
      memory: this.config.bool('memory'),
86
      prefix: this.config.prefix,
87
      persistent: this.config.bool('persistent-mempool'),
88
      maxSize: this.config.mb('mempool-size'),
89
      limitFree: this.config.bool('limit-free'),
90
      limitFreeRelay: this.config.uint('limit-free-relay'),
91
      requireStandard: this.config.bool('require-standard'),
92
      rejectAbsurdFees: this.config.bool('reject-absurd-fees'),
93
      indexAddress: this.config.bool('index-address'),
94
      expiryTime: this.config.uint('mempool-expiry-time')
95
    });
96

97
    // Pool needs access to the chain and mempool.
98
    this.pool = new Pool({
25✔
99
      network: this.network,
100
      logger: this.logger,
101
      chain: this.chain,
102
      mempool: this.mempool,
103
      prefix: this.config.prefix,
104
      compact: this.config.bool('compact'),
105
      bip37: this.config.bool('bip37'),
106
      identityKey: this.identityKey,
107
      maxOutbound: this.config.uint('max-outbound'),
108
      maxInbound: this.config.uint('max-inbound'),
109
      maxProofRPS: this.config.uint('max-proof-rps'),
110
      createSocket: this.config.func('create-socket'),
111
      proxy: this.config.str('proxy'),
112
      onion: this.config.bool('onion'),
113
      brontideOnly: this.config.bool('brontide-only'),
114
      upnp: this.config.bool('upnp'),
115
      seeds: this.config.array('seeds'),
116
      nodes: this.config.array('nodes'),
117
      only: this.config.array('only'),
118
      publicHost: this.config.str('public-host'),
119
      publicPort: this.config.uint('public-port'),
120
      publicBrontidePort: this.config.uint('public-brontide-port'),
121
      host: this.config.str('host'),
122
      port: this.config.uint('port'),
123
      brontidePort: this.config.uint('brontide-port'),
124
      listen: this.config.bool('listen'),
125
      memory: this.config.bool('memory'),
126
      agent: this.config.str('agent')
127
    });
128

129
    // Miner needs access to the chain and mempool.
130
    this.miner = new Miner({
25✔
131
      network: this.network,
132
      logger: this.logger,
133
      workers: this.workers,
134
      chain: this.chain,
135
      mempool: this.mempool,
136
      address: this.config.array('coinbase-address'),
137
      coinbaseFlags: this.config.str('coinbase-flags'),
138
      preverify: this.config.bool('preverify'),
139
      minWeight: this.config.uint('min-weight'),
140
      maxWeight: this.config.uint('max-weight'),
141
      reservedWeight: this.config.uint('reserved-weight'),
142
      reservedSigops: this.config.uint('reserved-sigops')
143
    });
144

145
    // RPC needs access to the node.
146
    this.rpc = new RPC(this);
25✔
147

148
    // HTTP needs access to the node.
149
    this.http = new HTTP({
25✔
150
      network: this.network,
151
      logger: this.logger,
152
      node: this,
153
      prefix: this.config.prefix,
154
      ssl: this.config.bool('ssl'),
155
      keyFile: this.config.path('ssl-key'),
156
      certFile: this.config.path('ssl-cert'),
157
      host: this.config.str('http-host'),
158
      port: this.config.uint('http-port'),
159
      apiKey: this.config.str('api-key'),
160
      noAuth: this.config.bool('no-auth'),
161
      cors: this.config.bool('cors')
162
    });
163

164
    if (!this.config.bool('no-dns')) {
25✔
165
      this.ns = new RootServer({
23✔
166
        logger: this.logger,
167
        key: this.identityKey,
168
        host: this.config.str('ns-host'),
169
        port: this.config.uint('ns-port', this.network.nsPort),
170
        lookup: key => this.chain.db.tree.get(key),
×
171
        publicHost: this.config.str('public-host'),
172
        noSig0: this.config.bool('no-sig0')
173
      });
174

175
      if (!this.config.bool('no-rs')) {
23!
176
        this.rs = new RecursiveServer({
23✔
177
          logger: this.logger,
178
          key: this.identityKey,
179
          host: this.config.str('rs-host'),
180
          port: this.config.uint('rs-port', this.network.rsPort),
181
          stubHost: this.ns.host,
182
          stubPort: this.ns.port,
183
          noUnbound: this.config.bool('rs-no-unbound'),
184
          noSig0: this.config.bool('no-sig0')
185
        });
186
      }
187
    }
188

189
    this.init();
25✔
190
  }
191

192
  /**
193
   * Initialize the node.
194
   * @private
195
   */
196

197
  init() {
198
    // Bind to errors
199
    this.chain.on('error', err => this.error(err));
25✔
200
    this.chain.on('abort', err => this.abort(err));
25✔
201

202
    this.mempool.on('error', err => this.error(err));
25✔
203
    this.pool.on('error', err => this.error(err));
25✔
204
    this.miner.on('error', err => this.error(err));
25✔
205

206
    if (this.http)
25!
207
      this.http.on('error', err => this.error(err));
25✔
208

209
    this.mempool.on('tx', (tx) => {
25✔
UNCOV
210
      this.miner.cpu.notifyEntry();
×
UNCOV
211
      this.emit('tx', tx);
×
212
    });
213

214
    this.mempool.on('claim', (claim) => {
25✔
UNCOV
215
      this.miner.cpu.notifyEntry();
×
UNCOV
216
      this.emit('claim', claim);
×
217
    });
218

219
    this.mempool.on('airdrop', (proof) => {
25✔
UNCOV
220
      this.miner.cpu.notifyEntry();
×
UNCOV
221
      this.emit('airdrop', proof);
×
222
    });
223

224
    this.chain.on('connect', async (entry, block, view) => {
25✔
UNCOV
225
      try {
×
UNCOV
226
        await this.mempool._addBlock(entry, block.txs, view);
×
227
      } catch (e) {
228
        this.error(e);
×
229
      }
UNCOV
230
      this.emit('block', block);
×
UNCOV
231
      this.emit('connect', entry, block);
×
232
    });
233

234
    this.chain.on('disconnect', async (entry, block) => {
25✔
UNCOV
235
      try {
×
UNCOV
236
        await this.mempool._removeBlock(entry, block.txs);
×
237
      } catch (e) {
238
        this.error(e);
×
239
      }
UNCOV
240
      this.emit('disconnect', entry, block);
×
241
    });
242

243
    this.chain.on('reorganize', async (tip, competitor, fork) => {
25✔
UNCOV
244
      try {
×
UNCOV
245
        await this.mempool._handleReorg();
×
246
      } catch (e) {
247
        this.error(e);
×
248
      }
UNCOV
249
      this.emit('reorganize', tip, competitor, fork);
×
250
    });
251

252
    this.chain.on('reset', async (tip) => {
25✔
253
      try {
×
254
        await this.mempool._reset();
×
255
      } catch (e) {
256
        this.error(e);
×
257
      }
258
      this.emit('reset', tip);
×
259
    });
260

261
    this.chain.on('tree compact start', (treeRoot, entry) => {
25✔
UNCOV
262
      this.emit('tree compact start', treeRoot, entry);
×
263
    });
264

265
    this.chain.on('tree compact end', (treeRoot, entry) => {
25✔
UNCOV
266
      this.emit('tree compact end', treeRoot, entry);
×
267
    });
268

269
    this.chain.on('tree reconstruct start', () => {
25✔
UNCOV
270
      this.emit('tree reconstruct start');
×
271
    });
272

273
    this.chain.on('tree reconstruct end', () => {
25✔
UNCOV
274
      this.emit('tree reconstruct end');
×
275
    });
276

277
    this.loadPlugins();
25✔
278
  }
279

280
  /**
281
   * Open the node and all its child objects,
282
   * wait for the database to load.
283
   * @alias FullNode#open
284
   * @returns {Promise}
285
   */
286

287
  async open() {
UNCOV
288
    assert(!this.opened, 'FullNode is already open.');
×
UNCOV
289
    this.opened = true;
×
290

UNCOV
291
    await this.handlePreopen();
×
UNCOV
292
    await this.blocks.open();
×
UNCOV
293
    await this.chain.open();
×
UNCOV
294
    await this.mempool.open();
×
UNCOV
295
    await this.miner.open();
×
UNCOV
296
    await this.pool.open();
×
297

UNCOV
298
    await this.openPlugins();
×
299

UNCOV
300
    await this.http.open();
×
301

UNCOV
302
    if (this.ns)
×
UNCOV
303
      await this.ns.open();
×
304

UNCOV
305
    if (this.rs)
×
UNCOV
306
      await this.rs.open();
×
307

UNCOV
308
    await this.handleOpen();
×
309

UNCOV
310
    if (this.has('walletdb')) {
×
UNCOV
311
      const {wdb} = this.require('walletdb');
×
UNCOV
312
      if (this.miner.addresses.length === 0) {
×
UNCOV
313
        const addr = await wdb.primary.receiveAddress();
×
UNCOV
314
        this.miner.addresses.push(addr);
×
315
      }
316
    }
317

UNCOV
318
    this.logger.info('Node is loaded.');
×
UNCOV
319
    this.emit('open');
×
320
  }
321

322
  /**
323
   * Close the node, wait for the database to close.
324
   * @alias FullNode#close
325
   * @returns {Promise}
326
   */
327

328
  async close() {
UNCOV
329
    assert(this.opened, 'FullNode is not open.');
×
UNCOV
330
    this.opened = false;
×
331

UNCOV
332
    await this.handlePreclose();
×
UNCOV
333
    await this.http.close();
×
334

UNCOV
335
    if (this.rs)
×
UNCOV
336
      await this.rs.close();
×
337

UNCOV
338
    if (this.ns)
×
UNCOV
339
      await this.ns.close();
×
340

UNCOV
341
    await this.closePlugins();
×
342

UNCOV
343
    await this.pool.close();
×
UNCOV
344
    await this.miner.close();
×
UNCOV
345
    await this.mempool.close();
×
UNCOV
346
    await this.chain.close();
×
UNCOV
347
    await this.blocks.close();
×
UNCOV
348
    await this.handleClose();
×
349

UNCOV
350
    this.logger.info('Node is closed.');
×
UNCOV
351
    this.emit('closed');
×
UNCOV
352
    this.emit('close');
×
353
  }
354

355
  /**
356
   * Rescan for any missed transactions.
357
   * @param {Number|Hash} start - Start block.
358
   * @param {BloomFilter} filter
359
   * @param {Function} iter - Iterator.
360
   * @returns {Promise}
361
   */
362

363
  scan(start, filter, iter) {
364
    return this.chain.scan(start, filter, iter);
×
365
  }
366

367
  /**
368
   * Interactive rescan for any missed transactions.
369
   * @param {Number|Hash} start - Start block.
370
   * @param {BloomFilter} filter
371
   * @param {Function} iter - Iterator.
372
   * @param {Boolean} [fullLock=false] - lock the whole chain instead of per
373
   * scan.
374
   * @returns {Promise}
375
   */
376

377
  scanInteractive(start, filter, iter, fullLock = false) {
×
UNCOV
378
    return this.chain.scanInteractive(start, filter, iter, fullLock);
×
379
  }
380

381
  /**
382
   * Broadcast a transaction.
383
   * @param {TX|Block|Claim|AirdropProof} item
384
   * @returns {Promise}
385
   */
386

387
  async broadcast(item) {
UNCOV
388
    try {
×
UNCOV
389
      await this.pool.broadcast(item);
×
390
    } catch (e) {
391
      this.emit('error', e);
×
392
    }
393
  }
394

395
  /**
396
   * Add transaction to mempool, broadcast.
397
   * @param {TX} tx
398
   */
399

400
  async sendTX(tx) {
401
    let missing;
402

UNCOV
403
    try {
×
UNCOV
404
      missing = await this.mempool.addTX(tx);
×
405
    } catch (err) {
UNCOV
406
      if (err.type === 'VerifyError' && err.score === 0) {
×
407
        this.error(err);
×
408
        this.logger.warning('Verification failed for tx: %x.', tx.hash());
×
409
        this.logger.warning('Attempting to broadcast anyway...');
×
410
        this.broadcast(tx);
×
411
        return;
×
412
      }
UNCOV
413
      throw err;
×
414
    }
415

UNCOV
416
    if (missing) {
×
417
      this.logger.warning('TX was orphaned in mempool: %x.', tx.hash());
×
418
      this.logger.warning('Attempting to broadcast anyway...');
×
419
      this.broadcast(tx);
×
420
    }
421
  }
422

423
  /**
424
   * Add transaction to mempool, broadcast. Silence errors.
425
   * @param {TX} tx
426
   * @returns {Promise}
427
   */
428

429
  async relay(tx) {
UNCOV
430
    try {
×
UNCOV
431
      await this.sendTX(tx);
×
432
    } catch (e) {
UNCOV
433
      this.error(e);
×
434
    }
435
  }
436

437
  /**
438
   * Add claim to mempool, broadcast.
439
   * @param {Claim} claim
440
   */
441

442
  async sendClaim(claim) {
UNCOV
443
    try {
×
UNCOV
444
      await this.mempool.addClaim(claim);
×
445
    } catch (err) {
UNCOV
446
      if (err.type === 'VerifyError' && err.score === 0) {
×
UNCOV
447
        this.error(err);
×
UNCOV
448
        this.logger.warning('Verification failed for claim: %x.', claim.hash());
×
UNCOV
449
        this.logger.warning('Attempting to broadcast anyway...');
×
UNCOV
450
        this.broadcast(claim);
×
UNCOV
451
        return;
×
452
      }
453
      throw err;
×
454
    }
455
  }
456

457
  /**
458
   * Add claim to mempool, broadcast. Silence errors.
459
   * @param {Claim} claim
460
   * @returns {Promise}
461
   */
462

463
  async relayClaim(claim) {
UNCOV
464
    try {
×
UNCOV
465
      await this.sendClaim(claim);
×
466
    } catch (e) {
467
      this.error(e);
×
468
    }
469
  }
470

471
  /**
472
   * Add airdrop proof to mempool, broadcast.
473
   * @param {AirdropProof} proof
474
   */
475

476
  async sendAirdrop(proof) {
477
    try {
×
478
      await this.mempool.addAirdrop(proof);
×
479
    } catch (err) {
480
      if (err.type === 'VerifyError' && err.score === 0) {
×
481
        this.error(err);
×
482
        this.logger.warning('Verification failed for proof: %x.', proof.hash());
×
483
        this.logger.warning('Attempting to broadcast anyway...');
×
484
        this.broadcast(proof);
×
485
        return;
×
486
      }
487
      throw err;
×
488
    }
489
  }
490

491
  /**
492
   * Add airdrop proof to mempool, broadcast. Silence errors.
493
   * @param {AirdropProof} proof
494
   * @returns {Promise}
495
   */
496

497
  async relayAirdrop(proof) {
498
    try {
×
499
      await this.sendAirdrop(proof);
×
500
    } catch (e) {
501
      this.error(e);
×
502
    }
503
  }
504

505
  /**
506
   * Connect to the network.
507
   * @returns {Promise}
508
   */
509

510
  connect() {
UNCOV
511
    return this.pool.connect();
×
512
  }
513

514
  /**
515
   * Disconnect from the network.
516
   * @returns {Promise}
517
   */
518

519
  disconnect() {
520
    return this.pool.disconnect();
×
521
  }
522

523
  /**
524
   * Start the blockchain sync.
525
   */
526

527
  startSync() {
UNCOV
528
    return this.pool.startSync();
×
529
  }
530

531
  /**
532
   * Stop syncing the blockchain.
533
   */
534

535
  stopSync() {
536
    return this.pool.stopSync();
×
537
  }
538

539
  /**
540
   * Retrieve a block from the chain database.
541
   * @param {Hash} hash
542
   * @returns {Promise} - Returns {@link Block}.
543
   */
544

545
  getBlock(hash) {
546
    return this.chain.getBlock(hash);
×
547
  }
548

549
  /**
550
   * Retrieve a coin from the mempool or chain database.
551
   * Takes into account spent coins in the mempool.
552
   * @param {Hash} hash
553
   * @param {Number} index
554
   * @returns {Promise} - Returns {@link Coin}.
555
   */
556

557
  async getCoin(hash, index) {
UNCOV
558
    const coin = this.mempool.getCoin(hash, index);
×
559

UNCOV
560
    if (coin)
×
561
      return coin;
×
562

UNCOV
563
    if (this.mempool.isSpent(hash, index))
×
564
      return null;
×
565

UNCOV
566
    return this.chain.getCoin(hash, index);
×
567
  }
568

569
  /**
570
   * Get coins that pertain to an address from the mempool or chain database.
571
   * Takes into account spent coins in the mempool.
572
   * @param {Address} addrs
573
   * @returns {Promise} - Returns {@link Coin}[].
574
   */
575

576
  async getCoinsByAddress(addrs) {
UNCOV
577
    const mempool = this.mempool.getCoinsByAddress(addrs);
×
UNCOV
578
    const chain = await this.chain.getCoinsByAddress(addrs);
×
UNCOV
579
    const out = [];
×
580

UNCOV
581
    for (const coin of chain) {
×
UNCOV
582
      const spent = this.mempool.isSpent(coin.hash, coin.index);
×
583

UNCOV
584
      if (spent)
×
585
        continue;
×
586

UNCOV
587
      out.push(coin);
×
588
    }
589

UNCOV
590
    for (const coin of mempool)
×
591
      out.push(coin);
×
592

UNCOV
593
    return out;
×
594
  }
595

596
  /**
597
   * Retrieve transactions pertaining to an
598
   * address from the mempool or chain database.
599
   * @param {Address} addrs
600
   * @returns {Promise} - Returns {@link TXMeta}[].
601
   */
602

603
  async getMetaByAddress(addrs) {
604
    const mempool = this.mempool.getMetaByAddress(addrs);
×
605
    const chain = await this.chain.getMetaByAddress(addrs);
×
606
    return chain.concat(mempool);
×
607
  }
608

609
  /**
610
   * Retrieve a transaction from the mempool or chain database.
611
   * @param {Hash} hash
612
   * @returns {Promise} - Returns {@link TXMeta}.
613
   */
614

615
  async getMeta(hash) {
UNCOV
616
    const meta = this.mempool.getMeta(hash);
×
617

UNCOV
618
    if (meta)
×
619
      return meta;
×
620

UNCOV
621
    return this.chain.getMeta(hash);
×
622
  }
623

624
  /**
625
   * Retrieve a spent coin viewpoint from mempool or chain database.
626
   * @param {TXMeta} meta
627
   * @returns {Promise} - Returns {@link CoinView}.
628
   */
629

630
  async getMetaView(meta) {
631
    if (meta.height === -1)
×
632
      return this.mempool.getSpentView(meta.tx);
×
633
    return this.chain.getSpentView(meta.tx);
×
634
  }
635

636
  /**
637
   * Retrieve transactions pertaining to an
638
   * address from the mempool or chain database.
639
   * @param {Address} addrs
640
   * @returns {Promise} - Returns {@link TX}[].
641
   */
642

643
  async getTXByAddress(addrs) {
644
    const mtxs = await this.getMetaByAddress(addrs);
×
645
    const out = [];
×
646

647
    for (const mtx of mtxs)
×
648
      out.push(mtx.tx);
×
649

650
    return out;
×
651
  }
652

653
  /**
654
   * Retrieve a transaction from the mempool or chain database.
655
   * @param {Hash} hash
656
   * @returns {Promise} - Returns {@link TX}.
657
   */
658

659
  async getTX(hash) {
660
    const mtx = await this.getMeta(hash);
×
661

662
    if (!mtx)
×
663
      return null;
×
664

665
    return mtx.tx;
×
666
  }
667

668
  /**
669
   * Test whether the mempool or chain contains a transaction.
670
   * @param {Hash} hash
671
   * @returns {Promise} - Returns Boolean.
672
   */
673

674
  async hasTX(hash) {
675
    if (this.mempool.hasEntry(hash))
×
676
      return true;
×
677

678
    return this.chain.hasTX(hash);
×
679
  }
680

681
  /**
682
   * Get current name state.
683
   * @param {Buffer} nameHash
684
   * @returns {NameState}
685
   */
686

687
  async getNameStatus(nameHash) {
UNCOV
688
    const height = this.chain.height + 1;
×
UNCOV
689
    return this.chain.db.getNameStatus(nameHash, height);
×
690
  }
691
}
692

693
/*
694
 * Expose
695
 */
696

697
module.exports = FullNode;
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