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

handshake-org / hsd / 19786082508

29 Nov 2025 03:55PM UTC coverage: 71.608% (-0.03%) from 71.638%
19786082508

Pull #950

github

web-flow
Merge e4f7fda70 into 698e252eb
Pull Request #950: Nb ev loop fixes

8310 of 13473 branches covered (61.68%)

Branch coverage included in aggregate %.

260 of 292 new or added lines in 11 files covered. (89.04%)

39 existing lines in 7 files now uncovered.

26344 of 34921 relevant lines covered (75.44%)

35015.76 hits per line

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

35.37
/lib/node/rpc.js
1
/*!
2
 * rpc.js - bitcoind-compatible json rpc 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 bweb = require('bweb');
1✔
11
const {Lock} = require('bmutex');
1✔
12
const IP = require('binet');
1✔
13
const Validator = require('bval');
1✔
14
const {BufferMap, BufferSet} = require('buffer-map');
1✔
15
const blake2b = require('bcrypto/lib/blake2b');
1✔
16
const {safeEqual} = require('bcrypto/lib/safe');
1✔
17
const secp256k1 = require('bcrypto/lib/secp256k1');
1✔
18
const util = require('../utils/util');
1✔
19
const common = require('../blockchain/common');
1✔
20
const Amount = require('../ui/amount');
1✔
21
const NetAddress = require('../net/netaddress');
1✔
22
const Script = require('../script/script');
1✔
23
const Address = require('../primitives/address');
1✔
24
const Block = require('../primitives/block');
1✔
25
const Input = require('../primitives/input');
1✔
26
const KeyRing = require('../primitives/keyring');
1✔
27
const MerkleBlock = require('../primitives/merkleblock');
1✔
28
const Headers = require('../primitives/headers');
1✔
29
const MTX = require('../primitives/mtx');
1✔
30
const Network = require('../protocol/network');
1✔
31
const Outpoint = require('../primitives/outpoint');
1✔
32
const Output = require('../primitives/output');
1✔
33
const TX = require('../primitives/tx');
1✔
34
const Claim = require('../primitives/claim');
1✔
35
const consensus = require('../protocol/consensus');
1✔
36
const pkg = require('../pkg');
1✔
37
const rules = require('../covenants/rules');
1✔
38
const {Resource} = require('../dns/resource');
1✔
39
const NameState = require('../covenants/namestate');
1✔
40
const {ownership} = require('../covenants/ownership');
1✔
41
const AirdropProof = require('../primitives/airdropproof');
1✔
42
const {EXP} = consensus;
1✔
43
const RPCBase = bweb.RPC;
1✔
44
const RPCError = bweb.RPCError;
1✔
45

46
/*
47
 * Constants
48
 */
49

50
const errs = {
1✔
51
  // Standard JSON-RPC 2.0 errors
52
  INVALID_REQUEST: bweb.errors.INVALID_REQUEST,
53
  METHOD_NOT_FOUND: bweb.errors.METHOD_NOT_FOUND,
54
  INVALID_PARAMS: bweb.errors.INVALID_PARAMS,
55
  INTERNAL_ERROR: bweb.errors.INTERNAL_ERROR,
56
  PARSE_ERROR: bweb.errors.PARSE_ERROR,
57

58
  // General application defined errors
59
  MISC_ERROR: -1,
60
  FORBIDDEN_BY_SAFE_MODE: -2,
61
  TYPE_ERROR: -3,
62
  INVALID_ADDRESS_OR_KEY: -5,
63
  OUT_OF_MEMORY: -7,
64
  INVALID_PARAMETER: -8,
65
  DATABASE_ERROR: -20,
66
  DESERIALIZATION_ERROR: -22,
67
  VERIFY_ERROR: -25,
68
  VERIFY_REJECTED: -26,
69
  VERIFY_ALREADY_IN_CHAIN: -27,
70
  IN_WARMUP: -28,
71

72
  // P2P client errors
73
  CLIENT_NOT_CONNECTED: -9,
74
  CLIENT_IN_INITIAL_DOWNLOAD: -10,
75
  CLIENT_NODE_ALREADY_ADDED: -23,
76
  CLIENT_NODE_NOT_ADDED: -24,
77
  CLIENT_NODE_NOT_CONNECTED: -29,
78
  CLIENT_INVALID_IP_OR_SUBNET: -30,
79
  CLIENT_P2P_DISABLED: -31
80
};
81

82
const MAGIC_STRING = `${pkg.currency} signed message:\n`;
1✔
83

84
/**
85
 * Handshake RPC
86
 * @alias module:http.RPC
87
 * @extends bweb.RPC
88
 */
89

