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

handshake-org / hsd / 18331449358

08 Oct 2025 02:01AM UTC coverage: 71.65% (+0.01%) from 71.638%
18331449358

Pull #946

github

web-flow
Merge 2e9bb4a81 into 698e252eb
Pull Request #946: Add size_on_disk to getblockchaininfo

8250 of 13372 branches covered (61.7%)

Branch coverage included in aggregate %.

21 of 33 new or added lines in 1 file covered. (63.64%)

3 existing lines in 3 files now uncovered.

26208 of 34720 relevant lines covered (75.48%)

34047.94 hits per line

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

35.83
/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'
553✔
140
        && cmd.method !== 'getblocktemplate'
141
        && cmd.method !== 'getbestblockhash') {
142
      this.logger.debug('Handling RPC call: %s.', cmd.method);
549✔
143

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

148
    if (cmd.method === 'getwork') {
553!
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

260
  /*
261
   * Overall control/query calls
262
   */
263

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

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

288
  async help(args, _help) {
289
    if (args.length === 0)
×
290
      return 'Select a command.';
×
291

292
    const json = {
×
293
      method: args[0],
294
      params: []
295
    };
296

297
    return this.execute(json, true);
×
298
  }
299

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

304
    this.node.close().catch((err) => {
×
305
      setImmediate(() => {
×
306
        throw err;
×
307
      });
308
    });
309

310
    return 'Stopping.';
×
311
  }
312

313
  /*
314
   * P2P networking
315
   */
316

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

321
    const hosts = this.pool.hosts;
1✔
322
    const locals = [];
1✔
323

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

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

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

355
    const valid = new Validator(args);
×
356
    const node = valid.str(0, '');
×
357
    const cmd = valid.str(1, '');
×
358

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

367
        if (!this.pool.peers.get(addr.hostname)) {
×
368
          const peer = this.pool.createOutbound(addr);
×
369
          this.pool.peers.add(peer);
×
370
        }
371

372
        break;
×
373
      }
374
      case 'remove': {
375
        this.pool.hosts.removeNode(node);
×
376
        break;
×
377
      }
378
    }
379

380
    return null;
×
381
  }
382

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

387
    const valid = new Validator(args);
×
388
    const str = valid.str(0, '');
×
389

390
    const addr = parseIP(str, this.network);
×
391
    const peer = this.pool.peers.get(addr.hostname);
×
392

393
    if (peer)
×
394
      peer.destroy();
×
395

396
    return null;
×
397
  }
398

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

403
    const hosts = this.pool.hosts;
×
404
    const valid = new Validator(args);
×
405
    const addr = valid.str(0, '');
×
406

407
    let target;
408
    if (args.length === 1)
×
409
      target = parseIP(addr, this.network);
×
410

411
    const result = [];
×
412

413
    for (const node of hosts.nodes) {
×
414
      if (target) {
×
415
        if (node.host !== target.host)
×
416
          continue;
×
417

418
        if (node.port !== target.port)
×
419
          continue;
×
420
      }
421

422
      const peer = this.pool.peers.get(node.hostname);
×
423

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

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

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

452
    return result;
×
453
  }
454

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

459
    return this.pool.peers.size();
×
460
  }
461

462
  async getNetTotals(args, help) {
463
    let sent = 0;
×
464
    let recv = 0;
×
465

466
    if (help || args.length > 0)
×
467
      throw new RPCError(errs.MISC_ERROR, 'getnettotals');
×
468

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

474
    return {
×
475
      totalbytesrecv: recv,
476
      totalbytessent: sent,
477
      timemillis: Date.now()
478
    };
479
  }
480

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

485
    const valid = new Validator(args);
×
486
    const type = valid.str(0);
×
487
    const peers = [];
×
488

489
    for (let peer = this.pool.peers.head(); peer; peer = peer.next) {
×
490
      if (!peer.connected)
×
491
        continue;
×
492

493
      if (type && peer.outbound !== (type === 'outbound'))
×
494
        continue;
×
495

496
      const offset = this.network.time.known.get(peer.hostname()) || 0;
×
497
      const hashes = [];
×
498

499
      for (const hash in peer.blockMap.keys())
×
500
        hashes.push(hash.toString('hex'));
×
501

502
      peer.getName();
×
503

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

536
    return peers;
×
537
  }
538

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

543
    for (let peer = this.pool.peers.head(); peer; peer = peer.next)
×
544
      peer.sendPing();
×
545

546
    return null;
×
547
  }
548

549
  async setBan(args, help) {
550
    const valid = new Validator(args);
×
551
    const str = valid.str(0, '');
×
552
    const action = valid.str(1, '');
×
553

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

561
    const addr = parseNetAddress(str, this.network);
×
562

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

572
    return null;
×
573
  }
574

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

579
    const banned = [];
×
580

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

590
    return banned;
×
591
  }
592

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

597
    this.pool.hosts.clearBanned();
×
598

599
    return null;
×
600
  }
601

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

607
    const sizes = await this.getBlockchainSizes();
101✔
608

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

632
  async getBlockchainSizes() {
633
    const fs = require('bfile');
101✔
634
    const path = require('path');
101✔
635

636
    /**
637
     * Get directory size recursively
638
     * @param {String} dirPath - Directory path
639
     * @returns {Promise<Number>} - Size in bytes
640
     */
641
    async function getDirectorySize(dirPath) {
642
      let totalSize = 0;
303✔
643

644
      try {
303✔
645
        const stat = await fs.stat(dirPath);
303✔
646

NEW
647
        if (!stat.isDirectory()) {
×
NEW
648
          return stat.size;
×
649
        }
650

NEW
651
        const items = await fs.readdir(dirPath);
×
652

NEW
653
        for (const item of items) {
×
NEW
654
          const itemPath = path.join(dirPath, item);
×
NEW
655
          const itemStat = await fs.stat(itemPath);
×
656

NEW
657
          if (itemStat.isDirectory()) {
×
NEW
658
            totalSize += await getDirectorySize(itemPath);
×
659
          } else {
NEW
660
            totalSize += itemStat.size;
×
661
          }
662
        }
663
      } catch (e) {
664
        // Directory doesn't exist or can't be accessed
665
        return 0;
303✔
666
      }
667

NEW
668
      return totalSize;
×
669
    }
670

671
    const prefix = this.chain.options.prefix;
101✔
672
    const spv = this.chain.options.spv;
101✔
673

674
    let blockchain = 0;
101✔
675
    let tree = 0;
101✔
676
    let total = 0;
101✔
677

678
    // Calculate blockchain size (blocks + chain or spvchain)
679
    if (spv) {
101!
680
      // SPV mode: only spvchain directory
NEW
681
      const spvchainPath = path.join(prefix, 'spvchain');
×
NEW
682
      blockchain = await getDirectorySize(spvchainPath);
×
683
    } else {
684
      // Full node: blocks + chain directories
685
      const blocksPath = path.join(prefix, 'blocks');
101✔
686
      const chainPath = path.join(prefix, 'chain');
101✔
687

688
      blockchain = await getDirectorySize(blocksPath) +
101✔
689
                   await getDirectorySize(chainPath);
690
    }
691

692
    // Calculate tree size (not included in size_on_disk for full nodes)
693
    if (!spv) {
101!
694
      const treePath = path.join(prefix, 'tree');
101✔
695
      tree = await getDirectorySize(treePath);
101✔
696
    }
697

698
    // Calculate total size blockchain + tree
699
    total = blockchain + tree;
101✔
700

701
    return {
101✔
702
      blockchain: blockchain,
703
      tree: tree,
704
      total: total
705
    };
706
  }
707

708
  async getBestBlockHash(args, help) {
709
    if (help || args.length !== 0)
×
710
      throw new RPCError(errs.MISC_ERROR, 'getbestblockhash');
×
711

712
    return this.chain.tip.hash.toString('hex');
×
713
  }
714

715
  async getBlockCount(args, help) {
716
    if (help || args.length !== 0)
1!
717
      throw new RPCError(errs.MISC_ERROR, 'getblockcount');
×
718

719
    return this.chain.tip.height;
1✔
720
  }
721

722
  async getBlock(args, help) {
723
    if (help || args.length < 1 || args.length > 3)
145!
724
      throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )');
×
725

726
    const valid = new Validator(args);
145✔
727
    const hash = valid.bhash(0);
145✔
728
    const verbose = valid.bool(1, true);
145✔
729
    const details = valid.bool(2, false);
145✔
730

731
    if (!hash)
145!
732
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
733

734
    const entry = await this.chain.getEntry(hash);
145✔
735

736
    if (!entry)
145!
737
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
738

739
    const block = await this.chain.getBlock(entry.hash);
145✔
740

741
    if (!block) {
145✔
742
      if (this.chain.options.spv)
10!
743
        throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)');
×
744

745
      if (this.chain.options.prune) {
10!
746
        throw new RPCError(errs.MISC_ERROR,
10✔
747
          'Block not available (pruned data)');
748
      }
749

750
      throw new RPCError(errs.MISC_ERROR, 'Can\'t read block from disk');
×
751
    }
752

753
    if (!verbose)
135!
754
      return block.toHex();
×
755

756
    return this.blockToJSON(entry, block, details);
135✔
757
  }
758

759
  async getBlockByHeight(args, help) {
760
    if (help || args.length < 1 || args.length > 3) {
20!
761
      throw new RPCError(errs.MISC_ERROR,
×
762
        'getblockbyheight "height" ( verbose )');
763
    }
764

765
    const valid = new Validator(args);
20✔
766
    const height = valid.u32(0, -1);
20✔
767
    const verbose = valid.bool(1, true);
20✔
768
    const details = valid.bool(2, false);
20✔
769

770
    if (height === -1)
20!
771
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block height.');
×
772

773
    const entry = await this.chain.getEntry(height);
20✔
774

775
    if (!entry)
20!
776
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
777

778
    const block = await this.chain.getBlock(entry.hash);
20✔
779

780
    if (!block) {
20!
781
      if (this.chain.options.spv)
×
782
        throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)');
×
783

784
      if (this.chain.options.prune) {
×
785
        throw new RPCError(errs.MISC_ERROR,
×
786
          'Block not available (pruned data)');
787
      }
788

789
      throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk');
×
790
    }
791

792
    if (!verbose)
20!
793
      return block.toHex();
×
794

795
    return this.blockToJSON(entry, block, details);
20✔
796
  }
797

798
  async getBlockHash(args, help) {
799
    if (help || args.length !== 1)
×
800
      throw new RPCError(errs.MISC_ERROR, 'getblockhash index');
×
801

802
    const valid = new Validator(args);
×
803
    const height = valid.u32(0);
×
804

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

808
    const hash = await this.chain.getHash(height);
×
809

810
    if (!hash)
×
811
      throw new RPCError(errs.MISC_ERROR, 'Not found.');
×
812

813
    return hash.toString('hex');
×
814
  }
815

816
  async getBlockHeader(args, help) {
817
    if (help || args.length < 1 || args.length > 2)
×
818
      throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )');
×
819

820
    const valid = new Validator(args);
×
821
    const hash = valid.bhash(0);
×
822
    const verbose = valid.bool(1, true);
×
823

824
    if (!hash)
×
825
      throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.');
×
826

827
    const entry = await this.chain.getEntry(hash);
×
828

829
    if (!entry)
×
830
      throw new RPCError(errs.MISC_ERROR, 'Block not found');
×
831

832
    if (!verbose)
×
833
      return entry.encode().toString('hex', 36, 36 + consensus.HEADER_SIZE);
×
834

835
    return this.headerToJSON(entry);
×
836
  }
837

