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

handshake-org / hsd / 3953908740

pending completion
3953908740

push

github

Nodari Chkuaselidze
pkg: Release v5.0.0

7392 of 12654 branches covered (58.42%)

Branch coverage included in aggregate %.

23565 of 32581 relevant lines covered (72.33%)

31610.11 hits per line

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

56.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('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));
85✔
39

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

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

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

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

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

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

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

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

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

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

86
    if (!this.options.noAuth) {
85✔
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({
85✔
95
      type: 'json'
96
    }));
97

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

101
    this.error((err, req, res) => {
85✔
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) => {
85✔
113
      const totalTX = this.mempool ? this.mempool.map.size : 0;
16!
114
      const size = this.mempool ? this.mempool.getSize() : 0;
16!
115
      const claims = this.mempool ? this.mempool.claims.size : 0;
16!
116
      const airdrops = this.mempool ? this.mempool.airdrops.size : 0;
16!
117
      const orphans = this.mempool ? this.mempool.orphans.size : 0;
16!
118
      const brontide = this.pool.hosts.brontide;
16✔
119

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

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

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

135
      res.json(200, {
16✔
136
        version: pkg.version,
137
        network: this.network.type,
138
        chain: {
139
          height: this.chain.height,
140
          tip: this.chain.tip.hash.toString('hex'),
141
          treeRoot: this.chain.tip.treeRoot.toString('hex'),
142
          progress: this.chain.getProgress(),
143
          state: {
144
            tx: this.chain.db.state.tx,
145
            coin: this.chain.db.state.coin,
146
            value: this.chain.db.state.value,
147
            burned: this.chain.db.state.burned
148
          }
149
        },
150
        pool: {
151
          host: this.pool.options.host,
152
          port: this.pool.options.port,
153
          brontidePort: this.pool.options.brontidePort,
154
          identitykey: brontide.getKey('base32'),
155
          agent: this.pool.options.agent,
156
          services: this.pool.options.services.toString(2),
157
          outbound: this.pool.peers.outbound,
158
          inbound: this.pool.peers.inbound,
159
          public: pub
160
        },
161
        mempool: {
162
          tx: totalTX,
163
          size: size,
164
          claims: claims,
165
          airdrops: airdrops,
166
          orphans: orphans
167
        },
168
        time: {
169
          uptime: this.node.uptime(),
170
          system: util.now(),
171
          adjusted: this.network.now(),
172
          offset: this.network.time.offset
173
        },
174
        memory: this.logger.memoryUsage()
175
      });
176
    });
177

178
    // UTXO by address
179
    this.get('/coin/address/:address', async (req, res) => {
85✔
180
      const valid = Validator.fromRequest(req);
×
181
      const address = valid.str('address');
×
182

183
      enforce(address, 'Address is required.');
×
184
      enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
×
185

186
      const addr = Address.fromString(address, this.network);
×
187
      const coins = await this.node.getCoinsByAddress(addr);
×
188
      const result = [];
×
189

190
      for (const coin of coins)
×
191
        result.push(coin.getJSON(this.network));
×
192

193
      res.json(200, result);
×
194
    });
195

196
    // UTXO by id
197
    this.get('/coin/:hash/:index', async (req, res) => {
85✔
198
      const valid = Validator.fromRequest(req);
1✔
199
      const hash = valid.bhash('hash');
1✔
200
      const index = valid.u32('index');
1✔
201

202
      enforce(hash, 'Hash is required.');
1✔
203
      enforce(index != null, 'Index is required.');
1✔
204
      enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
1✔
205

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

208
      if (!coin) {
1!
209
        res.json(404);
×
210
        return;
×
211
      }
212

213
      res.json(200, coin.getJSON(this.network));
1✔
214
    });
215

216
    // Bulk read UTXOs
217
    // TODO(boymanjor): Deprecate this endpoint
218
    // once the equivalent functionality is included
219
    // in the wallet API.
220
    this.post('/coin/address', async (req, res) => {
85✔
221
      const valid = Validator.fromRequest(req);
1✔
222
      const addresses = valid.array('addresses');
1✔
223

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

227
      this.logger.warning('%s %s %s',
1✔
228
        'Warning: endpoint being considered for deprecation.',
229
        'Known to cause CPU exhaustion if too many addresses',
230
        'are queried or too many results are found.');
231

232
      const addrs = [];
1✔
233
      for (const address of addresses) {
1✔
234
        addrs.push(Address.fromString(address, this.network));
1✔
235
      }
236

237
      const coins = await this.node.getCoinsByAddress(addrs);
1✔
238
      const result = [];
1✔
239

240
      for (const coin of coins)
1✔
241
        result.push(coin.getJSON(this.network));
5✔
242

243
      res.json(200, result);
1✔
244
    });
245

246
    // TX by hash
247
    this.get('/tx/:hash', async (req, res) => {
85✔
248
      const valid = Validator.fromRequest(req);
×
249
      const hash = valid.bhash('hash');
×
250

251
      enforce(hash, 'Hash is required.');
×
252
      enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
×
253

254
      const meta = await this.node.getMeta(hash);
×
255

256
      if (!meta) {
×
257
        res.json(404);
×
258
        return;
×
259
      }
260

261
      const view = await this.node.getMetaView(meta);
×
262

263
      res.json(200, meta.getJSON(this.network, view, this.chain.height));
×
264
    });
265

266
    // TX by address
267
    this.get('/tx/address/:address', async (req, res) => {
85✔
268
      const valid = Validator.fromRequest(req);
×
269
      const address = valid.str('address');
×
270

271
      enforce(address, 'Address is required.');
×
272
      enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
×
273

274
      const addr = Address.fromString(address, this.network);
×
275
      const metas = await this.node.getMetaByAddress(addr);
×
276
      const result = [];
×
277

278
      for (const meta of metas) {
×
279
        const view = await this.node.getMetaView(meta);
×
280
        result.push(meta.getJSON(this.network, view, this.chain.height));
×
281
      }
282

283
      res.json(200, result);
×
284
    });
285

286
    // Bulk read TXs
287
    // TODO(boymanjor): Deprecate this endpoint
288
    // once the equivalent functionality is included
289
    // in the wallet API.
290
    this.post('/tx/address', async (req, res) => {
85✔
291
      const valid = Validator.fromRequest(req);
×
292
      const addresses = valid.array('addresses');
×
293

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

297
      this.logger.warning('%s %s %s',
×
298
        'Warning: endpoint being considered for deprecation.',
299
        'Known to cause CPU exhaustion if too many addresses',
300
        'are queried or too many results are found.');
301

302
      const addrs = [];
×
303
      for (const address of addresses) {
×
304
        addrs.push(Address.fromString(address, this.network));
×
305
      }
306

307
      const metas = await this.node.getMetaByAddress(addrs);
×
308
      const result = [];
×
309

310
      for (const meta of metas) {
×
311
        const view = await this.node.getMetaView(meta);
×
312
        result.push(meta.getJSON(this.network, view, this.chain.height));
×
313
      }
314

315
      res.json(200, result);
×
316
    });
317

318
    // Block by hash/height
319
    this.get('/block/:block', async (req, res) => {
85✔
320
      const valid = Validator.fromRequest(req);
1✔
321
      const hash = valid.uintbhash('block');
1✔
322

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

326
      const block = await this.chain.getBlock(hash);
1✔
327

328
      if (!block) {
1!
329
        res.json(404);
×
330
        return;
×
331
      }
332

333
      const view = await this.chain.getBlockView(block);
1✔
334

335
      if (!view) {
1!
336
        res.json(404);
×
337
        return;
×
338
      }
339

340
      const height = await this.chain.getHeight(hash);
1✔
341
      const depth = this.chain.height - height + 1;
1✔
342

343
      res.json(200, block.getJSON(this.network, view, height, depth));
1✔
344
    });
345

346
    // Block Header by hash/height
347
    this.get('/header/:block', async (req, res) => {
85✔
348
      const valid = Validator.fromRequest(req);
14✔
349
      const hash = valid.uintbhash('block');
14✔
350

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

353
      const entry = await this.chain.getEntry(hash);
14✔
354

355
      if (!entry) {
14✔
356
        res.json(404);
1✔
357
        return;
1✔
358
      }
359

360
      res.json(200, entry.toJSON());
13✔
361
    });
362

363
    // Mempool snapshot
364
    this.get('/mempool', async (req, res) => {
85✔
365
      enforce(this.mempool, 'No mempool available.');
4✔
366

367
      const hashes = this.mempool.getSnapshot();
4✔
368
      const result = [];
4✔
369

370
      for (const hash of hashes)
4✔
371
        result.push(hash.toString('hex'));
2✔
372

373
      res.json(200, result);
4✔
374
    });
375

376
    // Mempool Rejection Filter
377
    this.get('/mempool/invalid', async (req, res) => {
85✔
378
      enforce(this.mempool, 'No mempool available.');
2✔
379

380
      const valid = Validator.fromRequest(req);
2✔
381
      const verbose = valid.bool('verbose', false);
2✔
382

383
      const rejects = this.mempool.rejects;
2✔
384
      res.json(200, {
2✔
385
        items: rejects.items,
386
        filter: verbose ? rejects.filter.toString('hex') : undefined,
2✔
387
        size: rejects.size,
388
        entries: rejects.entries,
389
        n: rejects.n,
390
        limit: rejects.limit,
391
        tweak: rejects.tweak
392
      });
393
    });
394

395
    // Mempool Rejection Test
396
    this.get('/mempool/invalid/:hash', async (req, res) => {
85✔
397
      enforce(this.mempool, 'No mempool available.');
1✔
398

399
      const valid = Validator.fromRequest(req);
1✔
400
      const hash = valid.bhash('hash');
1✔
401

402
      assert(hash, 'Must pass hash.');
1✔
403

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

406
      res.json(200, { invalid });
1✔
407
    });
408

409
    // Broadcast TX
410
    this.post('/broadcast', async (req, res) => {
85✔
411
      const valid = Validator.fromRequest(req);
1✔
412
      const raw = valid.buf('tx');
1✔
413

414
      enforce(raw, 'TX is required.');
1✔
415

416
      const tx = TX.decode(raw);
1✔
417

418
      await this.node.sendTX(tx);
1✔
419

420
      res.json(200, { success: true });
1✔
421
    });
422

423
    // Broadcast Claim
424
    this.post('/claim', async (req, res) => {
85✔
425
      const valid = Validator.fromRequest(req);
×
426
      const raw = valid.buf('claim');
×
427

428
      enforce(raw, 'Claim is required.');
×
429

430
      const claim = Claim.decode(raw);
×
431

432
      await this.node.sendClaim(claim);
×
433

434
      res.json(200, { success: true });
×
435
    });
436

437
    // Estimate fee
438
    this.get('/fee', async (req, res) => {
85✔
439
      const valid = Validator.fromRequest(req);
×
440
      const blocks = valid.u32('blocks');
×
441

442
      if (!this.fees) {
×
443
        res.json(200, { rate: this.network.feeRate });
×
444
        return;
×
445
      }
446

447
      const fee = this.fees.estimateFee(blocks);
×
448

449
      res.json(200, { rate: fee });
×
450
    });
451

452
    // Reset chain
453
    this.post('/reset', async (req, res) => {
85✔
454
      const valid = Validator.fromRequest(req);
×
455
      const height = valid.u32('height');
×
456

457
      enforce(height != null, 'Height is required.');
×
458
      enforce(height <= this.chain.height,
×
459
        'Height cannot be greater than chain tip.');
460

461
      await this.chain.reset(height);
×
462

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

467
  /**
468
   * Handle new websocket.
469
   * @private
470
   * @param {WebSocket} socket
471
   */
472

473
  handleSocket(socket) {
474
    socket.hook('auth', (...args) => {
11✔
475
      if (socket.channel('auth'))
11!
476
        throw new Error('Already authed.');
×
477

478
      if (!this.options.noAuth) {
11✔
479
        const valid = new Validator(args);
9✔
480
        const key = valid.str(0, '');
9✔
481

482
        if (key.length > 255)
9!
483
          throw new Error('Invalid API key.');
×
484

485
        const data = Buffer.from(key, 'ascii');
9✔
486
        const hash = sha256.digest(data);
9✔
487

488
        if (!safeEqual(hash, this.options.apiHash))
9!
489
          throw new Error('Invalid API key.');
×
490
      }
491

492
      socket.join('auth');
11✔
493

494
      this.logger.info('Successful auth from %s.', socket.host);
11✔
495
      this.handleAuth(socket);
11✔
496

497
      return null;
11✔
498
    });
499

500
    socket.fire('version', {
11✔
501
      version: pkg.version,
502
      network: this.network.type
503
    });
504
  }
505

506
  /**
507
   * Handle new auth'd websocket.
508
   * @private
509
   * @param {WebSocket} socket
510
   */
511

512
  handleAuth(socket) {
513
    socket.hook('watch chain', () => {
11✔
514
      socket.join('chain');
10✔
515
      return null;
10✔
516
    });
517

518
    socket.hook('unwatch chain', () => {
11✔
519
      socket.leave('chain');
×
520
      return null;
×
521
    });
522

523
    socket.hook('watch mempool', () => {
11✔
524
      socket.join('mempool');
9✔
525
      return null;
9✔
526
    });
527

528
    socket.hook('unwatch mempool', () => {
11✔
529
      socket.leave('mempool');
×
530
      return null;
×
531
    });
532

533
    socket.hook('set filter', (...args) => {
11✔
534
      const valid = new Validator(args);
×
535
      const data = valid.buf(0);
×
536

537
      if (!data)
×
538
        throw new Error('Invalid parameter.');
×
539

540
      socket.filter = BloomFilter.decode(data);
×
541

542
      return null;
×
543
    });
544

545
    socket.hook('get tip', () => {
11✔
546
      return this.chain.tip.encode();
×
547
    });
548

549
    socket.hook('get entry', async (...args) => {
11✔
550
      const valid = new Validator(args);
×
551
      const block = valid.uintbhash(0);
×
552

553
      if (block == null)
×
554
        throw new Error('Invalid parameter.');
×
555

556
      const entry = await this.chain.getEntry(block);
×
557

558
      if (!entry)
×
559
        return null;
×
560

561
      if (!await this.chain.isMainChain(entry))
×
562
        return null;
×
563

564
      return entry.encode();
×
565
    });
566

567
    socket.hook('get hashes', async (...args) => {
11✔
568
      const valid = new Validator(args);
×
569
      const start = valid.i32(0, -1);
×
570
      const end = valid.i32(1, -1);
×
571

572
      return this.chain.getHashes(start, end);
×
573
    });
574

575
    socket.hook('add filter', (...args) => {
11✔
576
      const valid = new Validator(args);
×
577
      const chunks = valid.array(0);
×
578

579
      if (!chunks)
×
580
        throw new Error('Invalid parameter.');
×
581

582
      if (!socket.filter)
×
583
        throw new Error('No filter set.');
×
584

585
      const items = new Validator(chunks);
×
586

587
      for (let i = 0; i < chunks.length; i++) {
×
588
        const data = items.buf(i);
×
589

590
        if (!data)
×
591
          throw new Error('Bad data chunk.');
×
592

593
        socket.filter.add(data);
×
594

595
        if (this.node.spv)
×
596
          this.pool.watch(data);
×
597
      }
598

599
      return null;
×
600
    });
601

602
    socket.hook('reset filter', () => {
11✔
603
      socket.filter = null;
×
604
      return null;
×
605
    });
606

607
    socket.hook('estimate fee', (...args) => {
11✔
608
      const valid = new Validator(args);
×
609
      const blocks = valid.u32(0);
×
610

611
      if (!this.fees)
×
612
        return this.network.feeRate;
×
613

614
      return this.fees.estimateFee(blocks);
×
615
    });
616

617
    socket.hook('send', (...args) => {
11✔
618
      const valid = new Validator(args);
×
619
      const data = valid.buf(0);
×
620

621
      if (!data)
×
622
        throw new Error('Invalid parameter.');
×
623

624
      const tx = TX.decode(data);
×
625

626
      this.node.relay(tx);
×
627

628
      return null;
×
629
    });
630

631
    socket.hook('send claim', (...args) => {
11✔
632
      const valid = new Validator(args);
×
633
      const data = valid.buf(0);
×
634

635
      if (!data)
×
636
        throw new Error('Invalid parameter.');
×
637

638
      const claim = Claim.decode(data);
×
639

640
      this.node.relayClaim(claim);
×
641

642
      return null;
×
643
    });
644

645
    socket.hook('get name', async (...args) => {
11✔
646
      const valid = new Validator(args);
×
647
      const nameHash = valid.bhash(0);
×
648

649
      if (!nameHash)
×
650
        throw new Error('Invalid parameter.');
×
651

652
      const ns = await this.node.getNameStatus(nameHash);
×
653

654
      return ns.getJSON(this.chain.height + 1, this.network);
×
655
    });
656

657
    socket.hook('rescan', (...args) => {
11✔
658
      const valid = new Validator(args);
×
659
      const start = valid.uintbhash(0);
×
660

661
      if (start == null)
×
662
        throw new Error('Invalid parameter.');
×
663

664
      return this.scan(socket, start);
×
665
    });
666
  }
667

668
  /**
669
   * Bind to chain events.
670
   * @private
671
   */
672

673
  initSockets() {
674
    const pool = this.mempool || this.pool;
85✔
675

676
    this.chain.on('connect', (entry, block, view) => {
85✔
677
      const sockets = this.channel('chain');
3,745✔
678

679
      if (!sockets)
3,745✔
680
        return;
2,832✔
681

682
      const raw = entry.encode();
913✔
683

684
      this.to('chain', 'chain connect', raw);
913✔
685

686
      for (const socket of sockets) {
913✔
687
        const txs = this.filterBlock(socket, block);
913✔
688
        socket.fire('block connect', raw, txs);
913✔
689
      }
690
    });
691

692
    this.chain.on('disconnect', (entry, block, view) => {
85✔
693
      const sockets = this.channel('chain');
35✔
694

695
      if (!sockets)
35✔
696
        return;
33✔
697

698
      const raw = entry.encode();
2✔
699

700
      this.to('chain', 'chain disconnect', raw);
2✔
701
      this.to('chain', 'block disconnect', raw);
2✔
702
    });
703

704
    this.chain.on('reset', (tip) => {
85✔
705
      const sockets = this.channel('chain');
6✔
706

707
      if (!sockets)
6!
708
        return;
6✔
709

710
      this.to('chain', 'chain reset', tip.encode());
×
711
    });
712

713
    pool.on('tx', (tx) => {
85✔
714
      const sockets = this.channel('mempool');
329✔
715

716
      if (!sockets)
329✔
717
        return;
230✔
718

719
      const raw = tx.encode();
99✔
720

721
      for (const socket of sockets) {
99✔
722
        if (!this.filterTX(socket, tx))
99!
723
          continue;
99✔
724

725
        socket.fire('tx', raw);
×
726
      }
727
    });
728

729
    this.chain.on('tree commit', (root, entry, block) => {
85✔
730
      const sockets = this.channel('chain');
735✔
731

732
      if (!sockets)
735✔
733
        return;
555✔
734

735
      this.to('chain', 'tree commit', root, entry, block);
180✔
736
    });
737
  }
738

739
  /**
740
   * Filter block by socket.
741
   * @private
742
   * @param {WebSocket} socket
743
   * @param {Block} block
744
   * @returns {TX[]}
745
   */
746

747
  filterBlock(socket, block) {
748
    if (!socket.filter)
913!
749
      return [];
913✔
750

751
    const txs = [];
×
752

753
    for (const tx of block.txs) {
×
754
      if (this.filterTX(socket, tx))
×
755
        txs.push(tx.encode());
×
756
    }
757

758
    return txs;
×
759
  }
760

761
  /**
762
   * Filter transaction by socket.
763
   * @private
764
   * @param {WebSocket} socket
765
   * @param {TX} tx
766
   * @returns {Boolean}
767
   */
768

769
  filterTX(socket, tx) {
770
    if (!socket.filter)
99!
771
      return false;
99✔
772

773
    return tx.test(socket.filter);
×
774
  }
775

776
  /**
777
   * Scan using a socket's filter.
778
   * @private
779
   * @param {WebSocket} socket
780
   * @param {Hash} start
781
   * @returns {Promise}
782
   */
783

784
  async scan(socket, start) {
785
    await this.node.scan(start, socket.filter, (entry, txs) => {
×
786
      const block = entry.encode();
×
787
      const raw = [];
×
788

789
      for (const tx of txs)
×
790
        raw.push(tx.encode());
×
791

792
      return socket.call('block rescan', block, raw);
×
793
    });
794
    return null;
×
795
  }
796
}
797

798
class HTTPOptions {
799
  /**
800
   * HTTPOptions
801
   * @alias module:http.HTTPOptions
802
   * @constructor
803
   * @param {Object} options
804
   */
805

806
  constructor(options) {
807
    this.network = Network.primary;
85✔
808
    this.logger = null;
85✔
809
    this.node = null;
85✔
810
    this.apiKey = base58.encode(random.randomBytes(20));
85✔
811
    this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii'));
85✔
812
    this.noAuth = false;
85✔
813
    this.cors = false;
85✔
814

815
    this.prefix = null;
85✔
816
    this.host = '127.0.0.1';
85✔
817
    this.port = 8080;
85✔
818
    this.ssl = false;
85✔
819
    this.keyFile = null;
85✔
820
    this.certFile = null;
85✔
821

822
    this.fromOptions(options);
85✔
823
  }
824

825
  /**
826
   * Inject properties from object.
827
   * @private
828
   * @param {Object} options
829
   * @returns {HTTPOptions}
830
   */
831

832
  fromOptions(options) {
833
    assert(options);
85✔
834
    assert(options.node && typeof options.node === 'object',
85✔
835
      'HTTP Server requires a Node.');
836

837
    this.node = options.node;
85✔
838
    this.network = options.node.network;
85✔
839
    this.logger = options.node.logger;
85✔
840

841
    this.port = this.network.rpcPort;
85✔
842

843
    if (options.logger != null) {
85!
844
      assert(typeof options.logger === 'object');
85✔
845
      this.logger = options.logger;
85✔
846
    }
847

848
    if (options.apiKey != null) {
85✔
849
      assert(typeof options.apiKey === 'string',
15✔
850
        'API key must be a string.');
851
      assert(options.apiKey.length <= 255,
15✔
852
        'API key must be under 256 bytes.');
853
      this.apiKey = options.apiKey;
15✔
854
      this.apiHash = sha256.digest(Buffer.from(this.apiKey, 'ascii'));
15✔
855
    }
856

857
    if (options.noAuth != null) {
85!
858
      assert(typeof options.noAuth === 'boolean');
×
859
      this.noAuth = options.noAuth;
×
860
    }
861

862
    if (options.cors != null) {
85!
863
      assert(typeof options.cors === 'boolean');
×
864
      this.cors = options.cors;
×
865
    }
866

867
    if (options.prefix != null) {
85!
868
      assert(typeof options.prefix === 'string');
85✔
869
      this.prefix = options.prefix;
85✔
870
      this.keyFile = path.join(this.prefix, 'key.pem');
85✔
871
      this.certFile = path.join(this.prefix, 'cert.pem');
85✔
872
    }
873

874
    if (options.host != null) {
85!
875
      assert(typeof options.host === 'string');
×
876
      this.host = options.host;
×
877
    }
878

879
    if (options.port != null) {
85✔
880
      assert((options.port & 0xffff) === options.port,
23✔
881
        'Port must be a number.');
882
      this.port = options.port;
23✔
883
    }
884

885
    if (options.ssl != null) {
85!
886
      assert(typeof options.ssl === 'boolean');
×
887
      this.ssl = options.ssl;
×
888
    }
889

890
    if (options.keyFile != null) {
85!
891
      assert(typeof options.keyFile === 'string');
×
892
      this.keyFile = options.keyFile;
×
893
    }
894

895
    if (options.certFile != null) {
85!
896
      assert(typeof options.certFile === 'string');
×
897
      this.certFile = options.certFile;
×
898
    }
899

900
    // Allow no-auth implicitly
901
    // if we're listening locally.
902
    if (!options.apiKey) {
85✔
903
      if (   this.host === '127.0.0.1'
70!
904
          || this.host === '::1'
905
          || this.host === 'localhost')
906
        this.noAuth = true;
70✔
907
    }
908

909
    return this;
85✔
910
  }
911

912
  /**
913
   * Instantiate http options from object.
914
   * @param {Object} options
915
   * @returns {HTTPOptions}
916
   */
917

918
  static fromOptions(options) {
919
    return new HTTPOptions().fromOptions(options);
×
920
  }
921
}
922

923
/*
924
 * Helpers
925
 */
926

927
function enforce(value, msg) {
928
  if (!value) {
29!
929
    const err = new Error(msg);
×
930
    err.statusCode = 400;
×
931
    throw err;
×
932
  }
933
}
934

935
/*
936
 * Expose
937
 */
938

939
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