90
class RPC extends RPCBase {
91
  /**
92
   * Create RPC.
93
   * @param {Node} node
94
   */
95

96
  constructor(node) {
97
    super();
190✔
98

99
    assert(node, 'RPC requires a Node.');
190✔
100

101
    this.node = node;
190✔
102
    this.network = node.network;
190✔
103
    this.workers = node.workers;
190✔
104
    this.chain = node.chain;
190✔
105
    this.mempool = node.mempool;
190✔
106
    this.pool = node.pool;
190✔
107
    this.fees = node.fees;
190✔
108
    this.miner = node.miner;
190✔
109
    this.logger = node.logger.context('node-rpc');
190✔
110
    this.locker = new Lock();
190✔
111

112
    this.mining = false;
190✔
113
    this.procLimit = 0;
190✔
114
    this.attempt = null;
190✔
115
    this.lastActivity = 0;
190✔
116
    this.boundChain = false;
190✔
117
    this.mask = Buffer.alloc(32, 0x00);
190✔
118
    this.merkleMap = new BufferMap();
190✔
119
    this.merkleList = [];
190✔
120
    this.pollers = [];
190✔
121

122
    this.init();
190✔
123
  }
124

125
  getCode(err) {
126
    switch (err.type) {
40!
127
      case 'RPCError':
128
        return err.code;
37✔
129
      case 'ValidationError':
130
        return errs.TYPE_ERROR;
×
131
      case 'EncodingError':
132
        return errs.DESERIALIZATION_ERROR;
×
133
      default:
134
        return errs.INTERNAL_ERROR;
3✔
135
    }
136
  }
137

138
  handleCall(cmd, query) {
139
    if (cmd.method !== 'getwork'
579✔
140
        && cmd.method !== 'getblocktemplate'
141
        && cmd.method !== 'getbestblockhash') {
142
      this.logger.debug('Handling RPC call: %s.', cmd.method);
575✔
143

144
      if (cmd.method !== 'submitblock')
575✔
145
        this.logger.debug(cmd.params);
574✔
146
    }
147

148
    if (cmd.method === 'getwork') {
579!
149
      if (query.longpoll)
×
150
        cmd.method = 'getworklp';
×
151
    }
152
  }
153

154
  handleError(err) {
155
    this.logger.error('RPC internal error.');
3✔
156
    this.logger.error(err);
3✔
157
  }
158

159
  init() {
160
    this.add('stop', this.stop);
190✔
161
    this.add('help', this.help);
190✔
162

163
    this.add('getblockchaininfo', this.getBlockchainInfo);
190✔
164
    this.add('getbestblockhash', this.getBestBlockHash);
190✔
165
    this.add('getblockcount', this.getBlockCount);
190✔
166
    this.add('getblock', this.getBlock);
190✔
167
    this.add('getblockbyheight', this.getBlockByHeight);
190✔
168
    this.add('getblockhash', this.getBlockHash);
190✔
169
    this.add('getblockheader', this.getBlockHeader);
190✔
170
    this.add('getchaintips', this.getChainTips);
190✔
171
    this.add('getdifficulty', this.getDifficulty);
190✔
172
    this.add('getmempoolancestors', this.getMempoolAncestors);
190✔
173
    this.add('getmempooldescendants', this.getMempoolDescendants);
190✔
174
    this.add('getmempoolentry', this.getMempoolEntry);
190✔
175
    this.add('getmempoolinfo', this.getMempoolInfo);
190✔
176
    this.add('getrawmempool', this.getRawMempool);
190✔
177
    this.add('gettxout', this.getTXOut);
190✔
178
    this.add('gettxoutsetinfo', this.getTXOutSetInfo);
190✔
179
    this.add('pruneblockchain', this.pruneBlockchain);
190✔
180
    this.add('compacttree', this.compactTree);
190✔
181
    this.add('reconstructtree', this.reconstructTree);
190✔
182
    this.add('verifychain', this.verifyChain);
190✔
183

184
    this.add('invalidateblock', this.invalidateBlock);
190✔
185
    this.add('reconsiderblock', this.reconsiderBlock);
190✔
186

187
    this.add('getnetworkhashps', this.getNetworkHashPS);
190✔
188
    this.add('getmininginfo', this.getMiningInfo);
190✔
189
    this.add('prioritisetransaction', this.prioritiseTransaction);
190✔
190
    this.add('getwork', this.getWork);
190✔
191
    this.add('getworklp', this.getWorkLongpoll);
190✔
192
    this.add('submitwork', this.submitWork);
190✔
193
    this.add('getblocktemplate', this.getBlockTemplate);
190✔
194
    this.add('submitblock', this.submitBlock);
190✔
195
    this.add('verifyblock', this.verifyBlock);
190✔
196

197
    this.add('setgenerate', this.setGenerate);
190✔
198
    this.add('getgenerate', this.getGenerate);
190✔
199
    this.add('generate', this.generate);
190✔
200
    this.add('generatetoaddress', this.generateToAddress);
190✔
201

202
    this.add('estimatefee', this.estimateFee);
190✔
203
    this.add('estimatepriority', this.estimatePriority);
190✔
204
    this.add('estimatesmartfee', this.estimateSmartFee);
190✔
205
    this.add('estimatesmartpriority', this.estimateSmartPriority);
190✔
206

207
    this.add('getinfo', this.getInfo);
190✔
208
    this.add('validateaddress', this.validateAddress);
190✔
209
    this.add('createmultisig', this.createMultisig);
190✔
210
    this.add('verifymessage', this.verifyMessage);
190✔
211
    this.add('verifymessagewithname', this.verifyMessageWithName);
190✔
212
    this.add('signmessagewithprivkey', this.signMessageWithPrivkey);
190✔
213

214
    this.add('setmocktime', this.setMockTime);
190✔
215

216
    this.add('getconnectioncount', this.getConnectionCount);
190✔
217
    this.add('ping', this.ping);
190✔
218
    this.add('getpeerinfo', this.getPeerInfo);
190✔
219
    this.add('addnode', this.addNode);
190✔
220
    this.add('disconnectnode', this.disconnectNode);
190✔
221
    this.add('getaddednodeinfo', this.getAddedNodeInfo);
190✔
222
    this.add('getnettotals', this.getNetTotals);
190✔
223
    this.add('getnetworkinfo', this.getNetworkInfo);
190✔
224
    this.add('setban', this.setBan);
190✔
225
    this.add('listbanned', this.listBanned);
190✔
226
    this.add('clearbanned', this.clearBanned);
190✔
227

228
    this.add('getrawtransaction', this.getRawTransaction);
190✔
229
    this.add('createrawtransaction', this.createRawTransaction);
190✔
230
    this.add('decoderawtransaction', this.decodeRawTransaction);
190✔
231
    this.add('decodescript', this.decodeScript);
190✔
232
    this.add('decoderesource', this.decodeResource);
190✔
233
    this.add('sendrawtransaction', this.sendRawTransaction);
190✔
234
    this.add('signrawtransaction', this.signRawTransaction);
190✔
235

236
    this.add('gettxoutproof', this.getTXOutProof);
190✔
237
    this.add('verifytxoutproof', this.verifyTXOutProof);
190✔
238

239
    this.add('getmemoryinfo', this.getMemoryInfo);
190✔
240
    this.add('setloglevel', this.setLogLevel);
190✔
241
    this.add('getnames', this.getNames);
190✔
242
    this.add('getnameinfo', this.getNameInfo);
190✔
243
    this.add('getnameresource', this.getNameResource);
190✔
244
    this.add('getnameproof', this.getNameProof);
190✔
245
    this.add('getdnssecproof', this.getDNSSECProof);
190✔
246
    this.add('getnamebyhash', this.getNameByHash);
190✔
247
    this.add('grindname', this.grindName);
190✔
248
    this.add('sendrawclaim', this.sendRawClaim);
190✔
249
    this.add('sendrawairdrop', this.sendRawAirdrop);
190✔
250
    this.add('validateresource', this.validateResource);
190✔
251

252
    this.add('resetrootcache', this.resetRootCache);
190✔
253

254
    // Compat
255
    // this.add('getnameinfo', this.getNameInfo);
256
    // this.add('getnameresource', this.getNameResource);
257
    // this.add('getnameproof', this.getNameProof);
258

259
    // Namebase specific events.
260
    this.add('emitnamebaseevent', this.emitNamebaseEvent);
190✔
261
  }
262

263
  /*
264
   * Overall control/query calls
265
   */
266

267
  async getInfo(args, help) {
268
    if (help || args.length !== 0)
×
269
      throw new RPCError(errs.MISC_ERROR, 'getinfo');
×
270

271
    return {
×
272
      version: pkg.version,
273
      protocolversion: this.pool.options.version,
274
      walletversion: 0,
275
      balance: 0,
276
      blocks: this.chain.height,
277
      timeoffset: this.network.time.offset,
278
      connections: this.pool.peers.size(),
279
      proxy: '',
280
      difficulty: toDifficulty(this.chain.tip.bits),
281
      testnet: this.network !== Network.main,
282
      keypoololdest: 0,
283
      keypoolsize: 0,
284
      unlocked_until: 0,
285
      paytxfee: Amount.coin(this.network.feeRate, true),
286
      relayfee: Amount.coin(this.network.minRelay, true),
287
      errors: ''
288
    };
289
  }
290

291
  async help(args, _help) {
292
    if (args.length === 0)
×
293
      return 'Select a command.';
×
294

295
    const json = {
×
296
      method: args[0],
297
      params: []
298
    };
299

300
    return this.execute(json, true);
×
301
  }
302

303
  async stop(args, help) {
304
    if (help || args.length !== 0)
×
305
      throw new RPCError(errs.MISC_ERROR, 'stop');
×
306

307
    this.node.close().catch((err) => {
×
308
      setImmediate(() => {
×
309
        throw err;
×
310
      });
311
    });
312

313
    return 'Stopping.';
×
314
  }
315

316
  /*
317
   * P2P networking
318
   */
319

320
  async getNetworkInfo(args, help) {
321
    if (help || args.length !== 0)
1!
322
      throw new RPCError(errs.MISC_ERROR, 'getnetworkinfo');
×
323

324
    const hosts = this.pool.hosts;
1✔
325
    const locals = [];
1✔
326

327
    for (const local of hosts.local.values()) {
1✔
328
      locals.push({
×
329
        address: local.addr.host,
330
        port: local.addr.port,
331
        score: local.score
332
      });
333
    }
334

335
    return {
1✔
336
      version: pkg.version,
337
      subversion: this.pool.options.agent,
338
      protocolversion: this.pool.options.version,
339
      identitykey: this.pool.hosts.brontide.getKey('base32'),
340
      localservices: util.hex32(this.pool.options.services),
341
      localservicenames: this.pool.getServiceNames(),
342
      localrelay: !this.pool.options.noRelay,
343
      timeoffset: this.network.time.offset,
344
      networkactive: this.pool.connected,
345
      connections: this.pool.peers.size(),
346
      networks: [],
347
      relayfee: Amount.coin(this.network.minRelay, true),
348
      incrementalfee: 0,
349
      localaddresses: locals,
350
      warnings: ''
351
    };
352
  }
353

354
  async addNode(args, help) {
355
    if (help || args.length !== 2)
×
356
      throw new RPCError(errs.MISC_ERROR, 'addnode "node" "add|remove|onetry"');
×
357

358
    const valid = new Validator(args);
×
359
    const node = valid.str(0, '');
×
360
    const cmd = valid.str(1, '');
×
361

362
    switch (cmd) {
×
363
      case 'add': {
364
        this.pool.hosts.addNode(node);
×
365
        ; // fall through
366
      }
367
      case 'onetry': {
368
        const addr = parseNetAddress(node, this.network);
×
369

370
        if (!this.pool.peers.get(addr.hostname)) {
×
371
          const peer = this.pool.createOutbound(addr);
×
372
          this.pool.peers.add(peer);
×
373
        }
374

375
        break;
×
376
      }
377
      case 'remove': {
378
        this.pool.hosts.removeNode(node);
×
379
        break;
×
380
      }
381
    }
382

383
    return null;
×
384
  }
385

386
  async disconnectNode(args, help) {
387
    if (help || args.length !== 1)
×
388
      throw new RPCError(errs.MISC_ERROR, 'disconnectnode "node"');
×
389

390
    const valid = new Validator(args);
×
391
    const str = valid.str(0, '');
×
392

393
    const addr = parseIP(str, this.network);
×
394
    const peer = this.pool.peers.get(addr.hostname);
×
395

396
    if (peer)
×
397
      peer.destroy();
×
398

399
    return null;
×
400
  }
401

402
  async getAddedNodeInfo(args, help) {
403
    if (help || args.length > 1)
×
404
      throw new RPCError(errs.MISC_ERROR, 'getaddednodeinfo ( "node" )');
×
405

406
    const hosts = this.pool.hosts;
×
407
    const valid = new Validator(args);
×
408
    const addr = valid.str(0, '');
×
409

410
    let target;
411
    if (args.length === 1)
×
412
      target = parseIP(addr, this.network);
×
413

414
    const result = [];
×
415

416
    for (const node of hosts.nodes) {
×
417
      if (target) {
×
418
        if (node.host !== target.host)
×
419
          continue;
×
420

421
        if (node.port !== target.port)
×
422
          continue;
×
423
      }
424

425
      const peer = this.pool.peers.get(node.hostname);
×
426

427
      if (!peer || !peer.connected) {
×
428
        result.push({
×
429
          addednode: node.hostname,
430
          connected: false,
431
          addresses: []
432
        });
433
        continue;
×
434
      }
435

436
      result.push({
×
437
        addednode: node.hostname,
438
        connected: peer.connected,
439
        addresses: [
440
          {
441
            address: peer.hostname(),
442
            connected: peer.outbound
×
443
              ? 'outbound'
444
              : 'inbound'
445
          }
446
        ]
447
      });
448
    }
449

450
    if (target && result.length === 0) {
×
451
      throw new RPCError(errs.CLIENT_NODE_NOT_ADDED,
×
452
        'Node has not been added.');
453
    }
454

455
    return result;
×
456
  }
457

458
  async getConnectionCount(args, help) {
459
    if (help || args.length !== 0)
×
460
      throw new RPCError(errs.MISC_ERROR, 'getconnectioncount');
×
461

462
    return this.pool.peers.size();
×
463
  }
464

465
  async getNetTotals(args, help) {
466
    let sent = 0;
×
467
    let recv = 0;
×
468

469
    if (help || args.length > 0)
×
470
      throw new RPCError(errs.MISC_ERROR, 'getnettotals');
×
471

472
    for (let peer = this.pool.peers.head(); peer; peer = peer.next) {
×
473
      sent += peer.socket.bytesWritten;
×
474
      recv += peer.socket.bytesRead;
×
475
    }
476

477
    return {
×
478
      totalbytesrecv: recv,
479
      totalbytessent: sent,
480
      timemillis: Date.now()
481
    };
482
  }
483

484
  async getPeerInfo(args, help) {
485
    if (help || args.length > 1)
×
486
      throw new RPCError(errs.MISC_ERROR, 'getpeerinfo ( "type" )');
×
487

488
    const valid = new Validator(args);
×
489
    const type = valid.str(0);
×
490
    const peers = [];
×
491

492
    for (let peer = this.pool.peers.head(); peer; peer = peer.next) {
×
493
      if (!peer.connected)
×
494
        continue;
×
495

496
      if (type && peer.outbound !== (type === 'outbound'))
×
497
        continue;
×
498

499
      const offset = this.network.time.known.get(peer.hostname()) || 0;
×
500
      const hashes = [];
×
501

502
      for (const hash in peer.blockMap.keys())
×
503
        hashes.push(hash.toString('hex'));
×
504

505
      peer.getName();
×
506

507
      peers.push({
×
508
        id: peer.id,
509
        addr: peer.hostname(),
510
        addrlocal: !peer.local.isNull()
×
511
          ? peer.local.hostname
512
          : undefined,
513
        name: peer.name || undefined,
×
514
        services: util.hex32(peer.services),
515
        servicenames: peer.getServiceNames(),
516
        relaytxes: !peer.noRelay,
517
        lastsend: peer.lastSend / 1000 | 0,
518
        lastrecv: peer.lastRecv / 1000 | 0,
519
        bytessent: peer.socket.bytesWritten,
520
        bytesrecv: peer.socket.bytesRead,
521
        conntime: peer.time !== 0 ? (Date.now() - peer.time) / 1000 | 0 : 0,
×
522
        timeoffset: offset,
523
        pingtime: peer.lastPong !== -1
×
524
          ? (peer.lastPong - peer.lastPing) / 1000
525
          : -1,
526
        minping: peer.minPing !== -1 ? peer.minPing / 1000 : -1,
×
527
        version: peer.version,
528
        subver: peer.agent,
529
        inbound: !peer.outbound,
530
        startingheight: peer.height,
531
        besthash: peer.bestHash.toString('hex'),
532
        bestheight: peer.bestHeight,
533
        banscore: peer.banScore,
534
        inflight: hashes,
535
        whitelisted: false
536
      });
537
    }
538

539
    return peers;
×
540
  }
541

542
  async ping(args, help) {
543
    if (help || args.length !== 0)
×
544
      throw new RPCError(errs.MISC_ERROR, 'ping');
×
545

546
    for (let peer = this.pool.peers.head(); peer; peer = peer.next)
×
547
      peer.sendPing();
×
548

549
    return null;
×
550
  }
551

552
  async setBan(args, help) {
553
    const valid = new Validator(args);
×
554
    const str = valid.str(0, '');
×
555
    const action = valid.str(1, '');
×
556

557
    if (help
×
558
        || args.length < 2
559
        || (action !== 'add' && action !== 'remove')) {
560
      throw new RPCError(errs.MISC_ERROR,
×
561
        'setban "ip(/netmask)" "add|remove" (bantime) (absolute)');
562
    }
563

564
    const addr = parseNetAddress(str, this.network);
×
565

566
    switch (action) {
×
567
      case 'add':
568
        this.pool.ban(addr);
×
569
        break;
×
570
      case 'remove':
571
        this.pool.unban(addr);
×
572
        break;
×
573
    }
574

575
    return null;
×
576
  }
577

578
  async listBanned(args, help) {
579
    if (help || args.length !== 0)
×
580
      throw new RPCError(errs.MISC_ERROR, 'listbanned');
×
581

582
    const banned = [];
×
583

584
    for (const [host, time] of this.pool.hosts.banned) {
×
585
      banned.push({
×
586
        address: host,
587
        banned_until: time + this.pool.options.banTime,
588
        ban_created: time,
589
        ban_reason: ''
590
      });
591
    }
592

593
    return banned;
×
594
  }
595

596
  async clearBanned(args, help) {
597
    if (help || args.length !== 0)
×
598
      throw new RPCError(errs.MISC_ERROR, 'clearbanned');
×
599

600
    this.pool.hosts.clearBanned();
×
601

602
    return null;
×
603
  }
604

605
  /* Block chain and UTXO */
606
  async getBlockchainInfo(args, help) {
607
    if (help || args.length !== 0)
101!
608
      throw new RPCError(errs.MISC_ERROR, 'getblockchaininfo');
×
609

610
    return {
101✔
611
      chain: this.network.type !== 'testnet'
101!
612
        ? this.network.type
613
        : 'test',
614
      blocks: this.chain.height,
615
      headers: this.chain.height,
616
      bestblockhash: this.chain.tip.hash.toString('hex'),
617
      treeroot: this.chain.tip.treeRoot.toString('hex'),
618
      difficulty: toDifficulty(this.chain.tip.bits),
619
      mediantime: await this.chain.getMedianTime(this.chain.tip),
620
      verificationprogress: this.chain.getProgress(),
621
      chainwork: this.chain.tip.chainwork.toString('hex', 64),
622
      pruned: this.chain.options.prune,
623
      softforks: await this.getSoftforks(),
624
      deflationary: this.chain.height >= this.network.deflationHeight,
625
      pruneheight: this.chain.options.prune
101!
626
        ? Math.max(0, this.chain.height - this.network.block.keepBlocks)
627
        : null
628
    };
629
  }
630

631
  async getBestBlockHash(args, help) {
632
    if (help || args.length !== 0)
×
633
      throw new RPCError(errs.MISC_ERROR, 'getbestblockhash');
×
634

635
    return this.chain.tip.hash.toString('hex');
×
636
  }
637

638
  async getBlockCount(args, help) {
639
    if (help || args.length !== 0)
1!
640
      throw new RPCError(errs.MISC_ERROR, 'getblockcount');
×
641

642
    return this.chain.tip.height;
1✔
643
  }
644

645
  async getBlock(args, help) {
646
    if (help || args.length < 1 || args.length > 3)
145!
647
      throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )');
×
648

649
    const valid = new Validator(args);
145✔
650
    const hash = valid.bhash(0);
145✔
651
    const verbose = valid.bool(1, true);
145✔
652
    const details = valid.bool(2, false);
145✔
653

654
    if (!hash)
145!
655
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
656

657
    const entry = await this.chain.getEntry(hash);
145✔
658

659
    if (!entry)
145!
660
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
661

662
    const block = await this.chain.getBlock(entry.hash);
145✔
663

664
    if (!block) {
145✔
665
      if (this.chain.options.spv)
10!
666
        throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)');
×
667

668
      if (this.chain.options.prune) {
10!
669
        throw new RPCError(errs.MISC_ERROR,
10✔
670
          'Block not available (pruned data)');
671
      }
672

673
      throw new RPCError(errs.MISC_ERROR, 'Can\'t read block from disk');
×
674
    }
675

676
    if (!verbose)
135!
677
      return block.toHex();
×
678

679
    return this.blockToJSON(entry, block, details);
135✔
680
  }
681

682
  async getBlockByHeight(args, help) {
683
    if (help || args.length < 1 || args.length > 3) {
20!
684
      throw new RPCError(errs.MISC_ERROR,
×
685
        'getblockbyheight "height" ( verbose )');
686
    }
687

688
    const valid = new Validator(args);
20✔
689
    const height = valid.u32(0, -1);
20✔
690
    const verbose = valid.bool(1, true);
20✔
691
    const details = valid.bool(2, false);
20✔
692

693
    if (height === -1)
20!
694
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block height.');
×
695

696
    const entry = await this.chain.getEntry(height);
20✔
697

698
    if (!entry)
20!
699
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
700

701
    const block = await this.chain.getBlock(entry.hash);
20✔
702

703
    if (!block) {
20!
704
      if (this.chain.options.spv)
×
705
        throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)');
×
706

707
      if (this.chain.options.prune) {
×
708
        throw new RPCError(errs.MISC_ERROR,
×
709
          'Block not available (pruned data)');
710
      }
711

712
      throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk');
×
713
    }
714

715
    if (!verbose)
20!
716
      return block.toHex();
×
717

718
    return this.blockToJSON(entry, block, details);
20✔
719
  }
720

721
  async getBlockHash(args, help) {
722
    if (help || args.length !== 1)
×
723
      throw new RPCError(errs.MISC_ERROR, 'getblockhash index');
×
724

725
    const valid = new Validator(args);
×
726
    const height = valid.u32(0);
×
727

728
    if (height == null || height > this.chain.height)
×
729
      throw new RPCError(errs.INVALID_PARAMETER, 'Block height out of range.');
×
730

731
    const hash = await this.chain.getHash(height);
×
732

733
    if (!hash)
×
734
      throw new RPCError(errs.MISC_ERROR, 'Not found.');
×
735

736
    return hash.toString('hex');
×
737
  }