838
  async getChainTips(args, help) {
839
    if (help || args.length !== 0)
×
840
      throw new RPCError(errs.MISC_ERROR, 'getchaintips');
×
841

842
    const tips = await this.chain.getTips();
×
843
    const result = [];
×
844

845
    for (const hash of tips) {
×
846
      const entry = await this.chain.getEntry(hash);
×
847

848
      assert(entry);
×
849

850
      const fork = await this.findFork(entry);
×
851
      const main = await this.chain.isMainChain(entry);
×
852

853
      result.push({
×
854
        height: entry.height,
855
        hash: entry.hash.toString('hex'),
856
        branchlen: entry.height - fork.height,
857
        status: main ? 'active' : 'valid-headers'
×
858
      });
859
    }
860

861
    return result;
×
862
  }
863

864
  async getDifficulty(args, help) {
865
    if (help || args.length !== 0)
×
866
      throw new RPCError(errs.MISC_ERROR, 'getdifficulty');
×
867

868
    return toDifficulty(this.chain.tip.bits);
×
869
  }
870

871
  async getMempoolInfo(args, help) {
872
    if (help || args.length !== 0)
×
873
      throw new RPCError(errs.MISC_ERROR, 'getmempoolinfo');
×
874

875
    if (!this.mempool)
×
876
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
877

878
    return {
×
879
      size: this.mempool.map.size,
880
      bytes: this.mempool.getSize(),
881
      usage: this.mempool.getSize(),
882
      maxmempool: this.mempool.options.maxSize,
883
      mempoolminfee: Amount.coin(this.mempool.options.minRelay, true)
884
    };
885
  }
886

887
  async getMempoolAncestors(args, help) {
888
    if (help || args.length < 1 || args.length > 2)
×
889
      throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)');
×
890

891
    const valid = new Validator(args);
×
892
    const hash = valid.bhash(0);
×
893
    const verbose = valid.bool(1, false);
×
894

895
    if (!this.mempool)
×
896
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
897

898
    if (!hash)
×
899
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
900

901
    const entry = this.mempool.getEntry(hash);
×
902

903
    if (!entry)
×
904
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
905

906
    const entries = this.mempool.getAncestors(entry);
×
907
    const out = [];
×
908

909
    if (verbose) {
×
910
      for (const entry of entries)
×
911
        out.push(this.entryToJSON(entry));
×
912
    } else {
913
      for (const entry of entries)
×
914
        out.push(entry.txid());
×
915
    }
916

917
    return out;
×
918
  }
919

920
  async getMempoolDescendants(args, help) {
921
    if (help || args.length < 1 || args.length > 2) {
×
922
      throw new RPCError(errs.MISC_ERROR,
×
923
        'getmempooldescendants txid (verbose)');
924
    }
925

926
    const valid = new Validator(args);
×
927
    const hash = valid.bhash(0);
×
928
    const verbose = valid.bool(1, false);
×
929

930
    if (!this.mempool)
×
931
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
932

933
    if (!hash)
×
934
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
935

936
    const entry = this.mempool.getEntry(hash);
×
937

938
    if (!entry)
×
939
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
940

941
    const entries = this.mempool.getDescendants(entry);
×
942
    const out = [];
×
943

944
    if (verbose) {
×
945
      for (const entry of entries)
×
946
        out.push(this.entryToJSON(entry));
×
947
    } else {
948
      for (const entry of entries)
×
949
        out.push(entry.txid());
×
950
    }
951

952
    return out;
×
953
  }
954

955
  async getMempoolEntry(args, help) {
956
    if (help || args.length !== 1)
×
957
      throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid');
×
958

959
    const valid = new Validator(args);
×
960
    const hash = valid.bhash(0);
×
961

962
    if (!this.mempool)
×
963
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
964

965
    if (!hash)
×
966
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
967

968
    const entry = this.mempool.getEntry(hash);
×
969

970
    if (!entry)
×
971
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
972

973
    return this.entryToJSON(entry);
×
974
  }
975

976
  async getRawMempool(args, help) {
977
    if (help || args.length > 1)
1!
978
      throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )');
×
979

980
    const valid = new Validator(args);
1✔
981
    const verbose = valid.bool(0, false);
1✔
982

983
    if (!this.mempool)
1!
984
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
985

986
    if (verbose) {
1!
987
      const out = Object.create(null);
1✔
988

989
      for (const entry of this.mempool.map.values())
1✔
990
        out[entry.txid()] = this.entryToJSON(entry);
×
991

992
      return out;
1✔
993
    }
994

995
    const hashes = this.mempool.getSnapshot();
×
996

997
    return hashes.map(hash => hash.toString('hex'));
×
998
  }
999

1000
  async getTXOut(args, help) {
1001
    if (help || args.length < 2 || args.length > 3) {
×
1002
      throw new RPCError(errs.MISC_ERROR,
×
1003
        'gettxout "txid" n ( includemempool )');
1004
    }
1005

1006
    const valid = new Validator(args);
×
1007
    const hash = valid.bhash(0);
×
1008
    const index = valid.u32(1);
×
1009
    const mempool = valid.bool(2, true);
×
1010

1011
    if (this.chain.options.spv)
×
1012
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.');
×
1013

1014
    if (this.chain.options.prune)
×
1015
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.');
×
1016

1017
    if (!hash || index == null)
×
1018
      throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.');
×
1019

1020
    let coin;
1021
    if (mempool) {
×
1022
      if (!this.mempool)
×
1023
        throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
1024
      coin = this.mempool.getCoin(hash, index);
×
1025
    }
1026

1027
    if (!coin)
×
1028
      coin = await this.chain.getCoin(hash, index);
×
1029

1030
    if (!coin)
×
1031
      return null;
×
1032

1033
    return {
×
1034
      bestblock: this.chain.tip.hash.toString('hex'),
1035
      confirmations: coin.getDepth(this.chain.height),
1036
      value: Amount.coin(coin.value, true),
1037
      address: this.addrToJSON(coin.address),
1038
      version: coin.version,
1039
      coinbase: coin.coinbase
1040
    };
1041
  }
1042

1043
  async getTXOutProof(args, help) {
1044
    if (help || (args.length !== 1 && args.length !== 2)) {
×
1045
      throw new RPCError(errs.MISC_ERROR,
×
1046
        'gettxoutproof ["txid",...] ( blockhash )');
1047
    }
1048

1049
    const valid = new Validator(args);
×
1050
    const txids = valid.array(0);
×
1051
    const hash = valid.bhash(1);
×
1052

1053
    if (this.chain.options.spv)
×
1054
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.');
×
1055

1056
    if (this.chain.options.prune)
×
1057
      throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.');
×
1058

1059
    if (!txids || txids.length === 0)
×
1060
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.');
×
1061

1062
    const items = new Validator(txids);
×
1063
    const set = new BufferSet();
×
1064
    const hashes = [];
×
1065

1066
    let last = null;
×
1067

1068
    for (let i = 0; i < txids.length; i++) {
×
1069
      const hash = items.bhash(i);
×
1070

1071
      if (!hash)
×
1072
        throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
1073

1074
      if (set.has(hash))
×
1075
        throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate txid.');
×
1076

1077
      set.add(hash);
×
1078
      hashes.push(hash);
×
1079

1080
      last = hash;
×
1081
    }
1082

1083
    let block = null;
×
1084

1085
    if (hash) {
×
1086
      block = await this.chain.getBlock(hash);
×
1087
    } else if (this.chain.options.indexTX) {
×
1088
      const tx = await this.chain.getMeta(last);
×
1089
      if (tx)
×
1090
        block = await this.chain.getBlock(tx.block);
×
1091
    } else {
1092
      const coin = await this.chain.getCoin(last, 0);
×
1093
      if (coin)
×
1094
        block = await this.chain.getBlock(coin.height);
×
1095
    }
1096

1097
    if (!block)
×
1098
      throw new RPCError(errs.MISC_ERROR, 'Block not found.');
×
1099

1100
    const whashes = [];
×
1101

1102
    for (const hash of hashes) {
×
1103
      const index = block.indexOf(hash);
×
1104

1105
      if (index === -1) {
×
1106
        throw new RPCError(errs.VERIFY_ERROR,
×
1107
          'Block does not contain all txids.');
1108
      }
1109

1110
      const tx = block.txs[index];
×
1111

1112
      whashes.push(tx.hash());
×
1113
    }
1114

1115
    const mblock = MerkleBlock.fromHashes(block, whashes);
×
1116

1117
    return mblock.toHex();
×
1118
  }
1119

1120
  async verifyTXOutProof(args, help) {
1121
    if (help || args.length !== 1)
×
1122
      throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"');
×
1123

1124
    const valid = new Validator(args);
×
1125
    const data = valid.buf(0);
×
1126

1127
    if (!data)
×
1128
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1129

1130
    const block = MerkleBlock.decode(data);
×
1131

1132
    if (!block.verify())
×
1133
      return [];
×
1134

1135
    const entry = await this.chain.getEntry(block.hash());
×
1136

1137
    if (!entry)
×
1138
      throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.');
×
1139

1140
    const tree = block.getTree();
×
1141
    const out = [];
×
1142

1143
    for (const hash of tree.matches)
×
1144
      out.push(hash.toString('hex'));
×
1145

1146
    return out;
×
1147
  }
1148

1149
  async getTXOutSetInfo(args, help) {
1150
    if (help || args.length !== 0)
×
1151
      throw new RPCError(errs.MISC_ERROR, 'gettxoutsetinfo');
×
1152

1153
    if (this.chain.options.spv) {
×
1154
      throw new RPCError(errs.MISC_ERROR,
×
1155
        'Chainstate not available (SPV mode).');
1156
    }
1157

1158
    return {
×
1159
      height: this.chain.height,
1160
      bestblock: this.chain.tip.hash.toString('hex'),
1161
      transactions: this.chain.db.state.tx,
1162
      txouts: this.chain.db.state.coin,
1163
      bytes_serialized: 0,
1164
      hash_serialized: 0,
1165
      total_amount: Amount.coin(this.chain.db.state.value, true),
1166
      total_burned: Amount.coin(this.chain.db.state.burned, true)
1167
    };
1168
  }
1169

1170
  async pruneBlockchain(args, help) {
1171
    if (help || args.length !== 0)
5✔
1172
      throw new RPCError(errs.MISC_ERROR, 'pruneblockchain');
1✔
1173

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

1177
    if (this.chain.options.prune)
3✔
1178
      throw new RPCError(errs.MISC_ERROR, 'Chain is already pruned.');
1✔
1179

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

1183
    try {
1✔
1184
      await this.chain.prune();
1✔
1185
    } catch (e) {
1186
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1187
    }
1188
  }
1189

1190
    async compactTree(args, help) {
1191
    if (help || args.length !== 0)
2!
1192
      throw new RPCError(errs.MISC_ERROR, 'compacttree');
×
1193

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

1197
    try {
1✔
1198
      await this.chain.compactTree();
1✔
1199
    } catch (e) {
1200
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1201
    }
1202
  }
1203

1204
  async reconstructTree(args, help) {
1205
    if (help || args.length !== 0)
2!
1206
      throw new RPCError(errs.MISC_ERROR, 'reconstructtree');
×
1207

1208
    if (this.chain.options.spv) {
2✔
1209
      throw new RPCError(errs.MISC_ERROR,
1✔
1210
        'Cannot reconstruct tree in SPV mode.');
1211
    }
1212

1213
    if (this.chain.options.prune) {
1!
1214
      throw new RPCError(errs.MISC_ERROR,
×
1215
        'Cannot reconstruct tree in pruned node.');
1216
    }
1217

1218
    try {
1✔
1219
      await this.chain.reconstructTree();
1✔
1220
    } catch (e) {
1221
      throw new RPCError(errs.DATABASE_ERROR, e.message);
×
1222
    }
1223
  }
1224

