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

handshake-org / hsd / 7113784955

06 Dec 2023 11:15AM UTC coverage: 68.615% (+0.003%) from 68.612%
7113784955

push

github

nodech
Merge PR #879 from 'nodech/cache-test-dns'

7499 of 12765 branches covered (0.0%)

Branch coverage included in aggregate %.

23952 of 33072 relevant lines covered (72.42%)

34147.95 hits per line

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

71.54
/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);
106✔
39

40
    this.opened = false;
106✔
41

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

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

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

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

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

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

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

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

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

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

234
    this.chain.on('disconnect', async (entry, block) => {
105✔
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) => {
105✔
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) => {
105✔
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) => {
105✔
262
      this.emit('tree compact start', treeRoot, entry);
7✔
263
    });
264

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

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

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

277
    this.loadPlugins();
105✔
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.');
105✔
289
    this.opened = true;
105✔
290

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

298
    await this.openPlugins();
104✔
299

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

302
    if (this.ns)
104✔
303
      await this.ns.open();
74✔
304

305
    if (this.rs)
104✔
306
      await this.rs.open();
73✔
307

308
    await this.handleOpen();
104✔
309

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

318
    this.logger.info('Node is loaded.');
104✔
319
    this.emit('open');
104✔
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.');
105✔
330
    this.opened = false;
105✔
331

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

335
    if (this.rs)
104✔
336
      await this.rs.close();
73✔
337

338
    if (this.ns)
104✔
339
      await this.ns.close();
74✔
340

341
    await this.closePlugins();
104✔
342

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

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

355
  /**
356
   * Rescan for any missed transactions.
357
   * @param {Number|Hash} start - Start block.
358
   * @param {Bloom} 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
   * Broadcast a transaction.
369
   * @param {TX|Block|Claim|AirdropProof} item
370
   * @returns {Promise}
371
   */
372

373
  async broadcast(item) {
374
    try {
2✔
375
      await this.pool.broadcast(item);
2✔
376
    } catch (e) {
377
      this.emit('error', e);
×
378
    }
379
  }
380

381
  /**
382
   * Add transaction to mempool, broadcast.
383
   * @param {TX} tx
384
   */
385

386
  async sendTX(tx) {
387
    let missing;
388

389
    try {
939✔
390
      missing = await this.mempool.addTX(tx);
939✔
391
    } catch (err) {
392
      if (err.type === 'VerifyError' && err.score === 0) {
2!
393
        this.error(err);
×
394
        this.logger.warning('Verification failed for tx: %x.', tx.hash());
×
395
        this.logger.warning('Attempting to broadcast anyway...');
×
396
        this.broadcast(tx);
×
397
        return;
×
398
      }
399
      throw err;
2✔
400
    }
401

402
    if (missing) {
937✔
403
      this.logger.warning('TX was orphaned in mempool: %x.', tx.hash());
1✔
404
      this.logger.warning('Attempting to broadcast anyway...');
1✔
405
      this.broadcast(tx);
1✔
406
    }
407
  }
408

409
  /**
410
   * Add transaction to mempool, broadcast. Silence errors.
411
   * @param {TX} tx
412
   * @returns {Promise}
413
   */
414

415
  async relay(tx) {
416
    try {
935✔
417
      await this.sendTX(tx);
935✔
418
    } catch (e) {
419
      this.error(e);
2✔
420
    }
421
  }
422

423
  /**
424
   * Add claim to mempool, broadcast.
425
   * @param {Claim} claim
426
   */
427

428
  async sendClaim(claim) {
429
    try {
8✔
430
      await this.mempool.addClaim(claim);
8✔
431
    } catch (err) {
432
      if (err.type === 'VerifyError' && err.score === 0) {
1!
433
        this.error(err);
1✔
434
        this.logger.warning('Verification failed for claim: %x.', claim.hash());
1✔
435
        this.logger.warning('Attempting to broadcast anyway...');
1✔
436
        this.broadcast(claim);
1✔
437
        return;
1✔
438
      }
439
      throw err;
×
440
    }
441
  }
442

443
  /**
444
   * Add claim to mempool, broadcast. Silence errors.
445
   * @param {Claim} claim
446
   * @returns {Promise}
447
   */
448

449
  async relayClaim(claim) {
450
    try {
8✔
451
      await this.sendClaim(claim);
8✔
452
    } catch (e) {
453
      this.error(e);
×
454
    }
455
  }
456

457
  /**
458
   * Add airdrop proof to mempool, broadcast.
459
   * @param {AirdropProof} proof
460
   */
461

462
  async sendAirdrop(proof) {
463
    try {
×
464
      await this.mempool.addAirdrop(proof);
×
465
    } catch (err) {
466
      if (err.type === 'VerifyError' && err.score === 0) {
×
467
        this.error(err);
×
468
        this.logger.warning('Verification failed for proof: %x.', proof.hash());
×
469
        this.logger.warning('Attempting to broadcast anyway...');
×
470
        this.broadcast(proof);
×
471
        return;
×
472
      }
473
      throw err;
×
474
    }
475
  }
476

477
  /**
478
   * Add airdrop proof to mempool, broadcast. Silence errors.
479
   * @param {AirdropProof} proof
480
   * @returns {Promise}
481
   */
482

483
  async relayAirdrop(proof) {
484
    try {
×
485
      await this.sendAirdrop(proof);
×
486
    } catch (e) {
487
      this.error(e);
×
488
    }
489
  }