738

739
  async getBlockHeader(args, help) {
740
    if (help || args.length < 1 || args.length > 2)
×
741
      throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )');
×
742

743
    const valid = new Validator(args);
×
744
    const hash = valid.bhash(0);
×
745
    const verbose = valid.bool(1, true);
×
746

747
    if (!hash)
×
748
      throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.');
×
749

750
    const entry = await this.chain.getEntry(hash);
×
751

752
    if (!entry)
×
753
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
754

755
    if (!verbose)
×
756
      return entry.encode().toString('hex', 36, 36 + consensus.HEADER_SIZE);
×
757

758
    return this.headerToJSON(entry);
×
759
  }
760

761
  async getChainTips(args, help) {
762
    if (help || args.length !== 0)
×
763
      throw new RPCError(errs.MISC_ERROR, 'getchaintips');
×
764

765
    const tips = await this.chain.getTips();
×
766
    const result = [];
×
767

768
    for (const hash of tips) {
×
769
      const entry = await this.chain.getEntry(hash);
×
770

771
      assert(entry);
×
772

773
      const fork = await this.findFork(entry);
×
774
      const main = await this.chain.isMainChain(entry);
×
775

776
      result.push({
×
777
        height: entry.height,
778
        hash: entry.hash.toString('hex'),
779
        branchlen: entry.height - fork.height,
780
        status: main ? 'active' : 'valid-headers'
×
781
      });
782
    }
783

784
    return result;
×
785
  }
786

787
  async getDifficulty(args, help) {
788
    if (help || args.length !== 0)
×
789
      throw new RPCError(errs.MISC_ERROR, 'getdifficulty');
×
790

791
    return toDifficulty(this.chain.tip.bits);
×
792
  }
793

794
  async getMempoolInfo(args, help) {
795
    if (help || args.length !== 0)
×
796
      throw new RPCError(errs.MISC_ERROR, 'getmempoolinfo');
×
797

798
    if (!this.mempool)
×
799
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
800

801
    return {
×
802
      size: this.mempool.map.size,
803
      bytes: this.mempool.getSize(),
804
      usage: this.mempool.getSize(),
805
      maxmempool: this.mempool.options.maxSize,
806
      mempoolminfee: Amount.coin(this.mempool.options.minRelay, true)
807
    };
808
  }
809

810
  async getMempoolAncestors(args, help) {
811
    if (help || args.length < 1 || args.length > 2)
×
812
      throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)');
×
813

814
    const valid = new Validator(args);
×
815
    const hash = valid.bhash(0);
×
816
    const verbose = valid.bool(1, false);
×
817

818
    if (!this.mempool)
×
819
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
820

821
    if (!hash)
×
822
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
823

824
    const entry = this.mempool.getEntry(hash);
×
825

826
    if (!entry)
×
827
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
828

829
    const entries = this.mempool.getAncestors(entry);
×
830
    const out = [];
×
831

832
    if (verbose) {
×
833
      for (const entry of entries)
×
834
        out.push(this.entryToJSON(entry));
×
835
    } else {
836
      for (const entry of entries)
×
837
        out.push(entry.txid());
×
838
    }
839

840
    return out;
×
841
  }
842

843
  async getMempoolDescendants(args, help) {
844
    if (help || args.length < 1 || args.length > 2) {
×
845
      throw new RPCError(errs.MISC_ERROR,
×
846
        'getmempooldescendants txid (verbose)');
847
    }
848

849
    const valid = new Validator(args);
×
850
    const hash = valid.bhash(0);
×
851
    const verbose = valid.bool(1, false);
×
852

853
    if (!this.mempool)
×
854
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
855

856
    if (!hash)
×
857
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
858

859
    const entry = this.mempool.getEntry(hash);
×
860

861
    if (!entry)
×
862
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
863

864
    const entries = this.mempool.getDescendants(entry);
×
865
    const out = [];
×
866

867
    if (verbose) {
×
868
      for (const entry of entries)
×
869
        out.push(this.entryToJSON(entry));
×
870
    } else {
871
      for (const entry of entries)
×
872
        out.push(entry.txid());
×
873
    }
874

875
    return out;
×
876
  }
877

878
  async getMempoolEntry(args, help) {
879
    if (help || args.length !== 1)
×
880
      throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid');
×
881

882
    const valid = new Validator(args);
×
883
    const hash = valid.bhash(0);
×
884

885
    if (!this.mempool)
×
886
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
887

888
    if (!hash)
×
889
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
890

891
    const entry = this.mempool.getEntry(hash);
×
892

893
    if (!entry)
×
894
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
895

896
    return this.entryToJSON(entry);
×
897
  }
898

899
  async getRawMempool(args, help) {
900
    if (help || args.length > 1)
1!
901
      throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )');
×
902

903
    const valid = new Validator(args);
1✔
904
    const verbose = valid.bool(0, false);
1✔
905

906
    if (!this.mempool)
1!
907
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
908

909
    if (verbose) {
1!
910
      const out = Object.create(null);
1✔
911

912
      for (const entry of this.mempool.map.values())
1✔
913
        out[entry.txid()] = this.entryToJSON(entry);
×
914

915
      return out;
1✔
916
    }
917

918
    const hashes = this.mempool.getSnapshot();
×
919

920
    return hashes.map(hash => hash.toString('hex'));
×
921
  }
922

923
  async getTXOut(args, help) {
924
    if (help || args.length < 2 || args.length > 3) {
×
925
      throw new RPCError(errs.MISC_ERROR,
×
926
        'gettxout "txid" n ( includemempool )');
927
    }
928

929
    const valid = new Validator(args);
×
930
    const hash = valid.bhash(0);
×
931
    const index = valid.u32(1);
×
932
    const mempool = valid.bool(2, true);
×
933

934
    if (this.chain.options.spv)
×
935
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.');
×
936

937
    if (this.chain.options.prune)
×
938
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.');
×
939

940
    if (!hash || index == null)
×
941
      throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.');
×
942

943
    let coin;
944
    if (mempool) {
×
945
      if (!this.mempool)
×
946
        throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
947
      coin = this.mempool.getCoin(hash, index);
×
948
    }
949

950
    if (!coin)
×
951
      coin = await this.chain.getCoin(hash, index);
×
952

953
    if (!coin)
×
954
      return null;
×
955

956
    return {
×
957
      bestblock: this.chain.tip.hash.toString('hex'),
958
      confirmations: coin.getDepth(this.chain.height),
959
      value: Amount.coin(coin.value, true),
960
      address: this.addrToJSON(coin.address),
961
      version: coin.version,
962
      coinbase: coin.coinbase
963
    };
964
  }
965

966
  async getTXOutProof(args, help) {
967
    if (help || (args.length !== 1 && args.length !== 2)) {
×
968
      throw new RPCError(errs.MISC_ERROR,
×
969
        'gettxoutproof ["txid",...] ( blockhash )');
970
    }
971

972
    const valid = new Validator(args);
×
973
    const txids = valid.array(0);
×
974
    const hash = valid.bhash(1);
×
975

976
    if (this.chain.options.spv)
×
977
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.');
×
978

979
    if (this.chain.options.prune)
×
980
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.');
×
981

982
    if (!txids || txids.length === 0)
×
983
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.');
×
984

985
    const items = new Validator(txids);
×
986
    const set = new BufferSet();
×
987
    const hashes = [];
×
988

989
    let last = null;
×
990

991
    for (let i = 0; i < txids.length; i++) {
×
992
      const hash = items.bhash(i);
×
993

994
      if (!hash)
×
995
        throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
996

997
      if (set.has(hash))
×
998
        throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate txid.');
×
999

1000
      set.add(hash);
×
1001
      hashes.push(hash);
×
1002

1003
      last = hash;
×
1004
    }
1005

1006
    let block = null;
×
1007

1008
    if (hash) {
×
1009
      block = await this.chain.getBlock(hash);
×
1010
    } else if (this.chain.options.indexTX) {
×
1011
      const tx = await this.chain.getMeta(last);
×
1012
      if (tx)
×
1013
        block = await this.chain.getBlock(tx.block);
×
1014
    } else {
1015
      const coin = await this.chain.getCoin(last, 0);
×
1016
      if (coin)
×
1017
        block = await this.chain.getBlock(coin.height);
×
1018
    }
1019

1020
    if (!block)
×
1021
      throw new RPCError(errs.MISC_ERROR, 'Block not found.');
×
1022

1023
    const whashes = [];
×
1024

1025
    for (const hash of hashes) {
×
1026
      const index = block.indexOf(hash);
×
1027

1028
      if (index === -1) {
×
1029
        throw new RPCError(errs.VERIFY_ERROR,
×
1030
          'Block does not contain all txids.');
1031
      }
1032

1033
      const tx = block.txs[index];
×
1034

1035
      whashes.push(tx.hash());
×
1036
    }
1037

1038
    const mblock = MerkleBlock.fromHashes(block, whashes);
×
1039

1040
    return mblock.toHex();
×
1041
  }
1042

1043
  async verifyTXOutProof(args, help) {
1044
    if (help || args.length !== 1)
×
1045
      throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"');
×
1046

1047
    const valid = new Validator(args);
×
1048
    const data = valid.buf(0);
×
1049

1050
    if (!data)
×
1051
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1052

1053
    const block = MerkleBlock.decode(data);
×
1054

1055
    if (!block.verify())
×
1056
      return [];
×
1057

1058
    const entry = await this.chain.getEntry(block.hash());
×
1059

1060
    if (!entry)
×
1061
      throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.');
×
1062

1063
    const tree = block.getTree();
×
1064
    const out = [];
×
1065

1066
    for (const hash of tree.matches)
×
1067
      out.push(hash.toString('hex'));
×
1068

1069
    return out;
×
1070
  }
1071

1072
  async getTXOutSetInfo(args, help) {
1073
    if (help || args.length !== 0)
×
1074
      throw new RPCError(errs.MISC_ERROR, 'gettxoutsetinfo');
×
1075

1076
    if (this.chain.options.spv) {
×
1077
      throw new RPCError(errs.MISC_ERROR,
×
1078
        'Chainstate not available (SPV mode).');
1079
    }
1080

1081
    return {
×
1082
      height: this.chain.height,
1083
      bestblock: this.chain.tip.hash.toString('hex'),
1084
      transactions: this.chain.db.state.tx,
1085
      txouts: this.chain.db.state.coin,
1086
      bytes_serialized: 0,
1087
      hash_serialized: 0,
1088
      total_amount: Amount.coin(this.chain.db.state.value, true),
1089
      total_burned: Amount.coin(this.chain.db.state.burned, true)
1090
    };
1091
  }
1092

1093
  async pruneBlockchain(args, help) {
1094
    if (help || args.length !== 0)
5✔
1095
      throw new RPCError(errs.MISC_ERROR, 'pruneblockchain');
1✔
1096

1097
    if (this.chain.options.spv)
4✔
1098
      throw new RPCError(errs.MISC_ERROR, 'Cannot prune chain in SPV mode.');
1✔
1099

1100
    if (this.chain.options.prune)
3✔
1101
      throw new RPCError(errs.MISC_ERROR, 'Chain is already pruned.');
1✔
1102

1103
    if (this.chain.height < this.network.block.pruneAfterHeight)
2✔
1104
      throw new RPCError(errs.MISC_ERROR, 'Chain is too short for pruning.');
1✔
1105

1106
    try {
1✔
1107
      await this.chain.prune();
1✔
1108
    } catch (e) {
1109
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1110
    }
1111
  }
1112

1113
    async compactTree(args, help) {
1114
    if (help || args.length !== 0)
2!
1115
      throw new RPCError(errs.MISC_ERROR, 'compacttree');
×
1116

1117
    if (this.chain.options.spv)
2✔
1118
      throw new RPCError(errs.MISC_ERROR, 'Cannot compact tree in SPV mode.');
1✔
1119

1120
    try {
1✔
1121
      await this.chain.compactTree();
1✔
1122
    } catch (e) {
1123
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1124
    }
1125
  }
1126

1127
  async reconstructTree(args, help) {
1128
    if (help || args.length !== 0)
2!
1129
      throw new RPCError(errs.MISC_ERROR, 'reconstructtree');
×
1130

1131
    if (this.chain.options.spv) {
2✔
1132
      throw new RPCError(errs.MISC_ERROR,
1✔
1133
        'Cannot reconstruct tree in SPV mode.');
1134
    }
1135

1136
    if (this.chain.options.prune) {
1!
1137
      throw new RPCError(errs.MISC_ERROR,
×
1138
        'Cannot reconstruct tree in pruned node.');
1139
    }
1140

1141
    try {
1✔
1142
      await this.chain.reconstructTree();
1✔
1143
    } catch (e) {
1144
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1145
    }
1146
  }
1147

1148
  async verifyChain(args, help) {
1149
    if (help || args.length > 2) {
×
1150
      throw new RPCError(errs.MISC_ERROR,
×
1151
        'verifychain ( checklevel numblocks )');
1152
    }
1153

1154
    const valid = new Validator(args);
×
1155
    const level = valid.u32(0);
×
1156
    const blocks = valid.u32(1);
×
1157

1158
    if (level == null || blocks == null)
×
1159
      throw new RPCError(errs.TYPE_ERROR, 'Missing parameters.');
×
1160

1161
    if (this.chain.options.spv)
×
1162
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain in SPV mode.');
×
1163

1164
    if (this.chain.options.prune)
×
1165
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain when pruned.');
×
1166

1167
    return null;
×
1168
  }
1169

1170
  /*
1171
   * Mining
1172
   */
1173

1174
  async handleWork(data, mask) {
1175
    const unlock = await this.locker.lock();
7✔
1176
    try {
7✔
1177
      return await this._handleWork(data, mask);
7✔
1178
    } finally {
1179
      unlock();
7✔
1180
    }
1181
  }
1182