1225
  async verifyChain(args, help) {
1226
    if (help || args.length > 2) {
×
1227
      throw new RPCError(errs.MISC_ERROR,
×
1228
        'verifychain ( checklevel numblocks )');
1229
    }
1230

1231
    const valid = new Validator(args);
×
1232
    const level = valid.u32(0);
×
1233
    const blocks = valid.u32(1);
×
1234

1235
    if (level == null || blocks == null)
×
1236
      throw new RPCError(errs.TYPE_ERROR, 'Missing parameters.');
×
1237

1238
    if (this.chain.options.spv)
×
1239
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain in SPV mode.');
×
1240

1241
    if (this.chain.options.prune)
×
1242
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain when pruned.');
×
1243

1244
    return null;
×
1245
  }
1246

1247
  /*
1248
   * Mining
1249
   */
1250

1251
  async handleWork(data, mask) {
1252
    const unlock = await this.locker.lock();
7✔
1253
    try {
7✔
1254
      return await this._handleWork(data, mask);
7✔
1255
    } finally {
1256
      unlock();
7✔
1257
    }
1258
  }
1259

1260
  async _handleWork(data, mask) {
1261
    if (data.length !== 256)
7!
1262
      return [false, 'invalid-data-length'];
×
1263

1264
    const hdr = Headers.fromMiner(data);
7✔
1265
    const maskHash = blake2b.multi(hdr.prevBlock, mask);
7✔
1266

1267
    if (!hdr.maskHash().equals(maskHash))
7!
1268
      return [false, 'bad-maskhash'];
×
1269

1270
    const attempt = this.merkleMap.get(hdr.witnessRoot);
7✔
1271

1272
    if (!attempt)
7✔
1273
      return [false, 'stale'];
3✔
1274

1275
    if (!hdr.prevBlock.equals(attempt.prevBlock)
4!
1276
        || hdr.bits !== attempt.bits) {
1277
      return [false, 'stale'];
×
1278
    }
1279

1280
    const {nonce, time, extraNonce} = hdr;
4✔
1281
    const proof = attempt.getProof(nonce, time, extraNonce, mask);
4✔
1282

1283
    if (!proof.verify(attempt.target, this.network))
4!
1284
      return [false, 'bad-diffbits'];
×
1285

1286
    const block = attempt.commit(proof);
4✔
1287

1288
    let entry;
1289
    try {
4✔
1290
      entry = await this.chain.add(block);
4✔
1291
    } catch (err) {
1292
      if (err.type === 'VerifyError') {
×
1293
        this.logger.warning('RPC block rejected: %x (%s).',
×
1294
          block.hash(), err.reason);
1295
        return [false, err.reason];
×
1296
      }
1297
      throw err;
×
1298
    }
1299

1300
    if (!entry) {
4!
1301
      this.logger.warning('RPC block rejected: %x (bad-prevblk).',
×
1302
        block.hash());
1303
      return [false, 'bad-prevblk'];
×
1304
    }
1305

1306
    return [true, 'valid'];
4✔
1307
  }
1308

1309
  async createWork() {
1310
    const unlock = await this.locker.lock();
10✔
1311
    try {
10✔
1312
      return await this._createWork();
10✔
1313
    } finally {
1314
      unlock();
10✔
1315
    }
1316
  }
1317

1318
  async _createWork() {
1319
    const attempt = await this.updateWork();
10✔
1320
    const time = attempt.time;
10✔
1321
    const nonce = consensus.ZERO_NONCE;
10✔
1322
    const mask = consensus.ZERO_HASH;
10✔
1323
    const data = attempt.getHeader(0, time, nonce, mask);
10✔
1324

1325
    return {
10✔
1326
      network: this.network.type,
1327
      data: data.toString('hex'),
1328
      target: attempt.target.toString('hex'),
1329
      height: attempt.height,
1330
      time: this.network.now(),
1331
      fee: attempt.fees
1332
    };
1333
  }
1334

1335
  async getWorkLongpoll(args, help) {
1336
    await this.longpoll();
×
1337
    return this.createWork();
×
1338
  }
1339

1340
  async getWork(args, help) {
1341
    if (help || args.length !== 0)
10!
1342
      throw new RPCError(errs.MISC_ERROR, 'getwork');
×
1343

1344
    return this.createWork();
10✔
1345
  }
1346

1347
  async submitWork(args, help) {
1348
    if (help || args.length < 1 || args.length > 2)
7!
1349
      throw new RPCError(errs.MISC_ERROR, 'submitwork ( "data" "mask" )');
×
1350

1351
    const valid = new Validator(args);
7✔
1352
    const data = valid.buf(0);
7✔
1353

1354
    if (!data)
7!
1355
      throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.');
×
1356

1357
    let mask = consensus.ZERO_HASH;
7✔
1358

1359
    if (args.length === 2) {
7!
1360
      mask = valid.bhash(1);
×
1361

1362
      if (!mask)
×
1363
        throw new RPCError(errs.TYPE_ERROR, 'Invalid mask.');
×
1364
    }
1365

1366
    return this.handleWork(data, mask);
7✔
1367
  }
1368

1369
  async submitBlock(args, help) {
1370
    if (help || args.length < 1 || args.length > 2) {
1!
1371
      throw new RPCError(errs.MISC_ERROR,
×
1372
        'submitblock "hexdata" ( "jsonparametersobject" )');
1373
    }
1374

1375
    const valid = new Validator(args);
1✔
1376
    const data = valid.buf(0);
1✔
1377

1378
    const block = Block.decode(data);
1✔
1379

1380
    return this.addBlock(block);
1✔
1381
  }
1382

1383
  async getBlockTemplate(args, help) {
1384
    if (help || args.length > 1) {
4!
1385
      throw new RPCError(errs.MISC_ERROR,
×
1386
        'getblocktemplate ( "jsonrequestobject" )');
1387
    }
1388

1389
    const validator = new Validator(args);
4✔
1390
    const options = validator.obj(0, {});
4✔
1391
    const valid = new Validator(options);
4✔
1392
    const mode = valid.str('mode', 'template');
4✔
1393

1394
    if (mode !== 'template' && mode !== 'proposal')
4!
1395
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid mode.');
×
1396

1397
    if (mode === 'proposal') {
4✔
1398
      const data = valid.buf('data');
1✔
1399

1400
      if (!data)
1!
1401
        throw new RPCError(errs.TYPE_ERROR, 'Missing data parameter.');
×
1402

1403
      const block = Block.decode(data);
1✔
1404

1405
      if (!block.prevBlock.equals(this.chain.tip.hash))
1!
1406
        return 'inconclusive-not-best-prevblk';
×
1407

1408
      try {
1✔
1409
        await this.chain.verifyBlock(block);
1✔
1410
      } catch (e) {
1411
        if (e.type === 'VerifyError')
×
1412
          return e.reason;
×
1413
        throw e;
×
1414
      }
1415

1416
      return null;
1✔
1417
    }
1418

1419
    let maxVersion = valid.u32('maxversion', -1);
3✔
1420
    let rules = valid.array('rules');
3✔
1421

1422
    if (rules)
3✔
1423
      maxVersion = -1;
2✔
1424

1425
    const capabilities = valid.array('capabilities');
3✔
1426
    let coinbase = false;
3✔
1427

1428
    if (capabilities) {
3!
1429
      let txnCap = false;
×
1430
      let valueCap = false;
×
1431

1432
      for (const capability of capabilities) {
×
1433
        if (typeof capability !== 'string')
×
1434
          throw new RPCError(errs.TYPE_ERROR, 'Invalid capability.');
×
1435

1436
        switch (capability) {
×
1437
          case 'coinbasetxn':
1438
            txnCap = true;
×
1439
            break;
×
1440
          case 'coinbasevalue':
1441
            // Prefer value if they support it.
1442
            valueCap = true;
×
1443
            break;
×
1444
        }
1445
      }
1446

1447
      if (txnCap && !valueCap) {
×
1448
        if (this.miner.addresses.length === 0) {
×
1449
          throw new RPCError(errs.MISC_ERROR,
×
1450
            'No addresses available for coinbase.');
1451
        }
1452
        coinbase = true;
×
1453
      }
1454
    }
1455

1456
    if (!this.network.selfConnect) {
3!
1457
      if (this.pool.peers.size() === 0) {
×
1458
        throw new RPCError(errs.CLIENT_NOT_CONNECTED,
×
1459
          'Node is not connected!');
1460
      }
1461

1462
      if (!this.chain.synced) {
×
1463
        throw new RPCError(errs.CLIENT_IN_INITIAL_DOWNLOAD,
×
1464
          'Node is downloading blocks...');
1465
      }
1466
    }
1467

1468
    const lpid = valid.str('longpollid');
3✔
1469

1470
    if (lpid)
3!
1471
      await this.handleLongpoll(lpid);
×
1472

1473
    if (!rules)
3✔
1474
      rules = [];
1✔
1475

1476
    return this.createTemplate(maxVersion, coinbase, rules);
3✔
1477
  }
1478

1479
  async createTemplate(maxVersion, coinbase, rules) {
1480
    const unlock = await this.locker.lock();
3✔
1481
    try {
3✔
1482
      return await this._createTemplate(maxVersion, coinbase, rules);
3✔
1483
    } finally {
1484
      unlock();
3✔
1485
    }
1486
  }
1487

