• 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

58.58
/lib/node/http.js
1
/*!
2
 * server.js - http server 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 path = require('path');
1✔
11
const {Server} = require('bweb');
1✔
12
const Validator = require('bval');
1✔
13
const base58 = require('bcrypto/lib/encoding/base58');
1✔
14
const {BloomFilter} = require('@handshake-org/bfilter');
1✔
15
const sha256 = require('bcrypto/lib/sha256');
1✔
16
const random = require('bcrypto/lib/random');
1✔
17
const {safeEqual} = require('bcrypto/lib/safe');
1✔
18
const util = require('../utils/util');
1✔
19
const TX = require('../primitives/tx');
1✔
20
const Claim = require('../primitives/claim');
1✔
21
const Address = require('../primitives/address');
1✔
22
const Network = require('../protocol/network');
1✔
23
const pkg = require('../pkg');
1✔
24

25
/**
26
 * HTTP
27
 * @alias module:http.Server
28
 */
29

30
class HTTP extends Server {
31
  /**
32
   * Create an http server.
33
   * @constructor
34
   * @param {Object} options
35
   */
36

37
  constructor(options) {
38
    super(new HTTPOptions(options));
117✔
39

40
    this.network = this.options.network;
117✔
41
    this.logger = this.options.logger.context('node-http');
117✔
42
    this.node = this.options.node;
117✔
43

44
    this.chain = this.node.chain;
117✔
45
    this.mempool = this.node.mempool;
117✔
46
    this.pool = this.node.pool;
117✔
47
    this.fees = this.node.fees;
117✔
48
    this.miner = this.node.miner;
117✔
49
    this.rpc = this.node.rpc;
117✔
50

51
    this.init();
117✔
52
  }
53

54
  /**
55
   * Initialize routes.
56
   * @private
57
   */
58

59
  init() {
60
    this.on('request', (req, res) => {
117✔
61
      if (req.method === 'POST' && req.pathname === '/')
951✔
62
        return;
893✔
63

64
      this.logger.debug('Request for method=%s path=%s (%s).',
58✔
65
        req.method, req.pathname, req.socket.remoteAddress);
66
    });
67

68
    this.on('listening', (address) => {
117✔
69
      this.logger.info('Node HTTP server listening on %s (port=%d).',
116✔
70
        address.address, address.port);
71
    });
72

73
    this.initRouter();
117✔
74
    this.initSockets();
117✔
75
  }
76

77
  /**
78
   * Initialize routes.
79
   * @private
80
   */
81

82
  initRouter() {
83
    if (this.options.cors)
117!
84
      this.use(this.cors());
×
85

86
    if (!this.options.noAuth) {
117✔
87
      this.use(this.basicAuth({
15✔
88
        hash: sha256.digest,
89
        password: this.options.apiKey,
90
        realm: 'node'
91
      }));
92
    }
93

94
    this.use(this.bodyParser({
117✔
95
      type: 'json'
96
    }));
97

98
    this.use(this.jsonRPC());
117✔
99
    this.use(this.router());
117✔
100

101
    this.error((err, req, res) => {
117✔
102
      const code = err.statusCode || 500;
×
103
      res.json(code, {
×
104
        error: {
105
          type: err.type,
106
          code: err.code,
107
          message: err.message
108
        }
109
      });
110
    });
111

112
    this.get('/', async (req, res) => {
117✔
113
      const totalTX = this.mempool ? this.mempool.map.size : 0;
33✔
114
      const size = this.mempool ? this.mempool.getSize() : 0;
33✔
115
      const claims = this.mempool ? this.mempool.claims.size : 0;
33✔
116
      const airdrops = this.mempool ? this.mempool.airdrops.size : 0;
33✔
117
      const orphans = this.mempool ? this.mempool.orphans.size : 0;
33✔
118
      const brontide = this.pool.hosts.brontide;
33✔
119

120
      const pub = {
33✔
121
        listen: this.pool.options.listen,
122
        host: null,
123
        port: null,
124
        brontidePort: null
125
      };
126

127
      const addr = this.pool.hosts.getLocal();
33✔
128

129
      if (addr && pub.listen) {
33✔
130
        pub.host = addr.host;
1✔
131
        pub.port = addr.port;
1✔
132
        pub.brontidePort = brontide.port;
1✔
133
      }
134

135
      const treeInterval = this.network.names.treeInterval;
33✔
136
      const prevHeight = this.chain.height - 1;
33✔
137
      const treeRootHeight = this.chain.height === 0 ? 0 :
33✔
138
        prevHeight - (prevHeight % treeInterval) + 1;
139

140
      const treeCompaction = {
33✔
141
        compacted: false,
142
        compactOnInit: false,
143
        compactInterval: null,
144
        lastCompaction: null,
145
        nextCompaction: null
146
      };
147

148
      if (!this.chain.options.spv) {
33✔
149
        const chainOptions = this.chain.options;
32✔
150
        const {
151
          compactionHeight,
152
          compactFrom
153
        } = await this.chain.getCompactionHeights();
32✔
154

155
        treeCompaction.compactOnInit = chainOptions.compactTreeOnInit;
32✔
156

157
        if (chainOptions.compactTreeOnInit) {
32✔
158
          treeCompaction.compactInterval = chainOptions.compactTreeInitInterval;
1✔
159
          treeCompaction.nextCompaction = compactFrom;
1✔
160
        }
161

162
        if (compactionHeight > 0) {
32!
163
          treeCompaction.compacted = true;
×
164
          treeCompaction.lastCompaction = compactionHeight;
×
165
        }
166
      }
167

168
      res.json(200, {
33✔
169
        version: pkg.version,
170
        network: this.network.type,
171
        chain: {
172
          height: this.chain.height,
173
          tip: this.chain.tip.hash.toString('hex'),
174
          treeRoot: this.chain.tip.treeRoot.toString('hex'),
175
          treeRootHeight: treeRootHeight,
176
          progress: this.chain.getProgress(),
177
          indexers: {
178
            indexTX: this.chain.options.indexTX,
179
            indexAddress: this.chain.options.indexAddress
180
          },
181
          options: {
182
            spv: this.chain.options.spv,
183
            prune: this.chain.options.prune
184
          },
185
          treeCompaction: treeCompaction,
186
          state: {
187
            tx: this.chain.db.state.tx,
188
            coin: this.chain.db.state.coin,
189
            value: this.chain.db.state.value,
190
            burned: this.chain.db.state.burned
191
          }
192
        },
193
        pool: {
194
          host: this.pool.options.host,
195
          port: this.pool.options.port,
196
          brontidePort: this.pool.options.brontidePort,
197
          identitykey: brontide.getKey('base32'),
198
          agent: this.pool.options.agent,
199
          services: this.pool.options.services.toString(2),
200
          outbound: this.pool.peers.outbound,
201
          inbound: this.pool.peers.inbound,
202
          public: pub
203
        },
204
        mempool: {
205
          tx: totalTX,
206
          size: size,
207
          claims: claims,
208
          airdrops: airdrops,
209
          orphans: orphans
210
        },
211
        time: {
212
          uptime: this.node.uptime(),
213
          system: util.now(),
214
          adjusted: this.network.now(),
215
          offset: this.network.time.offset
216
        },
217
        memory: this.logger.memoryUsage()
218
      });
219
    });
220

221
    // UTXO by address
222
    this.get('/coin/address/:address', async (req, res) => {
117✔
223
      const valid = Validator.fromRequest(req);
×
224
      const address = valid.str('address');
×
225

226
      enforce(address, 'Address is required.');
×
227
      enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
×
228

229
      const addr = Address.fromString(address, this.network);
×
230
      const coins = await this.node.getCoinsByAddress(addr);
×
231
      const result = [];
×
232

233
      for (const coin of coins)
×
234
        result.push(coin.getJSON(this.network));
×
235

236
      res.json(200, result);
×
237
    });
238

239
    // UTXO by id
240
    this.get('/coin/:hash/:index', async (req, res) => {
117✔
241
      const valid = Validator.fromRequest(req);
1✔
242
      const hash = valid.bhash('hash');
1✔
243
      const index = valid.u32('index');
1✔
244

245
      enforce(hash, 'Hash is required.');
1✔
246
      enforce(index != null, 'Index is required.');
1✔
247
      enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
1✔
248

249
      const coin = await this.node.getCoin(hash, index);
1✔
250

251
      if (!coin) {
1!
252
        res.json(404);
×
253
        return;
×
254
      }
255

256
      res.json(200, coin.getJSON(this.network));
1✔
257
    });
258

259
    // Bulk read UTXOs
260
    // TODO(boymanjor): Deprecate this endpoint
261
    // once the equivalent functionality is included
262
    // in the wallet API.
263
    this.post('/coin/address', async (req, res) => {
117✔
264
      const valid = Validator.fromRequest(req);
1✔
265
      const addresses = valid.array('addresses');
1✔
266

267
      enforce(addresses, 'Addresses is required.');
1✔
268
      enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
1✔
269

270
      this.logger.warning('%s %s %s',
1✔
271
        'Warning: endpoint being considered for deprecation.',
272
        'Known to cause CPU exhaustion if too many addresses',
273
        'are queried or too many results are found.');
274

275
      const addrs = [];
1✔
276
      for (const address of addresses) {
1✔
277
        addrs.push(Address.fromString(address, this.network));
1✔
278
      }
279

280
      const coins = await this.node.getCoinsByAddress(addrs);
1✔
281
      const result = [];
1✔
282

283
      for (const coin of coins)
1✔
284
        result.push(coin.getJSON(this.network));
5✔
285

286
      res.json(200, result);
1✔
287
    });
288

289
    // TX by hash
290
    this.get('/tx/:hash', async (req, res) => {
117✔
291
      const valid = Validator.fromRequest(req);
×
292
      const hash = valid.bhash('hash');
×
293

294
      enforce(hash, 'Hash is required.');
×
295
      enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
×
296

297
      const meta = await this.node.getMeta(hash);
×
298

299
      if (!meta) {
×
300
        res.json(404);
×
301
        return;
×
302
      }
303

304
      const view = await this.node.getMetaView(meta);
×
305

306
      res.json(200, meta.getJSON(this.network, view, this.chain.height));
×
307
    });
308

309
    // TX by address
310
    this.get('/tx/address/:address', async (req, res) => {
117✔
311
      const valid = Validator.fromRequest(req);
×
312
      const address = valid.str('address');
×
313

314
      enforce(address, 'Address is required.');
×
315
      enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
×
316

317
      const addr = Address.fromString(address, this.network);
×
318
      const metas = await this.node.getMetaByAddress(addr);
×
319
      const result = [];
×
320

321
      for (const meta of metas) {
×
322
        const view = await this.node.getMetaView(meta);
×
323
        result.push(meta.getJSON(this.network, view, this.chain.height));
×
324
      }
325

326
      res.json(200, result);
×
327
    });
328

329
    // Bulk read TXs
330
    // TODO(boymanjor): Deprecate this endpoint
331
    // once the equivalent functionality is included
332
    // in the wallet API.
333
    this.post('/tx/address', async (req, res) => {
117✔
334
      const valid = Validator.fromRequest(req);
×
335
      const addresses = valid.array('addresses');
×
336

337
      enforce(addresses, 'Addresses is required.');
×
338
      enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
×
339

340
      this.logger.warning('%s %s %s',
×
341
        'Warning: endpoint being considered for deprecation.',
342
        'Known to cause CPU exhaustion if too many addresses',
343
        'are queried or too many results are found.');
344

345
      const addrs = [];
×
346
      for (const address of addresses) {
×
347
        addrs.push(Address.fromString(address, this.network));
×
348
      }
349

350
      const metas = await this.node.getMetaByAddress(addrs);
×
351
      const result = [];
×
352

353
      for (const meta of metas) {
×
354
        const view = await this.node.getMetaView(meta);
×
355
        result.push(meta.getJSON(this.network, view, this.chain.height));
×
356
      }
357

358
      res.json(200, result);
×
359
    });
360

361
    // Block by hash/height
362
    this.get('/block/:block', async (req, res) => {
117✔
363
      const valid = Validator.fromRequest(req);
1✔
364
      const hash = valid.uintbhash('block');
1✔
365

366
      enforce(hash != null, 'Hash or height required.');
1✔
367
      enforce(!this.chain.options.spv, 'Cannot get block in SPV mode.');
1✔
368

369
      const block = await this.chain.getBlock(hash);
1✔
370

371
      if (!block) {
1!
372
        res.json(404);
×
373
        return;
×
374
      }
375

376
      const view = await this.chain.getBlockView(block);
1✔
377

378
      if (!view) {
1!
379
        res.json(404);
×
380
        return;
×
381
      }
382

383
      const height = await this.chain.getHeight(hash);
1✔
384
      const depth = this.chain.height - height + 1;
1✔
385

386
      res.json(200, block.getJSON(this.network, view, height, depth));
1✔
387
    });
388

389
    // Block Header by hash/height
390
    this.get('/header/:block', async (req, res) => {
117✔
391
      const valid = Validator.fromRequest(req);
14✔
392
      const hash = valid.uintbhash('block');
14✔
393

394
      enforce(hash != null, 'Hash or height required.');
14✔
395

396
      const entry = await this.chain.getEntry(hash);
14✔
397

398
      if (!entry) {
14✔
399
        res.json(404);
1✔
400
        return;
1✔
401
      }
402

403
      res.json(200, entry.toJSON());
13✔
404
    });
405

406
    // Mempool snapshot
407
    this.get('/mempool', async (req, res) => {
117✔
408
      enforce(this.mempool, 'No mempool available.');
4✔
409

410
      const hashes = this.mempool.getSnapshot();
4✔
411
      const result = [];
4✔
412

413
      for (const hash of hashes)
4✔
414
        result.push(hash.toString('hex'));
2✔
415

416
      res.json(200, result);
4✔
417
    });
418

419
    // Mempool Rejection Filter
420
    this.get('/mempool/invalid', async (req, res) => {
117✔
421
      enforce(this.mempool, 'No mempool available.');
2✔
422

423
      const valid = Validator.fromRequest(req);
2✔
424
      const verbose = valid.bool('verbose', false);
2✔
425

426
      const rejects = this.mempool.rejects;
2✔
427
      res.json(200, {
2✔
428
        items: rejects.items,
429
        filter: verbose ? rejects.filter.toString('hex') : undefined,
2✔
430
        size: rejects.size,
431
        entries: rejects.entries,
432
        n: rejects.n,
433
        limit: rejects.limit,
434
        tweak: rejects.tweak
435
      });
436
    });
437

438
    // Mempool Rejection Test
439
    this.get('/mempool/invalid/:hash', async (req, res) => {
117✔
440
      enforce(this.mempool, 'No mempool available.');
1✔
441

442
      const valid = Validator.fromRequest(req);
1✔
443
      const hash = valid.bhash('hash');
1✔
444

445
      enforce(hash, 'Must pass hash.');
1✔
446

447
      const invalid = this.mempool.rejects.test(hash, 'hex');
1✔
448

449
      res.json(200, { invalid });
1✔
450
    });
451

452
    // Broadcast TX
453
    this.post('/broadcast', async (req, res) => {
117✔
454
      const valid = Validator.fromRequest(req);
1✔
455
      const raw = valid.buf('tx');
1✔
456

457
      enforce(raw, 'TX is required.');
1✔
458

459
      const tx = TX.decode(raw);
1✔
460

461
      await this.node.sendTX(tx);
1✔
462

463
      res.json(200, { success: true });
1✔
464
    });
465

466
    // Broadcast Claim
467
    this.post('/claim', async (req, res) => {
117✔
468
      const valid = Validator.fromRequest(req);
×
469
      const raw = valid.buf('claim');
×
470

471
      enforce(raw, 'Claim is required.');
×
472

473
      const claim = Claim.decode(raw);
×
474

475
      await this.node.sendClaim(claim);
×
476

477
      res.json(200, { success: true });
×
478
    });
479

480
    // Estimate fee
481
    this.get('/fee', async (req, res) => {
117✔
482
      const valid = Validator.fromRequest(req);
×
483
      const blocks = valid.u32('blocks');
×
484

485
      if (!this.fees) {
×
486
        res.json(200, { rate: this.network.feeRate });
×
487
        return;
×
488
      }
489

490
      const fee = this.fees.estimateFee(blocks);
×
491

492
      res.json(200, { rate: fee });
×
493
    });
494

495
    // Reset chain
496
    this.post('/reset', async (req, res) => {
117✔
497
      const valid = Validator.fromRequest(req);
×
498
      const height = valid.u32('height');
×
499

500
      enforce(height != null, 'Height is required.');
×
501
      enforce(height <= this.chain.height,
×
502
        'Height cannot be greater than chain tip.');
503

504
      await this.chain.reset(height);
×
505

506
      res.json(200, { success: true });
×
507
    });
508
  }
509

510
  /**
511
   * Handle new websocket.
512
   * @private
513
   * @param {WebSocket} socket
514
   */
515

516
  handleSocket(socket) {
517
    socket.hook('auth', (...args) => {
11✔
518
      if (socket.channel('auth'))
11!
519
        throw new Error('Already authed.');
×
520

521
      if (!this.options.noAuth) {
11✔
522
        const valid = new Validator(args);
9✔
523
        const key = valid.str(0, '');
9✔
524

525
        if (key.length > 255)
9!
526
          throw new Error('Invalid API key.');
×
527

528
        const data = Buffer.from(key, 'ascii');
9✔
529
        const hash = sha256.digest(data);
9✔
530

531
        if (!safeEqual(hash, this.options.apiHash))
9!
532
          throw new Error('Invalid API key.');
×
533
      }
534

535
      socket.join('auth');
11✔
536

537
      this.logger.info('Successful auth from %s.', socket.host);
11✔
538
      this.handleAuth(socket);
11✔
539

540
      return null;
11✔
541
    });
542

543
    socket.fire('version', {
11✔
544
      version: pkg.version,
545
      network: this.network.type
546
    });
547
  }
548

549
  /**
550
   * Handle new auth'd websocket.
551
   * @private
552
   * @param {WebSocket} socket
553
   */
554

555
  handleAuth(socket) {
556
    socket.hook('watch chain', () => {
11✔
557
      socket.join('chain');
10✔
558
      return null;
10✔
559
    });
560

561
    socket.hook('unwatch chain', () => {
11✔
562
      socket.leave('chain');
×
563
      return null;
×
564
    });
565

566
    socket.hook('watch mempool', () => {
11✔
567
      socket.join('mempool');
9✔
568
      return null;
9✔
569
    });
570

571
    socket.hook('unwatch mempool', () => {
11✔
572
      socket.leave('mempool');
×
573
      return null;
×
574
    });
575

576
    socket.hook('set filter', (...args) => {
11✔
577
      const valid = new Validator(args);
×
578
      const data = valid.buf(0);
×
579

580
      if (!data)
×
581
        throw new Error('Invalid parameter.');
×
582

583
      socket.filter = BloomFilter.decode(data);
×
584

585
      return null;
×
586
    });
587

588
    socket.hook('get tip', () => {
11✔
589
      return this.chain.tip.encode();
×
590
    });
591

592
    socket.hook('get entry', async (...args) => {
11✔
593
      const valid = new Validator(args);
×
594
      const block = valid.uintbhash(0);
×
595

596
      if (block == null)
×
597
        throw new Error('Invalid parameter.');
×
598

599
      const entry = await this.chain.getEntry(block);
×
600

601
      if (!entry)
×
602
        return null;
×
603

604
      if (!await this.chain.isMainChain(entry))
×
605
        return null;
×
606

607
      return entry.encode();
×
608
    });
609

610
    socket.hook('get hashes', async (...args) => {
11✔
611
      const valid = new Validator(args);
×
612
      const start = valid.i32(0, -1);
×
613
      const end = valid.i32(1, -1);
×
614

615
      return this.chain.getHashes(start, end);
×
616
    });
617

618
    socket.hook('add filter', (...args) => {
11✔
619
      const valid = new Validator(args);
×
620
      const chunks = valid.array(0);
×
621

622
      if (!chunks)
×
623
        throw new Error('Invalid parameter.');
×
624

625
      if (!socket.filter)
×
626
        throw new Error('No filter set.');
×
627

628
      const items = new Validator(chunks);
×
629

630
      for (let i = 0; i < chunks.length; i++) {
×
631
        const data = items.buf(i);
×
632

633
        if (!data)
×
634
          throw new Error('Bad data chunk.');
×
635

636
        socket.filter.add(data);
×
637

638
        if (this.node.spv)
×
639
          this.pool.watch(data);
×
640
      }
641

642
      return null;
×
643
    });
644

645
    socket.hook('reset filter', () => {
11✔
646
      socket.filter = null;
×
647
      return null;
×
648
    });
649

650
    socket.hook('estimate fee', (...args) => {
11✔
651
      const valid = new Validator(args);
×
652
      const blocks = valid.u32(0);
×
653

654
      if (!this.fees)
×
655
        return this.network.feeRate;
×
656

657
      return this.fees.estimateFee(blocks);
×
658
    });
659

660
    socket.hook('send', (...args) => {
11✔
661
      const valid = new Validator(args);
×
662
      const data = valid.buf(0);
×
663

664
      if (!data)
×
665
        throw new Error('Invalid parameter.');
×
666

667
      const tx = TX.decode(data);
×
668

669
      this.node.relay(tx);
×
670

671
      return null;
×
672
    });
673

674
    socket.hook('send claim', (...args) => {
11✔
675
      const valid = new Validator(args);
×
676
      const data = valid.buf(0);
×
677

678
      if (!data)
×
679
        throw new Error('Invalid parameter.');
×
680

681
      const claim = Claim.decode(data);
×
682

683
      this.node.relayClaim(claim);
×
684

685
      return null;
×
686
    });
687

688
    socket.hook('get name', async (...args) => {
11✔
689
      const valid = new Validator(args);
×
690
      const nameHash = valid.bhash(0);
×
691

692
      if (!nameHash)
×
693
        throw new Error('Invalid parameter.');
×
694

695
      const ns = await this.node.getNameStatus(nameHash);
×
696

697
      return ns.getJSON(this.chain.height + 1, this.network);
×
698
    });
699

700
    socket.hook('rescan', (...args) => {
11✔
701
      const valid = new Validator(args);
×
702
      const start = valid.uintbhash(0);
×
703

704
      if (start == null)
×
705
        throw new Error('Invalid parameter.');
×
706

707
      return this.scan(socket, start);
×
708
    });
709
  }
710

711
  /**
712
   * Bind to chain events.
713
   * @private
714
   */
715

716
  initSockets() {
717
    const pool = this.mempool || this.pool;
117✔
718

719
    this.chain.on('connect', (entry, block, view) => {
117✔
720
      const sockets = this.channel('chain');
7,994✔
721

722
      if (!sockets)
7,994✔
723
        return;
7,008✔
724

725
      const raw = entry.encode();
986✔
726

727
      this.to('chain', 'chain connect', raw);
986✔
728

729
      for (const socket of sockets) {
986✔
730
        const txs = this.filterBlock(socket, block);
986✔
731
        socket.fire('block connect', raw, txs);
986✔
732
      }
733
    });
734

735
    this.chain.on('disconnect', (entry, block, view) => {
117✔
736
      const sockets = this.channel('chain');
36✔
737

738
      if (!sockets)
36✔
739
        return;
34✔
740

741
      const raw = entry.encode();
2✔
742

743
      this.to('chain', 'chain disconnect', raw);
2✔
744
      this.to('chain', 'block disconnect', raw);
2✔
745
    });
746

747
    this.chain.on('reset', (tip) => {
117✔
748
      const sockets = this.channel('chain');
6✔
749

750
      if (!sockets)
6!
751
        return;
6✔
752

753
      this.to('chain', 'chain reset', tip.encode());
×
754
    });
755

756
    pool.on('tx', (tx) => {
117✔
757
      const sockets = this.channel('mempool');
1,017✔
758

759
      if (!sockets)
1,017✔
760
        return;
825✔
761

762
      const raw = tx.encode();
192✔
763

764
      for (const socket of sockets) {
192✔
765
        if (!this.filterTX(socket, tx))
192!
766
          continue;
192✔
767

768
        socket.fire('tx', raw);
×
769
      }
770
    });
771

772
    this.chain.on('tree commit', (root, entry, block) => {
117✔
773
      const sockets = this.channel('chain');
1,576✔
774

775
      if (!sockets)
1,576✔
776
        return;
1,381✔
777

778
      this.to('chain', 'tree commit', root, entry, block);
195✔
779
    });
780
  }
781

782
  /**
783
   * Filter block by socket.
784
   * @private
785
   * @param {WebSocket} socket
786
   * @param {Block} block
787
   * @returns {TX[]}
788
   */
789

790
  filterBlock(socket, block) {
791
    if (!socket.filter)
986!
792
      return [];
986✔
793

794
    const txs = [];
×
795

796
    for (const tx of block.txs) {
×
797
      if (this.filterTX(socket, tx))
×
798
        txs.push(tx.encode());
×
799
    }
800

801
    return txs;
×
802
  }
803

804
  /**
805
   * Filter transaction by socket.
806
   * @private
807
   * @param {WebSocket} socket
808
   * @param {TX} tx
809
   * @returns {Boolean}
810
   */
811

812
  filterTX(socket, tx) {
813
    if (!socket.filter)
192!
814
      return false;
192✔
815

816
    return tx.test(socket.filter);
×
817
  }
818

819
  /**
820
   * Scan using a socket's filter.
821
   * @private
822
   * @param {WebSocket} socket
823
   * @param {Hash} start
824
   * @returns {Promise}
825
   */
826

827
  async scan(socket, start) {
828
    await this.node.scan(start, socket.filter, (entry, txs) => {
×
829
      const block = entry.encode();
×
830
      const raw = [];
×
831

832
      for (const tx of txs)
×
833
        raw.push(tx.encode());
×
834

835
      return socket.call('block rescan', block, raw);
×
836
    });
837
    return null;
×
838
  }
839
}
840