1183
  async _handleWork(data, mask) {
1184
    if (data.length !== 256)
7!
1185
      return [false, 'invalid-data-length'];
×
1186

1187
    const hdr = Headers.fromMiner(data);
7✔
1188
    const maskHash = blake2b.multi(hdr.prevBlock, mask);
7✔
1189

1190
    if (!hdr.maskHash().equals(maskHash))
7!
1191
      return [false, 'bad-maskhash'];
×
1192

1193
    const attempt = this.merkleMap.get(hdr.witnessRoot);
7✔
1194

1195
    if (!attempt)
7✔
1196
      return [false, 'stale'];
3✔
1197

1198
    if (!hdr.prevBlock.equals(attempt.prevBlock)
4!
1199
        || hdr.bits !== attempt.bits) {
1200
      return [false, 'stale'];
×
1201
    }
1202

1203
    const {nonce, time, extraNonce} = hdr;
4✔
1204
    const proof = attempt.getProof(nonce, time, extraNonce, mask);
4✔
1205

1206
    if (!proof.verify(attempt.target, this.network))
4!
1207
      return [false, 'bad-diffbits'];
×
1208

1209
    const block = attempt.commit(proof);
4✔
1210

1211
    let entry;
1212
    try {
4✔
1213
      entry = await this.chain.add(block);
4✔
1214
    } catch (err) {
1215
      if (err.type === 'VerifyError') {
×
1216
        this.logger.warning('RPC block rejected: %x (%s).',
×
1217
          block.hash(), err.reason);
1218
        return [false, err.reason];
×
1219
      }
1220
      throw err;
×
1221
    }
1222

1223
    if (!entry) {
4!
1224
      this.logger.warning('RPC block rejected: %x (bad-prevblk).',
×
1225
        block.hash());
1226
      return [false, 'bad-prevblk'];
×
1227
    }
1228

1229
    return [true, 'valid'];
4✔
1230
  }
1231

1232
  async createWork() {
1233
    const unlock = await this.locker.lock();
10✔
1234
    try {
10✔
1235
      return await this._createWork();
10✔
1236
    } finally {
1237
      unlock();
10✔
1238
    }
1239
  }
1240

1241
  async _createWork() {
1242
    const attempt = await this.updateWork();
10✔
1243
    const time = attempt.time;
10✔
1244
    const nonce = consensus.ZERO_NONCE;
10✔
1245
    const mask = consensus.ZERO_HASH;
10✔
1246
    const data = attempt.getHeader(0, time, nonce, mask);
10✔
1247

1248
    return {
10✔
1249
      network: this.network.type,
1250
      data: data.toString('hex'),
1251
      target: attempt.target.toString('hex'),
1252
      height: attempt.height,
1253
      time: this.network.now(),
1254
      fee: attempt.fees
1255
    };
1256
  }
1257

1258
  async getWorkLongpoll(args, help) {
1259
    await this.longpoll();
×
1260
    return this.createWork();
×
1261
  }
1262

1263
  async getWork(args, help) {
1264
    if (help || args.length !== 0)
10!
1265
      throw new RPCError(errs.MISC_ERROR, 'getwork');
×
1266

1267
    return this.createWork();
10✔
1268
  }
1269

1270
  async submitWork(args, help) {
1271
    if (help || args.length < 1 || args.length > 2)
7!
1272
      throw new RPCError(errs.MISC_ERROR, 'submitwork ( "data" "mask" )');
×
1273

1274
    const valid = new Validator(args);
7✔
1275
    const data = valid.buf(0);
7✔
1276

1277
    if (!data)
7!
1278
      throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.');
×
1279

1280
    let mask = consensus.ZERO_HASH;
7✔
1281

1282
    if (args.length === 2) {
7!
1283
      mask = valid.bhash(1);
×
1284

1285
      if (!mask)
×
1286
        throw new RPCError(errs.TYPE_ERROR, 'Invalid mask.');
×
1287
    }
1288

1289
    return this.handleWork(data, mask);
7✔
1290
  }
1291

1292
  async submitBlock(args, help) {
1293
    if (help || args.length < 1 || args.length > 2) {
1!
1294
      throw new RPCError(errs.MISC_ERROR,
×
1295
        'submitblock "hexdata" ( "jsonparametersobject" )');
1296
    }
1297

1298
    const valid = new Validator(args);
1✔
1299
    const data = valid.buf(0);
1✔
1300

1301
    const block = Block.decode(data);
1✔
1302

1303
    return this.addBlock(block);
1✔
1304
  }
1305

1306
  async getBlockTemplate(args, help) {
1307
    if (help || args.length > 1) {
4!
1308
      throw new RPCError(errs.MISC_ERROR,
×
1309
        'getblocktemplate ( "jsonrequestobject" )');
1310
    }
1311

1312
    const validator = new Validator(args);
4✔
1313
    const options = validator.obj(0, {});
4✔
1314
    const valid = new Validator(options);
4✔
1315
    const mode = valid.str('mode', 'template');
4✔
1316

1317
    if (mode !== 'template' && mode !== 'proposal')
4!
1318
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid mode.');
×
1319

1320
    if (mode === 'proposal') {
4✔
1321
      const data = valid.buf('data');
1✔
1322

1323
      if (!data)
1!
1324
        throw new RPCError(errs.TYPE_ERROR, 'Missing data parameter.');
×
1325

1326
      const block = Block.decode(data);
1✔
1327

1328
      if (!block.prevBlock.equals(this.chain.tip.hash))
1!
1329
        return 'inconclusive-not-best-prevblk';
×
1330

1331
      try {
1✔
1332
        await this.chain.verifyBlock(block);
1✔
1333
      } catch (e) {
1334
        if (e.type === 'VerifyError')
×
1335
          return e.reason;
×
1336
        throw e;
×
1337
      }
1338

1339
      return null;
1✔
1340
    }
1341

1342
    let maxVersion = valid.u32('maxversion', -1);
3✔
1343
    let rules = valid.array('rules');
3✔
1344

1345
    if (rules)
3✔
1346
      maxVersion = -1;
2✔
1347

1348
    const capabilities = valid.array('capabilities');
3✔
1349
    let coinbase = false;
3✔
1350

1351
    if (capabilities) {
3!
1352
      let txnCap = false;
×
1353
      let valueCap = false;
×
1354

1355
      for (const capability of capabilities) {
×
1356
        if (typeof capability !== 'string')
×
1357
          throw new RPCError(errs.TYPE_ERROR, 'Invalid capability.');
×
1358

1359
        switch (capability) {
×
1360
          case 'coinbasetxn':
1361
            txnCap = true;
×
1362
            break;
×
1363
          case 'coinbasevalue':
1364
            // Prefer value if they support it.
1365
            valueCap = true;
×
1366
            break;
×
1367
        }
1368
      }
1369

1370
      if (txnCap && !valueCap) {
×
1371
        if (this.miner.addresses.length === 0) {
×
1372
          throw new RPCError(errs.MISC_ERROR,
×
1373
            'No addresses available for coinbase.');
1374
        }
1375
        coinbase = true;
×
1376
      }
1377
    }
1378

1379
    if (!this.network.selfConnect) {
3!
1380
      if (this.pool.peers.size() === 0) {
×
1381
        throw new RPCError(errs.CLIENT_NOT_CONNECTED,
×
1382
          'Node is not connected!');
1383
      }
1384

1385
      if (!this.chain.synced) {
×
1386
        throw new RPCError(errs.CLIENT_IN_INITIAL_DOWNLOAD,
×
1387
          'Node is downloading blocks...');
1388
      }
1389
    }
1390

1391
    const lpid = valid.str('longpollid');
3✔
1392

1393
    if (lpid)
3!
1394
      await this.handleLongpoll(lpid);
×
1395

1396
    if (!rules)
3✔
1397
      rules = [];
1✔
1398

1399
    return this.createTemplate(maxVersion, coinbase, rules);
3✔
1400
  }
1401

1402
  async createTemplate(maxVersion, coinbase, rules) {
1403
    const unlock = await this.locker.lock();
3✔
1404
    try {
3✔
1405
      return await this._createTemplate(maxVersion, coinbase, rules);
3✔
1406
    } finally {
1407
      unlock();
3✔
1408
    }
1409
  }
1410

1411
  async _createTemplate(maxVersion, coinbase, rules) {
1412
    const attempt = await this.getTemplate();
3✔
1413
    const scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR;
3!
1414

1415
    // Default mutable fields.
1416
    const mutable = ['time', 'transactions', 'prevblock'];
3✔
1417

1418
    // The miner doesn't support
1419
    // versionbits. Force them to
1420
    // encode our version.
1421
    if (maxVersion >= 2)
3!
1422
      mutable.push('version/force');
×
1423

1424
    // Allow the miner to change
1425
    // our provided coinbase.
1426
    // Note that these are implied
1427
    // without `coinbasetxn`.
1428
    if (coinbase) {
3!
1429
      mutable.push('coinbase');
×
1430
      mutable.push('coinbase/append');
×
1431
      mutable.push('generation');
×
1432
    }
1433

1434
    // Build an index of every transaction.
1435
    const index = new BufferMap();
3✔
1436
    for (let i = 0; i < attempt.items.length; i++) {
3✔
1437
      const entry = attempt.items[i];
4✔
1438
      index.set(entry.hash, i + 1);
4✔
1439
    }
1440

1441
    // Calculate dependencies for each transaction.
1442
    const txs = [];
3✔
1443
    for (let i = 0; i < attempt.items.length; i++) {
3✔
1444
      const entry = attempt.items[i];
4✔
1445
      const tx = entry.tx;
4✔
1446
      const deps = [];
4✔
1447

1448
      for (let j = 0; j < tx.inputs.length; j++) {
4✔
1449
        const input = tx.inputs[j];
4✔
1450
        const dep = index.get(input.prevout.hash);
4✔
1451

1452
        if (dep == null)
4!
1453
          continue;
4✔
1454

1455
        if (deps.indexOf(dep) === -1) {
×
1456
          assert(dep < i + 1);
×
1457
          deps.push(dep);
×
1458
        }
1459
      }
1460

1461
      txs.push({
4✔
1462
        data: tx.toHex(),
1463
        txid: tx.txid(),
1464
        hash: tx.wtxid(),
1465
        depends: deps,
1466
        fee: entry.fee,
1467
        sigops: entry.sigops / scale | 0,
1468
        weight: tx.getWeight()
1469
      });
1470
    }
1471

1472
    // Calculate version based on given rules.
1473
    let version = attempt.version;
3✔
1474

1475
    const vbavailable = {};
3✔
1476
    const vbrules = [];
3✔
1477

1478
    for (const deploy of this.network.deploys) {
3✔
1479
      const state = await this.chain.getState(this.chain.tip, deploy);
12✔
1480

1481
      let name = deploy.name;
12✔
1482

1483
      switch (state) {
12!
1484
        case common.thresholdStates.DEFINED:
1485
        case common.thresholdStates.FAILED:
1486
          break;
12✔
1487
        case common.thresholdStates.LOCKED_IN:
1488
          version |= 1 << deploy.bit;
×
1489
        case common.thresholdStates.STARTED:
1490
          if (!deploy.force) {
×
1491
            if (rules.indexOf(name) === -1)
×
1492
              version &= ~(1 << deploy.bit);
×
1493
            if (deploy.required)
×
1494
              name = '!' + name;
×
1495
          }
1496
          vbavailable[name] = deploy.bit;
×
1497
          break;
×
1498
        case common.thresholdStates.ACTIVE:
1499
          if (!deploy.force && deploy.required) {
×
1500
            if (rules.indexOf(name) === -1) {
×
1501
              throw new RPCError(errs.INVALID_PARAMETER,
×
1502
                `Client must support ${name}.`);
1503
            }
1504
            name = '!' + name;
×
1505
          }
1506
          vbrules.push(name);
×
1507
          break;
×
1508
        default:
1509
          assert(false, 'Bad state.');
×
1510
          break;
×
1511
      }
1512
    }
1513

1514
    version >>>= 0;
3✔
1515

1516
    const json = {
3✔
1517
      capabilities: ['proposal'],
1518
      mutable: mutable,
1519
      version: version,
1520
      rules: vbrules,
1521
      vbavailable: vbavailable,
1522
      vbrequired: 0,
1523
      height: attempt.height,
1524
      previousblockhash: attempt.prevBlock.toString('hex'),
1525
      merkleroot: undefined,
1526
      witnessroot: undefined,
1527
      treeroot: attempt.treeRoot.toString('hex'),
1528
      reservedroot: attempt.reservedRoot.toString('hex'),
1529
      mask: consensus.ZERO_HASH.toString('hex'), // Compat.
1530
      target: attempt.target.toString('hex'),
1531
      bits: util.hex32(attempt.bits),
1532
      noncerange:
1533
        Array(consensus.NONCE_SIZE + 1).join('00')
1534
        + Array(consensus.NONCE_SIZE + 1).join('ff'),
1535
      curtime: attempt.time,
1536
      mintime: attempt.mtp + 1,
1537
      maxtime: attempt.time + 7200,
1538
      expires: attempt.time + 7200,
1539
      sigoplimit: consensus.MAX_BLOCK_SIGOPS,
1540
      // sizelimit: consensus.MAX_RAW_BLOCK_SIZE,
1541
      sizelimit: consensus.MAX_BLOCK_SIZE,
1542
      weightlimit: consensus.MAX_BLOCK_WEIGHT,
1543
      longpollid: this.chain.tip.hash.toString('hex')
1544
                  + util.hex32(this.totalTX()),
1545
      submitold: false,
1546
      coinbaseaux: {
1547
        flags: attempt.coinbaseFlags.toString('hex')
1548
      },
1549
      coinbasevalue: attempt.getReward(),
1550
      coinbasetxn: undefined,
1551
      claims: attempt.claims.map((claim) => {
1552
        let value = claim.value;
×
1553
        let fee = claim.fee;
×
1554

1555
        // Account for mining software which creates its own
1556
        // coinbase with something other than `coinbasevalue`.
1557
        if (attempt.height >= this.network.deflationHeight) {
×
1558
          if (claim.commitHeight !== 1) {
×
1559
            value = claim.value - claim.fee;
×
1560
            fee = 0;
×
1561
          }
1562
        }
1563

1564
        return {
×
1565
          data: claim.blob.toString('hex'),
1566
          name: claim.name.toString('binary'),
1567
          namehash: claim.nameHash.toString('hex'),
1568
          version: claim.address.version,
1569
          hash: claim.address.hash.toString('hex'),
1570
          value: value,
1571
          fee: fee,
1572
          weak: claim.weak,
1573
          commitHash: claim.commitHash.toString('hex'),
1574
          commitHeight: claim.commitHeight,
1575
          weight: claim.getWeight()
1576
        };
1577
      }),
1578
      airdrops: attempt.airdrops.map((airdrop) => {
1579
        return {
×
1580
          data: airdrop.blob.toString('hex'),
1581
          position: airdrop.position,
1582
          version: airdrop.address.version,
1583
          address: airdrop.address.hash.toString('hex'),
1584
          value: airdrop.value,
1585
          fee: airdrop.fee,
1586
          rate: airdrop.rate,
1587
          weak: airdrop.weak
1588
        };
1589
      }),
1590
      transactions: txs
1591
    };
1592

1593
    // The client wants a coinbasetxn
1594
    // instead of a coinbasevalue.
1595
    if (coinbase) {
3!
1596
      const tx = attempt.coinbase;
×
1597

1598
      json.merkleroot = attempt.merkleRoot.toString('hex');
×
1599
      json.witnessroot = attempt.witnessRoot.toString('hex');
×
1600

1601
      json.coinbasetxn = {
×
1602
        data: tx.toHex(),
1603
        txid: tx.txid(),
1604
        hash: tx.wtxid(),
1605
        depends: [],
1606
        fee: 0,
1607
        sigops: tx.getSigops() / scale | 0,
1608
        weight: tx.getWeight()
1609
      };
1610
    }
1611

1612
    return json;
3✔
1613
  }