1488
  async _createTemplate(maxVersion, coinbase, rules) {
1489
    const attempt = await this.getTemplate();
3✔
1490
    const scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR;
3!
1491

1492
    // Default mutable fields.
1493
    const mutable = ['time', 'transactions', 'prevblock'];
3✔
1494

1495
    // The miner doesn't support
1496
    // versionbits. Force them to
1497
    // encode our version.
1498
    if (maxVersion >= 2)
3!
1499
      mutable.push('version/force');
×
1500

1501
    // Allow the miner to change
1502
    // our provided coinbase.
1503
    // Note that these are implied
1504
    // without `coinbasetxn`.
1505
    if (coinbase) {
3!
1506
      mutable.push('coinbase');
×
1507
      mutable.push('coinbase/append');
×
1508
      mutable.push('generation');
×
1509
    }
1510

1511
    // Build an index of every transaction.
1512
    const index = new BufferMap();
3✔
1513
    for (let i = 0; i < attempt.items.length; i++) {
3✔
1514
      const entry = attempt.items[i];
4✔
1515
      index.set(entry.hash, i + 1);
4✔
1516
    }
1517

1518
    // Calculate dependencies for each transaction.
1519
    const txs = [];
3✔
1520
    for (let i = 0; i < attempt.items.length; i++) {
3✔
1521
      const entry = attempt.items[i];
4✔
1522
      const tx = entry.tx;
4✔
1523
      const deps = [];
4✔
1524

1525
      for (let j = 0; j < tx.inputs.length; j++) {
4✔
1526
        const input = tx.inputs[j];
4✔
1527
        const dep = index.get(input.prevout.hash);
4✔
1528

1529
        if (dep == null)
4!
1530
          continue;
4✔
1531

1532
        if (deps.indexOf(dep) === -1) {
×
1533
          assert(dep < i + 1);
×
1534
          deps.push(dep);
×
1535
        }
1536
      }
1537

1538
      txs.push({
4✔
1539
        data: tx.toHex(),
1540
        txid: tx.txid(),
1541
        hash: tx.wtxid(),
1542
        depends: deps,
1543
        fee: entry.fee,
1544
        sigops: entry.sigops / scale | 0,
1545
        weight: tx.getWeight()
1546
      });
1547
    }
1548

1549
    // Calculate version based on given rules.
1550
    let version = attempt.version;
3✔
1551

1552
    const vbavailable = {};
3✔
1553
    const vbrules = [];
3✔
1554

1555
    for (const deploy of this.network.deploys) {
3✔
1556
      const state = await this.chain.getState(this.chain.tip, deploy);
12✔
1557

1558
      let name = deploy.name;
12✔
1559

1560
      switch (state) {
12!
1561
        case common.thresholdStates.DEFINED:
1562
        case common.thresholdStates.FAILED:
1563
          break;
12✔
1564
        case common.thresholdStates.LOCKED_IN:
1565
          version |= 1 << deploy.bit;
×
1566
        case common.thresholdStates.STARTED:
1567
          if (!deploy.force) {
×
1568
            if (rules.indexOf(name) === -1)
×
1569
              version &= ~(1 << deploy.bit);
×
1570
            if (deploy.required)
×
1571
              name = '!' + name;
×
1572
          }
1573
          vbavailable[name] = deploy.bit;
×
1574
          break;
×
1575
        case common.thresholdStates.ACTIVE:
1576
          if (!deploy.force && deploy.required) {
×
1577
            if (rules.indexOf(name) === -1) {
×
1578
              throw new RPCError(errs.INVALID_PARAMETER,
×
1579
                `Client must support ${name}.`);
1580
            }
1581
            name = '!' + name;
×
1582
          }
1583
          vbrules.push(name);
×
1584
          break;
×
1585
        default:
1586
          assert(false, 'Bad state.');
×
1587
          break;
×
1588
      }
1589
    }
1590

1591
    version >>>= 0;
3✔
1592

1593
    const json = {
3✔
1594
      capabilities: ['proposal'],
1595
      mutable: mutable,
1596
      version: version,
1597
      rules: vbrules,
1598
      vbavailable: vbavailable,
1599
      vbrequired: 0,
1600
      height: attempt.height,
1601
      previousblockhash: attempt.prevBlock.toString('hex'),
1602
      merkleroot: undefined,
1603
      witnessroot: undefined,
1604
      treeroot: attempt.treeRoot.toString('hex'),
1605
      reservedroot: attempt.reservedRoot.toString('hex'),
1606
      mask: consensus.ZERO_HASH.toString('hex'), // Compat.
1607
      target: attempt.target.toString('hex'),
1608
      bits: util.hex32(attempt.bits),
1609
      noncerange:
1610
        Array(consensus.NONCE_SIZE + 1).join('00')
1611
        + Array(consensus.NONCE_SIZE + 1).join('ff'),
1612
      curtime: attempt.time,
1613
      mintime: attempt.mtp + 1,
1614
      maxtime: attempt.time + 7200,
1615
      expires: attempt.time + 7200,
1616
      sigoplimit: consensus.MAX_BLOCK_SIGOPS,
1617
      // sizelimit: consensus.MAX_RAW_BLOCK_SIZE,
1618
      sizelimit: consensus.MAX_BLOCK_SIZE,
1619
      weightlimit: consensus.MAX_BLOCK_WEIGHT,
1620
      longpollid: this.chain.tip.hash.toString('hex')
1621
                  + util.hex32(this.totalTX()),
1622
      submitold: false,
1623
      coinbaseaux: {
1624
        flags: attempt.coinbaseFlags.toString('hex')
1625
      },
1626
      coinbasevalue: attempt.getReward(),
1627
      coinbasetxn: undefined,
1628
      claims: attempt.claims.map((claim) => {
1629
        let value = claim.value;
×
1630
        let fee = claim.fee;
×
1631

1632
        // Account for mining software which creates its own
1633
        // coinbase with something other than `coinbasevalue`.
1634
        if (attempt.height >= this.network.deflationHeight) {
×
1635
          if (claim.commitHeight !== 1) {
×
1636
            value = claim.value - claim.fee;
×
1637
            fee = 0;
×
1638
          }
1639
        }
1640

1641
        return {
×
1642
          data: claim.blob.toString('hex'),
1643
          name: claim.name.toString('binary'),
1644
          namehash: claim.nameHash.toString('hex'),
1645
          version: claim.address.version,
1646
          hash: claim.address.hash.toString('hex'),
1647
          value: value,
1648
          fee: fee,
1649
          weak: claim.weak,
1650
          commitHash: claim.commitHash.toString('hex'),
1651
          commitHeight: claim.commitHeight,
1652
          weight: claim.getWeight()
1653
        };
1654
      }),
1655
      airdrops: attempt.airdrops.map((airdrop) => {
1656
        return {
×
1657
          data: airdrop.blob.toString('hex'),
1658
          position: airdrop.position,
1659
          version: airdrop.address.version,
1660
          address: airdrop.address.hash.toString('hex'),
1661
          value: airdrop.value,
1662
          fee: airdrop.fee,
1663
          rate: airdrop.rate,
1664
          weak: airdrop.weak
1665
        };
1666
      }),
1667
      transactions: txs
1668
    };
1669

1670
    // The client wants a coinbasetxn
1671
    // instead of a coinbasevalue.
1672
    if (coinbase) {
3!
1673
      const tx = attempt.coinbase;
×
1674

1675
      json.merkleroot = attempt.merkleRoot.toString('hex');
×
1676
      json.witnessroot = attempt.witnessRoot.toString('hex');
×
1677

1678
      json.coinbasetxn = {
×
1679
        data: tx.toHex(),
1680
        txid: tx.txid(),
1681
        hash: tx.wtxid(),
1682
        depends: [],
1683
        fee: 0,
1684
        sigops: tx.getSigops() / scale | 0,
1685
        weight: tx.getWeight()
1686
      };
1687
    }
1688

1689
    return json;
3✔
1690
  }
1691

1692
  async getMiningInfo(args, help) {
1693
    if (help || args.length !== 0)
×
1694
      throw new RPCError(errs.MISC_ERROR, 'getmininginfo');
×
1695

1696
    const attempt = this.attempt;
×
1697

1698
    let size = 0;
×
1699
    let weight = 0;
×
1700
    let txs = 0;
×
1701
    let diff = 0;
×
1702

1703
    if (attempt) {
×
1704
      weight = attempt.weight;
×
1705
      txs = attempt.items.length + 1;
×
1706
      diff = attempt.getDifficulty();
×
1707
      size = 1000;
×
1708
      for (const item of attempt.items)
×
1709
        size += item.tx.getBaseSize();
×
1710
    }
1711

1712
    return {
×
1713
      blocks: this.chain.height,
1714
      currentblocksize: size,
1715
      currentblockweight: weight,
1716
      currentblocktx: txs,
1717
      difficulty: diff,
1718
      errors: '',
1719
      genproclimit: this.procLimit,
1720
      networkhashps: await this.getHashRate(120),
1721
      pooledtx: this.totalTX(),
1722
      testnet: this.network !== Network.main,
1723
      chain: this.network.type !== 'testnet'
×
1724
        ? this.network.type
1725
        : 'test',
1726
      generate: this.mining
1727
    };
1728
  }
1729

1730
  async getNetworkHashPS(args, help) {
1731
    if (help || args.length > 2)
×
1732
      throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )');
×
1733

1734
    const valid = new Validator(args);
×
1735
    const lookup = valid.u32(0, 120);
×
1736
    const height = valid.u32(1);
×
1737

1738
    return this.getHashRate(lookup, height);
×
1739
  }
1740

1741
  async prioritiseTransaction(args, help) {
1742
    if (help || args.length !== 3) {
1!
1743
      throw new RPCError(errs.MISC_ERROR,
×
1744
        'prioritisetransaction <txid> <priority delta> <fee delta>');
1745
    }
1746

1747
    const valid = new Validator(args);
1✔
1748
    const hash = valid.bhash(0);
1✔
1749
    const pri = valid.i64(1);
1✔
1750
    const fee = valid.i64(2);
1✔
1751

1752
    if (!this.mempool)
1!
1753
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
1754

1755
    if (!hash)
1!
1756
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID');
×
1757

1758
    if (pri == null || fee == null)
1!
1759
      throw new RPCError(errs.TYPE_ERROR, 'Invalid fee or priority.');
×
1760

1761
    const entry = this.mempool.getEntry(hash);
1✔
1762

1763
    if (!entry)
1!
1764
      throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.');
×
1765

1766
    this.mempool.prioritise(entry, pri, fee);
1✔
1767

1768
    return true;
1✔
1769
  }
1770

1771
  async verifyBlock(args, help) {
1772
    if (help || args.length !== 1)
×
1773
      throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"');
×
1774

1775
    const valid = new Validator(args);
×
1776
    const data = valid.buf(0);
×
1777

1778
    if (!data)
×
1779
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hex.');
×
1780

1781
    if (this.chain.options.spv)
×
1782
      throw new RPCError(errs.MISC_ERROR, 'Cannot verify block in SPV mode.');
×
1783

1784
    const block = Block.decode(data);
×
1785

1786
    try {
×
1787
      await this.chain.verifyBlock(block);
×
1788
    } catch (e) {
1789
      if (e.type === 'VerifyError')
×
1790
        return e.reason;
×
1791
      throw e;
×
1792
    }
1793

1794
    return null;
×
1795
  }
1796

1797
  /*
1798
   * Coin generation
1799
   */
1800

1801
  async getGenerate(args, help) {
1802
    if (help || args.length !== 0)
×
1803
      throw new RPCError(errs.MISC_ERROR, 'getgenerate');
×
1804
    return this.mining;
×
1805
  }
1806

1807
  async setGenerate(args, help) {
1808
    if (help || args.length < 1 || args.length > 2)
×
1809
      throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )');
×
1810

1811
    const valid = new Validator(args);
×
1812
    const mine = valid.bool(0, false);
×
1813
    const limit = valid.u32(1, 0);
×
1814

1815
    if (mine && this.miner.addresses.length === 0) {
×
1816
      throw new RPCError(errs.MISC_ERROR,
×
1817
        'No addresses available for coinbase.');
1818
    }
1819

1820
    this.mining = mine;
×
1821
    this.procLimit = limit;
×
1822

1823
    if (mine) {
×
1824
      this.miner.cpu.start();
×
1825
      return true;
×
1826
    }
1827

1828
    await this.miner.cpu.stop();
×
1829

1830
    return false;
×
1831
  }
1832

1833
  async generate(args, help) {
1834
    if (help || args.length < 1 || args.length > 2)
20!
1835
      throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )');
×
1836

1837
    const valid = new Validator(args);
20✔
1838
    const blocks = valid.u32(0, 1);
20✔
1839
    const tries = valid.u32(1);
20✔
1840

1841
    if (this.miner.addresses.length === 0) {
20!
1842
      throw new RPCError(errs.MISC_ERROR,
×
1843
        'No addresses available for coinbase.');
1844
    }
1845

1846
    return this.mineBlocks(blocks, null, tries);
20✔
1847
  }
1848

1849
  async generateToAddress(args, help) {
1850
    if (help || args.length < 2 || args.length > 3) {
500!
1851
      throw new RPCError(errs.MISC_ERROR,
×
1852
        'generatetoaddress numblocks address ( maxtries )');
1853
    }
1854

1855
    const valid = new Validator(args);
500✔
1856
    const blocks = valid.u32(0, 1);
500✔
1857
    const str = valid.str(1, '');
500✔
1858
    const tries = valid.u32(2);
500✔
1859

1860
    const addr = parseAddress(str, this.network);
500✔
1861

1862
    return this.mineBlocks(blocks, addr, tries);
500✔
1863
  }
1864

1865
  /*
1866
   * Raw transactions
1867
   */
1868

