diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 01988518..7c5316fd 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -158,7 +158,7 @@ RPC.prototype.init = function init() { RPC.prototype.getInfo = co(function* getInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getinfo'); + throw new RPCError(errs.MISC_ERROR, 'getinfo'); return { version: pkg.version, @@ -196,7 +196,7 @@ RPC.prototype.help = co(function* _help(args, help) { RPC.prototype.stop = co(function* stop(args, help) { if (help || args.length !== 0) - throw new RPCError('stop'); + throw new RPCError(errs.MISC_ERROR, 'stop'); this.node.close(); @@ -209,7 +209,7 @@ RPC.prototype.stop = co(function* stop(args, help) { RPC.prototype.getNetworkInfo = co(function* getNetworkInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getnetworkinfo'); + throw new RPCError(errs.MISC_ERROR, 'getnetworkinfo'); return { version: pkg.version, @@ -233,7 +233,7 @@ RPC.prototype.addNode = co(function* addNode(args, help) { var addr, peer; if (help || args.length !== 2) - throw new RPCError('addnode "node" "add|remove|onetry"'); + throw new RPCError(errs.MISC_ERROR, 'addnode "node" "add|remove|onetry"'); addr = NetAddress.fromHostname(node, this.network); @@ -261,7 +261,7 @@ RPC.prototype.disconnectNode = co(function* disconnectNode(args, help) { var peer; if (help || args.length !== 1) - throw new RPCError('disconnectnode "node"'); + throw new RPCError(errs.MISC_ERROR, 'disconnectnode "node"'); addr = IP.fromHostname(addr, this.network.port); peer = this.pool.peers.get(addr.hostname); @@ -279,13 +279,15 @@ RPC.prototype.getAddedNodeInfo = co(function* getAddedNodeInfo(args, help) { var peer; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getaddednodeinfo dummy ( "node" )'); + 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('Node has not been added.'); + if (!peer) { + throw new RPCError(errs.CLIENT_NODE_NOT_ADDED, + 'Node has not been added.'); + } return [toAddedNode(peer)]; } @@ -297,7 +299,7 @@ RPC.prototype.getAddedNodeInfo = co(function* getAddedNodeInfo(args, help) { RPC.prototype.getConnectionCount = co(function* getConnectionCount(args, help) { if (help || args.length !== 0) - throw new RPCError('getconnectioncount'); + throw new RPCError(errs.MISC_ERROR, 'getconnectioncount'); return this.pool.peers.size(); }); @@ -308,7 +310,7 @@ RPC.prototype.getNetTotals = co(function* getNetTotals(args, help) { var peer; if (help || args.length > 0) - throw new RPCError('getnettotals'); + throw new RPCError(errs.MISC_ERROR, 'getnettotals'); for (peer = this.pool.peers.head(); peer; peer = peer.next) { sent += peer.socket.bytesWritten; @@ -328,7 +330,7 @@ RPC.prototype.getPeerInfo = co(function* getPeerInfo(args, help) { var peer, offset; if (help || args.length !== 0) - throw new RPCError('getpeerinfo'); + throw new RPCError(errs.MISC_ERROR, 'getpeerinfo'); for (peer = this.pool.peers.head(); peer; peer = peer.next) { offset = this.network.time.known[peer.hostname()]; @@ -373,7 +375,7 @@ RPC.prototype.ping = co(function* ping(args, help) { var peer; if (help || args.length !== 0) - throw new RPCError('ping'); + throw new RPCError(errs.MISC_ERROR, 'ping'); for (peer = this.pool.peers.head(); peer; peer = peer.next) peer.sendPing(); @@ -389,8 +391,8 @@ RPC.prototype.setBan = co(function* setBan(args, help) { if (help || args.length < 2 || (action !== 'add' && action !== 'remove')) { - throw new RPCError('setban "ip(/netmask)"' - + ' "add|remove" (bantime) (absolute)'); + throw new RPCError(errs.MISC_ERROR, + 'setban "ip(/netmask)" "add|remove" (bantime) (absolute)'); } addr = NetAddress.fromHostname(addr, this.network); @@ -411,7 +413,7 @@ RPC.prototype.listBanned = co(function* listBanned(args, help) { var i, banned, keys, host, time; if (help || args.length !== 0) - throw new RPCError('listbanned'); + throw new RPCError(errs.MISC_ERROR, 'listbanned'); banned = []; keys = Object.keys(this.pool.hosts.banned); @@ -432,7 +434,7 @@ RPC.prototype.listBanned = co(function* listBanned(args, help) { RPC.prototype.clearBanned = co(function* clearBanned(args, help) { if (help || args.length !== 0) - throw new RPCError('clearbanned'); + throw new RPCError(errs.MISC_ERROR, 'clearbanned'); this.pool.hosts.clearBanned(); @@ -442,7 +444,7 @@ RPC.prototype.clearBanned = co(function* clearBanned(args, help) { /* Block chain and UTXO */ RPC.prototype.getBlockchainInfo = co(function* getBlockchainInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getblockchaininfo'); + throw new RPCError(errs.MISC_ERROR, 'getblockchaininfo'); return { chain: this.network.type !== 'testnet' @@ -466,14 +468,14 @@ RPC.prototype.getBlockchainInfo = co(function* getBlockchainInfo(args, help) { RPC.prototype.getBestBlockHash = co(function* getBestBlockHash(args, help) { if (help || args.length !== 0) - throw new RPCError('getbestblockhash'); + 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('getblockcount'); + throw new RPCError(errs.MISC_ERROR, 'getblockcount'); return this.chain.tip.height; }); @@ -485,26 +487,26 @@ RPC.prototype.getBlock = co(function* getBlock(args, help) { var entry, block; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getblock "hash" ( verbose )'); + throw new RPCError(errs.MISC_ERROR, 'getblock "hash" ( verbose )'); if (!hash) - throw new RPCError('Invalid block hash.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); if (!entry) - throw new RPCError('Block not found'); + 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('Block not available (spv mode)'); + throw new RPCError(errs.MISC_ERROR, 'Block not available (spv mode)'); if (this.chain.options.prune) - throw new RPCError('Block not available (pruned data)'); + throw new RPCError(errs.MISC_ERROR, 'Block not available (pruned data)'); - throw new RPCError('Can\'t read block from disk'); + throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk'); } if (!verbose) @@ -519,15 +521,15 @@ RPC.prototype.getBlockHash = co(function* getBlockHash(args, help) { var hash; if (help || args.length !== 1) - throw new RPCError('getblockhash index'); + throw new RPCError(errs.MISC_ERROR, 'getblockhash index'); if (height == null || height > this.chain.height) - throw new RPCError('Block height out of range.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Block height out of range.'); hash = yield this.chain.db.getHash(height); if (!hash) - throw new RPCError('Not found.'); + throw new RPCError(errs.MISC_ERROR, 'Not found.'); return util.revHex(hash); }); @@ -539,15 +541,15 @@ RPC.prototype.getBlockHeader = co(function* getBlockHeader(args, help) { var entry; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getblockheader "hash" ( verbose )'); + throw new RPCError(errs.MISC_ERROR, 'getblockheader "hash" ( verbose )'); if (!hash) - throw new RPCError('Invalid block hash.'); + throw new RPCError(errs.MISC_ERROR, 'Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); if (!entry) - throw new RPCError('Block not found'); + throw new RPCError(errs.MISC_ERROR, 'Block not found'); if (!verbose) return entry.toRaw().toString('hex', 0, 80); @@ -559,7 +561,7 @@ RPC.prototype.getChainTips = co(function* getChainTips(args, help) { var i, hash, tips, result, entry, fork, main; if (help || args.length !== 0) - throw new RPCError('getchaintips'); + throw new RPCError(errs.MISC_ERROR, 'getchaintips'); tips = yield this.chain.db.getTips(); result = []; @@ -585,17 +587,17 @@ RPC.prototype.getChainTips = co(function* getChainTips(args, help) { RPC.prototype.getDifficulty = co(function* getDifficulty(args, help) { if (help || args.length !== 0) - throw new RPCError('getdifficulty'); + throw new RPCError(errs.MISC_ERROR, 'getdifficulty'); return this.difficulty(); }); RPC.prototype.getMempoolInfo = co(function* getMempoolInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getmempoolinfo'); + throw new RPCError(errs.MISC_ERROR, 'getmempoolinfo'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); return { size: this.mempool.totalTX, @@ -614,18 +616,18 @@ RPC.prototype.getMempoolAncestors = co(function* getMempoolAncestors(args, help) var i, entry, entries; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getmempoolancestors txid (verbose)'); + throw new RPCError(errs.MISC_ERROR, 'getmempoolancestors txid (verbose)'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) - throw new RPCError('Invalid TXID.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) - throw new RPCError('Transaction not in mempool.'); + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); entries = this.mempool.getAncestors(entry); @@ -652,18 +654,18 @@ RPC.prototype.getMempoolDescendants = co(function* getMempoolDescendants(args, h var i, entry, entries; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getmempooldescendants txid (verbose)'); + throw new RPCError(errs.MISC_ERROR, 'getmempooldescendants txid (verbose)'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) - throw new RPCError('Invalid TXID.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) - throw new RPCError('Transaction not in mempool.'); + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); entries = this.mempool.getDescendants(entry); @@ -688,18 +690,18 @@ RPC.prototype.getMempoolEntry = co(function* getMempoolEntry(args, help) { var entry; if (help || args.length !== 1) - throw new RPCError('getmempoolentry txid'); + throw new RPCError(errs.MISC_ERROR, 'getmempoolentry txid'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) - throw new RPCError('Invalid TXID.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) - throw new RPCError('Transaction not in mempool.'); + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); return this.entryToJSON(entry); }); @@ -711,10 +713,10 @@ RPC.prototype.getRawMempool = co(function* getRawMempool(args, help) { var i, hashes, hash, entry; if (help || args.length > 1) - throw new RPCError('getrawmempool ( verbose )'); + throw new RPCError(errs.MISC_ERROR, 'getrawmempool ( verbose )'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (verbose) { hashes = this.mempool.getSnapshot(); @@ -745,20 +747,20 @@ RPC.prototype.getTXOut = co(function* getTXOut(args, help) { var coin; if (help || args.length < 2 || args.length > 3) - throw new RPCError('gettxout "txid" n ( includemempool )'); + throw new RPCError(errs.MISC_ERROR, 'gettxout "txid" n ( includemempool )'); if (this.chain.options.spv) - throw new RPCError('Cannot get coins in SPV mode.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); if (this.chain.options.prune) - throw new RPCError('Cannot get coins when pruned.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); if (!hash || index == null) - throw new RPCError('Invalid outpoint.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); if (mempool) { if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); coin = this.mempool.getCoin(hash, index); } @@ -785,17 +787,19 @@ RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { var uniq = {}; var i, block, txid, tx, coins; - if (help || (args.length !== 1 && args.length !== 2)) - throw new RPCError('gettxoutproof ["txid",...] ( blockhash )'); + 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('Cannot get coins in SPV mode.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins in SPV mode.'); if (this.chain.options.prune) - throw new RPCError('Cannot get coins when pruned.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot get coins when pruned.'); if (!txids || txids.length === 0) - throw new RPCError('Invalid TXIDs.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid TXIDs.'); valid = new Validator([txids]); @@ -803,10 +807,10 @@ RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { txid = valid.hash(i); if (!txid) - throw new RPCError('Invalid TXID.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); if (uniq[txid]) - throw new RPCError('Duplicate txid.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate txid.'); uniq[txid] = true; txids[i] = txid; @@ -827,12 +831,12 @@ RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { } if (!block) - throw new RPCError('Block not found.'); + 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('Block does not contain all txids.'); + throw new RPCError(errs.VERIFY_ERROR, 'Block does not contain all txids.'); } block = MerkleBlock.fromHashes(block, txids); @@ -847,12 +851,12 @@ RPC.prototype.verifyTXOutProof = co(function* verifyTXOutProof(args, help) { var i, block, hash, entry; if (help || args.length !== 1) - throw new RPCError('verifytxoutproof "proof"'); + throw new RPCError(errs.MISC_ERROR, 'verifytxoutproof "proof"'); if (!data) - throw new RPCError('Invalid hex string.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); - block = MerkleBlock.fromRaw(data); + block = fromRaw(MerkleBlock, data); if (!block.verify()) return out; @@ -860,7 +864,7 @@ RPC.prototype.verifyTXOutProof = co(function* verifyTXOutProof(args, help) { entry = yield this.chain.db.getEntry(block.hash('hex')); if (!entry) - throw new RPCError('Block not found in chain.'); + throw new RPCError(errs.MISC_ERROR, 'Block not found in chain.'); for (i = 0; i < block.matches.length; i++) { hash = block.matches[i]; @@ -872,10 +876,10 @@ RPC.prototype.verifyTXOutProof = co(function* verifyTXOutProof(args, help) { RPC.prototype.getTXOutSetInfo = co(function* getTXOutSetInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('gettxoutsetinfo'); + throw new RPCError(errs.MISC_ERROR, 'gettxoutsetinfo'); if (this.chain.options.spv) - throw new RPCError('Chainstate not available (SPV mode).'); + throw new RPCError(errs.MISC_ERROR, 'Chainstate not available (SPV mode).'); return { height: this.chain.height, @@ -894,16 +898,16 @@ RPC.prototype.verifyChain = co(function* verifyChain(args, help) { var blocks = valid.u32(1); if (help || args.length > 2) - throw new RPCError('verifychain ( checklevel numblocks )'); + throw new RPCError(errs.MISC_ERROR, 'verifychain ( checklevel numblocks )'); if (level == null || blocks == null) - throw new RPCError('Missing parameters.'); + throw new RPCError(errs.TYPE_ERROR, 'Missing parameters.'); if (this.chain.options.spv) - throw new RPCError('Cannot verify chain in SPV mode.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain in SPV mode.'); if (this.chain.options.prune) - throw new RPCError('Cannot verify chain when pruned.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot verify chain when pruned.'); return null; }); @@ -923,16 +927,20 @@ RPC.prototype.submitWork = co(function* submitWork(data) { RPC.prototype._submitWork = co(function* _submitWork(data) { var attempt = this.attempt; - var header = Headers.fromAbbr(data); - var nonce = header.nonce; - var ts = header.ts; - var nonces, n1, n2, proof, block, entry; + var header, nonce, ts, nonces; + var n1, n2, proof, block, entry; if (!attempt) return false; if (data.length !== 128) - throw new RPCError('Invalid work size.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid work size.'); + + try { + header = Headers.fromAbbr(data); + } catch (e) { + throw new RPCError(errs.DESERIALIZATION_ERROR, 'Deserialization error.'); + } data = data.slice(0, 80); data = swap32(data); @@ -952,6 +960,8 @@ RPC.prototype._submitWork = co(function* _submitWork(data) { n1 = nonces.nonce1; n2 = nonces.nonce2; + nonce = header.nonce; + ts = header.ts; proof = attempt.getProof(n1, n2, ts, nonce); @@ -1026,11 +1036,11 @@ RPC.prototype.getWork = co(function* getWork(args, help) { var data = valid.buf(0); if (args.length > 1) - throw new RPCError('getwork ( "data" )'); + throw new RPCError(errs.MISC_ERROR, 'getwork ( "data" )'); if (args.length === 1) { if (!data) - throw new RPCError('Invalid work data.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.'); return yield this.submitWork(data); } @@ -1043,10 +1053,12 @@ RPC.prototype.submitBlock = co(function* submitBlock(args, help) { var data = valid.buf(0); var block; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('submitblock "hexdata" ( "jsonparametersobject" )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'submitblock "hexdata" ( "jsonparametersobject" )'); + } - block = Block.fromRaw(data); + block = fromRaw(Block, data); return yield this.addBlock(block); }); @@ -1066,17 +1078,19 @@ RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { var valueCap = false; var i, capability, block; - if (help || args.length > 1) - throw new RPCError('getblocktemplate ( "jsonrequestobject" )'); + if (help || args.length > 1) { + throw new RPCError(errs.MISC_ERROR, + 'getblocktemplate ( "jsonrequestobject" )'); + } if (mode !== 'template' && mode !== 'proposal') - throw new RPCError('Invalid mode.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid mode.'); if (mode === 'proposal') { if (!data) - throw new RPCError('Missing data parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Missing data parameter.'); - block = Block.fromRaw(data); + block = fromRaw(Block, data); if (block.prevBlock !== this.chain.tip.hash) return 'inconclusive-not-best-prevblk'; @@ -1100,7 +1114,7 @@ RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { capability = capabilities[i]; if (typeof capability !== 'string') - throw new RPCError('Capability must be a string.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid capability.'); switch (capability) { case 'coinbasetxn': @@ -1116,18 +1130,24 @@ RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { } if (txnCap && !valueCap) { - if (this.miner.addresses.length === 0) - throw new RPCError('No addresses available for coinbase.'); + 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('Bitcoin is not connected!'); + if (this.pool.peers.size() === 0) { + throw new RPCError(errs.CLIENT_NOT_CONNECTED, + 'Bitcoin is not connected!'); + } - if (!this.chain.synced) - throw new RPCError('Bitcoin is downloading blocks...'); + if (!this.chain.synced) { + throw new RPCError(errs.CLIENT_IN_INITIAL_DOWNLOAD, + 'Bitcoin is downloading blocks...'); + } } if (lpid) @@ -1211,8 +1231,10 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, break; case common.thresholdStates.ACTIVE: if (!deploy.force) { - if (!rules || rules.indexOf(name) === -1) - throw new RPCError('Client must support ' + name + '.'); + if (!rules || rules.indexOf(name) === -1) { + throw new RPCError(errs.INVALID_PARAMETER, + 'Client must support ' + name + '.'); + } name = '!' + name; } vbrules.push(name); @@ -1306,7 +1328,7 @@ RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) { var i, item; if (help || args.length !== 0) - throw new RPCError('getmininginfo'); + throw new RPCError(errs.MISC_ERROR, 'getmininginfo'); if (attempt) { weight = attempt.weight; @@ -1342,7 +1364,7 @@ RPC.prototype.getNetworkHashPS = co(function* getNetworkHashPS(args, help) { var height = valid.u32(1); if (help || args.length > 2) - throw new RPCError('getnetworkhashps ( blocks height )'); + throw new RPCError(errs.MISC_ERROR, 'getnetworkhashps ( blocks height )'); return yield this.getHashRate(lookup, height); }); @@ -1355,23 +1377,23 @@ RPC.prototype.prioritiseTransaction = co(function* prioritiseTransaction(args, h var entry; if (help || args.length !== 3) { - throw new RPCError('prioritisetransaction' - + ' '); + throw new RPCError(errs.MISC_ERROR, + 'prioritisetransaction '); } if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); if (!hash) - throw new RPCError('Invalid TXID'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID'); if (pri == null || fee == null) - throw new RPCError('Invalid fee or priority.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid fee or priority.'); entry = this.mempool.getEntry(hash); if (!entry) - throw new RPCError('Transaction not in mempool.'); + throw new RPCError(errs.MISC_ERROR, 'Transaction not in mempool.'); this.mempool.prioritise(entry, pri, fee); @@ -1384,15 +1406,15 @@ RPC.prototype.verifyBlock = co(function* verifyBlock(args, help) { var block; if (help || args.length !== 1) - throw new RPCError('verifyblock "block-hex"'); + throw new RPCError(errs.MISC_ERROR, 'verifyblock "block-hex"'); if (!data) - throw new RPCError('Invalid block hex.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hex.'); if (this.chain.options.spv) - throw new RPCError('Cannot verify block in SPV mode.'); + throw new RPCError(errs.MISC_ERROR, 'Cannot verify block in SPV mode.'); - block = Block.fromRaw(data); + block = fromRaw(Block, data); try { yield this.chain.verifyBlock(block); @@ -1411,7 +1433,7 @@ RPC.prototype.verifyBlock = co(function* verifyBlock(args, help) { RPC.prototype.getGenerate = co(function* getGenerate(args, help) { if (help || args.length !== 0) - throw new RPCError('getgenerate'); + throw new RPCError(errs.MISC_ERROR, 'getgenerate'); return this.mining; }); @@ -1421,7 +1443,7 @@ RPC.prototype.setGenerate = co(function* setGenerate(args, help) { var limit = valid.u32(1, 0); if (help || args.length < 1 || args.length > 2) - throw new RPCError('setgenerate mine ( proclimit )'); + throw new RPCError(errs.MISC_ERROR, 'setgenerate mine ( proclimit )'); this.mining = mine; this.procLimit = limit; @@ -1451,7 +1473,7 @@ RPC.prototype._generate = co(function* generate(args, help) { var tries = valid.u32(1); if (help || args.length < 1 || args.length > 2) - throw new RPCError('generate numblocks ( maxtries )'); + throw new RPCError(errs.MISC_ERROR, 'generate numblocks ( maxtries )'); return yield this.mineBlocks(blocks, null, tries); }); @@ -1470,10 +1492,12 @@ RPC.prototype._generateToAddress = co(function* _generateToAddress(args, help) { var blocks = valid.u32(0, 1); var addr = valid.str(1, ''); - if (help || args.length < 2 || args.length > 3) - throw new RPCError('generatetoaddress numblocks address ( maxtries )'); + if (help || args.length < 2 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'generatetoaddress numblocks address ( maxtries )'); + } - addr = Address.fromBase58(addr, this.network); + addr = parseAddress(addr, this.network); return yield this.mineBlocks(blocks, addr); }); @@ -1491,14 +1515,17 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel var keys, addrs, key, value, address, b58; if (help || args.length < 2 || args.length > 3) { - throw new RPCError('createrawtransaction' + throw new RPCError(errs.MISC_ERROR, + 'createrawtransaction' + ' [{"txid":"id","vout":n},...]' + ' {"address":amount,"data":"hex",...}' + ' ( locktime )'); } - if (!inputs || !sendTo) - throw new RPCError('Invalid parameters (inputs and sendTo).'); + if (!inputs || !sendTo) { + throw new RPCError(errs.TYPE_ERROR, + 'Invalid parameters (inputs and sendTo).'); + } tx = new MTX(); @@ -1517,7 +1544,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel sequence--; if (!hash || index == null) - throw new RPCError('Invalid outpoint.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid outpoint.'); input = new Input(); input.prevout.hash = hash; @@ -1538,7 +1565,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel value = valid.buf(key); if (!value) - throw new RPCError('Invalid nulldata..'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid nulldata..'); output = new Output(); output.value = 0; @@ -1548,18 +1575,18 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel continue; } - address = Address.fromBase58(key, this.network); + address = parseAddress(key, this.network); b58 = address.toBase58(this.network); if (addrs[b58]) - throw new RPCError('Duplicate address'); + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address'); addrs[b58] = true; value = valid.btc(key); if (value == null) - throw new RPCError('Invalid output value.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid output value.'); output = new Output(); output.value = value; @@ -1577,12 +1604,12 @@ RPC.prototype.decodeRawTransaction = co(function* decodeRawTransaction(args, hel var tx; if (help || args.length !== 1) - throw new RPCError('decoderawtransaction "hexstring"'); + throw new RPCError(errs.MISC_ERROR, 'decoderawtransaction "hexstring"'); if (!data) - throw new RPCError('Invalid hex string.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); - tx = TX.fromRaw(data); + tx = fromRaw(TX, data); return this.txToJSON(tx); }); @@ -1593,12 +1620,12 @@ RPC.prototype.decodeScript = co(function* decodeScript(args, help) { var script, address; if (help || args.length !== 1) - throw new RPCError('decodescript "hex"'); + throw new RPCError(errs.MISC_ERROR, 'decodescript "hex"'); script = new Script(); if (data) - script.fromRaw(data); + script = fromRaw(Script, data); address = Address.fromScripthash(script.hash160()); @@ -1615,15 +1642,15 @@ RPC.prototype.getRawTransaction = co(function* getRawTransaction(args, help) { var json, meta, tx, entry; if (help || args.length < 1 || args.length > 2) - throw new RPCError('getrawtransaction "txid" ( verbose )'); + throw new RPCError(errs.MISC_ERROR, 'getrawtransaction "txid" ( verbose )'); if (!hash) - throw new RPCError('Invalid TXID.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid TXID.'); meta = yield this.node.getMeta(hash); if (!meta) - throw new RPCError('Transaction not found.'); + throw new RPCError(errs.MISC_ERROR, 'Transaction not found.'); tx = meta.tx; @@ -1645,13 +1672,15 @@ RPC.prototype.sendRawTransaction = co(function* sendRawTransaction(args, help) { var data = valid.buf(0); var tx; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('sendrawtransaction "hexstring" ( allowhighfees )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'sendrawtransaction "hexstring" ( allowhighfees )'); + } if (!data) - throw new RPCError('Invalid hex string.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); - tx = TX.fromRaw(data); + tx = fromRaw(TX, data); this.node.relay(tx); @@ -1664,7 +1693,8 @@ RPC.prototype.signRawTransaction = co(function* signRawTransaction(args, help) { var tx; if (help || args.length < 1 || args.length > 4) { - throw new RPCError('signrawtransaction' + throw new RPCError(errs.MISC_ERROR, + 'signrawtransaction' + ' "hexstring" (' + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' + 'redeemScript":"hex"},...] ["privatekey1",...]' @@ -1672,12 +1702,12 @@ RPC.prototype.signRawTransaction = co(function* signRawTransaction(args, help) { } if (!data) - throw new RPCError('Invalid hex string.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); if (!this.mempool) - throw new RPCError('No mempool available.'); + throw new RPCError(errs.MISC_ERROR, 'No mempool available.'); - tx = MTX.fromRaw(data); + tx = fromRaw(MTX, data); tx.view = yield this.mempool.getSpentView(tx); return yield this._signRawTransaction(tx, args); @@ -1700,7 +1730,7 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { valid = new Validator([secrets]); for (i = 0; i < secrets.length; i++) { secret = valid.str(i, ''); - key = KeyRing.fromSecret(secret, this.network); + key = parseSecret(secret, this.network); map[key.getPublicKey('hex')] = key; keys.push(key); } @@ -1717,9 +1747,9 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { redeem = valid.buf('redeemScript'); if (!hash || index == null || !script || value == null) - throw new RPCError('Invalid UTXO.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid UTXO.'); - script = Script.fromRaw(script); + script = fromRaw(Script, script); coin = new Output(); coin.script = script; @@ -1733,7 +1763,7 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { if (!script.isScripthash() && !script.isWitnessScripthash()) continue; - redeem = Script.fromRaw(redeem); + redeem = fromRaw(Script, redeem); for (j = 0; j < redeem.code.length; j++) { op = redeem.code[j]; @@ -1758,14 +1788,14 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { type = Script.hashType[parts[0]]; if (type == null) - throw new RPCError('Invalid sighash type.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); if (parts.length > 2) - throw new RPCError('Invalid sighash type.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); if (parts.length === 2) { if (parts[1] !== 'ANYONECANPAY') - throw new RPCError('Invalid sighash type.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid sighash type.'); type |= Script.hashType.ANYONECANPAY; } } @@ -1790,10 +1820,10 @@ RPC.prototype.createMultisig = co(function* createMultisig(args, help) { var i, script, key, address; if (help || args.length < 2 || args.length > 2) - throw new RPCError('createmultisig nrequired ["key",...]'); + throw new RPCError(errs.MISC_ERROR, 'createmultisig nrequired ["key",...]'); if (m < 1 || n < m || n > 16) - throw new RPCError('Invalid m and n values.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid m and n values.'); valid = new Validator([keys]); @@ -1801,10 +1831,10 @@ RPC.prototype.createMultisig = co(function* createMultisig(args, help) { key = valid.buf(i); if (!key) - throw new RPCError('Invalid key.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid key.'); if (!ec.publicKeyVerify(key)) - throw new RPCError('Invalid key.'); + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.'); keys[i] = key; } @@ -1812,7 +1842,7 @@ RPC.prototype.createMultisig = co(function* createMultisig(args, help) { script = Script.fromMultisig(m, n, keys); if (script.getSize() > consensus.MAX_SCRIPT_PUSH) - throw new RPCError('Redeem script exceeds size limit.'); + throw new RPCError(errs.VERIFY_ERROR, 'Redeem script exceeds size limit.'); address = script.getAddress(); @@ -1828,12 +1858,12 @@ RPC.prototype.createWitnessAddress = co(function* createWitnessAddress(args, hel var script, program, address; if (help || args.length !== 1) - throw new RPCError('createwitnessaddress "script"'); + throw new RPCError(errs.MISC_ERROR, 'createwitnessaddress "script"'); if (!raw) - throw new RPCError('Invalid script hex.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid script hex.'); - script = Script.fromRaw(raw); + script = fromRaw(Script, raw); program = script.forWitness(); address = program.getAddress(); @@ -1849,7 +1879,7 @@ RPC.prototype.validateAddress = co(function* validateAddress(args, help) { var address, script; if (help || args.length !== 1) - throw new RPCError('validateaddress "bitcoinaddress"'); + throw new RPCError(errs.MISC_ERROR, 'validateaddress "bitcoinaddress"'); try { address = Address.fromBase58(b58, this.network); @@ -1878,11 +1908,13 @@ RPC.prototype.verifyMessage = co(function* verifyMessage(args, help) { var hash = Address.getHash(b58); var key; - if (help || args.length !== 3) - throw new RPCError('verifymessage "bitcoinaddress" "signature" "message"'); + if (help || args.length !== 3) { + throw new RPCError(errs.MISC_ERROR, + 'verifymessage "bitcoinaddress" "signature" "message"'); + } if (!hash || !sig || !msg) - throw new RPCError('Invalid parameters.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); @@ -1903,10 +1935,12 @@ RPC.prototype.signMessageWithPrivkey = co(function* signMessageWithPrivkey(args, var msg = valid.str(1, ''); var sig; - if (help || args.length !== 2) - throw new RPCError('signmessagewithprivkey "privkey" "message"'); + if (help || args.length !== 2) { + throw new RPCError(errs.MISC_ERROR, + 'signmessagewithprivkey "privkey" "message"'); + } - key = KeyRing.fromSecret(key, this.network); + key = parseSecret(key, this.network); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); @@ -1921,10 +1955,10 @@ RPC.prototype.estimateFee = co(function* estimateFee(args, help) { var fee; if (help || args.length !== 1) - throw new RPCError('estimatefee nblocks'); + throw new RPCError(errs.MISC_ERROR, 'estimatefee nblocks'); if (!this.fees) - throw new RPCError('Fee estimation not available.'); + throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); if (blocks < 1) blocks = 1; @@ -1942,10 +1976,10 @@ RPC.prototype.estimatePriority = co(function* estimatePriority(args, help) { var blocks = valid.u32(0, 1); if (help || args.length !== 1) - throw new RPCError('estimatepriority nblocks'); + throw new RPCError(errs.MISC_ERROR, 'estimatepriority nblocks'); if (!this.fees) - throw new RPCError('Priority estimation not available.'); + throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); if (blocks < 1) blocks = 1; @@ -1959,10 +1993,10 @@ RPC.prototype.estimateSmartFee = co(function* estimateSmartFee(args, help) { var fee; if (help || args.length !== 1) - throw new RPCError('estimatesmartfee nblocks'); + throw new RPCError(errs.MISC_ERROR, 'estimatesmartfee nblocks'); if (!this.fees) - throw new RPCError('Fee estimation not available.'); + throw new RPCError(errs.MISC_ERROR, 'Fee estimation not available.'); if (blocks < 1) blocks = 1; @@ -1986,10 +2020,10 @@ RPC.prototype.estimateSmartPriority = co(function* estimateSmartPriority(args, h var pri; if (help || args.length !== 1) - throw new RPCError('estimatesmartpriority nblocks'); + throw new RPCError(errs.MISC_ERROR, 'estimatesmartpriority nblocks'); if (!this.fees) - throw new RPCError('Priority estimation not available.'); + throw new RPCError(errs.MISC_ERROR, 'Priority estimation not available.'); if (blocks < 1) blocks = 1; @@ -2007,10 +2041,10 @@ RPC.prototype.invalidateBlock = co(function* invalidateBlock(args, help) { var hash = valid.hash(0); if (help || args.length !== 1) - throw new RPCError('invalidateblock "hash"'); + throw new RPCError(errs.MISC_ERROR, 'invalidateblock "hash"'); if (!hash) - throw new RPCError('Block not found.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); yield this.chain.invalidate(hash); @@ -2022,10 +2056,10 @@ RPC.prototype.reconsiderBlock = co(function* reconsiderBlock(args, help) { var hash = valid.hash(0); if (help || args.length !== 1) - throw new RPCError('reconsiderblock "hash"'); + throw new RPCError(errs.MISC_ERROR, 'reconsiderblock "hash"'); if (!hash) - throw new RPCError('Block not found.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid block hash.'); this.chain.removeInvalid(hash); @@ -2038,10 +2072,10 @@ RPC.prototype.setMockTime = co(function* setMockTime(args, help) { var delta; if (help || args.length !== 1) - throw new RPCError('setmocktime timestamp'); + throw new RPCError(errs.MISC_ERROR, 'setmocktime timestamp'); if (ts == null) - throw new RPCError('Invalid timestamp.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid timestamp.'); this.network.time.offset = 0; @@ -2054,7 +2088,7 @@ RPC.prototype.setMockTime = co(function* setMockTime(args, help) { RPC.prototype.getMemoryInfo = co(function* getMemoryInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getmemoryinfo'); + throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); return util.memoryUsage(); }); @@ -2064,7 +2098,7 @@ RPC.prototype.setLogLevel = co(function* setLogLevel(args, help) { var level = valid.str(0, ''); if (help || args.length !== 1) - throw new RPCError('setloglevel "level"'); + throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); this.logger.setLevel(level); @@ -2079,13 +2113,13 @@ RPC.prototype.handleLongpoll = co(function* handleLongpoll(lpid) { var watched, lastTX; if (lpid.length !== 74) - throw new RPCError('Invalid longpoll ID.'); + 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)) - throw new RPCError('Invalid longpoll ID.'); + if (!util.isHex(watched) || !util.isNumber(lastTX) || lastTX < 0) + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid longpoll ID.'); watched = util.revHex(watched); @@ -2359,7 +2393,7 @@ RPC.prototype.getHashRate = co(function* getHashRate(lookup, height) { entry = yield entry.getPrevious(); if (!entry) - throw new RPCError('Not found.'); + throw new RPCError(errs.DATABASE_ERROR, 'Not found.'); minTime = Math.min(entry.ts, minTime); maxTime = Math.max(entry.ts, maxTime); @@ -2623,6 +2657,30 @@ function Nonces(n1, n2) { this.nonce2 = n2; } +function fromRaw(ctor, raw) { + try { + return ctor.fromRaw(raw); + } catch (e) { + throw new RPCError(errs.DESERIALIZATION_ERROR, 'Deserialization error.'); + } +} + +function parseAddress(raw, network) { + try { + return Address.fromBase58(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.'); + } +} + /* * Expose */ diff --git a/lib/http/rpcbase.js b/lib/http/rpcbase.js index cab7edac..c527d416 100644 --- a/lib/http/rpcbase.js +++ b/lib/http/rpcbase.js @@ -106,7 +106,7 @@ RPCBase.prototype.call = co(function* call(body, query) { var cmds = body; var out = []; var array = true; - var i, cmd, result; + var i, cmd, result, code; if (!Array.isArray(cmds)) { cmds = [cmds]; @@ -116,19 +116,56 @@ RPCBase.prototype.call = co(function* call(body, query) { for (i = 0; i < cmds.length; i++) { cmd = cmds[i]; - assert(cmd && typeof cmd === 'object', 'Command must be an object.'); - assert(typeof cmd.method === 'string', 'Method must be a string.'); + if (!cmd || typeof cmd !== 'object') { + out.push({ + result: null, + error: { + message: 'Invalid request.', + code: RPCBase.errors.INVALID_REQUEST + }, + id: null + }); + continue; + } + + if (cmd.id && typeof cmd.id === 'object') { + out.push({ + result: null, + error: { + message: 'Invalid ID.', + code: RPCBase.errors.INVALID_REQUEST + }, + id: null + }); + continue; + } + + if (typeof cmd.method !== 'string') { + out.push({ + result: null, + error: { + message: 'Method not found.', + code: RPCBase.errors.METHOD_NOT_FOUND + }, + id: cmd.id + }); + continue; + } if (!cmd.params) cmd.params = []; - assert(Array.isArray(cmd.params), 'Params must be an array.'); - - assert(!cmd.id || typeof cmd.id !== 'object', 'Invalid ID.'); - } - - for (i = 0; i < cmds.length; i++) { - cmd = cmds[i]; + if (!Array.isArray(cmd.params)) { + out.push({ + result: null, + error: { + message: 'Invalid params.', + code: RPCBase.errors.INVALID_PARAMS + }, + id: cmd.id + }); + continue; + } if (cmd.method !== 'getwork' && cmd.method !== 'getblocktemplate' @@ -148,25 +185,25 @@ RPCBase.prototype.call = co(function* call(body, query) { try { result = yield this.execute(cmd); } catch (err) { - if (err.type === 'RPCError') { - out.push({ - result: null, - error: { - message: err.message, - code: -1 - }, - id: cmd.id - }); - continue; + switch (err.type) { + case 'RPCError': + code = err.code; + break; + case 'ValidationError': + code = RPCBase.errors.TYPE_ERROR; + break; + default: + code = RPCBase.errors.INTERNAL_ERROR; + this.logger.error('RPC internal error.'); + this.logger.error(err); + break; } - this.logger.error(err); - out.push({ result: null, error: { message: err.message, - code: 1 + code: code }, id: cmd.id }); @@ -254,15 +291,18 @@ RPCBase.prototype.attach = function attach(rpc) { * @ignore */ -function RPCError(msg, code) { +function RPCError(code, msg) { Error.call(this); if (Error.captureStackTrace) Error.captureStackTrace(this, RPCError); + assert(typeof code === 'number'); + assert(typeof msg === 'string'); + this.type = 'RPCError'; this.message = msg; - this.code = code != null ? code : -1; + this.code = code; } util.inherits(RPCError, Error); diff --git a/lib/utils/validator.js b/lib/utils/validator.js index db255fd0..4b524df4 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -87,7 +87,7 @@ Validator.prototype.get = function get(key, fallback) { map = this.data[i]; if (!map || typeof map !== 'object') - throw new Error('Data is not an object.'); + throw new ValidationError('Data is not an object.'); value = map[key]; @@ -115,7 +115,7 @@ Validator.prototype.str = function str(key, fallback) { return fallback; if (typeof value !== 'string') - throw new Error(fmt(key) + ' must be a string.'); + throw new ValidationError(fmt(key) + ' must be a string.'); return value; }; @@ -138,17 +138,17 @@ Validator.prototype.num = function num(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); return value; } if (!/^\d+$/.test(value)) - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); value = parseInt(value, 10); if (!isFinite(value)) - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); return value; }; @@ -170,7 +170,7 @@ Validator.prototype.u32 = function u32(key, fallback) { return fallback; if (value % 1 !== 0 || value < 0 || value > 0xffffffff) - throw new Error(fmt(key) + ' must be a uint32.'); + throw new ValidationError(fmt(key) + ' must be a uint32.'); return value; }; @@ -192,7 +192,7 @@ Validator.prototype.u64 = function u64(key, fallback) { return fallback; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(fmt(key) + ' must be a uint64.'); + throw new ValidationError(fmt(key) + ' must be a uint64.'); return value; }; @@ -214,7 +214,7 @@ Validator.prototype.i32 = function i32(key, fallback) { return fallback; if (value % 1 !== 0 || Math.abs(value) > 0x7fffffff) - throw new Error(fmt(key) + ' must be an int32.'); + throw new ValidationError(fmt(key) + ' must be an int32.'); return value; }; @@ -236,7 +236,7 @@ Validator.prototype.i64 = function i64(key, fallback) { return fallback; if (value % 1 !== 0 || Math.abs(value) > 0x1fffffffffffff) - throw new Error(fmt(key) + ' must be an int64.'); + throw new ValidationError(fmt(key) + ' must be an int64.'); return value; }; @@ -259,22 +259,22 @@ Validator.prototype.amt = function amt(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); return value; } if (!/^\d+(\.\d{0,8})?$/.test(value)) - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); value = parseFloat(value); if (!isFinite(value)) - throw new Error(fmt(key) + ' must be a number.'); + throw new ValidationError(fmt(key) + ' must be a number.'); value *= 1e8; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(fmt(key) + ' must be a uint64.'); + throw new ValidationError(fmt(key) + ' must be a uint64.'); return value; }; @@ -298,7 +298,7 @@ Validator.prototype.btc = function btc(key, fallback) { value *= 1e8; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(fmt(key) + ' must be a uint64.'); + throw new ValidationError(fmt(key) + ' must be a uint64.'); return value; }; @@ -323,17 +323,17 @@ Validator.prototype.hash = function hash(key, fallback) { if (typeof value !== 'string') { if (!Buffer.isBuffer(value)) - throw new Error(fmt(key) + ' must be a hash.'); + throw new ValidationError(fmt(key) + ' must be a hash.'); if (value.length !== 32) - throw new Error(fmt(key) + ' must be a hash.'); + throw new ValidationError(fmt(key) + ' must be a hash.'); return value.toString('hex'); } if (value.length !== 64) - throw new Error(fmt(key) + ' must be a hex string.'); + throw new ValidationError(fmt(key) + ' must be a hex string.'); if (!/^[0-9a-f]+$/i.test(value)) - throw new Error(fmt(key) + ' must be a hex string.'); + throw new ValidationError(fmt(key) + ' must be a hex string.'); for (i = 0; i < value.length; i += 2) out = value.slice(i, i + 2) + out; @@ -379,7 +379,7 @@ Validator.prototype.numstr = function numstr(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(fmt(key) + ' must be a number or string.'); + throw new ValidationError(fmt(key) + ' must be a number or string.'); return value; } @@ -409,7 +409,7 @@ Validator.prototype.bool = function bool(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'boolean') - throw new Error(fmt(key) + ' must be a boolean.'); + throw new ValidationError(fmt(key) + ' must be a boolean.'); return value; } @@ -419,7 +419,7 @@ Validator.prototype.bool = function bool(key, fallback) { if (value === 'false' || value === '0') return false; - throw new Error(fmt(key) + ' must be a boolean.'); + throw new ValidationError(fmt(key) + ' must be a boolean.'); }; /** @@ -445,14 +445,14 @@ Validator.prototype.buf = function buf(key, fallback, enc) { if (typeof value !== 'string') { if (!Buffer.isBuffer(value)) - throw new Error(fmt(key) + ' must be a buffer.'); + throw new ValidationError(fmt(key) + ' must be a buffer.'); return value; } data = new Buffer(value, enc); if (data.length !== Buffer.byteLength(value, enc)) - throw new Error(fmt(key) + ' must be a ' + enc + ' string.'); + throw new ValidationError(fmt(key) + ' must be a ' + enc + ' string.'); return data; }; @@ -476,7 +476,7 @@ Validator.prototype.array = function array(key, fallback) { if (typeof value !== 'string') { if (!Array.isArray(value)) - throw new Error(fmt(key) + ' must be a list/array.'); + throw new ValidationError(fmt(key) + ' must be a list/array.'); return value; } @@ -512,7 +512,7 @@ Validator.prototype.obj = function obj(key, fallback) { return fallback; if (!value || typeof value !== 'object') - throw new Error(fmt(key) + ' must be an object.'); + throw new ValidationError(fmt(key) + ' must be an object.'); return value; }; @@ -534,7 +534,7 @@ Validator.prototype.func = function func(key, fallback) { return fallback; if (typeof value !== 'function') - throw new Error(fmt(key) + ' must be a function.'); + throw new ValidationError(fmt(key) + ' must be a function.'); return value; }; @@ -549,8 +549,31 @@ function fmt(key) { return key; } +function inherits(obj, from) { + var f = function() {}; + f.prototype = from.prototype; + obj.prototype = new f; + obj.prototype.constructor = obj; +} + +function ValidationError(msg) { + Error.call(this); + + if (Error.captureStackTrace) + Error.captureStackTrace(this, ValidationError); + + this.type = 'ValidationError'; + this.message = msg; +} + +inherits(ValidationError, Error); + /* * Expose */ -module.exports = Validator; +exports = Validator; +exports.Validator = Validator; +exports.Error = ValidationError; + +module.exports = exports; diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index 96e7af2a..6791bd94 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -26,6 +26,7 @@ var pkg = require('../pkg'); var Validator = require('../utils/validator'); var common = require('./common'); var RPCError = RPCBase.RPCError; +var errs = RPCBase.errors; var MAGIC_STRING = RPCBase.MAGIC_STRING; /** @@ -126,7 +127,7 @@ RPC.prototype.help = co(function* _help(args, help) { RPC.prototype.stop = co(function* stop(args, help) { if (help || args.length !== 0) - throw new RPCError('stop'); + throw new RPCError(errs.MISC_ERROR, 'stop'); this.wdb.close(); @@ -141,16 +142,20 @@ RPC.prototype.fundRawTransaction = co(function* fundRawTransaction(args, help) { var rate = this.feeRate; var change, tx; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('fundrawtransaction "hexstring" ( options )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'fundrawtransaction "hexstring" ( options )'); + } if (!data) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.'); - tx = MTX.fromRaw(data); + tx = fromRaw(MTX, data); - if (tx.outputs.length === 0) - throw new RPCError('TX must have at least one output.'); + if (tx.outputs.length === 0) { + throw new RPCError(errs.INVALID_PARAMETER, + 'TX must have at least one output.'); + } if (options) { valid = new Validator([options]); @@ -158,7 +163,7 @@ RPC.prototype.fundRawTransaction = co(function* fundRawTransaction(args, help) { rate = valid.btc('feeRate'); if (change) - change = Address.fromBase58(change, this.network); + change = parseAddress(change, this.network); } options = { @@ -166,7 +171,11 @@ RPC.prototype.fundRawTransaction = co(function* fundRawTransaction(args, help) { changeAddress: change }; - yield wallet.fund(tx, options); + try { + yield wallet.fund(tx, options); + } catch (e) { + throw new RPCError(errs.WALLET_INSUFFICIENT_FUNDS, e.message); + } return { hex: tx.toRaw().toString('hex'), @@ -185,7 +194,7 @@ RPC.prototype.resendWalletTransactions = co(function* resendWalletTransactions(a var i, tx, txs; if (help || args.length !== 0) - throw new RPCError('resendwallettransactions'); + throw new RPCError(errs.MISC_ERROR, 'resendwallettransactions'); txs = yield wallet.resend(); @@ -199,7 +208,7 @@ RPC.prototype.resendWalletTransactions = co(function* resendWalletTransactions(a RPC.prototype.addMultisigAddress = co(function* addMultisigAddress(args, help) { if (help || args.length < 2 || args.length > 3) { - throw new RPCError('addmultisigaddress' + throw new RPCError(errs.MISC_ERROR, 'addmultisigaddress' + ' nrequired ["key",...] ( "account" )'); } @@ -209,7 +218,7 @@ RPC.prototype.addMultisigAddress = co(function* addMultisigAddress(args, help) { RPC.prototype.addWitnessAddress = co(function* addWitnessAddress(args, help) { if (help || args.length < 1 || args.length > 1) - throw new RPCError('addwitnessaddress "address"'); + throw new RPCError(errs.MISC_ERROR, 'addwitnessaddress "address"'); // Unlikely to be implemented. throw new Error('Not implemented.'); @@ -220,7 +229,7 @@ RPC.prototype.backupWallet = co(function* backupWallet(args, help) { var dest = valid.str(0); if (help || args.length !== 1 || !dest) - throw new RPCError('backupwallet "destination"'); + throw new RPCError(errs.MISC_ERROR, 'backupwallet "destination"'); yield this.wdb.backup(dest); @@ -235,15 +244,15 @@ RPC.prototype.dumpPrivKey = co(function* dumpPrivKey(args, help) { var ring; if (help || args.length !== 1) - throw new RPCError('dumpprivkey "bitcoinaddress"'); + throw new RPCError(errs.MISC_ERROR, 'dumpprivkey "bitcoinaddress"'); if (!hash) - throw new RPCError('Invalid address.'); + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.'); ring = yield wallet.getPrivateKey(hash); if (!ring) - throw new RPCError('Key not found.'); + throw new RPCError(errs.MISC_ERROR, 'Key not found.'); return ring.toSecret(); }); @@ -256,10 +265,10 @@ RPC.prototype.dumpWallet = co(function* dumpWallet(args, help) { var i, tip, addr, fmt, str, out, hash, hashes, ring; if (help || args.length !== 1) - throw new RPCError('dumpwallet "filename"'); + throw new RPCError(errs.MISC_ERROR, 'dumpwallet "filename"'); if (!file) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); tip = yield this.wdb.getTip(); @@ -312,15 +321,21 @@ RPC.prototype.encryptWallet = co(function* encryptWallet(args, help) { var passphrase = valid.str(0, ''); if (!wallet.master.encrypted && (help || args.length !== 1)) - throw new RPCError('encryptwallet "passphrase"'); + throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); - if (wallet.master.encrypted) - throw new RPCError('Already running with an encrypted wallet'); + if (wallet.master.encrypted) { + throw new RPCError(errs.WALLET_WRONG_ENC_STATE, + 'Already running with an encrypted wallet.'); + } if (passphrase.length < 1) - throw new RPCError('encryptwallet "passphrase"'); + throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"'); - yield wallet.setPassphrase(passphrase); + try { + yield wallet.setPassphrase(passphrase); + } catch (e) { + throw new RPCError(errs.WALLET_ENCRYPTION_FAILED, 'Encryption failed.'); + } return 'wallet encrypted; we do not need to stop!'; }); @@ -332,7 +347,7 @@ RPC.prototype.getAccountAddress = co(function* getAccountAddress(args, help) { var account; if (help || args.length !== 1) - throw new RPCError('getaccountaddress "account"'); + throw new RPCError(errs.MISC_ERROR, 'getaccountaddress "account"'); if (!name) name = 'default'; @@ -353,10 +368,10 @@ RPC.prototype.getAccount = co(function* getAccount(args, help) { var path; if (help || args.length !== 1) - throw new RPCError('getaccount "bitcoinaddress"'); + throw new RPCError(errs.MISC_ERROR, 'getaccount "bitcoinaddress"'); if (!hash) - throw new RPCError('Invalid address.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid address.'); path = yield wallet.getPath(hash); @@ -373,7 +388,7 @@ RPC.prototype.getAddressesByAccount = co(function* getAddressesByAccount(args, h var i, path, address, addrs, paths; if (help || args.length !== 1) - throw new RPCError('getaddressesbyaccount "account"'); + throw new RPCError(errs.MISC_ERROR, 'getaddressesbyaccount "account"'); if (name === '') name = 'default'; @@ -399,8 +414,10 @@ RPC.prototype.getBalance = co(function* getBalance(args, help) { var watchOnly = valid.bool(2, false); var value, balance; - if (help || args.length > 3) - throw new RPCError('getbalance ( "account" minconf includeWatchonly )'); + if (help || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'getbalance ( "account" minconf includeWatchonly )'); + } if (name === '') name = 'default'; @@ -428,7 +445,7 @@ RPC.prototype.getNewAddress = co(function* getNewAddress(args, help) { var address; if (help || args.length > 1) - throw new RPCError('getnewaddress ( "account" )'); + throw new RPCError(errs.MISC_ERROR, 'getnewaddress ( "account" )'); if (name === '') name = 'default'; @@ -443,7 +460,7 @@ RPC.prototype.getRawChangeAddress = co(function* getRawChangeAddress(args, help) var address; if (help || args.length > 1) - throw new RPCError('getrawchangeaddress'); + throw new RPCError(errs.MISC_ERROR, 'getrawchangeaddress'); address = yield wallet.createChange(); @@ -461,8 +478,10 @@ RPC.prototype.getReceivedByAccount = co(function* getReceivedByAccount(args, hel var lastConf = -1; var i, j, path, wtx, output, conf, hash, paths, txs; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('getreceivedbyaccount "account" ( minconf )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getreceivedbyaccount "account" ( minconf )'); + } if (name === '') name = 'default'; @@ -508,11 +527,13 @@ RPC.prototype.getReceivedByAddress = co(function* getReceivedByAddress(args, hel var total = 0; var i, j, wtx, output, txs; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('getreceivedbyaddress "bitcoinaddress" ( minconf )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'getreceivedbyaddress "bitcoinaddress" ( minconf )'); + } if (!hash) - throw new RPCError('Invalid address'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid address'); txs = yield wallet.getHistory(); @@ -542,7 +563,7 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(wtx) { var i, member; if (!details) - throw new RPCError('TX not found.'); + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); for (i = 0; i < details.inputs.length; i++) { member = details.inputs[i]; @@ -613,16 +634,18 @@ RPC.prototype.getTransaction = co(function* getTransaction(args, help) { var watchOnly = valid.bool(1, false); var wtx; - if (help || args.length < 1 || args.length > 2) - throw new RPCError('gettransaction "txid" ( includeWatchonly )'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'gettransaction "txid" ( includeWatchonly )'); + } if (!hash) - throw new RPCError('Invalid parameter'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); wtx = yield wallet.getTX(hash); if (!wtx) - throw new RPCError('TX not found.'); + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); return yield this._toWalletTX(wtx, watchOnly); }); @@ -634,15 +657,15 @@ RPC.prototype.abandonTransaction = co(function* abandonTransaction(args, help) { var result; if (help || args.length !== 1) - throw new RPCError('abandontransaction "txid"'); + throw new RPCError(errs.MISC_ERROR, 'abandontransaction "txid"'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); result = yield wallet.abandon(hash); if (!result) - throw new RPCError('Transaction not in wallet.'); + throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); return null; }); @@ -652,7 +675,7 @@ RPC.prototype.getUnconfirmedBalance = co(function* getUnconfirmedBalance(args, h var balance; if (help || args.length > 0) - throw new RPCError('getunconfirmedbalance'); + throw new RPCError(errs.MISC_ERROR, 'getunconfirmedbalance'); balance = yield wallet.getBalance(); @@ -664,7 +687,7 @@ RPC.prototype.getWalletInfo = co(function* getWalletInfo(args, help) { var balance; if (help || args.length !== 0) - throw new RPCError('getwalletinfo'); + throw new RPCError(errs.MISC_ERROR, 'getwalletinfo'); balance = yield wallet.getBalance(); @@ -690,10 +713,12 @@ RPC.prototype.importPrivKey = co(function* importPrivKey(args, help) { var rescan = valid.bool(2, false); var key; - if (help || args.length < 1 || args.length > 3) - throw new RPCError('importprivkey "bitcoinprivkey" ( "label" rescan )'); + if (help || args.length < 1 || args.length > 3) { + throw new RPCError(errs.MISC_ERROR, + 'importprivkey "bitcoinprivkey" ( "label" rescan )'); + } - key = KeyRing.fromSecret(secret, this.network); + key = parseSecret(secret, this.network); yield wallet.importKey(0, key); @@ -714,10 +739,10 @@ RPC.prototype.importWallet = co(function* importWallet(args, help) { var data, key; if (help || args.length !== 1) - throw new RPCError('importwallet "filename" ( rescan )'); + throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )'); if (fs.unsupported) - throw new RPCError('FS not available.'); + throw new RPCError(errs.INTERNAL_ERROR, 'FS not available.'); data = yield fs.readFile(file, 'utf8'); @@ -735,9 +760,9 @@ RPC.prototype.importWallet = co(function* importWallet(args, help) { parts = line.split(/\s+/); if (parts.length < 4) - throw new RPCError('Malformed wallet.'); + throw new RPCError(errs.DESERIALIZATION_ERROR, 'Malformed wallet.'); - secret = KeyRing.fromSecret(parts[0], this.network); + secret = parseSecret(parts[0], this.network); time = +parts[1]; label = parts[2]; @@ -765,21 +790,23 @@ RPC.prototype.importAddress = co(function* importAddress(args, help) { var p2sh = valid.bool(3, false); var script; - if (help || args.length < 1 || args.length > 4) - throw new RPCError('importaddress "address" ( "label" rescan p2sh )'); + if (help || args.length < 1 || args.length > 4) { + throw new RPCError(errs.MISC_ERROR, + 'importaddress "address" ( "label" rescan p2sh )'); + } if (p2sh) { script = valid.buf(0); if (!script) - throw new RPCError('Invalid parameters.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.'); - script = Script.fromRaw(script); + script = fromRaw(Script, script); script = Script.fromScripthash(script.hash160()); addr = script.getAddress(); } else { - addr = Address.fromBase58(addr, this.network); + addr = parseAddress(addr, this.network); } yield wallet.importAddress(0, addr); @@ -797,11 +824,13 @@ RPC.prototype.importPubkey = co(function* importPubkey(args, help) { var rescan = valid.bool(2, false); var key; - if (help || args.length < 1 || args.length > 4) - throw new RPCError('importpubkey "pubkey" ( "label" rescan )'); + if (help || args.length < 1 || args.length > 4) { + throw new RPCError(errs.MISC_ERROR, + 'importpubkey "pubkey" ( "label" rescan )'); + } if (!data) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); key = KeyRing.fromPublic(data, this.network); @@ -815,7 +844,7 @@ RPC.prototype.importPubkey = co(function* importPubkey(args, help) { RPC.prototype.keyPoolRefill = co(function* keyPoolRefill(args, help) { if (help || args.length > 1) - throw new RPCError('keypoolrefill ( newsize )'); + throw new RPCError(errs.MISC_ERROR, 'keypoolrefill ( newsize )'); return null; }); @@ -827,8 +856,10 @@ RPC.prototype.listAccounts = co(function* listAccounts(args, help) { var map = {}; var i, accounts, account, balance, value; - if (help || args.length > 2) - throw new RPCError('listaccounts ( minconf includeWatchonly)'); + if (help || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'listaccounts ( minconf includeWatchonly)'); + } accounts = yield wallet.getAccounts(); @@ -852,7 +883,7 @@ RPC.prototype.listAccounts = co(function* listAccounts(args, help) { RPC.prototype.listAddressGroupings = co(function* listAddressGroupings(args, help) { if (help) - throw new RPCError('listaddressgroupings'); + throw new RPCError(errs.MISC_ERROR, 'listaddressgroupings'); throw new Error('Not implemented.'); }); @@ -861,7 +892,7 @@ RPC.prototype.listLockUnspent = co(function* listLockUnspent(args, help) { var i, outpoints, outpoint, out; if (help || args.length > 0) - throw new RPCError('listlockunspent'); + throw new RPCError(errs.MISC_ERROR, 'listlockunspent'); outpoints = wallet.getLocked(); out = []; @@ -884,8 +915,8 @@ RPC.prototype.listReceivedByAccount = co(function* listReceivedByAccount(args, h var watchOnly = valid.bool(2, false); if (help || args.length > 3) { - throw new RPCError('listreceivedbyaccount' - + ' ( minconf includeempty includeWatchonly )'); + throw new RPCError(errs.MISC_ERROR, + 'listreceivedbyaccount ( minconf includeempty includeWatchonly )'); } return yield this._listReceived(minconf, includeEmpty, watchOnly, true); @@ -898,8 +929,8 @@ RPC.prototype.listReceivedByAddress = co(function* listReceivedByAddress(args, h var watchOnly = valid.bool(2, false); if (help || args.length > 3) { - throw new RPCError('listreceivedbyaddress' - + ' ( minconf includeempty includeWatchonly )'); + throw new RPCError(errs.MISC_ERROR, + 'listreceivedbyaddress ( minconf includeempty includeWatchonly )'); } return yield this._listReceived(minconf, includeEmpty, watchOnly, false); @@ -1017,8 +1048,8 @@ RPC.prototype.listSinceBlock = co(function* listSinceBlock(args, help) { var i, entry, highest, txs, wtx, json; if (help) { - throw new RPCError('listsinceblock' - + ' ( "blockhash" target-confirmations includeWatchonly)'); + throw new RPCError(errs.MISC_ERROR, + 'listsinceblock ( "blockhash" target-confirmations includeWatchonly)'); } if (wallet.watchOnly !== watchOnly) @@ -1070,7 +1101,7 @@ RPC.prototype._toListTX = co(function* _toListTX(wtx) { var i, member, index; if (!details) - throw new RPCError('TX not found.'); + throw new RPCError(errs.WALLET_ERROR, 'TX not found.'); for (i = 0; i < details.inputs.length; i++) { member = details.inputs[i]; @@ -1145,7 +1176,7 @@ RPC.prototype.listTransactions = co(function* listTransactions(args, help) { var i, txs, wtx, json; if (help || args.length > 4) { - throw new RPCError( + throw new RPCError(errs.MISC_ERROR, 'listtransactions ( "account" count from includeWatchonly)'); } @@ -1182,8 +1213,8 @@ RPC.prototype.listUnspent = co(function* listUnspent(args, help) { var i, depth, address, hash, coins, coin, ring; if (help || args.length > 3) { - throw new RPCError('listunspent' - + ' ( minconf maxconf ["address",...] )'); + throw new RPCError(errs.MISC_ERROR, + 'listunspent ( minconf maxconf ["address",...] )'); } if (addrs) { @@ -1193,10 +1224,10 @@ RPC.prototype.listUnspent = co(function* listUnspent(args, help) { hash = Address.getHash(address, 'hex'); if (!hash) - throw new RPCError('Invalid address.'); + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.'); if (map[hash]) - throw new RPCError('Duplicate address.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address.'); map[hash] = true; } @@ -1254,8 +1285,8 @@ RPC.prototype.lockUnspent = co(function* lockUnspent(args, help) { var i, output, outpoint, hash, index; if (help || args.length < 1 || args.length > 2) { - throw new RPCError('lockunspent' - + ' unlock ([{"txid":"txid","vout":n},...])'); + throw new RPCError(errs.MISC_ERROR, + 'lockunspent unlock ([{"txid":"txid","vout":n},...])'); } if (args.length === 1) { @@ -1265,7 +1296,7 @@ RPC.prototype.lockUnspent = co(function* lockUnspent(args, help) { } if (!outputs) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); for (i = 0; i < outputs.length; i++) { output = outputs[i]; @@ -1274,7 +1305,7 @@ RPC.prototype.lockUnspent = co(function* lockUnspent(args, help) { index = valid.u32('vout'); if (hash == null || index == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); outpoint = new Outpoint(); outpoint.hash = hash; @@ -1306,15 +1337,15 @@ RPC.prototype.sendFrom = co(function* sendFrom(args, help) { var options, tx; if (help || args.length < 3 || args.length > 6) { - throw new RPCError('sendfrom' - + ' "fromaccount" "tobitcoinaddress"' + throw new RPCError(errs.MISC_ERROR, + 'sendfrom "fromaccount" "tobitcoinaddress"' + ' amount ( minconf "comment" "comment-to" )'); } if (!addr || value == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - addr = Address.fromBase58(addr, this.network); + addr = parseAddress(addr, this.network); if (name === '') name = 'default'; @@ -1348,8 +1379,8 @@ RPC.prototype.sendMany = co(function* sendMany(args, help) { var hash, output, options; if (help || args.length < 2 || args.length > 5) { - throw new RPCError('sendmany' - + ' "fromaccount" {"address":amount,...}' + throw new RPCError(errs.MISC_ERROR, + 'sendmany "fromaccount" {"address":amount,...}' + ' ( minconf "comment" ["address",...] )'); } @@ -1357,7 +1388,7 @@ RPC.prototype.sendMany = co(function* sendMany(args, help) { name = 'default'; if (!sendTo) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); keys = Object.keys(sendTo); valid = new Validator([sendTo]); @@ -1365,14 +1396,14 @@ RPC.prototype.sendMany = co(function* sendMany(args, help) { for (i = 0; i < keys.length; i++) { key = keys[i]; value = valid.btc(key); - address = Address.fromBase58(key, this.network); + address = parseAddress(key, this.network); hash = address.getHash('hex'); if (value == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); if (uniq[hash]) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.'); uniq[hash] = true; @@ -1403,16 +1434,15 @@ RPC.prototype.sendToAddress = co(function* sendToAddress(args, help) { var options, tx; if (help || args.length < 2 || args.length > 5) { - throw new RPCError('sendtoaddress' - + ' "bitcoinaddress" amount' - + ' ( "comment" "comment-to"' - + ' subtractfeefromamount )'); + throw new RPCError(errs.MISC_ERROR, + 'sendtoaddress "bitcoinaddress" amount' + + ' ( "comment" "comment-to" subtractfeefromamount )'); } - addr = Address.fromBase58(addr, this.network); + addr = parseAddress(addr, this.network); if (!addr || value == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); options = { subtractFee: subtractFee, @@ -1429,8 +1459,10 @@ RPC.prototype.sendToAddress = co(function* sendToAddress(args, help) { }); RPC.prototype.setAccount = co(function* setAccount(args, help) { - if (help || args.length < 1 || args.length > 2) - throw new RPCError('setaccount "bitcoinaddress" "account"'); + if (help || args.length < 1 || args.length > 2) { + throw new RPCError(errs.MISC_ERROR, + 'setaccount "bitcoinaddress" "account"'); + } // Impossible to implement in bcoin: throw new Error('Not implemented.'); @@ -1441,10 +1473,10 @@ RPC.prototype.setTXFee = co(function* setTXFee(args, help) { var rate = valid.btc(0); if (help || args.length < 1 || args.length > 1) - throw new RPCError('settxfee amount'); + throw new RPCError(errs.MISC_ERROR, 'settxfee amount'); if (rate == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); this.feeRate = rate; @@ -1458,21 +1490,23 @@ RPC.prototype.signMessage = co(function* signMessage(args, help) { var msg = valid.str(1, ''); var sig, ring; - if (help || args.length !== 2) - throw new RPCError('signmessage "bitcoinaddress" "message"'); + if (help || args.length !== 2) { + throw new RPCError(errs.MISC_ERROR, + 'signmessage "bitcoinaddress" "message"'); + } addr = Address.getHash(addr, 'hex'); if (!addr) - throw new RPCError('Invalid address.'); + throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.'); ring = yield wallet.getKey(addr); if (!ring) - throw new RPCError('Address not found.'); + throw new RPCError(errs.WALLET_ERROR, 'Address not found.'); if (!wallet.master.key) - throw new RPCError('Wallet is locked.'); + throw new RPCError(errs.WALLET_UNLOCK_NEEDED, 'Wallet is locked.'); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); @@ -1486,10 +1520,10 @@ RPC.prototype.walletLock = co(function* walletLock(args, help) { var wallet = this.wallet; if (help || (wallet.master.encrypted && args.length !== 0)) - throw new RPCError('walletlock'); + throw new RPCError(errs.MISC_ERROR, 'walletlock'); if (!wallet.master.encrypted) - throw new RPCError('Wallet is not encrypted.'); + throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); yield wallet.lock(); @@ -1503,15 +1537,15 @@ RPC.prototype.walletPassphraseChange = co(function* walletPassphraseChange(args, var new_ = valid.str(1, ''); if (help || (wallet.master.encrypted && args.length !== 2)) { - throw new RPCError('walletpassphrasechange' + throw new RPCError(errs.MISC_ERROR, 'walletpassphrasechange' + ' "oldpassphrase" "newpassphrase"'); } if (!wallet.master.encrypted) - throw new RPCError('Wallet is not encrypted.'); + throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); if (old.length < 1 || new_.length < 1) - throw new RPCError('Invalid parameter'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); yield wallet.setPassphrase(old, new_); @@ -1524,17 +1558,19 @@ RPC.prototype.walletPassphrase = co(function* walletPassphrase(args, help) { var passphrase = valid.str(0, ''); var timeout = valid.u32(1); - if (help || (wallet.master.encrypted && args.length !== 2)) - throw new RPCError('walletpassphrase "passphrase" timeout'); + if (help || (wallet.master.encrypted && args.length !== 2)) { + throw new RPCError(errs.MISC_ERROR, + 'walletpassphrase "passphrase" timeout'); + } if (!wallet.master.encrypted) - throw new RPCError('Wallet is not encrypted.'); + throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.'); if (passphrase.length < 1) - throw new RPCError('Invalid parameter'); + throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter'); if (timeout == null) - throw new RPCError('Invalid parameter'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter'); yield wallet.unlock(passphrase, timeout); @@ -1548,27 +1584,27 @@ RPC.prototype.importPrunedFunds = co(function* importPrunedFunds(args, help) { var hash, height; if (help || args.length < 2 || args.length > 3) { - throw new RPCError('importprunedfunds' - + ' "rawtransaction" "txoutproof" ( "label" )'); + throw new RPCError(errs.MISC_ERROR, + 'importprunedfunds "rawtransaction" "txoutproof" ( "label" )'); } if (!tx || !block) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); - tx = TX.fromRaw(tx); - block = MerkleBlock.fromRaw(block); + tx = fromRaw(TX, tx); + block = fromRaw(MerkleBlock, block); hash = block.hash('hex'); if (!block.verify()) - throw new RPCError('Invalid proof.'); + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); if (!block.hasTX(tx.hash('hex'))) - throw new RPCError('Invalid proof.'); + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); height = yield this.client.getEntry(hash); if (height === -1) - throw new RPCError('Invalid proof.'); + throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.'); block = { hash: hash, @@ -1577,7 +1613,7 @@ RPC.prototype.importPrunedFunds = co(function* importPrunedFunds(args, help) { }; if (!(yield this.wdb.addTX(tx, block))) - throw new RPCError('No tracked address for TX.'); + throw new RPCError(errs.WALLET_ERROR, 'No tracked address for TX.'); return null; }); @@ -1588,13 +1624,13 @@ RPC.prototype.removePrunedFunds = co(function* removePrunedFunds(args, help) { var hash = valid.hash(0); if (help || args.length !== 1) - throw new RPCError('removeprunedfunds "txid"'); + throw new RPCError(errs.MISC_ERROR, 'removeprunedfunds "txid"'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.'); if (!(yield wallet.remove(hash))) - throw new RPCError('Transaction not in wallet.'); + throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.'); return null; }); @@ -1605,12 +1641,12 @@ RPC.prototype.selectWallet = co(function* selectWallet(args, help) { var wallet; if (help || args.length !== 1) - throw new RPCError('selectwallet "id"'); + throw new RPCError(errs.MISC_ERROR, 'selectwallet "id"'); wallet = yield this.wdb.get(id); if (!wallet) - throw new RPCError('Wallet not found.'); + throw new RPCError(errs.WALLET_ERROR, 'Wallet not found.'); this.wallet = wallet; @@ -1619,7 +1655,7 @@ RPC.prototype.selectWallet = co(function* selectWallet(args, help) { RPC.prototype.getMemoryInfo = co(function* getMemoryInfo(args, help) { if (help || args.length !== 0) - throw new RPCError('getmemoryinfo'); + throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo'); return util.memoryUsage(); }); @@ -1629,13 +1665,41 @@ RPC.prototype.setLogLevel = co(function* setLogLevel(args, help) { var level = valid.str(0, ''); if (help || args.length !== 1) - throw new RPCError('setloglevel "level"'); + throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"'); this.logger.setLevel(level); return null; }); +/* + * Helpers + */ + +function fromRaw(ctor, raw) { + try { + return ctor.fromRaw(raw); + } catch (e) { + throw new RPCError(errs.DESERIALIZATION_ERROR, 'Deserialization error.'); + } +} + +function parseAddress(raw, network) { + try { + return Address.fromBase58(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.'); + } +} + /* * Expose */