1614

1615
  async getMiningInfo(args, help) {
1616
    if (help || args.length !== 0)
×
1617
      throw new RPCError(errs.MISC_ERROR, 'getmininginfo');
×
1618

1619
    const attempt = this.attempt;
×
1620

1621
    let size = 0;
×
1622
    let weight = 0;
×
1623
    let txs = 0;
×
1624
    let diff = 0;
×
1625

1626
    if (attempt) {
×
1627
      weight = attempt.weight;
×
1628
      txs = attempt.items.length + 1;
×
1629
      diff = attempt.getDifficulty();
×
1630
      size = 1000;
×
1631
      for (const item of attempt.items)
×
1632
        size += item.tx.getBaseSize();
×
1633
    }
1634

1635
    return {
×
1636
      blocks: this.chain.height,
1637
      currentblocksize: size,
1638
      currentblockweight: weight,
1639
      currentblocktx: txs,
1640
      difficulty: diff,
1641
      errors: '',
1642
      genproclimit: this.procLimit,
1643
      networkhashps: await this.getHashRate(120),
1644
      pooledtx: this.totalTX(),
1645
      testnet: this.network !== Network.main,
1646
      chain: this.network.type !== 'testnet'
×
1647
        ? this.network.type
1648
        : 'test',
1649
      generate: this.mining
1650
    };
1651
  }
1652

1653
  async getNetworkHashPS(args, help) {
1654
    if (help || args.length > 2)
×
1655
      throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )');
×
1656

1657
    const valid = new Validator(args);
×
1658
    const lookup = valid.u32(0, 120);
×
1659
    const height = valid.u32(1);
×
1660

1661
    return this.getHashRate(lookup, height);
×
1662
  }
1663

1664
  async prioritiseTransaction(args, help) {
1665
    if (help || args.length !== 3) {
1!
1666
      throw new RPCError(errs.MISC_ERROR,
×
1667
        'prioritisetransaction <txid> <priority delta> <fee delta>');
1668
    }
1669

1670
    const valid = new Validator(args);
1✔
1671
    const hash = valid.bhash(0);
1✔
1672
    const pri = valid.i64(1);
1✔
1673
    const fee = valid.i64(2);
1✔
1674

1675
    if (!this.mempool)
1!
1676
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
1677

1678
    if (!hash)
1!
1679
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID');
×
1680

1681
    if (pri == null || fee == null)
1!
1682
      throw new RPCError(errs.TYPE_ERROR, 'Invalid fee or priority.');
×
1683

1684
    const entry = this.mempool.getEntry(hash);
1✔
1685

1686
    if (!entry)
1!
1687
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
1688

1689
    this.mempool.prioritise(entry, pri, fee);
1✔
1690

1691
    return true;
1✔
1692
  }
1693

1694
  async verifyBlock(args, help) {
1695
    if (help || args.length !== 1)
×
1696
      throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"');
×
1697

1698
    const valid = new Validator(args);
×
1699
    const data = valid.buf(0);
×
1700

1701
    if (!data)
×
1702
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hex.');
×
1703

1704
    if (this.chain.options.spv)
×
1705
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify block in SPV mode.');
×
1706

1707
    const block = Block.decode(data);
×
1708

1709
    try {
×
1710
      await this.chain.verifyBlock(block);
×
1711
    } catch (e) {
1712
      if (e.type === 'VerifyError')
×
1713
        return e.reason;
×
1714
      throw e;
×
1715
    }
1716

1717
    return null;
×
1718
  }
1719

1720
  /*
1721
   * Coin generation
1722
   */
1723

1724
  async getGenerate(args, help) {
1725
    if (help || args.length !== 0)
×
1726
      throw new RPCError(errs.MISC_ERROR, 'getgenerate');
×
1727
    return this.mining;
×
1728
  }
1729

1730
  async setGenerate(args, help) {
1731
    if (help || args.length < 1 || args.length > 2)
×
1732
      throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )');
×
1733

1734
    const valid = new Validator(args);
×
1735
    const mine = valid.bool(0, false);
×
1736
    const limit = valid.u32(1, 0);
×
1737

1738
    if (mine && this.miner.addresses.length === 0) {
×
1739
      throw new RPCError(errs.MISC_ERROR,
×
1740
        'No addresses available for coinbase.');
1741
    }
1742

1743
    this.mining = mine;
×
1744
    this.procLimit = limit;
×
1745

1746
    if (mine) {
×
1747
      this.miner.cpu.start();
×
1748
      return true;
×
1749
    }
1750

1751
    await this.miner.cpu.stop();
×
1752

1753
    return false;
×
1754
  }
1755

1756
  async generate(args, help) {
1757
    if (help || args.length < 1 || args.length > 2)
20!
1758
      throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )');
×
1759

1760
    const valid = new Validator(args);
20✔
1761
    const blocks = valid.u32(0, 1);
20✔
1762
    const tries = valid.u32(1);
20✔
1763

1764
    if (this.miner.addresses.length === 0) {
20!
1765
      throw new RPCError(errs.MISC_ERROR,
×
1766
        'No addresses available for coinbase.');
1767
    }
1768

1769
    return this.mineBlocks(blocks, null, tries);
20✔
1770
  }
1771

1772
  async generateToAddress(args, help) {
1773
    if (help || args.length < 2 || args.length > 3) {
500!
1774
      throw new RPCError(errs.MISC_ERROR,
×
1775
        'generatetoaddress numblocks address ( maxtries )');
1776
    }
1777

1778
    const valid = new Validator(args);
500✔
1779
    const blocks = valid.u32(0, 1);
500✔
1780
    const str = valid.str(1, '');
500✔
1781
    const tries = valid.u32(2);
500✔
1782

1783
    const addr = parseAddress(str, this.network);
500✔
1784

1785
    return this.mineBlocks(blocks, addr, tries);
500✔
1786
  }
1787

1788
  /*
1789
   * Raw transactions
1790
   */
1791

1792
  async createRawTransaction(args, help) {
1793
    if (help || args.length < 2 || args.length > 3) {
×
1794
      throw new RPCError(errs.MISC_ERROR,
×
1795
        'createrawtransaction'
1796
        + ' [{"txid":"id","vout":n},...]'
1797
        + ' {"address":amount,"data":"hex",...}'
1798
        + ' ( locktime )');
1799
    }
1800

1801
    const valid = new Validator(args);
×
1802
    const inputs = valid.array(0);
×
1803
    const sendTo = valid.obj(1);
×
1804
    const locktime = valid.u32(2);
×
1805

1806
    if (!inputs || !sendTo) {
×
1807
      throw new RPCError(errs.TYPE_ERROR,
×
1808
        'Invalid parameters (inputs and sendTo).');
1809
    }
1810

1811
    const tx = new MTX();
×
1812

1813
    if (locktime != null)
×
1814
      tx.locktime = locktime;
×
1815

1816
    for (const obj of inputs) {
×
1817
      const valid = new Validator(obj);
×
1818
      const hash = valid.bhash('txid');
×
1819
      const index = valid.u32('vout');
×
1820

1821
      let sequence = valid.u32('sequence', 0xffffffff);
×
1822

1823
      if (tx.locktime)
×
1824
        sequence -= 1;
×
1825

1826
      if (!hash || index == null)
×
1827
        throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.');
×
1828

1829
      const input = new Input();
×
1830
      input.prevout.hash = hash;
×
1831
      input.prevout.index = index;
×
1832
      input.sequence = sequence;
×
1833

1834
      tx.inputs.push(input);
×
1835
    }
1836

1837
    const sends = new Validator(sendTo);
×
1838
    const uniq = new Set();
×
1839

1840
    for (const key of Object.keys(sendTo)) {
×
1841
      if (key === 'data') {
×
1842
        const value = sends.buf(key);
×
1843

1844
        if (!value)
×
1845
          throw new RPCError(errs.TYPE_ERROR, 'Invalid nulldata..');
×
1846

1847
        const output = new Output();
×
1848
        output.value = 0;
×
1849
        output.address.fromNulldata(value);
×
1850
        tx.outputs.push(output);
×
1851

1852
        continue;
×
1853
      }
1854

1855
      const addr = parseAddress(key, this.network);
×
1856
      const b58 = addr.toString(this.network);
×
1857

1858
      if (uniq.has(b58))
×
1859
        throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address');
×
1860

1861
      uniq.add(b58);
×
1862

1863
      const value = sends.ufixed(key, EXP);
×
1864

1865
      if (value == null)
×
1866
        throw new RPCError(errs.TYPE_ERROR, 'Invalid output value.');
×
1867

1868
      const output = new Output();
×
1869
      output.value = value;
×
1870
      output.address = addr;
×
1871

1872
      tx.outputs.push(output);
×
1873
    }
1874

1875
    return tx.toHex();
×
1876
  }
1877

1878
  async decodeRawTransaction(args, help) {
1879
    if (help || args.length !== 1)
×
1880
      throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"');
×
1881

1882
    const valid = new Validator(args);
×
1883
    const data = valid.buf(0);
×
1884

1885
    if (!data)
×
1886
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1887

1888
    const tx = TX.decode(data);
×
1889

1890
    return this.txToJSON(tx);
×
1891
  }
1892

1893
  async decodeScript(args, help) {
1894
    if (help || args.length !== 1)
×
1895
      throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"');
×
1896

1897
    const valid = new Validator(args);
×
1898
    const data = valid.buf(0);
×
1899
    const script = new Script();
×
1900

1901
    if (data)
×
1902
      script.decode(data);
×
1903

1904
    const addr = Address.fromScripthash(script.sha3());
×
1905

1906
    const json = this.scriptToJSON(script);
×
1907
    json.p2sh = addr.toString(this.network);
×
1908

1909
    return json;
×
1910
  }
1911

1912
  async decodeResource(args, help) {
1913
    if (help || args.length !== 1)
1!
1914
      throw new RPCError(errs.MISC_ERROR, 'decoderesource "hex"');
×
1915

1916
    const valid = new Validator(args);
1✔
1917
    const data = valid.buf(0);
1✔
1918

1919
    const res = Resource.decode(data);
1✔
1920
    return res.toJSON();
1✔
1921
  }
1922

1923
  async getRawTransaction(args, help) {
1924
    if (help || args.length < 1 || args.length > 2) {
2!
1925
      throw new RPCError(errs.MISC_ERROR,
×
1926
        'getrawtransaction "txid" ( verbose )');
1927
    }
1928

1929
    const valid = new Validator(args);
2✔
1930
    const hash = valid.bhash(0);
2✔
1931
    const verbose = valid.bool(1, false);
2✔
1932

1933
    if (!hash)
2!
1934
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
1935

1936
    const meta = await this.node.getMeta(hash);
2✔
1937

1938
    if (!meta)
2!
1939
      throw new RPCError(errs.MISC_ERROR, 'Transaction not found.');
×
1940

1941
    const tx = meta.tx;
2✔
1942

1943
    if (!verbose)
2✔
1944
      return tx.toHex();
1✔
1945

1946
    let entry;
1947
    if (meta.block)
1!
1948
      entry = await this.chain.getEntry(meta.block);
1✔
1949

1950
    const json = this.txToJSON(tx, entry);
1✔
1951
    json.time = meta.mtime;
1✔
1952
    json.hex = tx.toHex();
1✔
1953

1954
    return json;
1✔
1955
  }
1956

1957
  async sendRawTransaction(args, help) {
1958
    if (help || args.length < 1 || args.length > 2) {
14!
1959
      throw new RPCError(errs.MISC_ERROR,
×
1960
        'sendrawtransaction "hexstring" ( allowhighfees )');
1961
    }
1962

1963
    const valid = new Validator(args);
14✔
1964
    const data = valid.buf(0);
14✔
1965

1966
    if (!data)
14!
1967
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1968

1969
    const tx = TX.decode(data);
14✔
1970

1971
    this.node.relay(tx);
14✔
1972

1973
    return tx.txid();
14✔
1974
  }
1975