1869
  async createRawTransaction(args, help) {
1870
    if (help || args.length < 2 || args.length > 3) {
×
1871
      throw new RPCError(errs.MISC_ERROR,
×
1872
        'createrawtransaction'
1873
        + ' [{"txid":"id","vout":n},...]'
1874
        + ' {"address":amount,"data":"hex",...}'
1875
        + ' ( locktime )');
1876
    }
1877

1878
    const valid = new Validator(args);
×
1879
    const inputs = valid.array(0);
×
1880
    const sendTo = valid.obj(1);
×
1881
    const locktime = valid.u32(2);
×
1882

1883
    if (!inputs || !sendTo) {
×
1884
      throw new RPCError(errs.TYPE_ERROR,
×
1885
        'Invalid parameters (inputs and sendTo).');
1886
    }
1887

1888
    const tx = new MTX();
×
1889

1890
    if (locktime != null)
×
1891
      tx.locktime = locktime;
×
1892

1893
    for (const obj of inputs) {
×
1894
      const valid = new Validator(obj);
×
1895
      const hash = valid.bhash('txid');
×
1896
      const index = valid.u32('vout');
×
1897

1898
      let sequence = valid.u32('sequence', 0xffffffff);
×
1899

1900
      if (tx.locktime)
×
1901
        sequence -= 1;
×
1902

1903
      if (!hash || index == null)
×
1904
        throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.');
×
1905

1906
      const input = new Input();
×
1907
      input.prevout.hash = hash;
×
1908
      input.prevout.index = index;
×
1909
      input.sequence = sequence;
×
1910

1911
      tx.inputs.push(input);
×
1912
    }
1913

1914
    const sends = new Validator(sendTo);
×
1915
    const uniq = new Set();
×
1916

1917
    for (const key of Object.keys(sendTo)) {
×
1918
      if (key === 'data') {
×
1919
        const value = sends.buf(key);
×
1920

1921
        if (!value)
×
1922
          throw new RPCError(errs.TYPE_ERROR, 'Invalid nulldata..');
×
1923

1924
        const output = new Output();
×
1925
        output.value = 0;
×
1926
        output.address.fromNulldata(value);
×
1927
        tx.outputs.push(output);
×
1928

1929
        continue;
×
1930
      }
1931

1932
      const addr = parseAddress(key, this.network);
×
1933
      const b58 = addr.toString(this.network);
×
1934

1935
      if (uniq.has(b58))
×
1936
        throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address');
×
1937

1938
      uniq.add(b58);
×
1939

1940
      const value = sends.ufixed(key, EXP);
×
1941

1942
      if (value == null)
×
1943
        throw new RPCError(errs.TYPE_ERROR, 'Invalid output value.');
×
1944

1945
      const output = new Output();
×
1946
      output.value = value;
×
1947
      output.address = addr;
×
1948

1949
      tx.outputs.push(output);
×
1950
    }
1951

1952
    return tx.toHex();
×
1953
  }
1954

1955
  async decodeRawTransaction(args, help) {
1956
    if (help || args.length !== 1)
×
1957
      throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"');
×
1958

1959
    const valid = new Validator(args);
×
1960
    const data = valid.buf(0);
×
1961

1962
    if (!data)
×
1963
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
1964

1965
    const tx = TX.decode(data);
×
1966

1967
    return this.txToJSON(tx);
×
1968
  }
1969

1970
  async decodeScript(args, help) {
1971
    if (help || args.length !== 1)
×
1972
      throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"');
×
1973

1974
    const valid = new Validator(args);
×
1975
    const data = valid.buf(0);
×
1976
    const script = new Script();
×
1977

1978
    if (data)
×
1979
      script.decode(data);
×
1980

1981
    const addr = Address.fromScripthash(script.sha3());
×
1982

1983
    const json = this.scriptToJSON(script);
×
1984
    json.p2sh = addr.toString(this.network);
×
1985

1986
    return json;
×
1987
  }
1988

1989
  async decodeResource(args, help) {
1990
    if (help || args.length !== 1)
1!
1991
      throw new RPCError(errs.MISC_ERROR, 'decoderesource "hex"');
×
1992

1993
    const valid = new Validator(args);
1✔
1994
    const data = valid.buf(0);
1✔
1995

1996
    const res = Resource.decode(data);
1✔
1997
    return res.toJSON();
1✔
1998
  }
1999

2000
  async getRawTransaction(args, help) {
2001
    if (help || args.length < 1 || args.length > 2) {
2!
2002
      throw new RPCError(errs.MISC_ERROR,
×
2003
        'getrawtransaction "txid" ( verbose )');
2004
    }
2005

2006
    const valid = new Validator(args);
2✔
2007
    const hash = valid.bhash(0);
2✔
2008
    const verbose = valid.bool(1, false);
2✔
2009

2010
    if (!hash)
2!
2011
      throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.');
×
2012

2013
    const meta = await this.node.getMeta(hash);
2✔
2014

2015
    if (!meta)
2!
2016
      throw new RPCError(errs.MISC_ERROR, 'Transaction not found.');
×
2017

2018
    const tx = meta.tx;
2✔
2019

2020
    if (!verbose)
2✔
2021
      return tx.toHex();
1✔
2022

2023
    let entry;
2024
    if (meta.block)
1!
2025
      entry = await this.chain.getEntry(meta.block);
1✔
2026

2027
    const json = this.txToJSON(tx, entry);
1✔
2028
    json.time = meta.mtime;
1✔
2029
    json.hex = tx.toHex();
1✔
2030

2031
    return json;
1✔
2032
  }
2033

2034
  async sendRawTransaction(args, help) {
2035
    if (help || args.length < 1 || args.length > 2) {
14!
2036
      throw new RPCError(errs.MISC_ERROR,
×
2037
        'sendrawtransaction "hexstring" ( allowhighfees )');
2038
    }
2039

2040
    const valid = new Validator(args);
14✔
2041
    const data = valid.buf(0);
14✔
2042

2043
    if (!data)
14!
2044
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
2045

2046
    const tx = TX.decode(data);
14✔
2047

2048
    this.node.relay(tx);
14✔
2049

2050
    return tx.txid();
14✔
2051
  }
2052

2053
  async signRawTransaction(args, help) {
2054
    if (help || args.length < 1 || args.length > 4) {
×
2055
      throw new RPCError(errs.MISC_ERROR,
×
2056
        'signrawtransaction'
2057
        + ' "hexstring" ('
2058
        + ' [{"txid":"id","vout":n,"address":"bech32",'
2059
        + 'redeemScript":"hex"},...] ["privatekey1",...]'
2060
        + ' sighashtype )');
2061
    }
2062

2063
    const valid = new Validator(args);
×
2064
    const data = valid.buf(0);
×
2065
    const prevout = valid.array(1);
×
2066
    const secrets = valid.array(2);
×
2067
    const sighash = valid.str(3);
×
2068

2069
    if (!data)
×
2070
      throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
×
2071

2072
    if (!this.mempool)
×
2073
      throw new RPCError(errs.MISC_ERROR, 'No mempool available.');
×
2074

2075
    const tx = MTX.decode(data);
×
2076
    tx.view = await this.mempool.getSpentView(tx);
×
2077

2078
    const map = new BufferMap();
×
2079
    const keys = [];
×
2080

2081
    if (secrets) {
×
2082
      const valid = new Validator(secrets);
×
2083
      for (let i = 0; i < secrets.length; i++) {
×
2084
        const secret = valid.str(i, '');
×
2085
        const key = parseSecret(secret, this.network);
×
2086
        map.set(key.getPublicKey(), key);
×
2087
        keys.push(key);
×
2088
      }
2089
    }
2090

2091
    if (prevout) {
×
2092
      for (const prev of prevout) {
×
2093
        const valid = new Validator(prev);
×
2094
        const hash = valid.bhash('txid');
×
2095
        const index = valid.u32('vout');
×
2096
        const addrRaw = valid.str('address');
×
2097
        const value = valid.ufixed('amount', EXP);
×
2098
        const redeemRaw = valid.buf('redeemScript');
×
2099

2100
        if (!hash || index == null || !addrRaw || value == null)
×
2101
          throw new RPCError(errs.INVALID_PARAMETER, 'Invalid UTXO.');
×
2102

2103
        const outpoint = new Outpoint(hash, index);
×
2104

2105
        const addr = parseAddress(addrRaw, this.network);
×
2106
        const coin = Output.fromScript(addr, value);
×
2107

2108
        tx.view.addOutput(outpoint, coin);
×
2109

2110
        if (keys.length === 0 || !redeemRaw)
×
2111
          continue;
×
2112

2113
        if (!addr.isScripthash())
×
2114
          continue;
×
2115

2116
        if (!redeemRaw) {
×
2117
          throw new RPCError(errs.INVALID_PARAMETER,
×
2118
            'P2SH requires redeem script.');
2119
        }
2120

2121
        const redeem = Script.decode(redeemRaw);
×
2122

2123
        for (const op of redeem.code) {
×
2124
          if (!op.data)
×
2125
            continue;
×
2126

2127
          const key = map.get(op.data);
×
2128

2129
          if (key) {
×
2130
            key.script = redeem;
×
2131
            key.refresh();
×
2132
            break;
×
2133
          }
2134
        }
2135
      }
2136
    }
2137

2138
    let type = Script.hashType.ALL;
×
2139
    if (sighash) {
×
2140
      const parts = sighash.split('|');
×
2141

2142
      if (parts.length < 1 || parts.length > 2)
×
2143
        throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2144

2145
      type = Script.hashType[parts[0]];
×
2146

2147
      if (type == null)
×
2148
        throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2149

2150
      if (parts.length === 2) {
×
2151
        if (parts[1] !== 'NOINPUT' && parts[1] !== 'ANYONECANPAY')
×
2152
          throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.');
×
2153

2154
        if (parts[1] === 'NOINPUT')
×
2155
          type |= Script.hashType.NOINPUT;
×
2156

2157
        if (parts[1] === 'ANYONECANPAY')
×
2158
          type |= Script.hashType.ANYONECANPAY;
×
2159
      }
2160
    }
2161

2162
    await tx.signAsync(keys, type, this.workers);
×
2163

2164
    return {
×
2165
      hex: tx.toHex(),
2166
      complete: tx.isSigned()
2167
    };
2168
  }
2169

2170
  /*
2171
   * Utility Functions
2172
   */
2173

2174
  async createMultisig(args, help) {
2175
    if (help || args.length < 2 || args.length > 2) {
×
2176
      throw new RPCError(errs.MISC_ERROR,
×
2177
        'createmultisig nrequired ["key",...]');
2178
    }
2179

2180
    const valid = new Validator(args);
×
2181
    const keys = valid.array(1, []);
×
2182
    const m = valid.u32(0, 0);
×
2183
    const n = keys.length;
×
2184

2185
    if (m < 1 || n < m || n > 16)
×
2186
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.');
×
2187

2188
    const items = new Validator(keys);
×
2189

2190
    for (let i = 0; i < keys.length; i++) {
×
2191
      const key = items.buf(i);
×
2192

2193
      if (!key)
×
2194
        throw new RPCError(errs.TYPE_ERROR, 'Invalid key.');
×
2195

2196
      if (!secp256k1.publicKeyVerify(key) || key.length !== 33)
×
2197
        throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.');
×
2198

2199
      keys[i] = key;
×
2200
    }
2201

2202
    const script = Script.fromMultisig(m, n, keys);
×
2203

2204
    if (script.getSize() > consensus.MAX_SCRIPT_PUSH) {
×
2205
      throw new RPCError(errs.VERIFY_ERROR,
×
2206
        'Redeem script exceeds size limit.');
2207
    }
2208

2209
    const addr = Address.fromScripthash(script.sha3());
×
2210

2211
    return {
×
2212
      address: addr.toString(this.network),
2213
      redeemScript: script.toJSON()
2214
    };
2215
  }
2216

2217
  async validateAddress(args, help) {
2218
    if (help || args.length !== 1)
4!
2219
      throw new RPCError(errs.MISC_ERROR, 'validateaddress "address"');
×
2220

2221
    const valid = new Validator(args);
4✔
2222
    const str = valid.str(0, '');
4✔
2223

2224
    let addr;
2225
    try {
4✔
2226
      addr = Address.fromString(str, this.network);
4✔
2227
    } catch (e) {
2228
      return {
1✔
2229
        isvalid: false
2230
      };
2231
    }
2232

2233
    return {
3✔
2234
      isvalid: true,
2235
      address: addr.toString(this.network),
2236
      isscript: addr.isScripthash(),
2237
      isspendable: !addr.isUnspendable(),
2238
      witness_version: addr.version,
2239
      witness_program: addr.hash.toString('hex')
2240
    };
2241
  }
2242