841
class HTTPOptions {
842
  /**
843
   * HTTPOptions
844
   * @alias module:http.HTTPOptions
845
   * @constructor
846
   * @param {Object} options
847
   */
848

849
  constructor(options) {
850
    this.network = Network.primary;
117✔
851
    this.logger = null;
117✔
852
    this.node = null;
117✔
853
    this.apiKey = base58.encode(random.randomBytes(20));
117✔
854
    this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii'));
117✔
855
    this.noAuth = false;
117✔
856
    this.cors = false;
117✔
857

858
    this.prefix = null;
117✔
859
    this.host = '127.0.0.1';
117✔
860
    this.port = 8080;
117✔
861
    this.ssl = false;
117✔
862
    this.keyFile = null;
117✔
863
    this.certFile = null;
117✔
864

865
    this.fromOptions(options);
117✔
866
  }
867

868
  /**
869
   * Inject properties from object.
870
   * @private
871
   * @param {Object} options
872
   * @returns {HTTPOptions}
873
   */
874

875
  fromOptions(options) {
876
    assert(options);
117✔
877
    assert(options.node && typeof options.node === 'object',
117✔
878
      'HTTP Server requires a Node.');
879

880
    this.node = options.node;
117✔
881
    this.network = options.node.network;
117✔
882
    this.logger = options.node.logger;
117✔
883

884
    this.port = this.network.rpcPort;
117✔
885

886
    if (options.logger != null) {
117!
887
      assert(typeof options.logger === 'object');
117✔
888
      this.logger = options.logger;
117✔
889
    }
890

891
    if (options.apiKey != null) {
117✔
892
      assert(typeof options.apiKey === 'string',
15✔
893
        'API key must be a string.');
894
      assert(options.apiKey.length <= 255,
15✔
895
        'API key must be under 256 bytes.');
896
      this.apiKey = options.apiKey;
15✔
897
      this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii'));
15✔
898
    }
899

900
    if (options.noAuth != null) {
117!
901
      assert(typeof options.noAuth === 'boolean');
×
902
      this.noAuth = options.noAuth;
×
903
    }
904

905
    if (options.cors != null) {
117!
906
      assert(typeof options.cors === 'boolean');
×
907
      this.cors = options.cors;
×
908
    }
909

910
    if (options.prefix != null) {
117!
911
      assert(typeof options.prefix === 'string');
117✔
912
      this.prefix = options.prefix;
117✔
913
      this.keyFile = path.join(this.prefix, 'key.pem');
117✔
914
      this.certFile = path.join(this.prefix, 'cert.pem');
117✔
915
    }
916

917
    if (options.host != null) {
117!
918
      assert(typeof options.host === 'string');
×
919
      this.host = options.host;
×
920
    }
921

922
    if (options.port != null) {
117✔
923
      assert((options.port & 0xffff) === options.port,
24✔
924
        'Port must be a number.');
925
      this.port = options.port;
24✔
926
    }
927

928
    if (options.ssl != null) {
117!
929
      assert(typeof options.ssl === 'boolean');
×
930
      this.ssl = options.ssl;
×
931
    }
932

933
    if (options.keyFile != null) {
117!
934
      assert(typeof options.keyFile === 'string');
×
935
      this.keyFile = options.keyFile;
×
936
    }
937

938
    if (options.certFile != null) {
117!
939
      assert(typeof options.certFile === 'string');
×
940
      this.certFile = options.certFile;
×
941
    }
942

943
    // Allow no-auth implicitly
944
    // if we're listening locally.
945
    if (!options.apiKey) {
117✔
946
      if (   this.host === '127.0.0.1'
102!
947
          || this.host === '::1'
948
          || this.host === 'localhost')
949
        this.noAuth = true;
102✔
950
    }
951

952
    return this;
117✔
953
  }
954

955
  /**
956
   * Instantiate http options from object.
957
   * @param {Object} options
958
   * @returns {HTTPOptions}
959
   */
960

961
  static fromOptions(options) {
962
    return new HTTPOptions().fromOptions(options);
×
963
  }
964
}
965

966
/*
967
 * Helpers
968
 */
969

970
function enforce(value, msg) {
971
  if (!value) {
30!
972
    const err = new Error(msg);
×
973
    err.statusCode = 400;
×
974
    throw err;
×
975
  }
976
}
977

978
/*
979
 * Expose
980
 */
981

982
module.exports = HTTP;
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