1976
  async signRawTransaction(args, help) {
1977
    if (help || args.length < 1 || args.length > 4) {
×
1978
      throw new RPCError(errs.MISC_ERROR,
×
1979
        'signrawtransaction'
1980
        + ' "hexstring" ('
1981
        + ' [{"txid":"id","vout":n,"address":"bech32",'
1982
        + 'redeemScript":"hex"},...] ["privatekey1",...]'
1983
        + ' sighashtype )');
1984
    }
1985

1986
    const valid = new Validator(args);
×
1987
    const data = valid.buf(0);
×
1988
    const prevout = valid.array(1);
×
1989
    const secrets = valid.array(2);
×
1990
    const sighash = valid.str(3);
×
1991

1992
    if (!data)
×
1993
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1994

1995
    if (!this.mempool)
×
1996
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
1997

1998
    const tx = MTX.decode(data);
×
1999
    tx.view = await this.mempool.getSpentView(tx);
×
2000

2001
    const map = new BufferMap();
×
2002
    const keys = [];
×
2003

2004
    if (secrets) {
×
2005
      const valid = new Validator(secrets);
×
2006
      for (let i = 0; i < secrets.length; i++) {
×
2007
        const secret = valid.str(i, '');
×
2008
        const key = parseSecret(secret, this.network);
×
2009
        map.set(key.getPublicKey(), key);
×
2010
        keys.push(key);
×
2011
      }
2012
    }
2013

2014
    if (prevout) {
×
2015
      for (const prev of prevout) {
×
2016
        const valid = new Validator(prev);
×
2017
        const hash = valid.bhash('txid');
×
2018
        const index = valid.u32('vout');
×
2019
        const addrRaw = valid.str('address');
×
2020
        const value = valid.ufixed('amount', EXP);
×
2021
        const redeemRaw = valid.buf('redeemScript');
×
2022

2023
        if (!hash || index == null || !addrRaw || value == null)
×
2024
          throw new RPCError(errs.INVALID_PARAMETER, 'Invalid UTXO.');
×
2025

2026
        const outpoint = new Outpoint(hash, index);
×
2027

2028
        const addr = parseAddress(addrRaw, this.network);
×
2029
        const coin = Output.fromScript(addr, value);
×
2030

2031
        tx.view.addOutput(outpoint, coin);
×
2032

2033
        if (keys.length === 0 || !redeemRaw)
×
2034
          continue;
×
2035

2036
        if (!addr.isScripthash())
×
2037
          continue;
×
2038

2039
        if (!redeemRaw) {
×
2040
          throw new RPCError(errs.INVALID_PARAMETER,
×
2041
            'P2SH requires redeem script.');
2042
        }
2043

2044
        const redeem = Script.decode(redeemRaw);
×
2045

2046
        for (const op of redeem.code) {
×
2047
          if (!op.data)
×
2048
            continue;
×
2049

2050
          const key = map.get(op.data);
×
2051

2052
          if (key) {
×
2053
            key.script = redeem;
×
2054
            key.refresh();
×
2055
            break;
×
2056
          }
2057
        }
2058
      }
2059
    }
2060

2061
    let type = Script.hashType.ALL;
×
2062
    if (sighash) {
×
2063
      const parts = sighash.split('|');
×
2064

2065
      if (parts.length < 1 || parts.length > 2)
×
2066
        throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2067

2068
      type = Script.hashType[parts[0]];
×
2069

2070
      if (type == null)
×
2071
        throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2072

2073
      if (parts.length === 2) {
×
2074
        if (parts[1] !== 'NOINPUT' && parts[1] !== 'ANYONECANPAY')
×
2075
          throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2076

2077
        if (parts[1] === 'NOINPUT')
×
2078
          type |= Script.hashType.NOINPUT;
×
2079

2080
        if (parts[1] === 'ANYONECANPAY')
×
2081
          type |= Script.hashType.ANYONECANPAY;
×
2082
      }
2083
    }
2084

2085
    await tx.signAsync(keys, type, this.workers);
×
2086

2087
    return {
×
2088
      hex: tx.toHex(),
2089
      complete: tx.isSigned()
2090
    };
2091
  }
2092

2093
  /*
2094
   * Utility Functions
2095
   */
2096

2097
  async createMultisig(args, help) {
2098
    if (help || args.length < 2 || args.length > 2) {
×
2099
      throw new RPCError(errs.MISC_ERROR,
×
2100
        'createmultisig nrequired ["key",...]');
2101
    }
2102

2103
    const valid = new Validator(args);
×
2104
    const keys = valid.array(1, []);
×
2105
    const m = valid.u32(0, 0);
×
2106
    const n = keys.length;
×
2107

2108
    if (m < 1 || n < m || n > 16)
×
2109
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.');
×
2110

2111
    const items = new Validator(keys);
×
2112

2113
    for (let i = 0; i < keys.length; i++) {
×
2114
      const key = items.buf(i);
×
2115

2116
      if (!key)
×
2117
        throw new RPCError(errs.TYPE_ERROR, 'Invalid key.');
×
2118

2119
      if (!secp256k1.publicKeyVerify(key) || key.length !== 33)
×
2120
        throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.');
×
2121

2122
      keys[i] = key;
×
2123
    }
2124

2125
    const script = Script.fromMultisig(m, n, keys);
×
2126

2127
    if (script.getSize() > consensus.MAX_SCRIPT_PUSH) {
×
2128
      throw new RPCError(errs.VERIFY_ERROR,
×
2129
        'Redeem script exceeds size limit.');
2130
    }
2131

2132
    const addr = Address.fromScripthash(script.sha3());
×
2133

2134
    return {
×
2135
      address: addr.toString(this.network),
2136
      redeemScript: script.toJSON()
2137
    };
2138
  }
2139

2140
  async validateAddress(args, help) {
2141
    if (help || args.length !== 1)
4!
2142
      throw new RPCError(errs.MISC_ERROR, 'validateaddress "address"');
×
2143

2144
    const valid = new Validator(args);
4✔
2145
    const str = valid.str(0, '');
4✔
2146

2147
    let addr;
2148
    try {
4✔
2149
      addr = Address.fromString(str, this.network);
4✔
2150
    } catch (e) {
2151
      return {
1✔
2152
        isvalid: false
2153
      };
2154
    }
2155

2156
    return {
3✔
2157
      isvalid: true,
2158
      address: addr.toString(this.network),
2159
      isscript: addr.isScripthash(),
2160
      isspendable: !addr.isUnspendable(),
2161
      witness_version: addr.version,
2162
      witness_program: addr.hash.toString('hex')
2163
    };
2164
  }
2165

2166
  async verifyMessage(args, help) {
2167
    if (help || args.length !== 3) {
6!
2168
      throw new RPCError(errs.MISC_ERROR,
×
2169
        'verifymessage "address" "signature" "message"');
2170
    }
2171

2172
    const valid = new Validator(args);
6✔
2173
    const b58 = valid.str(0, '');
6✔
2174
    const sig = valid.buf(1, null, 'base64');
6✔
2175
    const str = valid.str(2);
6✔
2176

2177
    if (!sig || !str)
6!
2178
      throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.');
×
2179

2180
    const addr = parseAddress(b58, this.network);
6✔
2181

2182
    if (addr.version !== 0 || addr.hash.length !== 20)
6!
2183
      return false;
×
2184

2185
    const msg = Buffer.from(MAGIC_STRING + str, 'utf8');
6✔
2186
    const hash = blake2b.digest(msg);
6✔
2187

2188
    for (let i = 0; i < 4; i++) {
6✔
2189
      const key = secp256k1.recover(hash, sig, i, true);
6✔
2190

2191
      if (!key)
6!
2192
        continue;
×
2193

2194
      if (safeEqual(blake2b.digest(key, 20), addr.hash))
6!
2195
        return true;
6✔
2196
    }
2197

2198
    return false;
×
2199
  }
2200

2201
  async verifyMessageWithName(args, help) {
2202
    if (help || args.length < 3 || args.length > 4 ) {
15!
2203
      throw new RPCError(errs.MISC_ERROR,
×
2204
        'verifymessagewithname "name" "signature" "message" (safe)');
2205
    }
2206

2207
    const valid = new Validator(args);
15✔
2208
    const name = valid.str(0, '');
15✔
2209
    const sig = valid.buf(1, null, 'base64');
15✔
2210
    const str = valid.str(2);
15✔
2211
    const safe = valid.bool(3, false);
15✔
2212
    const network = this.network;
15✔
2213
    const height = this.chain.height;
15✔
2214

2215
    if (!name || !rules.verifyName(name))
15✔
2216
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
4✔
2217

2218
    const nameHash = rules.hashName(name);
11✔
2219

2220
    const ns = await this.getNameState(nameHash, safe);
11✔
2221

2222
    if (!ns || !ns.owner)
11✔
2223
      throw new RPCError(errs.MISC_ERROR, 'Cannot find the name owner.');
1✔
2224

2225
    if (!ns.isClosed(height, network))
10✔
2226
      throw new Error('Invalid name state.');
3✔
2227

2228
    const coin = await this.chain.getCoin(ns.owner.hash, ns.owner.index);
7✔
2229

2230
    if (!coin) {
7✔
2231
      throw new RPCError(
3✔
2232
        errs.DATABASE_ERROR,
2233
        'Cannot find the owner\'s address.'
2234
      );
2235
    }
2236

2237
    const address = coin.address.toString(this.network);
4✔
2238
    return this.verifyMessage([address, sig, str]);
4✔
2239
  }
2240

2241
  async signMessageWithPrivkey(args, help) {
2242
    if (help || args.length !== 2) {
×
2243
      throw new RPCError(errs.MISC_ERROR,
×
2244
        'signmessagewithprivkey "privkey" "message"');
2245
    }
2246

2247
    const valid = new Validator(args);
×
2248
    const wif = valid.str(0, '');
×
2249
    const str = valid.str(1, '');
×
2250

2251
    const key = parseSecret(wif, this.network);
×
2252
    const msg = Buffer.from(MAGIC_STRING + str, 'utf8');
×
2253
    const hash = blake2b.digest(msg);
×
2254
    const sig = key.sign(hash);
×
2255

2256
    return sig.toString('base64');
×
2257
  }
2258

2259
  async estimateFee(args, help) {
2260
    if (help || args.length !== 1)
×
2261
      throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks');
×
2262

2263
    const valid = new Validator(args);
×
2264
    const blocks = valid.u32(0, 1);
×
2265

2266
    if (!this.fees)
×
2267
      throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.');
×
2268

2269
    const fee = this.fees.estimateFee(blocks, false);
×
2270

2271
    if (fee === 0)
×
2272
      return -1;
×
2273

2274
    return Amount.coin(fee, true);
×
2275
  }
2276

2277
  async estimatePriority(args, help) {
2278
    if (help || args.length !== 1)
×
2279
      throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks');
×
2280

2281
    const valid = new Validator(args);
×
2282
    const blocks = valid.u32(0, 1);
×
2283

2284
    if (!this.fees)
×
2285
      throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.');
×
2286

2287
    return this.fees.estimatePriority(blocks, false);
×
2288
  }
2289

2290
  async estimateSmartFee(args, help) {
2291
    if (help || args.length !== 1)
×
2292
      throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks');
×
2293

2294
    const valid = new Validator(args);
×
2295
    const blocks = valid.u32(0, 1);
×
2296

2297
    if (!this.fees)
×
2298
      throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.');
×
2299

2300
    let fee = this.fees.estimateFee(blocks, true);
×
2301

2302
    if (fee === 0)
×
2303
      fee = -1;
×
2304
    else
2305
      fee = Amount.coin(fee, true);
×
2306

2307
    return {
×
2308
      fee: fee,
2309
      blocks: blocks
2310
    };
2311
  }
2312

2313
  async estimateSmartPriority(args, help) {
2314
    if (help || args.length !== 1)
×
2315
      throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks');
×
2316

2317
    const valid = new Validator(args);
×
2318
    const blocks = valid.u32(0, 1);
×
2319

2320
    if (!this.fees)
×
2321
      throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.');
×
2322

2323
    const pri = this.fees.estimatePriority(blocks, true);
×
2324

2325
    return {
×
2326
      priority: pri,
2327
      blocks: blocks
2328
    };
2329
  }
2330

2331
  async invalidateBlock(args, help) {
2332
    if (help || args.length !== 1)
×
2333
      throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"');
×
2334

2335
    const valid = new Validator(args);
×
2336
    const hash = valid.bhash(0);
×
2337

2338
    if (!hash)
×
2339
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
2340

2341
    await this.chain.invalidate(hash);
×
2342

2343
    return null;
×
2344
  }
2345

2346
  async reconsiderBlock(args, help) {
2347
    if (help || args.length !== 1)
×
2348
      throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"');
×
2349

2350
    const valid = new Validator(args);
×
2351
    const hash = valid.bhash(0);
×
2352

2353
    if (!hash)
×
2354
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
2355

2356
    this.chain.removeInvalid(hash);
×
2357

2358
    return null;
×
2359
  }
2360

2361
  async setMockTime(args, help) {
2362
    if (help || args.length !== 1)
80!
2363
      throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp');
×
2364

2365
    const valid = new Validator(args);
80✔
2366
    const time = valid.u32(0);
80✔
2367

2368
    if (time == null)
80!
2369
      throw new RPCError(errs.TYPE_ERROR, 'Invalid timestamp.');
×
2370

2371
    this.network.time.offset = 0;
80✔
2372

2373
    const delta = this.network.now() - time;
80✔
2374

2375
    this.network.time.offset = -delta;
80✔
2376

2377
    return null;
80✔
2378
  }
2379

2380
  async getMemoryInfo(args, help) {
2381
    if (help || args.length !== 0)
×
2382
      throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo');
×
2383

2384
    return this.logger.memoryUsage();
×
2385
  }
2386

2387
  async setLogLevel(args, help) {
2388
    if (help || args.length !== 1)
×
2389
      throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"');
×
2390

2391
    const valid = new Validator(args);
×
2392
    const level = valid.str(0, '');
×
2393

2394
    this.logger.setLevel(level);
×
2395

2396
    return null;
×
2397
  }
2398