2243
  async verifyMessage(args, help) {
2244
    if (help || args.length !== 3) {
6!
2245
      throw new RPCError(errs.MISC_ERROR,
×
2246
        'verifymessage "address" "signature" "message"');
2247
    }
2248

2249
    const valid = new Validator(args);
6✔
2250
    const b58 = valid.str(0, '');
6✔
2251
    const sig = valid.buf(1, null, 'base64');
6✔
2252
    const str = valid.str(2);
6✔
2253

2254
    if (!sig || !str)
6!
2255
      throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.');
×
2256

2257
    const addr = parseAddress(b58, this.network);
6✔
2258

2259
    if (addr.version !== 0 || addr.hash.length !== 20)
6!
2260
      return false;
×
2261

2262
    const msg = Buffer.from(MAGIC_STRING + str, 'utf8');
6✔
2263
    const hash = blake2b.digest(msg);
6✔
2264

2265
    for (let i = 0; i < 4; i++) {
6✔
2266
      const key = secp256k1.recover(hash, sig, i, true);
10✔
2267

2268
      if (!key)
10!
2269
        continue;
×
2270

2271
      if (safeEqual(blake2b.digest(key, 20), addr.hash))
10✔
2272
        return true;
6✔
2273
    }
2274

2275
    return false;
×
2276
  }
2277

2278
  async verifyMessageWithName(args, help) {
2279
    if (help || args.length < 3 || args.length > 4 ) {
15!
2280
      throw new RPCError(errs.MISC_ERROR,
×
2281
        'verifymessagewithname "name" "signature" "message" (safe)');
2282
    }
2283

2284
    const valid = new Validator(args);
15✔
2285
    const name = valid.str(0, '');
15✔
2286
    const sig = valid.buf(1, null, 'base64');
15✔
2287
    const str = valid.str(2);
15✔
2288
    const safe = valid.bool(3, false);
15✔
2289
    const network = this.network;
15✔
2290
    const height = this.chain.height;
15✔
2291

2292
    if (!name || !rules.verifyName(name))
15✔
2293
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
4✔
2294

2295
    const nameHash = rules.hashName(name);
11✔
2296

2297
    const ns = await this.getNameState(nameHash, safe);
11✔
2298

2299
    if (!ns || !ns.owner)
11✔
2300
      throw new RPCError(errs.MISC_ERROR, 'Cannot find the name owner.');
1✔
2301

2302
    if (!ns.isClosed(height, network))
10✔
2303
      throw new Error('Invalid name state.');
3✔
2304

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

2307
    if (!coin) {
7✔
2308
      throw new RPCError(
3✔
2309
        errs.DATABASE_ERROR,
2310
        'Cannot find the owner\'s address.'
2311
      );
2312
    }
2313

2314
    const address = coin.address.toString(this.network);
4✔
2315
    return this.verifyMessage([address, sig, str]);
4✔
2316
  }
2317

2318
  async signMessageWithPrivkey(args, help) {
2319
    if (help || args.length !== 2) {
×
2320
      throw new RPCError(errs.MISC_ERROR,
×
2321
        'signmessagewithprivkey "privkey" "message"');
2322
    }
2323

2324
    const valid = new Validator(args);
×
2325
    const wif = valid.str(0, '');
×
2326
    const str = valid.str(1, '');
×
2327

2328
    const key = parseSecret(wif, this.network);
×
2329
    const msg = Buffer.from(MAGIC_STRING + str, 'utf8');
×
2330
    const hash = blake2b.digest(msg);
×
2331
    const sig = key.sign(hash);
×
2332

2333
    return sig.toString('base64');
×
2334
  }
2335

2336
  async estimateFee(args, help) {
2337
    if (help || args.length !== 1)
×
2338
      throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks');
×
2339

2340
    const valid = new Validator(args);
×
2341
    const blocks = valid.u32(0, 1);
×
2342

2343
    if (!this.fees)
×
2344
      throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.');
×
2345

2346
    const fee = this.fees.estimateFee(blocks, false);
×
2347

2348
    if (fee === 0)
×
2349
      return -1;
×
2350

2351
    return Amount.coin(fee, true);
×
2352
  }
2353

2354
  async estimatePriority(args, help) {
2355
    if (help || args.length !== 1)
×
2356
      throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks');
×
2357

2358
    const valid = new Validator(args);
×
2359
    const blocks = valid.u32(0, 1);
×
2360

2361
    if (!this.fees)
×
2362
      throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.');
×
2363

2364
    return this.fees.estimatePriority(blocks, false);
×
2365
  }
2366

2367
  async estimateSmartFee(args, help) {
2368
    if (help || args.length !== 1)
×
2369
      throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks');
×
2370

2371
    const valid = new Validator(args);
×
2372
    const blocks = valid.u32(0, 1);
×
2373

2374
    if (!this.fees)
×
2375
      throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.');
×
2376

2377
    let fee = this.fees.estimateFee(blocks, true);
×
2378

2379
    if (fee === 0)
×
2380
      fee = -1;
×
2381
    else
2382
      fee = Amount.coin(fee, true);
×
2383

2384
    return {
×
2385
      fee: fee,
2386
      blocks: blocks
2387
    };
2388
  }
2389

2390
  async estimateSmartPriority(args, help) {
2391
    if (help || args.length !== 1)
×
2392
      throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks');
×
2393

2394
    const valid = new Validator(args);
×
2395
    const blocks = valid.u32(0, 1);
×
2396

2397
    if (!this.fees)
×
2398
      throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.');
×
2399

2400
    const pri = this.fees.estimatePriority(blocks, true);
×
2401

2402
    return {
×
2403
      priority: pri,
2404
      blocks: blocks
2405
    };
2406
  }
2407

2408
  async invalidateBlock(args, help) {
2409
    if (help || args.length !== 1)
×
2410
      throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"');
×
2411

2412
    const valid = new Validator(args);
×
2413
    const hash = valid.bhash(0);
×
2414

2415
    if (!hash)
×
2416
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
2417

2418
    await this.chain.invalidate(hash);
×
2419

2420
    return null;
×
2421
  }
2422

2423
  async reconsiderBlock(args, help) {
2424
    if (help || args.length !== 1)
×
2425
      throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"');
×
2426

2427
    const valid = new Validator(args);
×
2428
    const hash = valid.bhash(0);
×
2429

2430
    if (!hash)
×
2431
      throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.');
×
2432

2433
    this.chain.removeInvalid(hash);
×
2434

2435
    return null;
×
2436
  }
2437

2438
  async setMockTime(args, help) {
2439
    if (help || args.length !== 1)
80!
2440
      throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp');
×
2441

2442
    const valid = new Validator(args);
80✔
2443
    const time = valid.u32(0);
80✔
2444

2445
    if (time == null)
80!
2446
      throw new RPCError(errs.TYPE_ERROR, 'Invalid timestamp.');
×
2447

2448
    this.network.time.offset = 0;
80✔
2449

2450
    const delta = this.network.now() - time;
80✔
2451

2452
    this.network.time.offset = -delta;
80✔
2453

2454
    return null;
80✔
2455
  }
2456

2457
  async getMemoryInfo(args, help) {
2458
    if (help || args.length !== 0)
×
2459
      throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo');
×
2460

2461
    return this.logger.memoryUsage();
×
2462
  }
2463

2464
  async setLogLevel(args, help) {
2465
    if (help || args.length !== 1)
×
2466
      throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"');
×
2467

2468
    const valid = new Validator(args);
×
2469
    const level = valid.str(0, '');
×
2470

2471
    this.logger.setLevel(level);
×
2472

2473
    return null;
×
2474
  }
2475

2476
  async getNames(args, help) {
2477
    if (help || args.length !== 0)
×
2478
      throw new RPCError(errs.MISC_ERROR, 'getnames');
×
2479

2480
    const network = this.network;
×
2481
    const height = this.chain.height;
×
2482
    const txn = this.chain.db.txn;
×
2483
    const items = [];
×
2484

2485
    const iter = txn.iterator();
×
2486

2487
    while (await iter.next()) {
×
2488
      const {key, value} = iter;
×
2489
      const ns = NameState.decode(value);
×
2490
      ns.nameHash = key;
×
2491

2492
      const info = ns.getJSON(height, network);
×
2493
      items.push(info);
×
2494
    }
2495

2496
    return items;
×
2497
  }
2498

2499
  async getNameInfo(args, help) {
2500
    if (help || args.length < 1 || args.length > 2)
40!
2501
      throw new RPCError(errs.MISC_ERROR, 'getnameinfo "name" (safe)');
×
2502

2503
    const valid = new Validator(args);
40✔
2504
    const name = valid.str(0);
40✔
2505
    const safe = valid.bool(1, false);
40✔
2506

2507
    if (!name || !rules.verifyName(name))
40!
2508
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2509

2510
    const network = this.network;
40✔
2511
    const height = this.chain.height;
40✔
2512
    const nameHash = rules.hashName(name);
40✔
2513
    const reserved = rules.isReserved(nameHash, height + 1, network);
40✔
2514
    const [start, week] = rules.getRollout(nameHash, network);
40✔
2515
    const state = await this.chain.getNextState();
40✔
2516

2517
    const ns = await this.getNameState(nameHash, safe);
40✔
2518

2519
    let locked = undefined;
40✔
2520
    let info = null;
40✔
2521

2522
    if (ns) {
40✔
2523
      if (!ns.isExpired(height, network))
36✔
2524
        info = ns.getJSON(height, network);
31✔
2525
    }
2526

2527
    if (state.hasICANNLockup())
40✔
2528
      locked = rules.isLockedUp(nameHash, height + 1, network);
14✔
2529

2530
    return {
40✔
2531
      start: {
2532
        reserved: reserved,
2533
        week: week,
2534
        start: start,
2535
        locked: locked
2536
      },
2537
      info
2538
    };
2539
  }
2540

2541
  async getNameResource(args, help) {
2542
    if (help || args.length < 1 || args.length > 2)
12!
2543
      throw new RPCError(errs.MISC_ERROR, 'getnameresource "name" (safe)');
×
2544

2545
    const valid = new Validator(args);
12✔
2546
    const name = valid.str(0);
12✔
2547
    const safe = valid.bool(1, false);
12✔
2548

2549
    if (!name || !rules.verifyName(name))
12!
2550
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2551

2552
    const nameHash = rules.hashName(name);
12✔
2553

2554
    const ns = await this.getNameState(nameHash, safe);
12✔
2555

2556
    if (!ns || ns.data.length === 0)
12✔
2557
      return null;
1✔
2558

2559
    try {
11✔
2560
      const res = Resource.decode(ns.data);
11✔
2561
      return res.getJSON(name);
11✔
2562
    } catch (e) {
2563
      return {};
×
2564
    }
2565
  }
2566

2567
  async getNameProof(args, help) {
2568
    if (help || args.length < 1 || args.length > 2)
×
2569
      throw new RPCError(errs.MISC_ERROR, 'getnameproof "name" ("root")');
×
2570

2571
    const valid = new Validator(args);
×
2572
    const name = valid.str(0);
×
2573
    const treeRoot = valid.bhash(1);
×
2574

2575
    if (!name || !rules.verifyName(name))
×
2576
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name.');
×
2577

2578
    const hash = this.chain.tip.hash;
×
2579
    const height = this.chain.tip.height;
×
2580
    const root = treeRoot || this.chain.tip.treeRoot;
×
2581
    const key = rules.hashName(name);
×
2582
    const proof = await this.chain.db.prove(root, key);
×
2583

2584
    return {
×
2585
      hash: hash.toString('hex'),
2586
      height: height,
2587
      root: root.toString('hex'),
2588
      name: name,
2589
      key: key.toString('hex'),
2590
      proof: proof.toJSON()
2591
    };
2592
  }
2593

