/*! * rpc.js - bitcoind-compatible json rpc for bcoin. * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var util = require('../utils/util'); var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var assert = require('assert'); var common = require('../blockchain/common'); var ec = require('../crypto/ec'); var Amount = require('../btc/amount'); var NetAddress = require('../primitives/netaddress'); var Script = require('../script/script'); var Address = require('../primitives/address'); var Block = require('../primitives/block'); var Headers = require('../primitives/headers'); var Input = require('../primitives/input'); var KeyRing = require('../primitives/keyring'); var MerkleBlock = require('../primitives/merkleblock'); var MTX = require('../primitives/mtx'); var Network = require('../protocol/network'); var Output = require('../primitives/output'); var TX = require('../primitives/tx'); var IP = require('../utils/ip'); var encoding = require('../utils/encoding'); var consensus = require('../protocol/consensus'); var Validator = require('../utils/validator'); var RPCBase = require('./rpcbase'); var pkg = require('../pkg'); var RPCError = RPCBase.RPCError; var errs = RPCBase.errors; var MAGIC_STRING = RPCBase.MAGIC_STRING; /** * Bitcoin Core RPC * @alias module:http.RPC * @constructor * @param {Node} node */ function RPC(node) { if (!(this instanceof RPC)) return new RPC(node); RPCBase.call(this); assert(node, 'RPC requires a Node.'); this.node = node; this.network = node.network; this.chain = node.chain; this.mempool = node.mempool; this.pool = node.pool; this.fees = node.fees; this.miner = node.miner; this.logger = node.logger.context('rpc'); this.mining = false; this.procLimit = 0; this.attempt = null; this.lastActivity = 0; this.boundChain = false; this.nonce1 = 0; this.nonce2 = 0; this.merkleMap = {}; this.pollers = []; this.init(); } util.inherits(RPC, RPCBase); RPC.prototype.init = function init() { this.add('stop', this.stop); this.add('help', this.help); this.add('getblockchaininfo', this.getBlockchainInfo); this.add('getbestblockhash', this.getBestBlockHash); this.add('getblockcount', this.getBlockCount); this.add('getblock', this.getBlock); this.add('getblockbyheight', this.getBlockByHeight); this.add('getblockhash', this.getBlockHash); this.add('getblockheader', this.getBlockHeader); this.add('getchaintips', this.getChainTips); this.add('getdifficulty', this.getDifficulty); this.add('getmempoolancestors', this.getMempoolAncestors); this.add('getmempooldescendants', this.getMempoolDescendants); this.add('getmempoolentry', this.getMempoolEntry); this.add('getmempoolinfo', this.getMempoolInfo); this.add('getrawmempool', this.getRawMempool); this.add('gettxout', this.getTXOut); this.add('gettxoutsetinfo', this.getTXOutSetInfo); this.add('verifychain', this.verifyChain); this.add('invalidateblock', this.invalidateBlock); this.add('reconsiderblock', this.reconsiderBlock); this.add('getnetworkhashps', this.getNetworkHashPS); this.add('getmininginfo', this.getMiningInfo); this.add('prioritisetransaction', this.prioritiseTransaction); this.add('getwork', this.getWork); this.add('getworklp', this.getWorkLongpoll); this.add('getblocktemplate', this.getBlockTemplate); this.add('submitblock', this.submitBlock); this.add('verifyblock', this.verifyBlock); this.add('setgenerate', this.setGenerate); this.add('getgenerate', this.getGenerate); this.add('generate', this.generate); this.add('generatetoaddress', this.generateToAddress); this.add('estimatefee', this.estimateFee); this.add('estimatepriority', this.estimatePriority); this.add('estimatesmartfee', this.estimateSmartFee); this.add('estimatesmartpriority', this.estimateSmartPriority); this.add('getinfo', this.getInfo); this.add('validateaddress', this.validateAddress); this.add('createmultisig', this.createMultisig); this.add('createwitnessaddress', this.createWitnessAddress); this.add('verifymessage', this.verifyMessage); this.add('signmessagewithprivkey', this.signMessageWithPrivkey); this.add('setmocktime', this.setMockTime); this.add('getconnectioncount', this.getConnectionCount); this.add('ping', this.ping); this.add('getpeerinfo', this.getPeerInfo); this.add('addnode', this.addNode); this.add('disconnectnode', this.disconnectNode); this.add('getaddednodeinfo', this.getAddedNodeInfo); this.add('getnettotals', this.getNetTotals); this.add('getnetworkinfo', this.getNetworkInfo); this.add('setban', this.setBan); this.add('listbanned', this.listBanned); this.add('clearbanned', this.clearBanned); this.add('getrawtransaction', this.getRawTransaction); this.add('createrawtransaction', this.createRawTransaction); this.add('decoderawtransaction', this.decodeRawTransaction); this.add('decodescript', this.decodeScript); this.add('sendrawtransaction', this.sendRawTransaction); this.add('signrawtransaction', this.signRawTransaction); this.add('gettxoutproof', this.getTXOutProof); this.add('verifytxoutproof', this.verifyTXOutProof); this.add('getmemoryinfo', this.getMemoryInfo); this.add('setloglevel', this.setLogLevel); }; /* * Overall control/query calls */ RPC.prototype.getInfo = co(function* getInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getinfo'); return { version: pkg.version, protocolversion: this.pool.options.version, walletversion: 0, balance: 0, blocks: this.chain.height, timeoffset: this.network.time.offset, connections: this.pool.peers.size(), proxy: '', difficulty: toDifficulty(this.chain.tip.bits), testnet: this.network !== Network.main, keypoololdest: 0, keypoolsize: 0, unlocked_until: 0, paytxfee: Amount.btc(this.network.feeRate, true), relayfee: Amount.btc(this.network.minRelay, true), errors: '' }; }); RPC.prototype.help = co(function* _help(args, help) { var json; if (args.length === 0) return 'Select a command.'; json = { method: args[0], params: [] }; return yield this.execute(json, true); }); RPC.prototype.stop = co(function* stop(args, help) { var self = this; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'stop'); co(function* () { try { yield self.node.close(); } catch (e) { if (process.exit) process.exit(1); return; } if (process.exit) process.exit(0); })(); return 'Stopping.'; }); /* * P2P networking */ RPC.prototype.getNetworkInfo = co(function* getNetworkInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getnetworkinfo'); return { version: pkg.version, subversion: this.pool.options.agent, protocolversion: this.pool.options.version, localservices: util.hex32(this.pool.options.services), localrelay: true, timeoffset: this.network.time.offset, connections: this.pool.peers.size(), networks: [], relayfee: Amount.btc(this.network.minRelay, true), localaddresses: [], warnings: '' }; }); RPC.prototype.addNode = co(function* addNode(args, help) { var valid = new Validator([args]); var node = valid.str(0, ''); var cmd = valid.str(1, ''); var addr, peer; if (help || args.length !== 2) throw new RPCError(errs.MISC_ERROR, 'addnode "node" "add|remove|onetry"'); addr = NetAddress.fromHostname(node, this.network); switch (cmd) { case 'add': this.pool.hosts.add(addr); break; case 'remove': this.pool.hosts.remove(addr.hostname); break; case 'onetry': if (!this.pool.peers.get(addr.hostname)) { peer = this.pool.createOutbound(addr); this.pool.peers.add(peer); } break; } return null; }); RPC.prototype.disconnectNode = co(function* disconnectNode(args, help) { var valid = new Validator([args]); var addr = valid.str(0, ''); var peer; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'disconnectnode "node"'); addr = IP.fromHostname(addr, this.network.port); peer = this.pool.peers.get(addr.hostname); if (peer) peer.destroy(); return null; }); RPC.prototype.getAddedNodeInfo = co(function* getAddedNodeInfo(args, help) { var valid = new Validator([args]); var addr = valid.str(1, ''); var out = []; var peer; if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getaddednodeinfo dummy ( "node" )'); if (args.length === 2) { addr = IP.fromHostname(addr, this.network.port); peer = this.pool.peers.get(addr.hostname); if (!peer) { throw new RPCError(errs.CLIENT_NODE_NOT_ADDED, 'Node has not been added.'); } return [toAddedNode(peer)]; } for (peer = this.pool.peers.head(); peer; peer = peer.next) out.push(toAddedNode(peer)); return out; }); RPC.prototype.getConnectionCount = co(function* getConnectionCount(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getconnectioncount'); return this.pool.peers.size(); }); RPC.prototype.getNetTotals = co(function* getNetTotals(args, help) { var sent = 0; var recv = 0; var peer; if (help || args.length > 0) throw new RPCError(errs.MISC_ERROR, 'getnettotals'); for (peer = this.pool.peers.head(); peer; peer = peer.next) { sent += peer.socket.bytesWritten; recv += peer.socket.bytesRead; } return { totalbytesrecv: recv, totalbytessent: sent, timemillis: util.ms() }; }); RPC.prototype.getPeerInfo = co(function* getPeerInfo(args, help) { var peers = []; var id = 0; var peer, offset; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getpeerinfo'); for (peer = this.pool.peers.head(); peer; peer = peer.next) { offset = this.network.time.known[peer.hostname()]; if (offset == null) offset = 0; peers.push({ id: id++, addr: peer.hostname(), addrlocal: !peer.local.isNull() ? peer.local.hostname : undefined, services: util.hex32(peer.services), relaytxes: !peer.noRelay, lastsend: peer.lastSend / 1000 | 0, lastrecv: peer.lastRecv / 1000 | 0, bytessent: peer.socket.bytesWritten, bytesrecv: peer.socket.bytesRead, conntime: peer.ts !== 0 ? (util.ms() - peer.ts) / 1000 | 0 : 0, timeoffset: offset, pingtime: peer.lastPong !== -1 ? (peer.lastPong - peer.lastPing) / 1000 : -1, minping: peer.minPing !== -1 ? peer.minPing / 1000 : -1, version: peer.version, subver: peer.agent, inbound: !peer.outbound, startingheight: peer.height, besthash: peer.bestHash ? util.revHex(peer.bestHash) : null, bestheight: peer.bestHeight, banscore: peer.banScore, inflight: peer.blockMap.keys().map(util.revHex), whitelisted: false }); } return peers; }); RPC.prototype.ping = co(function* ping(args, help) { var peer; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'ping'); for (peer = this.pool.peers.head(); peer; peer = peer.next) peer.sendPing(); return null; }); RPC.prototype.setBan = co(function* setBan(args, help) { var valid = new Validator([args]); var addr = valid.str(0, ''); var action = valid.str(1, ''); if (help || args.length < 2 || (action !== 'add' && action !== 'remove')) { throw new RPCError(errs.MISC_ERROR, 'setban "ip(/netmask)" "add|remove" (bantime) (absolute)'); } addr = NetAddress.fromHostname(addr, this.network); switch (action) { case 'add': this.pool.ban(addr); break; case 'remove': this.pool.unban(addr); break; } return null; }); RPC.prototype.listBanned = co(function* listBanned(args, help) { var i, banned, keys, host, time; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'listbanned'); banned = []; keys = Object.keys(this.pool.hosts.banned); for (i = 0; i < keys.length; i++) { host = keys[i]; time = this.pool.hosts.banned[host]; banned.push({ address: host, banned_until: time + this.pool.options.banTime, ban_created: time, ban_reason: '' }); } return banned; }); RPC.prototype.clearBanned = co(function* clearBanned(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'clearbanned'); this.pool.hosts.clearBanned(); return null; }); /* Block chain and UTXO */ RPC.prototype.getBlockchainInfo = co(function* getBlockchainInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getblockchaininfo'); return { chain: this.network.type !== 'testnet' ? this.network.type : 'test', blocks: this.chain.height, headers: this.chain.height, bestblockhash: this.chain.tip.rhash(), difficulty: toDifficulty(this.chain.tip.bits), mediantime: yield this.chain.tip.getMedianTime(), verificationprogress: this.chain.getProgress(), chainwork: this.chain.tip.chainwork.toString('hex', 64), pruned: this.chain.options.prune, softforks: this.getSoftforks(), bip9_softforks: yield this.getBIP9Softforks(), pruneheight: this.chain.options.prune ? Math.max(0, this.chain.height - this.network.block.keepBlocks) : null }; }); RPC.prototype.getBestBlockHash = co(function* getBestBlockHash(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getbestblockhash'); return this.chain.tip.rhash(); }); RPC.prototype.getBlockCount = co(function* getBlockCount(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getblockcount'); return this.chain.tip.height; }); RPC.prototype.getBlock = co(function* getBlock(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var verbose = valid.bool(1, true); var details = valid.bool(2, false); var entry, block; if (help || args.length < 1 || args.length > 3) throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); block = yield this.chain.db.getBlock(entry.hash); if (!block) { if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)'); if (this.chain.options.prune) throw new RPCError(errs.MISC_ERROR, 'Block not available (pruned data)'); throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk'); } if (!verbose) return block.toRaw().toString('hex'); return yield this.blockToJSON(entry, block, details); }); RPC.prototype.getBlockByHeight = co(function* getBlockByHeight(args, help) { var valid = new Validator([args]); var height = valid.u32(0, -1); var verbose = valid.bool(1, true); var details = valid.bool(2, false); var entry, block; if (help || args.length < 1 || args.length > 3) throw new RPCError(errs.MISC_ERROR, 'getblockbyheight "height" ( verbose )'); if (height === -1) throw new RPCError(errs.TYPE_ERROR, 'Invalid block height.'); entry = yield this.chain.db.getEntry(height); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); block = yield this.chain.db.getBlock(entry.hash); if (!block) { if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)'); if (this.chain.options.prune) throw new RPCError(errs.MISC_ERROR, 'Block not available (pruned data)'); throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk'); } if (!verbose) return block.toRaw().toString('hex'); return yield this.blockToJSON(entry, block, details); }); RPC.prototype.getBlockHash = co(function* getBlockHash(args, help) { var valid = new Validator([args]); var height = valid.u32(0); var hash; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'getblockhash index'); if (height == null || height > this.chain.height) throw new RPCError(errs.INVALID_PARAMETER, 'Block height out of range.'); hash = yield this.chain.db.getHash(height); if (!hash) throw new RPCError(errs.MISC_ERROR, 'Not found.'); return util.revHex(hash); }); RPC.prototype.getBlockHeader = co(function* getBlockHeader(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var verbose = valid.bool(1, true); var entry; if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )'); if (!hash) throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found'); if (!verbose) return entry.toRaw().toString('hex', 0, 80); return yield this.headerToJSON(entry); }); RPC.prototype.getChainTips = co(function* getChainTips(args, help) { var i, hash, tips, result, entry, fork, main; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getchaintips'); tips = yield this.chain.db.getTips(); result = []; for (i = 0; i < tips.length; i++) { hash = tips[i]; entry = yield this.chain.db.getEntry(hash); assert(entry); fork = yield this.findFork(entry); main = yield entry.isMainChain(); result.push({ height: entry.height, hash: entry.rhash(), branchlen: entry.height - fork.height, status: main ? 'active' : 'valid-headers' }); } return result; }); RPC.prototype.getDifficulty = co(function* getDifficulty(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getdifficulty'); return toDifficulty(this.chain.tip.bits); }); RPC.prototype.getMempoolInfo = co(function* getMempoolInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getmempoolinfo'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); return { size: this.mempool.totalTX, bytes: this.mempool.getSize(), usage: this.mempool.getSize(), maxmempool: this.mempool.options.maxSize, mempoolminfee: Amount.btc(this.mempool.options.minRelay, true) }; }); RPC.prototype.getMempoolAncestors = co(function* getMempoolAncestors(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var verbose = valid.bool(1, false); var out = []; var i, entry, entries; if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); entries = this.mempool.getAncestors(entry); if (verbose) { for (i = 0; i < entries.length; i++) { entry = entries[i]; out.push(this.entryToJSON(entry)); } } else { for (i = 0; i < entries.length; i++) { entry = entries[i]; out.push(entry.txid()); } } return out; }); RPC.prototype.getMempoolDescendants = co(function* getMempoolDescendants(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var verbose = valid.bool(1, false); var out = []; var i, entry, entries; if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getmempooldescendants txid (verbose)'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); entries = this.mempool.getDescendants(entry); if (verbose) { for (i = 0; i < entries.length; i++) { entry = entries[i]; out.push(this.entryToJSON(entry)); } } else { for (i = 0; i < entries.length; i++) { entry = entries[i]; out.push(entry.txid()); } } return out; }); RPC.prototype.getMempoolEntry = co(function* getMempoolEntry(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var entry; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); return this.entryToJSON(entry); }); RPC.prototype.getRawMempool = co(function* getRawMempool(args, help) { var valid = new Validator([args]); var verbose = valid.bool(0, false); var out = {}; var i, hashes, hash, entry; if (help || args.length > 1) throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (verbose) { hashes = this.mempool.getSnapshot(); for (i = 0; i < hashes.length; i++) { hash = hashes[i]; entry = this.mempool.getEntry(hash); if (!entry) continue; out[entry.txid()] = this.entryToJSON(entry); } return out; } hashes = this.mempool.getSnapshot(); return hashes.map(util.revHex); }); RPC.prototype.getTXOut = co(function* getTXOut(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var index = valid.u32(1); var mempool = valid.bool(2, true); var coin; if (help || args.length < 2 || args.length > 3) throw new RPCError(errs.MISC_ERROR, 'gettxout "txid" n ( includemempool )'); if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); if (this.chain.options.prune) throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); if (!hash || index == null) throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); if (mempool) { if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); coin = this.mempool.getCoin(hash, index); } if (!coin) coin = yield this.chain.db.getCoin(hash, index); if (!coin) return null; return { bestblock: this.chain.tip.rhash(), confirmations: coin.getDepth(this.chain.height), value: Amount.btc(coin.value, true), scriptPubKey: this.scriptToJSON(coin.script, true), version: coin.version, coinbase: coin.coinbase }; }); RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { var valid = new Validator([args]); var txids = valid.array(0); var hash = valid.hash(1); var uniq = {}; var i, block, txid, tx, coins; if (help || (args.length !== 1 && args.length !== 2)) { throw new RPCError(errs.MISC_ERROR, 'gettxoutproof ["txid",...] ( blockhash )'); } if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); if (this.chain.options.prune) throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); if (!txids || txids.length === 0) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.'); valid = new Validator([txids]); for (i = 0; i < txids.length; i++) { txid = valid.hash(i); if (!txid) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); if (uniq[txid]) throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate txid.'); uniq[txid] = true; txids[i] = txid; } if (hash) { block = yield this.chain.db.getBlock(hash); } else if (this.chain.options.indexTX) { tx = yield this.chain.db.getMeta(txid); if (!tx) return; block = yield this.chain.db.getBlock(tx.block); } else { coins = yield this.chain.db.getCoins(txid); if (!coins) return; block = yield this.chain.db.getBlock(coins.height); } if (!block) throw new RPCError(errs.MISC_ERROR, 'Block not found.'); for (i = 0; i < txids.length; i++) { txid = txids[i]; if (!block.hasTX(txid)) { throw new RPCError(errs.VERIFY_ERROR, 'Block does not contain all txids.'); } } block = MerkleBlock.fromHashes(block, txids); return block.toRaw().toString('hex'); }); RPC.prototype.verifyTXOutProof = co(function* verifyTXOutProof(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var out = []; var i, block, hash, entry; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"'); if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); block = MerkleBlock.fromRaw(data); if (!block.verify()) return out; entry = yield this.chain.db.getEntry(block.hash('hex')); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.'); for (i = 0; i < block.matches.length; i++) { hash = block.matches[i]; out.push(util.revHex(hash)); } return out; }); RPC.prototype.getTXOutSetInfo = co(function* getTXOutSetInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'gettxoutsetinfo'); if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Chainstate not available (SPV mode).'); return { height: this.chain.height, bestblock: this.chain.tip.rhash(), transactions: this.chain.db.state.tx, txouts: this.chain.db.state.coin, bytes_serialized: 0, hash_serialized: 0, total_amount: Amount.btc(this.chain.db.state.value, true) }; }); RPC.prototype.verifyChain = co(function* verifyChain(args, help) { var valid = new Validator([args]); var level = valid.u32(0); var blocks = valid.u32(1); if (help || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'verifychain ( checklevel numblocks )'); if (level == null || blocks == null) throw new RPCError(errs.TYPE_ERROR, 'Missing parameters.'); if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain in SPV mode.'); if (this.chain.options.prune) throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain when pruned.'); return null; }); /* * Mining */ RPC.prototype.submitWork = co(function* submitWork(data) { var unlock = yield this.locker.lock(); try { return yield this._submitWork(data); } finally { unlock(); } }); RPC.prototype._submitWork = co(function* _submitWork(data) { var attempt = this.attempt; var header, nonce, ts, nonces; var n1, n2, proof, block, entry; if (!attempt) return false; if (data.length !== 128) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid work size.'); header = Headers.fromAbbr(data); data = data.slice(0, 80); data = swap32(data); if (header.prevBlock !== attempt.prevBlock || header.bits !== attempt.bits) { return false; } if (!header.verify()) return false; nonces = this.merkleMap[header.merkleRoot]; if (!nonces) return false; n1 = nonces.nonce1; n2 = nonces.nonce2; nonce = header.nonce; ts = header.ts; proof = attempt.getProof(n1, n2, ts, nonce); if (!proof.verify(attempt.target)) return false; block = attempt.commit(proof); try { entry = yield this.chain.add(block); } catch (err) { if (err.type === 'VerifyError') { this.logger.warning('RPC block rejected: %s (%s).', block.rhash(), err.reason); return false; } throw err; } if (!entry) { this.logger.warning('RPC block rejected: %s (bad-prevblk).', block.rhash()); return false; } return true; }); RPC.prototype.createWork = co(function* createWork(data) { var unlock = yield this.locker.lock(); try { return yield this._createWork(data); } finally { unlock(); } }); RPC.prototype._createWork = co(function* _createWork() { var attempt = yield this.updateWork(); var n1 = this.nonce1; var n2 = this.nonce2; var ts = attempt.ts; var data, root, head; data = new Buffer(128); data.fill(0); root = attempt.getRoot(n1, n2); head = attempt.getHeader(root, ts, 0); head.copy(data, 0); data[80] = 0x80; data.writeUInt32BE(80 * 8, data.length - 4, true); data = swap32(data); return { data: data.toString('hex'), target: attempt.target.toString('hex'), height: attempt.height }; }); RPC.prototype.getWorkLongpoll = co(function* getWorkLongpoll(args, help) { yield this.longpoll(); return yield this.createWork(); }); RPC.prototype.getWork = co(function* getWork(args, help) { var valid = new Validator([args]); var data = valid.buf(0); if (args.length > 1) throw new RPCError(errs.MISC_ERROR, 'getwork ( "data" )'); if (args.length === 1) { if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.'); return yield this.submitWork(data); } return yield this.createWork(); }); RPC.prototype.submitBlock = co(function* submitBlock(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var block; if (help || args.length < 1 || args.length > 2) { throw new RPCError(errs.MISC_ERROR, 'submitblock "hexdata" ( "jsonparametersobject" )'); } block = Block.fromRaw(data); return yield this.addBlock(block); }); RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { var validator = new Validator([args]); var options = validator.obj(0, {}); var valid = new Validator([options]); var mode = valid.str('mode', 'template'); var lpid = valid.str('longpollid'); var data = valid.buf('data'); var rules = valid.array('rules'); var capabilities = valid.array('capabilities'); var maxVersion = valid.u32('maxversion', -1); var coinbase = false; var txnCap = false; var valueCap = false; var i, capability, block; if (help || args.length > 1) { throw new RPCError(errs.MISC_ERROR, 'getblocktemplate ( "jsonrequestobject" )'); } if (mode !== 'template' && mode !== 'proposal') throw new RPCError(errs.INVALID_PARAMETER, 'Invalid mode.'); if (mode === 'proposal') { if (!data) throw new RPCError(errs.TYPE_ERROR, 'Missing data parameter.'); block = Block.fromRaw(data); if (block.prevBlock !== this.chain.tip.hash) return 'inconclusive-not-best-prevblk'; try { yield this.chain.verifyBlock(block); } catch (e) { if (e.type === 'VerifyError') return e.reason; throw e; } return null; } if (rules) maxVersion = -1; if (capabilities) { for (i = 0; i < capabilities.length; i++) { capability = capabilities[i]; if (typeof capability !== 'string') throw new RPCError(errs.TYPE_ERROR, 'Invalid capability.'); switch (capability) { case 'coinbasetxn': txnCap = true; break; case 'coinbasevalue': // Prefer value if they support it. valueCap = true; break; } } // BIP22 states that we can't have coinbasetxn // _and_ coinbasevalue in the same template. // The problem is, many clients _say_ they // support coinbasetxn when they don't (ckpool). // To make matters worse, some clients will // parse an undefined `coinbasevalue` as zero. // Because of all of this, coinbasetxn is // disabled for now. valueCap = true; if (txnCap && !valueCap) { if (this.miner.addresses.length === 0) { throw new RPCError(errs.MISC_ERROR, 'No addresses available for coinbase.'); } coinbase = true; } } if (!this.network.selfConnect) { if (this.pool.peers.size() === 0) { throw new RPCError(errs.CLIENT_NOT_CONNECTED, 'Bitcoin is not connected!'); } if (!this.chain.synced) { throw new RPCError(errs.CLIENT_IN_INITIAL_DOWNLOAD, 'Bitcoin is downloading blocks...'); } } if (lpid) yield this.handleLongpoll(lpid); return yield this.createTemplate(maxVersion, coinbase, rules); }); RPC.prototype.createTemplate = co(function* createTemplate(maxVersion, coinbase, rules) { var unlock = yield this.locker.lock(); try { return yield this._createTemplate(maxVersion, coinbase, rules); } finally { unlock(); } }); RPC.prototype._createTemplate = co(function* _createTemplate(maxVersion, coinbase, rules) { var attempt = yield this.getTemplate(); var version = attempt.version; var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR; var mutable = ['time', 'transactions', 'prevblock']; var txs = []; var index = {}; var vbavailable = {}; var vbrules = []; var i, j, entry, tx, input, output; var dep, deps, json, name, deploy; var state; // The miner doesn't support // versionbits. Force them to // encode our version. if (maxVersion >= 2) mutable.push('version/force'); // Allow the miner to change // our provided coinbase. // Note that these are implied // without `coinbasetxn`. if (coinbase) { mutable.push('coinbase'); mutable.push('coinbase/append'); mutable.push('generation'); } // Build an index of every transaction. for (i = 0; i < attempt.items.length; i++) { entry = attempt.items[i]; index[entry.hash] = i + 1; } // Calculate dependencies for each transaction. for (i = 0; i < attempt.items.length; i++) { entry = attempt.items[i]; tx = entry.tx; deps = []; for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; dep = index[input.prevout.hash]; if (dep == null) continue; if (deps.indexOf(dep) === -1) { assert(dep < i + 1); deps.push(dep); } } txs.push({ data: tx.toRaw().toString('hex'), txid: tx.txid(), hash: tx.wtxid(), depends: deps, fee: entry.fee, sigops: entry.sigops / scale | 0, weight: tx.getWeight() }); } // Calculate version based on given rules. for (i = 0; i < this.network.deploys.length; i++) { deploy = this.network.deploys[i]; state = yield this.chain.getState(this.chain.tip, deploy); name = deploy.name; switch (state) { case common.thresholdStates.DEFINED: case common.thresholdStates.FAILED: break; case common.thresholdStates.LOCKED_IN: version |= 1 << deploy.bit; case common.thresholdStates.STARTED: if (!deploy.force) { if (!rules || rules.indexOf(name) === -1) version &= ~(1 << deploy.bit); name = '!' + name; } vbavailable[name] = deploy.bit; break; case common.thresholdStates.ACTIVE: if (!deploy.force) { if (!rules || rules.indexOf(name) === -1) { throw new RPCError(errs.INVALID_PARAMETER, 'Client must support ' + name + '.'); } name = '!' + name; } vbrules.push(name); break; default: assert(false, 'Bad state.'); break; } } version >>>= 0; json = { capabilities: ['proposal'], mutable: mutable, version: version, rules: vbrules, vbavailable: vbavailable, vbrequired: 0, height: attempt.height, previousblockhash: util.revHex(attempt.prevBlock), target: util.revHex(attempt.target.toString('hex')), bits: util.hex32(attempt.bits), noncerange: '00000000ffffffff', curtime: attempt.ts, mintime: attempt.mtp + 1, maxtime: attempt.ts + 7200, expires: attempt.ts + 7200, sigoplimit: consensus.MAX_BLOCK_SIGOPS_COST / scale | 0, sizelimit: consensus.MAX_BLOCK_SIZE, weightlimit: undefined, longpollid: this.chain.tip.rhash() + util.pad32(this.totalTX()), submitold: false, coinbaseaux: { flags: attempt.coinbaseFlags.toString('hex') }, coinbasevalue: undefined, coinbasetxn: undefined, default_witness_commitment: undefined, transactions: txs }; // See: // bitcoin/bitcoin#9fc7f0bce94f1cea0239b1543227f22a3f3b9274 if (attempt.witness) { json.sizelimit = consensus.MAX_RAW_BLOCK_SIZE; json.weightlimit = consensus.MAX_BLOCK_WEIGHT; } // The client wants a coinbasetxn // instead of a coinbasevalue. if (coinbase) { tx = attempt.toCoinbase(); // Pop off the nonces. tx.inputs[0].script.code.pop(); tx.inputs[0].script.compile(); if (attempt.witness) { // We don't include the commitment // output (see bip145). output = tx.outputs.pop(); assert(output.script.isCommitment()); // Also not including the witness nonce. tx.inputs[0].witness.length = 0; tx.inputs[0].witness.compile(); tx.refresh(); } json.coinbasetxn = { data: tx.toRaw().toString('hex'), txid: tx.txid(), hash: tx.wtxid(), depends: [], fee: 0, sigops: tx.getSigopsCost() / scale | 0, weight: tx.getWeight() }; } else { json.coinbasevalue = attempt.getReward(); } if (rules && rules.indexOf('segwit') !== -1) json.default_witness_commitment = attempt.getWitnessScript().toJSON(); return json; }); RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) { var attempt = this.attempt; var size = 0; var weight = 0; var txs = 0; var diff = 0; var i, item; if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getmininginfo'); if (attempt) { weight = attempt.weight; txs = attempt.items.length + 1; diff = attempt.getDifficulty(); size = 1000; for (i = 0; i < attempt.items.length; i++) { item = attempt.items[i]; size += item.tx.getBaseSize(); } } return { blocks: this.chain.height, currentblocksize: size, currentblockweight: weight, currentblocktx: txs, difficulty: diff, errors: '', genproclimit: this.procLimit, networkhashps: yield this.getHashRate(120), pooledtx: this.totalTX(), testnet: this.network !== Network.main, chain: this.network.type !== 'testnet' ? this.network.type : 'test', generate: this.mining }; }); RPC.prototype.getNetworkHashPS = co(function* getNetworkHashPS(args, help) { var valid = new Validator([args]); var lookup = valid.u32(0, 120); var height = valid.u32(1); if (help || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )'); return yield this.getHashRate(lookup, height); }); RPC.prototype.prioritiseTransaction = co(function* prioritiseTransaction(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var pri = valid.num(1); var fee = valid.i64(2); var entry; if (help || args.length !== 3) { throw new RPCError(errs.MISC_ERROR, 'prioritisetransaction '); } if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID'); if (pri == null || fee == null) throw new RPCError(errs.TYPE_ERROR, 'Invalid fee or priority.'); entry = this.mempool.getEntry(hash); if (!entry) throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); this.mempool.prioritise(entry, pri, fee); return true; }); RPC.prototype.verifyBlock = co(function* verifyBlock(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var block; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"'); if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hex.'); if (this.chain.options.spv) throw new RPCError(errs.MISC_ERROR, 'Cannot verify block in SPV mode.'); block = Block.fromRaw(data); try { yield this.chain.verifyBlock(block); } catch (e) { if (e.type === 'VerifyError') return e.reason; throw e; } return null; }); /* * Coin generation */ RPC.prototype.getGenerate = co(function* getGenerate(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getgenerate'); return this.mining; }); RPC.prototype.setGenerate = co(function* setGenerate(args, help) { var valid = new Validator([args]); var mine = valid.bool(0, false); var limit = valid.u32(1, 0); if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )'); this.mining = mine; this.procLimit = limit; if (mine) { this.miner.cpu.start(); return true; } yield this.miner.cpu.stop(); return false; }); RPC.prototype.generate = co(function* generate(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); var tries = valid.u32(1); if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )'); return yield this.mineBlocks(blocks, null, tries); }); RPC.prototype.generateToAddress = co(function* _generateToAddress(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); var addr = valid.str(1, ''); var tries = valid.u32(2); if (help || args.length < 2 || args.length > 3) { throw new RPCError(errs.MISC_ERROR, 'generatetoaddress numblocks address ( maxtries )'); } addr = parseAddress(addr, this.network); return yield this.mineBlocks(blocks, addr, tries); }); /* * Raw transactions */ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, help) { var valid = new Validator([args]); var inputs = valid.array(0); var sendTo = valid.obj(1); var locktime = valid.u32(2); var i, tx, input, output, hash, index, sequence; var keys, addrs, key, value, address, b58; if (help || args.length < 2 || args.length > 3) { throw new RPCError(errs.MISC_ERROR, 'createrawtransaction' + ' [{"txid":"id","vout":n},...]' + ' {"address":amount,"data":"hex",...}' + ' ( locktime )'); } if (!inputs || !sendTo) { throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters (inputs and sendTo).'); } tx = new MTX(); if (locktime != null) tx.locktime = locktime; for (i = 0; i < inputs.length; i++) { input = inputs[i]; valid = new Validator([input]); hash = valid.hash('txid'); index = valid.u32('vout'); sequence = valid.u32('sequence', 0xffffffff); if (tx.locktime) sequence--; if (!hash || index == null) throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); input = new Input(); input.prevout.hash = hash; input.prevout.index = index; input.sequence = sequence; tx.inputs.push(input); } keys = Object.keys(sendTo); valid = new Validator([sendTo]); addrs = {}; for (i = 0; i < keys.length; i++) { key = keys[i]; if (key === 'data') { value = valid.buf(key); if (!value) throw new RPCError(errs.TYPE_ERROR, 'Invalid nulldata..'); output = new Output(); output.value = 0; output.script.fromNulldata(value); tx.outputs.push(output); continue; } address = parseAddress(key, this.network); b58 = address.toString(this.network); if (addrs[b58]) throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address'); addrs[b58] = true; value = valid.btc(key); if (value == null) throw new RPCError(errs.TYPE_ERROR, 'Invalid output value.'); output = new Output(); output.value = value; output.script.fromAddress(address); tx.outputs.push(output); } return tx.toRaw().toString('hex'); }); RPC.prototype.decodeRawTransaction = co(function* decodeRawTransaction(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var tx; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"'); if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); tx = TX.fromRaw(data); return this.txToJSON(tx); }); RPC.prototype.decodeScript = co(function* decodeScript(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var script, address; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"'); script = new Script(); if (data) script = Script.fromRaw(data); address = Address.fromScripthash(script.hash160()); script = this.scriptToJSON(script); script.p2sh = address.toString(this.network); return script; }); RPC.prototype.getRawTransaction = co(function* getRawTransaction(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); var verbose = valid.bool(1, false); var json, meta, tx, entry; if (help || args.length < 1 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'getrawtransaction "txid" ( verbose )'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); meta = yield this.node.getMeta(hash); if (!meta) throw new RPCError(errs.MISC_ERROR, 'Transaction not found.'); tx = meta.tx; if (!verbose) return tx.toRaw().toString('hex'); if (meta.block) entry = yield this.chain.db.getEntry(meta.block); json = this.txToJSON(tx, entry); json.time = meta.ps; json.hex = tx.toRaw().toString('hex'); return json; }); RPC.prototype.sendRawTransaction = co(function* sendRawTransaction(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var tx; if (help || args.length < 1 || args.length > 2) { throw new RPCError(errs.MISC_ERROR, 'sendrawtransaction "hexstring" ( allowhighfees )'); } if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); tx = TX.fromRaw(data); this.node.relay(tx); return tx.txid(); }); RPC.prototype.signRawTransaction = co(function* signRawTransaction(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var prevout = valid.array(1); var secrets = valid.array(2); var sighash = valid.str(3); var type = Script.hashType.ALL; var keys = []; var map = {}; var i, j, tx, secret, key, coin; var hash, index, script, value; var prev, redeem, op, parts; if (help || args.length < 1 || args.length > 4) { throw new RPCError(errs.MISC_ERROR, 'signrawtransaction' + ' "hexstring" (' + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' + 'redeemScript":"hex"},...] ["privatekey1",...]' + ' sighashtype )'); } if (!data) throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); if (!this.mempool) throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); tx = MTX.fromRaw(data); tx.view = yield this.mempool.getSpentView(tx); if (secrets) { valid = new Validator([secrets]); for (i = 0; i < secrets.length; i++) { secret = valid.str(i, ''); key = parseSecret(secret, this.network); map[key.getPublicKey('hex')] = key; keys.push(key); } } if (prevout) { for (i = 0; i < prevout.length; i++) { prev = prevout[i]; valid = new Validator([prev]); hash = valid.hash('txid'); index = valid.u32('index'); script = valid.buf('scriptPubKey'); value = valid.btc('amount'); redeem = valid.buf('redeemScript'); if (!hash || index == null || !script || value == null) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid UTXO.'); script = Script.fromRaw(script); coin = new Output(); coin.script = script; coin.value = value; tx.view.addOutput(hash, index, coin); if (keys.length === 0 || !redeem) continue; if (!script.isScripthash() && !script.isWitnessScripthash()) continue; if (!redeem) { throw new RPCError(errs.INVALID_PARAMETER, 'P2SH requires redeem script.'); } redeem = Script.fromRaw(redeem); for (j = 0; j < redeem.code.length; j++) { op = redeem.code[j]; if (!op.data) continue; key = map[op.data.toString('hex')]; if (key) { key.script = redeem; key.witness = script.isWitnessScripthash(); key.refresh(); break; } } } } if (sighash) { parts = sighash.split('|'); type = Script.hashType[parts[0]]; if (type == null) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); if (parts.length > 2) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); if (parts.length === 2) { if (parts[1] !== 'ANYONECANPAY') throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); type |= Script.hashType.ANYONECANPAY; } } yield tx.signAsync(keys, type); return { hex: tx.toRaw().toString('hex'), complete: tx.isSigned() }; }); /* * Utility Functions */ RPC.prototype.createMultisig = co(function* createMultisig(args, help) { var valid = new Validator([args]); var keys = valid.array(1, []); var m = valid.u32(0, 0); var n = keys.length; var i, script, key, address; if (help || args.length < 2 || args.length > 2) throw new RPCError(errs.MISC_ERROR, 'createmultisig nrequired ["key",...]'); if (m < 1 || n < m || n > 16) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.'); valid = new Validator([keys]); for (i = 0; i < keys.length; i++) { key = valid.buf(i); if (!key) throw new RPCError(errs.TYPE_ERROR, 'Invalid key.'); if (!ec.publicKeyVerify(key)) throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.'); keys[i] = key; } script = Script.fromMultisig(m, n, keys); if (script.getSize() > consensus.MAX_SCRIPT_PUSH) throw new RPCError(errs.VERIFY_ERROR, 'Redeem script exceeds size limit.'); address = script.getAddress(); return { address: address.toString(this.network), redeemScript: script.toJSON() }; }); RPC.prototype.createWitnessAddress = co(function* createWitnessAddress(args, help) { var valid = new Validator([args]); var raw = valid.buf(0); var script, program, address; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'createwitnessaddress "script"'); if (!raw) throw new RPCError(errs.TYPE_ERROR, 'Invalid script hex.'); script = Script.fromRaw(raw); program = script.forWitness(); address = program.getAddress(); return { address: address.toString(this.network), witnessScript: program.toJSON() }; }); RPC.prototype.validateAddress = co(function* validateAddress(args, help) { var valid = new Validator([args]); var b58 = valid.str(0, ''); var address, script; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'validateaddress "bitcoinaddress"'); try { address = Address.fromString(b58, this.network); } catch (e) { return { isvalid: false }; } script = Script.fromAddress(address); return { isvalid: true, address: address.toString(this.network), scriptPubKey: script.toJSON(), ismine: false, iswatchonly: false }; }); RPC.prototype.verifyMessage = co(function* verifyMessage(args, help) { var valid = new Validator([args]); var b58 = valid.str(0, ''); var sig = valid.buf(1, null, 'base64'); var msg = valid.str(2); var hash = Address.getHash(b58); var key; if (help || args.length !== 3) { throw new RPCError(errs.MISC_ERROR, 'verifymessage "bitcoinaddress" "signature" "message"'); } if (!hash || !sig || !msg) throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); key = ec.recover(msg, sig, 0, true); if (!key) return false; key = crypto.hash160(key); return crypto.ccmp(key, hash); }); RPC.prototype.signMessageWithPrivkey = co(function* signMessageWithPrivkey(args, help) { var valid = new Validator([args]); var key = valid.str(0, ''); var msg = valid.str(1, ''); var sig; if (help || args.length !== 2) { throw new RPCError(errs.MISC_ERROR, 'signmessagewithprivkey "privkey" "message"'); } key = parseSecret(key, this.network); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); sig = key.sign(msg); return sig.toString('base64'); }); RPC.prototype.estimateFee = co(function* estimateFee(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); var fee; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks'); if (!this.fees) throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); if (blocks < 1) blocks = 1; fee = this.fees.estimateFee(blocks, false); if (fee === 0) return -1; return Amount.btc(fee, true); }); RPC.prototype.estimatePriority = co(function* estimatePriority(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks'); if (!this.fees) throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); if (blocks < 1) blocks = 1; return this.fees.estimatePriority(blocks, false); }); RPC.prototype.estimateSmartFee = co(function* estimateSmartFee(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); var fee; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks'); if (!this.fees) throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); if (blocks < 1) blocks = 1; fee = this.fees.estimateFee(blocks, true); if (fee === 0) fee = -1; else fee = Amount.btc(fee, true); return { fee: fee, blocks: blocks }; }); RPC.prototype.estimateSmartPriority = co(function* estimateSmartPriority(args, help) { var valid = new Validator([args]); var blocks = valid.u32(0, 1); var pri; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks'); if (!this.fees) throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); if (blocks < 1) blocks = 1; pri = this.fees.estimatePriority(blocks, true); return { priority: pri, blocks: blocks }; }); RPC.prototype.invalidateBlock = co(function* invalidateBlock(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); yield this.chain.invalidate(hash); return null; }); RPC.prototype.reconsiderBlock = co(function* reconsiderBlock(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"'); if (!hash) throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); this.chain.removeInvalid(hash); return null; }); RPC.prototype.setMockTime = co(function* setMockTime(args, help) { var valid = new Validator([args]); var ts = valid.u32(0); var delta; if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp'); if (ts == null) throw new RPCError(errs.TYPE_ERROR, 'Invalid timestamp.'); this.network.time.offset = 0; delta = this.network.now() - ts; this.network.time.offset = -delta; return null; }); RPC.prototype.getMemoryInfo = co(function* getMemoryInfo(args, help) { if (help || args.length !== 0) throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); return util.memoryUsage(); }); RPC.prototype.setLogLevel = co(function* setLogLevel(args, help) { var valid = new Validator([args]); var level = valid.str(0, ''); if (help || args.length !== 1) throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); this.logger.setLevel(level); return null; }); /* * Helpers */ RPC.prototype.handleLongpoll = co(function* handleLongpoll(lpid) { var watched, lastTX; if (lpid.length !== 74) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); watched = lpid.slice(0, 64); lastTX = +lpid.slice(64, 74); if (!util.isHex(watched) || !util.isNumber(lastTX) || lastTX < 0) throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); watched = util.revHex(watched); if (this.chain.tip.hash !== watched) return; yield this.longpoll(); }); RPC.prototype.longpoll = function longpoll() { var self = this; return new Promise(function(resolve, reject) { self.pollers.push(co.job(resolve, reject)); }); }; RPC.prototype.refreshBlock = function refreshBlock() { var pollers = this.pollers; var i, job; this.attempt = null; this.lastActivity = 0; this.merkleMap = {}; this.nonce1 = 0; this.nonce2 = 0; this.pollers = []; for (i = 0; i < pollers.length; i++) { job = pollers[i]; job.resolve(); } }; RPC.prototype.bindChain = function bindChain() { var self = this; if (this.boundChain) return; this.boundChain = true; this.node.on('connect', function() { if (!self.attempt) return; self.refreshBlock(); }); if (!this.mempool) return; this.node.on('tx', function() { if (!self.attempt) return; if (util.now() - self.lastActivity > 10) self.refreshBlock(); }); }; RPC.prototype.getTemplate = co(function* getTemplate() { var attempt = this.attempt; this.bindChain(); if (attempt) { this.miner.updateTime(attempt); } else { attempt = yield this.miner.createBlock(); this.attempt = attempt; this.lastActivity = util.now(); } return attempt; }); RPC.prototype.updateWork = co(function* updateWork() { var attempt = this.attempt; var root, n1, n2; this.bindChain(); if (attempt) { if (attempt.address.isNull()) { throw new RPCError(errs.MISC_ERROR, 'No addresses available for coinbase.'); } this.miner.updateTime(attempt); if (++this.nonce2 === 0x100000000) { this.nonce2 = 0; this.nonce1++; } n1 = this.nonce1; n2 = this.nonce2; root = attempt.getRoot(n1, n2); root = root.toString('hex'); this.merkleMap[root] = new Nonces(n1, n2); return attempt; } if (this.miner.addresses.length === 0) { throw new RPCError(errs.MISC_ERROR, 'No addresses available for coinbase.'); } attempt = yield this.miner.createBlock(); n1 = this.nonce1; n2 = this.nonce2; root = attempt.getRoot(n1, n2); root = root.toString('hex'); this.attempt = attempt; this.lastActivity = util.now(); this.merkleMap[root] = new Nonces(n1, n2); return attempt; }); RPC.prototype.addBlock = co(function* addBlock(block) { var unlock1 = yield this.locker.lock(); var unlock2 = yield this.chain.locker.lock(); try { return yield this._addBlock(block); } finally { unlock2(); unlock1(); } }); RPC.prototype._addBlock = co(function* _addBlock(block) { var entry, prev, state, tx; this.logger.info('Handling submitted block: %s.', block.rhash()); prev = yield this.chain.db.getEntry(block.prevBlock); if (prev) { state = yield this.chain.getDeployments(block.ts, prev); // Fix eloipool bug (witness nonce is not present). if (state.hasWitness() && block.getCommitmentHash()) { tx = block.txs[0]; if (!tx.hasWitness()) { this.logger.warning('Submitted block had no witness nonce.'); this.logger.debug(tx); // Recreate witness nonce (all zeroes). tx.inputs[0].witness.set(0, encoding.ZERO_HASH); tx.inputs[0].witness.compile(); tx.refresh(); block.refresh(); } } } try { entry = yield this.chain._add(block); } catch (err) { if (err.type === 'VerifyError') { this.logger.warning('RPC block rejected: %s (%s).', block.rhash(), err.reason); return 'rejected: ' + err.reason; } throw err; } if (!entry) { this.logger.warning('RPC block rejected: %s (bad-prevblk).', block.rhash()); return 'rejected: bad-prevblk'; } return null; }); RPC.prototype.totalTX = function totalTX() { return this.mempool ? this.mempool.totalTX : 0; }; RPC.prototype.getSoftforks = function getSoftforks() { return [ toDeployment('bip34', 2, this.chain.state.hasBIP34()), toDeployment('bip66', 3, this.chain.state.hasBIP66()), toDeployment('bip65', 4, this.chain.state.hasCLTV()) ]; }; RPC.prototype.getBIP9Softforks = co(function* getBIP9Softforks() { var tip = this.chain.tip; var forks = {}; var i, deployment, state, status; for (i = 0; i < this.network.deploys.length; i++) { deployment = this.network.deploys[i]; state = yield this.chain.getState(tip, deployment); switch (state) { case common.thresholdStates.DEFINED: status = 'defined'; break; case common.thresholdStates.STARTED: status = 'started'; break; case common.thresholdStates.LOCKED_IN: status = 'locked_in'; break; case common.thresholdStates.ACTIVE: status = 'active'; break; case common.thresholdStates.FAILED: status = 'failed'; break; default: assert(false, 'Bad state.'); break; } forks[deployment.name] = { status: status, bit: deployment.bit, startTime: deployment.startTime, timeout: deployment.timeout }; } return forks; }); RPC.prototype.getHashRate = co(function* getHashRate(lookup, height) { var tip = this.chain.tip; var i, minTime, maxTime, workDiff, timeDiff, ps, entry; if (height != null) tip = yield this.chain.db.getEntry(height); if (!tip) return 0; if (lookup <= 0) lookup = tip.height % this.network.pow.retargetInterval + 1; if (lookup > tip.height) lookup = tip.height; minTime = tip.ts; maxTime = minTime; entry = tip; for (i = 0; i < lookup; i++) { entry = yield entry.getPrevious(); if (!entry) throw new RPCError(errs.DATABASE_ERROR, 'Not found.'); minTime = Math.min(entry.ts, minTime); maxTime = Math.max(entry.ts, maxTime); } if (minTime === maxTime) return 0; workDiff = tip.chainwork.sub(entry.chainwork); timeDiff = maxTime - minTime; ps = +workDiff.toString(10) / timeDiff; return ps; }); RPC.prototype.mineBlocks = co(function* mineBlocks(blocks, address, tries) { var unlock = yield this.locker.lock(); try { return yield this._mineBlocks(blocks, address, tries); } finally { unlock(); } }); RPC.prototype._mineBlocks = co(function* _mineBlocks(blocks, address, tries) { var hashes = []; var i, block; for (i = 0; i < blocks; i++) { block = yield this.miner.mineBlock(null, address); hashes.push(block.rhash()); assert(yield this.chain.add(block)); } return hashes; }); RPC.prototype.findFork = co(function* findFork(entry) { while (entry) { if (yield entry.isMainChain()) return entry; entry = yield entry.getPrevious(); } throw new Error('Fork not found.'); }); RPC.prototype.txToJSON = function txToJSON(tx, entry) { var height = -1; var conf = 0; var time = 0; var hash = null; var vin = []; var vout = []; var i, input, output, json; if (entry) { height = entry.height; time = entry.ts; hash = entry.rhash(); conf = this.chain.height - height + 1; } for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; json = { coinbase: undefined, txid: undefined, scriptSig: undefined, txinwitness: undefined, sequence: input.sequence }; if (tx.isCoinbase()) { json.coinbase = input.script.toJSON(); } else { json.txid = input.prevout.txid(); json.vout = input.prevout.index; json.scriptSig = { asm: input.script.toASM(), hex: input.script.toJSON() }; } if (input.witness.items.length > 0) { json.txinwitness = input.witness.items.map(function(item) { return item.toString('hex'); }); } vin.push(json); } for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; vout.push({ value: Amount.btc(output.value, true), n: i, scriptPubKey: this.scriptToJSON(output.script, true) }); } return { txid: tx.txid(), hash: tx.wtxid(), size: tx.getSize(), vsize: tx.getVirtualSize(), version: tx.version, locktime: tx.locktime, vin: vin, vout: vout, blockhash: hash, confirmations: conf, time: time, blocktime: time, hex: undefined }; }; RPC.prototype.scriptToJSON = function scriptToJSON(script, hex) { var type = script.getType(); var address = script.getAddress(); var out; out = { asm: script.toASM(), hex: undefined, type: Script.typesByVal[type], reqSigs: 1, addresses: [], p2sh: undefined }; if (hex) out.hex = script.toJSON(); if (script.isMultisig()) out.reqSigs = script.getSmall(0); if (address) { address = address.toString(this.network); out.addresses.push(address); } return out; }; RPC.prototype.headerToJSON = co(function* headerToJSON(entry) { var medianTime = yield entry.getMedianTime(); var nextHash = yield this.chain.db.getNextHash(entry.hash); return { hash: entry.rhash(), confirmations: this.chain.height - entry.height + 1, height: entry.height, version: entry.version, versionHex: util.hex32(entry.version), merkleroot: util.revHex(entry.merkleRoot), time: entry.ts, mediantime: medianTime, bits: entry.bits, difficulty: toDifficulty(entry.bits), chainwork: entry.chainwork.toString('hex', 64), previousblockhash: entry.prevBlock !== encoding.NULL_HASH ? util.revHex(entry.prevBlock) : null, nextblockhash: nextHash ? util.revHex(nextHash) : null }; }); RPC.prototype.blockToJSON = co(function* blockToJSON(entry, block, details) { var mtp = yield entry.getMedianTime(); var nextHash = yield this.chain.db.getNextHash(entry.hash); var txs = []; var i, tx, json; for (i = 0; i < block.txs.length; i++) { tx = block.txs[i]; if (details) { json = this.txToJSON(tx, entry); txs.push(json); continue; } txs.push(tx.txid()); } return { hash: entry.rhash(), confirmations: this.chain.height - entry.height + 1, strippedsize: block.getBaseSize(), size: block.getSize(), weight: block.getWeight(), height: entry.height, version: entry.version, versionHex: util.hex32(entry.version), merkleroot: util.revHex(entry.merkleRoot), coinbase: block.txs[0].inputs[0].script, tx: txs, time: entry.ts, mediantime: mtp, bits: entry.bits, difficulty: toDifficulty(entry.bits), chainwork: entry.chainwork.toString('hex', 64), previousblockhash: entry.prevBlock !== encoding.NULL_HASH ? util.revHex(entry.prevBlock) : null, nextblockhash: nextHash ? util.revHex(nextHash) : null }; }); RPC.prototype.entryToJSON = function entryToJSON(entry) { return { size: entry.size, fee: Amount.btc(entry.deltaFee, true), modifiedfee: 0, time: entry.ts, height: entry.height, startingpriority: entry.priority, currentpriority: entry.getPriority(this.chain.height), descendantcount: this.mempool.countDescendants(entry), descendantsize: entry.descSize, descendantfees: entry.descFee, ancestorcount: this.mempool.countAncestors(entry), ancestorsize: 0, ancestorfees: 0, depends: this.mempool.getDepends(entry.tx).map(util.revHex) }; }; /* * Helpers */ function swap32(data) { var i, field; for (i = 0; i < data.length; i += 4) { field = data.readUInt32LE(i, true); data.writeUInt32BE(field, i, true); } return data; } function toAddedNode(peer) { return { addednode: peer.hostname(), connected: peer.connected, addresses: [ { address: peer.hostname(), connected: peer.outbound ? 'outbound' : 'inbound' } ] }; } function toDeployment(id, version, status) { return { id: id, version: version, reject: { status: status } }; } function Nonces(n1, n2) { this.nonce1 = n1; this.nonce2 = n2; } function parseAddress(raw, network) { try { return Address.fromString(raw, network); } catch (e) { throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.'); } } function parseSecret(raw, network) { try { return KeyRing.fromSecret(raw, network); } catch (e) { throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.'); } } function toDifficulty(bits) { var shift = (bits >>> 24) & 0xff; var diff = 0x0000ffff / (bits & 0x00ffffff); while (shift < 29) { diff *= 256.0; shift++; } while (shift > 29) { diff /= 256.0; shift--; } return diff; } /* * Expose */ module.exports = RPC;