2399
  async getNames(args, help) {
2400
    if (help || args.length !== 0)
×
2401
      throw new RPCError(errs.MISC_ERROR, 'getnames');
×
2402

2403
    const network = this.network;
×
2404
    const height = this.chain.height;
×
2405
    const txn = this.chain.db.txn;
×
2406
    const items = [];
×
2407

2408
    const iter = txn.iterator();
×
2409

2410
    while (await iter.next()) {
×
2411
      const {key, value} = iter;
×
2412
      const ns = NameState.decode(value);
×
2413
      ns.nameHash = key;
×
2414

2415
      const info = ns.getJSON(height, network);
×
2416
      items.push(info);
×
2417
    }
2418

2419
    return items;
×
2420
  }
2421

2422
  async getNameInfo(args, help) {
2423
    if (help || args.length < 1 || args.length > 2)
44!
2424
      throw new RPCError(errs.MISC_ERROR, 'getnameinfo "name" (safe)');
×
2425

2426
    const valid = new Validator(args);
44✔
2427
    const name = valid.str(0);
44✔
2428
    const safe = valid.bool(1, false);
44✔
2429

2430
    if (!name || !rules.verifyName(name))
44!
2431
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2432

2433
    const network = this.network;
44✔
2434
    const height = this.chain.height;
44✔
2435
    const nameHash = rules.hashName(name);
44✔
2436
    const reserved = rules.isReserved(nameHash, height + 1, network);
44✔
2437
    const [start, week] = rules.getRollout(nameHash, network);
44✔
2438
    const state = await this.chain.getNextState();
44✔
2439

2440
    const ns = await this.getNameState(nameHash, safe);
44✔
2441

2442
    let locked = undefined;
44✔
2443
    let info = null;
44✔
2444

2445
    if (ns) {
44✔
2446
      if (!ns.isExpired(height, network))
40✔
2447
        info = ns.getJSON(height, network);
35✔
2448
    }
2449

2450
    if (state.hasICANNLockup())
44✔
2451
      locked = rules.isLockedUp(nameHash, height + 1, network);
14✔
2452

2453
    return {
44✔
2454
      start: {
2455
        reserved: reserved,
2456
        week: week,
2457
        start: start,
2458
        locked: locked
2459
      },
2460
      info
2461
    };
2462
  }
2463

2464
  async getNameResource(args, help) {
2465
    if (help || args.length < 1 || args.length > 2)
12!
2466
      throw new RPCError(errs.MISC_ERROR, 'getnameresource "name" (safe)');
×
2467

2468
    const valid = new Validator(args);
12✔
2469
    const name = valid.str(0);
12✔
2470
    const safe = valid.bool(1, false);
12✔
2471

2472
    if (!name || !rules.verifyName(name))
12!
2473
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2474

2475
    const nameHash = rules.hashName(name);
12✔
2476

2477
    const ns = await this.getNameState(nameHash, safe);
12✔
2478

2479
    if (!ns || ns.data.length === 0)
12✔
2480
      return null;
1✔
2481

2482
    try {
11✔
2483
      const res = Resource.decode(ns.data);
11✔
2484
      return res.getJSON(name);
11✔
2485
    } catch (e) {
2486
      return {};
×
2487
    }
2488
  }
2489

2490
  async getNameProof(args, help) {
2491
    if (help || args.length < 1 || args.length > 2)
×
2492
      throw new RPCError(errs.MISC_ERROR, 'getnameproof "name" ("root")');
×
2493

2494
    const valid = new Validator(args);
×
2495
    const name = valid.str(0);
×
2496
    const treeRoot = valid.bhash(1);
×
2497

2498
    if (!name || !rules.verifyName(name))
×
2499
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2500

2501
    const hash = this.chain.tip.hash;
×
2502
    const height = this.chain.tip.height;
×
2503
    const root = treeRoot || this.chain.tip.treeRoot;
×
2504
    const key = rules.hashName(name);
×
2505
    const proof = await this.chain.db.prove(root, key);
×
2506

2507
    return {
×
2508
      hash: hash.toString('hex'),
2509
      height: height,
2510
      root: root.toString('hex'),
2511
      name: name,
2512
      key: key.toString('hex'),
2513
      proof: proof.toJSON()
2514
    };
2515
  }
2516

2517
  async getDNSSECProof(args, help) {
2518
    if (help || args.length < 1 || args.length > 3)
×
2519
      throw new RPCError(errs.MISC_ERROR,
×
2520
        'getdnssecproof "name" ( estimate ) ( verbose )');
2521

2522
    const valid = new Validator(args);
×
2523
    const name = valid.str(0);
×
2524
    const estimate = valid.bool(1, false);
×
2525
    const verbose = valid.bool(2, true);
×
2526

2527
    const proof = await ownership.prove(name, estimate);
×
2528

2529
    if (!verbose)
×
2530
      return proof.toHex();
×
2531

2532
    return proof.toJSON();
×
2533
  }
2534

2535
  async getNameByHash(args, help) {
2536
    if (help || args.length < 1 || args.length > 2)
×
2537
      throw new RPCError(errs.MISC_ERROR, 'getnamebyhash "hash" (safe)');
×
2538

2539
    const valid = new Validator(args);
×
2540
    const hash = valid.bhash(0);
×
2541
    const safe = valid.bool(1, false);
×
2542

2543
    if (!hash)
×
2544
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name hash.');
×
2545

2546
    const ns = await this.getNameState(hash, safe);
×
2547

2548
    if (!ns)
×
2549
      return null;
×
2550

2551
    return ns.name.toString('binary');
×
2552
  }
2553

2554
  async grindName(args, help) {
2555
    if (help || args.length > 1)
68!
2556
      throw new RPCError(errs.MISC_ERROR, 'grindname size');
×
2557

2558
    const valid = new Validator(args);
68✔
2559
    const size = valid.u32(0, 10);
68✔
2560

2561
    if (size < 1 || size > 63)
68!
2562
      throw new RPCError(errs.TYPE_ERROR, 'Invalid length.');
×
2563

2564
    const network = this.network;
68✔
2565
    const height = this.chain.height;
68✔
2566

2567
    return rules.grindName(size, height + 1, network);
68✔
2568
  }
2569

2570
  async sendRawClaim(args, help) {
2571
    if (help || args.length < 1 || args.length > 2)
×
2572
      throw new RPCError(errs.MISC_ERROR, 'sendrawclaim "base64-string"');
×
2573

2574
    const valid = new Validator(args);
×
2575
    const data = valid.buf(0, null, 'base64');
×
2576

2577
    if (!data)
×
2578
      throw new RPCError(errs.TYPE_ERROR, 'Invalid base64 string.');
×
2579

2580
    const claim = Claim.fromBlob(data);
×
2581

2582
    this.node.relayClaim(claim);
×
2583

2584
    return claim.hash().toString('hex');
×
2585
  }
2586

2587
  async sendRawAirdrop(args, help) {
2588
    if (help || args.length < 1 || args.length > 2)
×
2589
      throw new RPCError(errs.MISC_ERROR, 'sendrawairdrop "base64-string"');
×
2590

2591
    if (this.network.type !== 'main')
×
2592
      throw new RPCError(errs.MISC_ERROR, 'Currently disabled.');
×
2593

2594
    const valid = new Validator(args);
×
2595
    const data = valid.buf(0, null, 'base64');
×
2596

2597
    if (!data)
×
2598
      throw new RPCError(errs.TYPE_ERROR, 'Invalid base64 string.');
×
2599

2600
    const proof = AirdropProof.decode(data);
×
2601

2602
    this.node.relayAirdrop(proof);
×
2603

2604
    return proof.hash().toString('hex');
×
2605
  }
2606

2607
  async validateResource(args, help) {
2608
    if (help || args.length < 1 || args.length > 2)
22!
2609
      throw new RPCError(errs.MISC_ERROR, 'validateresource'
×
2610
        + ' \'{"records": [{...}]}\'');
2611

2612
    const valid = new Validator(args);
22✔
2613
    const data = valid.obj(0);
22✔
2614

2615
    if (!data)
22!
2616
      throw new RPCError(errs.TYPE_ERROR, 'Invalid resource object.');
×
2617

2618
    let resource;
2619
    try {
22✔
2620
      resource = Resource.fromJSON(data);
22✔
2621
    } catch (e) {
2622
      throw new RPCError(errs.PARSE_ERROR, e.message);
15✔
2623
    }
2624

2625
    return resource.toJSON();
7✔
2626
  }
2627

2628
  async resetRootCache(args, help) {
2629
    if (help || args.length !== 0)
×
2630
      throw new RPCError(errs.MISC_ERROR, 'resetrootcache');
×
2631

2632
    if (!this.node.ns)
×
2633
      return null;
×
2634

2635
    this.node.ns.resetCache();
×
2636

2637
    return null;
×
2638
  }
2639

2640
  async emitNamebaseEvent(args) {
NEW
2641
    if (args.length < 1)
×
NEW
2642
      throw new RPCError(errs.MISC_ERROR, 'emitnamebaseevent ( ...args )');
×
2643

NEW
2644
    const valid = new Validator(args);
×
NEW
2645
    const eventName = valid.str(0);
×
2646

NEW
2647
    this.emit(`namebase-${eventName}`, args.slice(1));
×
2648

NEW
2649
    return null;
×
2650
  }
2651

2652
  /*
2653
   * Helpers
2654
   */
2655

2656
  async handleLongpoll(lpid) {
2657
    if (lpid.length !== 72)
×
2658
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.');
×
2659

2660
    const watched = lpid.slice(0, 64);
×
2661
    const lastTX = parseInt(lpid.slice(64, 72), 16);
×
2662

2663
    if ((lastTX >>> 0) !== lastTX)
×
2664
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.');
×
2665

2666
    const hash = util.parseHex(watched, 32);
×
2667

2668
    if (!this.chain.tip.hash.equals(hash))
×
2669
      return;
×
2670

2671
    await this.longpoll();
×
2672
  }
2673

2674
  longpoll() {
2675
    return new Promise((resolve, reject) => {
×
2676
      this.pollers.push({ resolve, reject });
×
2677
    });
2678
  }
2679

2680
  refreshBlock() {
2681
    const pollers = this.pollers;
12✔
2682

2683
    this.attempt = null;
12✔
2684
    this.lastActivity = 0;
12✔
2685
    this.pollers = [];
12✔
2686

2687
    for (const job of pollers)
12✔
2688
      job.resolve();
×
2689
  }
2690

2691
  bindChain() {
2692
    if (this.boundChain)
13✔
2693
      return;
11✔
2694

2695
    this.boundChain = true;
2✔
2696

2697
    const refresh = () => {
2✔
2698
      if (!this.attempt)
16✔
2699
        return;
10✔
2700

2701
      this.refreshBlock();
6✔
2702
      this.merkleMap.clear();
6✔
2703
      this.merkleList.length = 0;
6✔
2704
    };
2705

2706
    this.node.on('connect', refresh);
2✔
2707
    this.node.on('reset', refresh);
2✔
2708

2709
    if (!this.mempool)
2!
2710
      return;
×
2711

2712
    const tryRefresh = () => {
2✔
2713
      if (!this.attempt)
6✔
2714
        return;
2✔
2715

2716
      if (util.now() - this.lastActivity > 10)
4!
2717
        this.refreshBlock();
4✔
2718
    };
2719

2720
    this.node.on('tx', tryRefresh);
2✔
2721
    this.node.on('claim', tryRefresh);
2✔
2722
    this.node.on('airdrop', tryRefresh);
2✔
2723
  }
2724

2725
  async getTemplate() {
2726
    this.bindChain();
3✔
2727

2728
    let attempt = this.attempt;
3✔
2729

2730
    if (attempt) {
3!
2731
      this.miner.updateTime(attempt);
×
2732
    } else {
2733
      attempt = await this.miner.createBlock();
3✔
2734
      this.attempt = attempt;
3✔
2735
      this.lastActivity = util.now();
3✔
2736
    }
2737

2738
    return attempt;
3✔
2739
  }
2740

2741
  async updateWork() {
2742
    this.bindChain();
10✔
2743

2744
    let attempt = this.attempt;
10✔
2745

2746
    if (attempt) {
10✔
2747
      if (attempt.address.isNull()) {
1!
2748
        throw new RPCError(errs.MISC_ERROR,
×
2749
          'No addresses available for coinbase.');
2750
      }
2751

2752
      this.miner.updateTime(attempt);
1✔
2753

2754
      return attempt;
1✔
2755
    }
2756

2757
    if (this.miner.addresses.length === 0) {
9!
2758
      throw new RPCError(errs.MISC_ERROR,
×
2759
        'No addresses available for coinbase.');
2760
    }
2761

2762
    attempt = await this.miner.createBlock();
9✔
2763

2764
    if (this.merkleMap.size >= 10)
9!
2765
      this.merkleMap.delete(this.merkleList.shift());
×
2766

2767
    this.attempt = attempt;
9✔
2768
    this.lastActivity = util.now();
9✔
2769
    this.merkleMap.set(attempt.witnessRoot, attempt);
9✔
2770
    this.merkleList.push(attempt.witnessRoot);
9✔
2771

2772
    return attempt;
9✔
2773
  }
2774

2775
  async addBlock(block) {
2776
    const unlock1 = await this.locker.lock();
1✔
2777
    const unlock2 = await this.chain.locker.lock();
1✔
2778
    try {
1✔
2779
      return await this._addBlock(block);
1✔
2780
    } finally {
2781
      unlock2();
1✔
2782
      unlock1();
1✔
2783
    }
2784
  }
2785

2786
  async _addBlock(block) {
2787
    this.logger.info('Handling submitted block: %x.', block.hash());
1✔
2788

2789
    let entry;
2790
    try {
1✔
2791
      entry = await this.chain._add(block);
1✔
2792
    } catch (err) {
2793
      if (err.type === 'VerifyError') {
×
2794
        this.logger.warning('RPC block rejected: %x (%s).',
×
2795
          block.hash(), err.reason);
2796
        return `rejected: ${err.reason}`;
×
2797
      }
2798
      throw err;
×
2799
    }
2800

2801
    if (!entry) {
1!
2802
      this.logger.warning('RPC block rejected: %x (bad-prevblk).',
×
2803
        block.hash());
2804
      return 'rejected: bad-prevblk';
×
2805
    }
2806

2807
    return null;
1✔
2808
  }
2809