2594
  async getDNSSECProof(args, help) {
2595
    if (help || args.length < 1 || args.length > 3)
×
2596
      throw new RPCError(errs.MISC_ERROR,
×
2597
        'getdnssecproof "name" ( estimate ) ( verbose )');
2598

2599
    const valid = new Validator(args);
×
2600
    const name = valid.str(0);
×
2601
    const estimate = valid.bool(1, false);
×
2602
    const verbose = valid.bool(2, true);
×
2603

2604
    const proof = await ownership.prove(name, estimate);
×
2605

2606
    if (!verbose)
×
2607
      return proof.toHex();
×
2608

2609
    return proof.toJSON();
×
2610
  }
2611

2612
  async getNameByHash(args, help) {
2613
    if (help || args.length < 1 || args.length > 2)
×
2614
      throw new RPCError(errs.MISC_ERROR, 'getnamebyhash "hash" (safe)');
×
2615

2616
    const valid = new Validator(args);
×
2617
    const hash = valid.bhash(0);
×
2618
    const safe = valid.bool(1, false);
×
2619

2620
    if (!hash)
×
2621
      throw new RPCError(errs.TYPE_ERROR, 'Invalid name hash.');
×
2622

2623
    const ns = await this.getNameState(hash, safe);
×
2624

2625
    if (!ns)
×
2626
      return null;
×
2627

2628
    return ns.name.toString('binary');
×
2629
  }
2630

2631
  async grindName(args, help) {
2632
    if (help || args.length > 1)
46!
2633
      throw new RPCError(errs.MISC_ERROR, 'grindname size');
×
2634

2635
    const valid = new Validator(args);
46✔
2636
    const size = valid.u32(0, 10);
46✔
2637

2638
    if (size < 1 || size > 63)
46!
2639
      throw new RPCError(errs.TYPE_ERROR, 'Invalid length.');
×
2640

2641
    const network = this.network;
46✔
2642
    const height = this.chain.height;
46✔
2643

2644
    return rules.grindName(size, height + 1, network);
46✔
2645
  }
2646

2647
  async sendRawClaim(args, help) {
2648
    if (help || args.length < 1 || args.length > 2)
×
2649
      throw new RPCError(errs.MISC_ERROR, 'sendrawclaim "base64-string"');
×
2650

2651
    const valid = new Validator(args);
×
2652
    const data = valid.buf(0, null, 'base64');
×
2653

2654
    if (!data)
×
2655
      throw new RPCError(errs.TYPE_ERROR, 'Invalid base64 string.');
×
2656

2657
    const claim = Claim.fromBlob(data);
×
2658

2659
    this.node.relayClaim(claim);
×
2660

2661
    return claim.hash().toString('hex');
×
2662
  }
2663

2664
  async sendRawAirdrop(args, help) {
2665
    if (help || args.length < 1 || args.length > 2)
×
2666
      throw new RPCError(errs.MISC_ERROR, 'sendrawairdrop "base64-string"');
×
2667

2668
    if (this.network.type !== 'main')
×
2669
      throw new RPCError(errs.MISC_ERROR, 'Currently disabled.');
×
2670

2671
    const valid = new Validator(args);
×
2672
    const data = valid.buf(0, null, 'base64');
×
2673

2674
    if (!data)
×
2675
      throw new RPCError(errs.TYPE_ERROR, 'Invalid base64 string.');
×
2676

2677
    const proof = AirdropProof.decode(data);
×
2678

2679
    this.node.relayAirdrop(proof);
×
2680

2681
    return proof.hash().toString('hex');
×
2682
  }
2683

2684
  async validateResource(args, help) {
2685
    if (help || args.length < 1 || args.length > 2)
22!
2686
      throw new RPCError(errs.MISC_ERROR, 'validateresource'
×
2687
        + ' \'{"records": [{...}]}\'');
2688

2689
    const valid = new Validator(args);
22✔
2690
    const data = valid.obj(0);
22✔
2691

2692
    if (!data)
22!
2693
      throw new RPCError(errs.TYPE_ERROR, 'Invalid resource object.');
×
2694

2695
    let resource;
2696
    try {
22✔
2697
      resource = Resource.fromJSON(data);
22✔
2698
    } catch (e) {
2699
      throw new RPCError(errs.PARSE_ERROR, e.message);
15✔
2700
    }
2701

2702
    return resource.toJSON();
7✔
2703
  }
2704

2705
  async resetRootCache(args, help) {
2706
    if (help || args.length !== 0)
×
2707
      throw new RPCError(errs.MISC_ERROR, 'resetrootcache');
×
2708

2709
    if (!this.node.ns)
×
2710
      return null;
×
2711

2712
    this.node.ns.resetCache();
×
2713

2714
    return null;
×
2715
  }
2716

2717
  /*
2718
   * Helpers
2719
   */
2720

2721
  async handleLongpoll(lpid) {
2722
    if (lpid.length !== 72)
×
2723
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.');
×
2724

2725
    const watched = lpid.slice(0, 64);
×
2726
    const lastTX = parseInt(lpid.slice(64, 72), 16);
×
2727

2728
    if ((lastTX >>> 0) !== lastTX)
×
2729
      throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.');
×
2730

2731
    const hash = util.parseHex(watched, 32);
×
2732

2733
    if (!this.chain.tip.hash.equals(hash))
×
2734
      return;
×
2735

2736
    await this.longpoll();
×
2737
  }
2738

2739
  longpoll() {
2740
    return new Promise((resolve, reject) => {
×
2741
      this.pollers.push({ resolve, reject });
×
2742
    });
2743
  }
2744

2745
  refreshBlock() {
2746
    const pollers = this.pollers;
12✔
2747

2748
    this.attempt = null;
12✔
2749
    this.lastActivity = 0;
12✔
2750
    this.pollers = [];
12✔
2751

2752
    for (const job of pollers)
12✔
2753
      job.resolve();
×
2754
  }
2755

2756
  bindChain() {
2757
    if (this.boundChain)
13✔
2758
      return;
11✔
2759

2760
    this.boundChain = true;
2✔
2761

2762
    const refresh = () => {
2✔
2763
      if (!this.attempt)
16✔
2764
        return;
10✔
2765

2766
      this.refreshBlock();
6✔
2767
      this.merkleMap.clear();
6✔
2768
      this.merkleList.length = 0;
6✔
2769
    };
2770

2771
    this.node.on('connect', refresh);
2✔
2772
    this.node.on('reset', refresh);
2✔
2773

2774
    if (!this.mempool)
2!
2775
      return;
×
2776

2777
    const tryRefresh = () => {
2✔
2778
      if (!this.attempt)
6✔
2779
        return;
2✔
2780

2781
      if (util.now() - this.lastActivity > 10)
4!
2782
        this.refreshBlock();
4✔
2783
    };
2784

2785
    this.node.on('tx', tryRefresh);
2✔
2786
    this.node.on('claim', tryRefresh);
2✔
2787
    this.node.on('airdrop', tryRefresh);
2✔
2788
  }
2789

2790
  async getTemplate() {
2791
    this.bindChain();
3✔
2792

2793
    let attempt = this.attempt;
3✔
2794

2795
    if (attempt) {
3!
2796
      this.miner.updateTime(attempt);
×
2797
    } else {
2798
      attempt = await this.miner.createBlock();
3✔
2799
      this.attempt = attempt;
3✔
2800
      this.lastActivity = util.now();
3✔
2801
    }
2802

2803
    return attempt;
3✔
2804
  }
2805

2806
  async updateWork() {
2807
    this.bindChain();
10✔
2808

2809
    let attempt = this.attempt;
10✔
2810

2811
    if (attempt) {
10✔
2812
      if (attempt.address.isNull()) {
1!
2813
        throw new RPCError(errs.MISC_ERROR,
×
2814
          'No addresses available for coinbase.');
2815
      }
2816

2817
      this.miner.updateTime(attempt);
1✔
2818

2819
      return attempt;
1✔
2820
    }
2821

2822
    if (this.miner.addresses.length === 0) {
9!
2823
      throw new RPCError(errs.MISC_ERROR,
×
2824
        'No addresses available for coinbase.');
2825
    }
2826

2827
    attempt = await this.miner.createBlock();
9✔
2828

2829
    if (this.merkleMap.size >= 10)
9!
2830
      this.merkleMap.delete(this.merkleList.shift());
×
2831

2832
    this.attempt = attempt;
9✔
2833
    this.lastActivity = util.now();
9✔
2834
    this.merkleMap.set(attempt.witnessRoot, attempt);
9✔
2835
    this.merkleList.push(attempt.witnessRoot);
9✔
2836

2837
    return attempt;
9✔
2838
  }
2839

2840
  async addBlock(block) {
2841
    const unlock1 = await this.locker.lock();
1✔
2842
    const unlock2 = await this.chain.locker.lock();
1✔
2843
    try {
1✔
2844
      return await this._addBlock(block);
1✔
2845
    } finally {
2846
      unlock2();
1✔
2847
      unlock1();
1✔
2848
    }
2849
  }
2850

2851
  async _addBlock(block) {
2852
    this.logger.info('Handling submitted block: %x.', block.hash());
1✔
2853

2854
    let entry;
2855
    try {
1✔
2856
      entry = await this.chain._add(block);
1✔
2857
    } catch (err) {
2858
      if (err.type === 'VerifyError') {
×
2859
        this.logger.warning('RPC block rejected: %x (%s).',
×
2860
          block.hash(), err.reason);
2861
        return `rejected: ${err.reason}`;
×
2862
      }
2863
      throw err;
×
2864
    }
2865

2866
    if (!entry) {
1!
2867
      this.logger.warning('RPC block rejected: %x (bad-prevblk).',
×
2868
        block.hash());
2869
      return 'rejected: bad-prevblk';
×
2870
    }
2871

2872
    return null;
1✔
2873
  }
2874

2875
  totalTX() {
2876
    return this.mempool ? this.mempool.map.size : 0;
3!
2877
  }
2878

2879
  async getSoftforks() {
2880
    const tip = this.chain.tip;
101✔
2881
    const forks = {};
101✔
2882

2883
    for (const deployment of this.network.deploys) {
101✔
2884
      const state = await this.chain.getState(tip, deployment);
404✔
2885
      let status;
2886

2887
      switch (state) {
404!
2888
        case common.thresholdStates.DEFINED:
2889
          status = 'defined';
36✔
2890
          break;
36✔
2891
        case common.thresholdStates.STARTED:
2892
          status = 'started';
180✔
2893
          break;
180✔
2894
        case common.thresholdStates.LOCKED_IN:
2895
          status = 'locked_in';
2✔
2896
          break;
2✔
2897
        case common.thresholdStates.ACTIVE:
2898
          status = 'active';
1✔
2899
          break;
1✔
2900
        case common.thresholdStates.FAILED:
2901
          status = 'failed';
185✔
2902
          break;
185✔
2903
        default:
2904
          assert(false, 'Bad state.');
×
2905
          break;
×
2906
      }
2907

2908
      forks[deployment.name] = {
404✔
2909
        status: status,
2910
        bit: deployment.bit,
2911
        startTime: deployment.startTime,
2912
        timeout: deployment.timeout
2913
      };
2914

2915
      if (status === 'started') {
404✔
2916
        forks[deployment.name].statistics =
180✔
2917
          await this.chain.getBIP9Stats(tip, deployment);
2918
      }
2919
    }
2920

2921
    return forks;
101✔
2922
  }
2923

2924
  async getHashRate(lookup, height) {
2925
    let tip = this.chain.tip;
×
2926

2927
    if (height != null)
×
2928
      tip = await this.chain.getEntry(height);
×
2929

2930
    if (!tip)
×
2931
      return 0;
×
2932

2933
    assert(typeof lookup === 'number');
×
2934
    assert(lookup >= 0);
×
2935

2936
    if (lookup === 0)
×
2937
      lookup = tip.height % this.network.pow.targetWindow + 1;
×
2938

2939
    if (lookup > tip.height)
×
2940
      lookup = tip.height;
×
2941

2942
    let min = tip.time;
×
2943
    let max = min;
×
2944
    let entry = tip;
×
2945

2946
    for (let i = 0; i < lookup; i++) {
×
2947
      entry = await this.chain.getPrevious(entry);
×
2948

2949
      if (!entry)
×
2950
        throw new RPCError(errs.DATABASE_ERROR, 'Not found.');
×
2951

2952
      min = Math.min(entry.time, min);
×
2953
      max = Math.max(entry.time, max);
×
2954
    }
2955

2956
    const diff = max - min;
×
2957

2958
    if (diff === 0)
×
2959
      return 0;
×
2960

2961
    const work = tip.chainwork.sub(entry.chainwork);
×
2962

2963
    return Number(work.toString()) / diff;
×
2964
  }