490

491
  /**
492
   * Connect to the network.
493
   * @returns {Promise}
494
   */
495

496
  connect() {
497
    return this.pool.connect();
22✔
498
  }
499

500
  /**
501
   * Disconnect from the network.
502
   * @returns {Promise}
503
   */
504

505
  disconnect() {
506
    return this.pool.disconnect();
×
507
  }
508

509
  /**
510
   * Start the blockchain sync.
511
   */
512

513
  startSync() {
514
    return this.pool.startSync();
2✔
515
  }
516

517
  /**
518
   * Stop syncing the blockchain.
519
   */
520

521
  stopSync() {
522
    return this.pool.stopSync();
×
523
  }
524

525
  /**
526
   * Retrieve a block from the chain database.
527
   * @param {Hash} hash
528
   * @returns {Promise} - Returns {@link Block}.
529
   */
530

531
  getBlock(hash) {
532
    return this.chain.getBlock(hash);
×
533
  }
534

535
  /**
536
   * Retrieve a coin from the mempool or chain database.
537
   * Takes into account spent coins in the mempool.
538
   * @param {Hash} hash
539
   * @param {Number} index
540
   * @returns {Promise} - Returns {@link Coin}.
541
   */
542

543
  async getCoin(hash, index) {
544
    const coin = this.mempool.getCoin(hash, index);
6✔
545

546
    if (coin)
6!
547
      return coin;
×
548

549
    if (this.mempool.isSpent(hash, index))
6!
550
      return null;
×
551

552
    return this.chain.getCoin(hash, index);
6✔
553
  }
554

555
  /**
556
   * Get coins that pertain to an address from the mempool or chain database.
557
   * Takes into account spent coins in the mempool.
558
   * @param {Address} addrs
559
   * @returns {Promise} - Returns {@link Coin}[].
560
   */
561

562
  async getCoinsByAddress(addrs) {
563
    const mempool = this.mempool.getCoinsByAddress(addrs);
1✔
564
    const chain = await this.chain.getCoinsByAddress(addrs);
1✔
565
    const out = [];
1✔
566

567
    for (const coin of chain) {
1✔
568
      const spent = this.mempool.isSpent(coin.hash, coin.index);
5✔
569

570
      if (spent)
5!
571
        continue;
×
572

573
      out.push(coin);
5✔
574
    }
575

576
    for (const coin of mempool)
1✔
577
      out.push(coin);
×
578

579
    return out;
1✔
580
  }
581

582
  /**
583
   * Retrieve transactions pertaining to an
584
   * address from the mempool or chain database.
585
   * @param {Address} addrs
586
   * @returns {Promise} - Returns {@link TXMeta}[].
587
   */
588

589
  async getMetaByAddress(addrs) {
590
    const mempool = this.mempool.getMetaByAddress(addrs);
×
591
    const chain = await this.chain.getMetaByAddress(addrs);
×
592
    return chain.concat(mempool);
×
593
  }
594

595
  /**
596
   * Retrieve a transaction from the mempool or chain database.
597
   * @param {Hash} hash
598
   * @returns {Promise} - Returns {@link TXMeta}.
599
   */
600

601
  async getMeta(hash) {
602
    const meta = this.mempool.getMeta(hash);
2✔
603

604
    if (meta)
2!
605
      return meta;
×
606

607
    return this.chain.getMeta(hash);
2✔
608
  }
609

610
  /**
611
   * Retrieve a spent coin viewpoint from mempool or chain database.
612
   * @param {TXMeta} meta
613
   * @returns {Promise} - Returns {@link CoinView}.
614
   */
615

616
  async getMetaView(meta) {
617
    if (meta.height === -1)
×
618
      return this.mempool.getSpentView(meta.tx);
×
619
    return this.chain.getSpentView(meta.tx);
×
620
  }
621

622
  /**
623
   * Retrieve transactions pertaining to an
624
   * address from the mempool or chain database.
625
   * @param {Address} addrs
626
   * @returns {Promise} - Returns {@link TX}[].
627
   */
628

629
  async getTXByAddress(addrs) {
630
    const mtxs = await this.getMetaByAddress(addrs);
×
631
    const out = [];
×
632

633
    for (const mtx of mtxs)
×
634
      out.push(mtx.tx);
×
635

636
    return out;
×
637
  }
638

639
  /**
640
   * Retrieve a transaction from the mempool or chain database.
641
   * @param {Hash} hash
642
   * @returns {Promise} - Returns {@link TX}.
643
   */
644

645
  async getTX(hash) {
646
    const mtx = await this.getMeta(hash);
×
647

648
    if (!mtx)
×
649
      return null;
×
650

651
    return mtx.tx;
×
652
  }
653

654
  /**
655
   * Test whether the mempool or chain contains a transaction.
656
   * @param {Hash} hash
657
   * @returns {Promise} - Returns Boolean.
658
   */
659

660
  async hasTX(hash) {
661
    if (this.mempool.hasEntry(hash))
×
662
      return true;
×
663

664
    return this.chain.hasTX(hash);
×
665
  }
666

667
  /**
668
   * Get current name state.
669
   * @param {Buffer} nameHash
670
   * @returns {NameState}
671
   */
672

673
  async getNameStatus(nameHash) {
674
    const height = this.chain.height + 1;
419✔
675
    return this.chain.db.getNameStatus(nameHash, height);
419✔
676
  }
677
}
678

679
/*
680
 * Expose
681
 */
682

683
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