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

handshake-org / hsd / 7247659214

18 Dec 2023 11:52AM UTC coverage: 68.626% (-0.07%) from 68.695%
7247659214

push

github

nodech
Merge PR #881 from 'nodech/test-cleanup'

7545 of 12828 branches covered (0.0%)

Branch coverage included in aggregate %.

24038 of 33194 relevant lines covered (72.42%)

34030.13 hits per line

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

70.08
/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);
127✔
39

40
    this.opened = false;
127✔
41

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

45
    // Instantiate block storage.
46
    this.blocks = blockstore.create({
127✔
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({
127✔
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);
126✔
76
    this.fees.init();
126✔
77

78
    // Mempool needs access to the chain.
79
    this.mempool = new Mempool({
126✔
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({
126✔
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({
126✔
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);
126✔
147

148
    // HTTP needs access to the node.
149
    this.http = new HTTP({
126✔
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')) {
126✔
165
      this.ns = new RootServer({
97✔
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')) {
97✔
176
        this.rs = new RecursiveServer({
96✔
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();
126✔
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));
126✔
200
    this.chain.on('abort', err => this.abort(err));
126✔
201

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

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

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

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

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

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

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

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

252
    this.chain.on('reset', async (tip) => {
126✔
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) => {
126✔
262
      this.emit('tree compact start', treeRoot, entry);
7✔
263
    });
264

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

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

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

277
    this.loadPlugins();
126✔
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() {
288
    assert(!this.opened, 'FullNode is already open.');
126✔
289
    this.opened = true;
126✔
290

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

298
    await this.openPlugins();
125✔
299

300
    await this.http.open();
125✔
301

302
    if (this.ns)
125✔
303
      await this.ns.open();
95✔
304

305
    if (this.rs)
125✔
306
      await this.rs.open();
94✔
307

308
    await this.handleOpen();
125✔
309

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

318
    this.logger.info('Node is loaded.');
125✔
319
    this.emit('open');
125✔
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() {
329
    assert(this.opened, 'FullNode is not open.');
126✔
330
    this.opened = false;
126✔
331

332
    await this.handlePreclose();
126✔
333
    await this.http.close();
126✔
334

335
    if (this.rs)
125✔
336
      await this.rs.close();
94✔
337

338
    if (this.ns)
125✔
339
      await this.ns.close();
95✔
340

341
    await this.closePlugins();
125✔
342

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

350
    this.logger.info('Node is closed.');
125✔
351
    this.emit('closed');
125✔
352
    this.emit('close');
125✔
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
   * @returns {Promise}
373
   */
374

375
  scanInteractive(start, filter, iter) {
376
    return this.chain.scanInteractive(start, filter, iter);
58✔
377
  }
378

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

385
  async broadcast(item) {
386
    try {
1✔
387
      await this.pool.broadcast(item);
1✔
388
    } catch (e) {
389
      this.emit('error', e);
×
390
    }
391
  }
392

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

398
  async sendTX(tx) {
399
    let missing;
400

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

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

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

427
  async relay(tx) {
428
    try {
935✔
429
      await this.sendTX(tx);
935✔
430
    } catch (e) {
431
      this.error(e);
2✔
432
    }
433
  }
434

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

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

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

461
  async relayClaim(claim) {
462
    try {
8✔
463
      await this.sendClaim(claim);
8✔
464
    } catch (e) {
465
      this.error(e);
×
466
    }
467
  }
468

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

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

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

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

503
  /**
504
   * Connect to the network.
505
   * @returns {Promise}
506
   */
507

508
  connect() {
509
    return this.pool.connect();
64✔
510
  }
511

512
  /**
513
   * Disconnect from the network.
514
   * @returns {Promise}
515
   */
516

517
  disconnect() {
518
    return this.pool.disconnect();
×
519
  }
520

521
  /**
522
   * Start the blockchain sync.
523
   */
524

525
  startSync() {
526
    return this.pool.startSync();
49✔
527
  }
528

529
  /**
530
   * Stop syncing the blockchain.
531
   */
532

533
  stopSync() {
534
    return this.pool.stopSync();
×
535
  }
536

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

543
  getBlock(hash) {
544
    return this.chain.getBlock(hash);
×
545
  }
546

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

555
  async getCoin(hash, index) {
556
    const coin = this.mempool.getCoin(hash, index);
6✔
557

558
    if (coin)
6!
559
      return coin;
×
560

561
    if (this.mempool.isSpent(hash, index))
6!
562
      return null;
×
563

564
    return this.chain.getCoin(hash, index);
6✔
565
  }
566

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

574
  async getCoinsByAddress(addrs) {
575
    const mempool = this.mempool.getCoinsByAddress(addrs);
1✔
576
    const chain = await this.chain.getCoinsByAddress(addrs);
1✔
577
    const out = [];
1✔
578

579
    for (const coin of chain) {
1✔
580
      const spent = this.mempool.isSpent(coin.hash, coin.index);
5✔
581

582
      if (spent)
5!
583
        continue;
×
584

585
      out.push(coin);
5✔
586
    }
587

588
    for (const coin of mempool)
1✔
589
      out.push(coin);
×
590

591
    return out;
1✔
592
  }
593

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

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

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

613
  async getMeta(hash) {
614
    const meta = this.mempool.getMeta(hash);
2✔
615

616
    if (meta)
2!
617
      return meta;
×
618

619
    return this.chain.getMeta(hash);
2✔
620
  }
621

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

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

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

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

645
    for (const mtx of mtxs)
×
646
      out.push(mtx.tx);
×
647

648
    return out;
×
649
  }
650

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

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

660
    if (!mtx)
×
661
      return null;
×
662

663
    return mtx.tx;
×
664
  }
665

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

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

676
    return this.chain.hasTX(hash);
×
677
  }
678

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

685
  async getNameStatus(nameHash) {
686
    const height = this.chain.height + 1;
419✔
687
    return this.chain.db.getNameStatus(nameHash, height);
419✔
688
  }
689
}
690

691
/*
692
 * Expose
693
 */
694

695
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

© 2025 Coveralls, Inc