2965

2966
  async mineBlocks(blocks, addr, tries) {
2967
    const unlock = await this.locker.lock();
520✔
2968
    try {
520✔
2969
      return await this._mineBlocks(blocks, addr, tries);
520✔
2970
    } finally {
2971
      unlock();
520✔
2972
    }
2973
  }
2974

2975
  async _mineBlocks(blocks, addr, tries) {
2976
    const hashes = [];
520✔
2977

2978
    for (let i = 0; i < blocks; i++) {
520✔
2979
      const block = await this.miner.mineBlock(null, addr);
3,094✔
2980
      const entry = await this.chain.add(block);
3,094✔
2981
      assert(entry);
3,094✔
2982
      hashes.push(entry.hash.toString('hex'));
3,094✔
2983
    }
2984

2985
    return hashes;
520✔
2986
  }
2987

2988
  async findFork(entry) {
2989
    while (entry) {
×
2990
      if (await this.chain.isMainChain(entry))
×
2991
        return entry;
×
2992
      entry = await this.chain.getPrevious(entry);
×
2993
    }
2994
    throw new Error('Fork not found.');
×
2995
  }
2996

2997
  txToJSON(tx, entry) {
2998
    let height = -1;
1✔
2999
    let time = 0;
1✔
3000
    let hash = null;
1✔
3001
    let conf = 0;
1✔
3002

3003
    if (entry) {
1!
3004
      height = entry.height;
1✔
3005
      time = entry.time;
1✔
3006
      hash = entry.hash;
1✔
3007
      conf = this.chain.height - height + 1;
1✔
3008
    }
3009

3010
    const vin = [];
1✔
3011

3012
    for (const input of tx.inputs) {
1✔
3013
      const json = {
1✔
3014
        coinbase: undefined,
3015
        txid: undefined,
3016
        vout: undefined,
3017
        txinwitness: undefined,
3018
        sequence: input.sequence,
3019
        link: input.link
3020
      };
3021

3022
      json.coinbase = tx.isCoinbase();
1✔
3023
      json.txid = input.prevout.txid();
1✔
3024
      json.vout = input.prevout.index;
1✔
3025
      json.txinwitness = input.witness.toJSON();
1✔
3026

3027
      vin.push(json);
1✔
3028
    }
3029

3030
    const vout = [];
1✔
3031

3032
    for (let i = 0; i < tx.outputs.length; i++) {
1✔
3033
      const output = tx.outputs[i];
2✔
3034
      vout.push({
2✔
3035
        value: Amount.coin(output.value, true),
3036
        n: i,
3037
        address: this.addrToJSON(output.address),
3038
        covenant: output.covenant.toJSON()
3039
      });
3040
    }
3041

3042
    return {
1✔
3043
      txid: tx.txid(),
3044
      hash: tx.wtxid(),
3045
      size: tx.getSize(),
3046
      vsize: tx.getVirtualSize(),
3047
      version: tx.version,
3048
      locktime: tx.locktime,
3049
      vin: vin,
3050
      vout: vout,
3051
      blockhash: hash ? hash.toString('hex') : null,
1!
3052
      confirmations: conf,
3053
      time: time,
3054
      blocktime: time,
3055
      hex: undefined
3056
    };
3057
  }
3058

3059
  scriptToJSON(script, hex) {
3060
    const type = script.getType();
×
3061

3062
    const json = {
×
3063
      asm: script.toASM(),
3064
      hex: undefined,
3065
      type: Script.typesByVal[type],
3066
      reqSigs: 1,
3067
      totalSigs: 1,
3068
      p2sh: undefined
3069
    };
3070

3071
    if (hex)
×
3072
      json.hex = script.toJSON();
×
3073

3074
    const [m, n] = script.getMultisig();
×
3075

3076
    if (m !== -1)
×
3077
      json.reqSigs = m;
×
3078

3079
    if (n !== -1)
×
3080
      json.totalSigs = n;
×
3081

3082
    return json;
×
3083
  }
3084

3085
  addrToJSON(addr) {
3086
    return {
2✔
3087
      version: addr.version,
3088
      hash: addr.hash.toString('hex'),
3089
      string: addr.toString(this.network)
3090
    };
3091
  }
3092

3093
  async headerToJSON(entry) {
3094
    const mtp = await this.chain.getMedianTime(entry);
×
3095
    const next = await this.chain.getNextHash(entry.hash);
×
3096

3097
    let confirmations = -1;
×
3098
    if (await this.chain.isMainChain(entry))
×
3099
      confirmations = this.chain.height - entry.height + 1;
×
3100

3101
    return {
×
3102
      hash: entry.hash.toString('hex'),
3103
      confirmations: confirmations,
3104
      height: entry.height,
3105
      version: entry.version,
3106
      versionHex: util.hex32(entry.version),
3107
      merkleroot: entry.merkleRoot.toString('hex'),
3108
      witnessroot: entry.witnessRoot.toString('hex'),
3109
      treeroot: entry.treeRoot.toString('hex'),
3110
      reservedroot: entry.reservedRoot.toString('hex'),
3111
      mask: entry.mask.toString('hex'),
3112
      time: entry.time,
3113
      mediantime: mtp,
3114
      nonce: entry.nonce,
3115
      extranonce: entry.extraNonce.toString('hex'),
3116
      bits: util.hex32(entry.bits),
3117
      difficulty: toDifficulty(entry.bits),
3118
      chainwork: entry.chainwork.toString('hex', 64),
3119
      previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH)
×
3120
        ? entry.prevBlock.toString('hex')
3121
        : null,
3122
      nextblockhash: next ? next.toString('hex') : null
×
3123
    };
3124
  }
3125

3126
  async blockToJSON(entry, block, details) {
3127
    const mtp = await this.chain.getMedianTime(entry);
155✔
3128
    const next = await this.chain.getNextHash(entry.hash);
155✔
3129

3130
    let confirmations = -1;
155✔
3131
    if (await this.chain.isMainChain(entry))
155✔
3132
      confirmations = this.chain.height - entry.height + 1;
153✔
3133

3134
    const txs = [];
155✔
3135

3136
    for (const tx of block.txs) {
155✔
3137
      if (details) {
955!
3138
        const json = this.txToJSON(tx, entry);
×
3139
        txs.push(json);
×
3140
        continue;
×
3141
      }
3142
      txs.push(tx.txid());
955✔
3143
    }
3144

3145
    return {
155✔
3146
      hash: entry.hash.toString('hex'),
3147
      confirmations: confirmations,
3148
      strippedsize: block.getBaseSize(),
3149
      size: block.getSize(),
3150
      weight: block.getWeight(),
3151
      height: entry.height,
3152
      version: entry.version,
3153
      versionHex: util.hex32(entry.version),
3154
      merkleroot: entry.merkleRoot.toString('hex'),
3155
      witnessroot: entry.witnessRoot.toString('hex'),
3156
      treeroot: entry.treeRoot.toString('hex'),
3157
      reservedroot: entry.reservedRoot.toString('hex'),
3158
      mask: entry.mask.toString('hex'),
3159
      coinbase: !details
155!
3160
        ? block.txs[0].inputs[0].witness.toJSON()
3161
        : undefined,
3162
      tx: txs,
3163
      time: entry.time,
3164
      mediantime: mtp,
3165
      nonce: entry.nonce,
3166
      extranonce: entry.extraNonce.toString('hex'),
3167
      bits: util.hex32(entry.bits),
3168
      difficulty: toDifficulty(entry.bits),
3169
      chainwork: entry.chainwork.toString('hex', 64),
3170
      nTx: txs.length,
3171
      previousblockhash: !entry.prevBlock.equals(consensus.ZERO_HASH)
155✔
3172
        ? entry.prevBlock.toString('hex')
3173
        : null,
3174
      nextblockhash: next ? next.toString('hex') : null
155✔
3175
    };
3176
  }
3177

3178
  entryToJSON(entry) {
3179
    return {
×
3180
      size: entry.size,
3181
      fee: Amount.coin(entry.deltaFee, true),
3182
      modifiedfee: 0,
3183
      time: entry.time,
3184
      height: entry.height,
3185
      startingpriority: entry.priority,
3186
      currentpriority: entry.getPriority(this.chain.height),
3187
      descendantcount: this.mempool.countDescendants(entry),
3188
      descendantsize: entry.descSize,
3189
      descendantfees: entry.descFee,
3190
      ancestorcount: this.mempool.countAncestors(entry),
3191
      ancestorsize: 0,
3192
      ancestorfees: 0,
3193
      depends: this.mempool.getDepends(entry.tx)
3194
    };
3195
  }
3196

3197
  async getNameState(nameHash, safe) {
3198
    if (!safe) {
63✔
3199
      // Will always return null in SPV mode
3200
      return this.chain.db.getNameState(nameHash);
57✔
3201
    }
3202

3203
    // Safe roots are the last Urkel tree commitment
3204
    // with more than 12 confirmations.
3205
    const root = await this.chain.getSafeRoot();
6✔
3206
    let data;
3207
    if (this.chain.options.spv)
6✔
3208
      data = await this.pool.resolveAtRoot(nameHash, root);
3✔
3209
    else
3210
      data = await this.chain.db.lookup(root, nameHash);
3✔
3211

3212
    if (!data)
6!
3213
      return null;
×
3214

3215
    const ns = NameState.decode(data);
6✔
3216
    ns.nameHash = nameHash;
6✔
3217
    return ns;
6✔
3218
  }
3219
}
3220

3221
/*
3222
 * Helpers
3223
 */
3224

3225
function parseAddress(raw, network) {
3226
  try {
506✔
3227
    return Address.fromString(raw, network);
506✔
3228
  } catch (e) {
3229
    throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.');
×
3230
  }
3231
}
3232

3233
function parseSecret(raw, network) {
3234
  try {
×
3235
    return KeyRing.fromSecret(raw, network);
×
3236
  } catch (e) {
3237
    throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.');
×
3238
  }
3239
}
3240

3241
function parseIP(addr, network) {
3242
  let ip;
3243

3244
  try {
×
3245
    ip = IP.fromHostname(addr);
×
3246
  } catch (e) {
3247
    throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET,
×
3248
      'Invalid IP address or subnet.');
3249
  }
3250

3251
  if (ip.port === 0)
×
3252
    ip.port = ip.key ? network.brontidePort : network.port;
×
3253

3254
  return ip;
×
3255
}
3256

3257
function parseNetAddress(addr, network) {
3258
  try {
×
3259
    return NetAddress.fromHostname(addr, network);
×
3260
  } catch (e) {
3261
    throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET,
×
3262
      'Invalid IP address or subnet.');
3263
  }
3264
}
3265

3266
function toDifficulty(bits) {
3267
  let shift = (bits >>> 24) & 0xff;
256✔
3268
  let diff = 0x0000ffff / (bits & 0x00ffffff);
256✔
3269

3270
  while (shift < 29) {
256✔
3271
    diff *= 256.0;
×
3272
    shift++;
×
3273
  }
3274

3275
  while (shift > 29) {
256✔
3276
    diff /= 256.0;
768✔
3277
    shift--;
768✔
3278
  }
3279

3280
  return diff;
256✔
3281
}
3282

3283
/*
3284
 * Expose
3285
 */
3286

3287
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