2810
  totalTX() {
2811
    return this.mempool ? this.mempool.map.size : 0;
3!
2812
  }
2813

2814
  async getSoftforks() {
2815
    const tip = this.chain.tip;
101✔
2816
    const forks = {};
101✔
2817

2818
    for (const deployment of this.network.deploys) {
101✔
2819
      const state = await this.chain.getState(tip, deployment);
404✔
2820
      let status;
2821

2822
      switch (state) {
404!
2823
        case common.thresholdStates.DEFINED:
2824
          status = 'defined';
36✔
2825
          break;
36✔
2826
        case common.thresholdStates.STARTED:
2827
          status = 'started';
180✔
2828
          break;
180✔
2829
        case common.thresholdStates.LOCKED_IN:
2830
          status = 'locked_in';
2✔
2831
          break;
2✔
2832
        case common.thresholdStates.ACTIVE:
2833
          status = 'active';
1✔
2834
          break;
1✔
2835
        case common.thresholdStates.FAILED:
2836
          status = 'failed';
185✔
2837
          break;
185✔
2838
        default:
2839
          assert(false, 'Bad state.');
×
2840
          break;
×
2841
      }
2842

2843
      forks[deployment.name] = {
404✔
2844
        status: status,
2845
        bit: deployment.bit,
2846
        startTime: deployment.startTime,
2847
        timeout: deployment.timeout
2848
      };
2849

2850
      if (status === 'started') {
404✔
2851
        forks[deployment.name].statistics =
180✔
2852
          await this.chain.getBIP9Stats(tip, deployment);
2853
      }
2854
    }
2855

2856
    return forks;
101✔
2857
  }
2858

2859
  async getHashRate(lookup, height) {
2860
    let tip = this.chain.tip;
×
2861

2862
    if (height != null)
×
2863
      tip = await this.chain.getEntry(height);
×
2864

2865
    if (!tip)
×
2866
      return 0;
×
2867

2868
    assert(typeof lookup === 'number');
×
2869
    assert(lookup >= 0);
×
2870

2871
    if (lookup === 0)
×
2872
      lookup = tip.height % this.network.pow.targetWindow + 1;
×
2873

2874
    if (lookup > tip.height)
×
2875
      lookup = tip.height;
×
2876

2877
    let min = tip.time;
×
2878
    let max = min;
×
2879
    let entry = tip;
×
2880

2881
    for (let i = 0; i < lookup; i++) {
×
2882
      entry = await this.chain.getPrevious(entry);
×
2883

2884
      if (!entry)
×
2885
        throw new RPCError(errs.DATABASE_ERROR, 'Not found.');
×
2886

2887
      min = Math.min(entry.time, min);
×
2888
      max = Math.max(entry.time, max);
×
2889
    }
2890

2891
    const diff = max - min;
×
2892

2893
    if (diff === 0)
×
2894
      return 0;
×
2895

2896
    const work = tip.chainwork.sub(entry.chainwork);
×
2897

2898
    return Number(work.toString()) / diff;
×
2899
  }
2900

2901
  async mineBlocks(blocks, addr, tries) {
2902
    const unlock = await this.locker.lock();
520✔
2903
    try {
520✔
2904
      return await this._mineBlocks(blocks, addr, tries);
520✔
2905
    } finally {
2906
      unlock();
520✔
2907
    }
2908
  }
2909

2910
  async _mineBlocks(blocks, addr, tries) {
2911
    const hashes = [];
520✔
2912

2913
    for (let i = 0; i < blocks; i++) {
520✔
2914
      const block = await this.miner.mineBlock(null, addr);
3,094✔
2915
      const entry = await this.chain.add(block);
3,094✔
2916
      assert(entry);
3,094✔
2917
      hashes.push(entry.hash.toString('hex'));
3,094✔
2918
    }
2919

2920
    return hashes;
520✔
2921
  }
2922

2923
  async findFork(entry) {
2924
    while (entry) {
×
2925
      if (await this.chain.isMainChain(entry))
×
2926
        return entry;
×
2927
      entry = await this.chain.getPrevious(entry);
×
2928
    }
2929
    throw new Error('Fork not found.');
×
2930
  }
2931

2932
  txToJSON(tx, entry) {
2933
    let height = -1;
1✔
2934
    let time = 0;
1✔
2935
    let hash = null;
1✔
2936
    let conf = 0;
1✔
2937

2938
    if (entry) {
1!
2939
      height = entry.height;
1✔
2940
      time = entry.time;
1✔
2941
      hash = entry.hash;
1✔
2942
      conf = this.chain.height - height + 1;
1✔
2943
    }
2944

2945
    const vin = [];
1✔
2946

2947
    for (const input of tx.inputs) {
1✔
2948
      const json = {
1✔
2949
        coinbase: undefined,
2950
        txid: undefined,
2951
        vout: undefined,
2952
        txinwitness: undefined,
2953
        sequence: input.sequence,
2954
        link: input.link
2955
      };
2956

2957
      json.coinbase = tx.isCoinbase();
1✔
2958
      json.txid = input.prevout.txid();
1✔
2959
      json.vout = input.prevout.index;
1✔
2960
      json.txinwitness = input.witness.toJSON();
1✔
2961

2962
      vin.push(json);
1✔
2963
    }
2964

2965
    const vout = [];
1✔
2966

2967
    for (let i = 0; i < tx.outputs.length; i++) {
1✔
2968
      const output = tx.outputs[i];
2✔
2969
      vout.push({
2✔
2970
        value: Amount.coin(output.value, true),
2971
        n: i,
2972
        address: this.addrToJSON(output.address),
2973
        covenant: output.covenant.toJSON()
2974
      });
2975
    }
2976

2977
    return {
1✔
2978
      txid: tx.txid(),
2979
      hash: tx.wtxid(),
2980
      size: tx.getSize(),
2981
      vsize: tx.getVirtualSize(),
2982
      version: tx.version,
2983
      locktime: tx.locktime,
2984
      vin: vin,
2985
      vout: vout,
2986
      blockhash: hash ? hash.toString('hex') : null,
1!
2987
      confirmations: conf,
2988
      time: time,
2989
      blocktime: time,
2990
      hex: undefined
2991
    };
2992
  }
2993

2994
  scriptToJSON(script, hex) {
2995
    const type = script.getType();
×
2996

2997
    const json = {
×
2998
      asm: script.toASM(),
2999
      hex: undefined,
3000
      type: Script.typesByVal[type],
3001
      reqSigs: 1,
3002
      totalSigs: 1,
3003
      p2sh: undefined
3004
    };
3005

3006
    if (hex)
×
3007
      json.hex = script.toJSON();
×
3008

3009
    const [m, n] = script.getMultisig();
×
3010

3011
    if (m !== -1)
×
3012
      json.reqSigs = m;
×
3013

3014
    if (n !== -1)
×
3015
      json.totalSigs = n;
×
3016

3017
    return json;
×
3018
  }
3019

3020
  addrToJSON(addr) {
3021
    return {
2✔
3022
      version: addr.version,
3023
      hash: addr.hash.toString('hex'),
3024
      string: addr.toString(this.network)
3025
    };
3026
  }
3027

3028
  async headerToJSON(entry) {
3029
    const mtp = await this.chain.getMedianTime(entry);
×
3030
    const next = await this.chain.getNextHash(entry.hash);
×
3031

3032
    let confirmations = -1;
×
3033
    if (await this.chain.isMainChain(entry))
×
3034
      confirmations = this.chain.height - entry.height + 1;
×
3035

3036
    return {
×
3037
      hash: entry.hash.toString('hex'),
3038
      confirmations: confirmations,
3039
      height: entry.height,
3040
      version: entry.version,
3041
      versionHex: util.hex32(entry.version),
3042
      merkleroot: entry.merkleRoot.toString('hex'),
3043
      witnessroot: entry.witnessRoot.toString('hex'),
3044
      treeroot: entry.treeRoot.toString('hex'),
3045
      reservedroot: entry.reservedRoot.toString('hex'),
3046
      mask: entry.mask.toString('hex'),
3047
      time: entry.time,
3048
      mediantime: mtp,
3049
      nonce: entry.nonce,
3050
      extranonce: entry.extraNonce.toString('hex'),
3051
      bits: util.hex32(entry.bits),
3052
      difficulty: toDifficulty(entry.bits),
3053
      chainwork: entry.chainwork.toString('hex', 64),
3054
      previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH)
×
3055
        ? entry.prevBlock.toString('hex')
3056
        : null,
3057
      nextblockhash: next ? next.toString('hex') : null
×
3058
    };
3059
  }
3060

3061
  async blockToJSON(entry, block, details) {
3062
    const mtp = await this.chain.getMedianTime(entry);
155✔
3063
    const next = await this.chain.getNextHash(entry.hash);
155✔
3064

3065
    let confirmations = -1;
155✔
3066
    if (await this.chain.isMainChain(entry))
155✔
3067
      confirmations = this.chain.height - entry.height + 1;
153✔
3068

3069
    const txs = [];
155✔
3070

3071
    for (const tx of block.txs) {
155✔
3072
      if (details) {
955!
3073
        const json = this.txToJSON(tx, entry);
×
3074
        txs.push(json);
×
3075
        continue;
×
3076
      }
3077
      txs.push(tx.txid());
955✔
3078
    }
3079

3080
    return {
155✔
3081
      hash: entry.hash.toString('hex'),
3082
      confirmations: confirmations,
3083
      strippedsize: block.getBaseSize(),
3084
      size: block.getSize(),
3085
      weight: block.getWeight(),
3086
      height: entry.height,
3087
      version: entry.version,
3088
      versionHex: util.hex32(entry.version),
3089
      merkleroot: entry.merkleRoot.toString('hex'),
3090
      witnessroot: entry.witnessRoot.toString('hex'),
3091
      treeroot: entry.treeRoot.toString('hex'),
3092
      reservedroot: entry.reservedRoot.toString('hex'),
3093
      mask: entry.mask.toString('hex'),
3094
      coinbase: !details
155!
3095
        ? block.txs[0].inputs[0].witness.toJSON()
3096
        : undefined,
3097
      tx: txs,
3098
      time: entry.time,
3099
      mediantime: mtp,
3100
      nonce: entry.nonce,
3101
      extranonce: entry.extraNonce.toString('hex'),
3102
      bits: util.hex32(entry.bits),
3103
      difficulty: toDifficulty(entry.bits),
3104
      chainwork: entry.chainwork.toString('hex', 64),
3105
      nTx: txs.length,
3106
      previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH)
155✔
3107
        ? entry.prevBlock.toString('hex')
3108
        : null,
3109
      nextblockhash: next ? next.toString('hex') : null
155✔
3110
    };
3111
  }
3112

3113
  entryToJSON(entry) {
3114
    return {
×
3115
      size: entry.size,
3116
      fee: Amount.coin(entry.deltaFee, true),
3117
      modifiedfee: 0,
3118
      time: entry.time,
3119
      height: entry.height,
3120
      startingpriority: entry.priority,
3121
      currentpriority: entry.getPriority(this.chain.height),
3122
      descendantcount: this.mempool.countDescendants(entry),
3123
      descendantsize: entry.descSize,
3124
      descendantfees: entry.descFee,
3125
      ancestorcount: this.mempool.countAncestors(entry),
3126
      ancestorsize: 0,
3127
      ancestorfees: 0,
3128
      depends: this.mempool.getDepends(entry.tx)
3129
    };
3130
  }
3131

3132
  async getNameState(nameHash, safe) {
3133
    if (!safe) {
67✔
3134
      // Will always return null in SPV mode
3135
      return this.chain.db.getNameState(nameHash);
61✔
3136
    }
3137

3138
    // Safe roots are the last Urkel tree commitment
3139
    // with more than 12 confirmations.
3140
    const root = await this.chain.getSafeRoot();
6✔
3141
    let data;
3142
    if (this.chain.options.spv)
6✔
3143
      data = await this.pool.resolveAtRoot(nameHash, root);
3✔
3144
    else
3145
      data = await this.chain.db.lookup(root, nameHash);
3✔
3146

3147
    if (!data)
6!
3148
      return null;
×
3149

3150
    const ns = NameState.decode(data);
6✔
3151
    ns.nameHash = nameHash;
6✔
3152
    return ns;
6✔
3153
  }
3154
}
3155

3156
/*
3157
 * Helpers
3158
 */
3159

3160
function parseAddress(raw, network) {
3161
  try {
506✔
3162
    return Address.fromString(raw, network);
506✔
3163
  } catch (e) {
3164
    throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.');
×
3165
  }
3166
}
3167

3168
function parseSecret(raw, network) {
3169
  try {
×
3170
    return KeyRing.fromSecret(raw, network);
×
3171
  } catch (e) {
3172
    throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.');
×
3173
  }
3174
}
3175

3176
function parseIP(addr, network) {
3177
  let ip;
3178

3179
  try {
×
3180
    ip = IP.fromHostname(addr);
×
3181
  } catch (e) {
3182
    throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET,
×
3183
      'Invalid IP address or subnet.');
3184
  }
3185

3186
  if (ip.port === 0)
×
3187
    ip.port = ip.key ? network.brontidePort : network.port;
×
3188

3189
  return ip;
×
3190
}
3191

3192
function parseNetAddress(addr, network) {
3193
  try {
×
3194
    return NetAddress.fromHostname(addr, network);
×
3195
  } catch (e) {
3196
    throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET,
×
3197
      'Invalid IP address or subnet.');
3198
  }
3199
}
3200

3201
function toDifficulty(bits) {
3202
  let shift = (bits >>> 24) & 0xff;
256✔
3203
  let diff = 0x0000ffff / (bits & 0x00ffffff);
256✔
3204

3205
  while (shift < 29) {
256✔
3206
    diff *= 256.0;
×
3207
    shift++;
×
3208
  }
3209

3210
  while (shift > 29) {
256✔
3211
    diff /= 256.0;
768✔
3212
    shift--;
768✔
3213
  }
3214

3215
  return diff;
256✔
3216
}
3217

3218
/*
3219
 * Expose
3220
 */
3221

3222
module.exports = RPC;
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