diff --git a/lib/http/rpc.js b/lib/http/rpc.js index ae073057..0030ebef 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -58,18 +58,14 @@ function RPC(node) { this.fees = node.fees; this.miner = node.miner; this.logger = node.logger; - this.calls = {}; - this.locker = new Lock(); - - this.feeRate = null; this.mining = false; - this.proclimit = 0; - + this.procLimit = 0; this.attempt = null; - this.start = 0; - this._boundChain = false; - this.coinbase = {}; + this.lastActivity = 0; + this.boundChain = false; + this.nonces = {}; + this.pollers = []; this.init(); } @@ -171,7 +167,7 @@ RPC.prototype.getInfo = co(function* getInfo(args, help) { timeoffset: this.network.time.offset, connections: this.pool.peers.size(), proxy: '', - difficulty: this._getDifficulty(), + difficulty: this.difficulty(), testnet: this.network.type !== Network.main, keypoololdest: 0, keypoolsize: 0, @@ -287,30 +283,15 @@ RPC.prototype.getAddedNodeInfo = co(function* getAddedNodeInfo(args, help) { peer = this.pool.peers.get(addr.hostname); if (!peer) throw new RPCError('Node has not been added.'); - return [this._toAddedNode(peer)]; + return [toAddedNode(peer)]; } for (peer = this.pool.peers.head(); peer; peer = peer.next) - out.push(this._toAddedNode(peer)); + out.push(toAddedNode(peer)); return out; }); -RPC.prototype._toAddedNode = function _toAddedNode(peer) { - return { - addednode: peer.hostname(), - connected: peer.connected, - addresses: [ - { - address: peer.hostname(), - connected: peer.outbound - ? 'outbound' - : 'inbound' - } - ] - }; -}; - RPC.prototype.getConnectionCount = co(function* getConnectionCount(args, help) { if (help || args.length !== 0) throw new RPCError('getconnectioncount'); @@ -454,65 +435,6 @@ RPC.prototype.clearBanned = co(function* clearBanned(args, help) { return null; }); -RPC.prototype._deployment = function _deployment(id, version, status) { - return { - id: id, - version: version, - reject: { - status: status - } - }; -}; - -RPC.prototype._getSoftforks = function _getSoftforks() { - return [ - this._deployment('bip34', 2, this.chain.state.hasBIP34()), - this._deployment('bip66', 3, this.chain.state.hasBIP66()), - this._deployment('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; -}); - /* Block chain and UTXO */ RPC.prototype.getBlockchainInfo = co(function* getBlockchainInfo(args, help) { if (help || args.length !== 0) @@ -523,44 +445,19 @@ RPC.prototype.getBlockchainInfo = co(function* getBlockchainInfo(args, help) { blocks: this.chain.height, headers: this.chain.height, bestblockhash: this.chain.tip.rhash(), - difficulty: this._getDifficulty(), + difficulty: this.difficulty(), 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(), + 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._getDifficulty = function _getDifficulty(entry) { - var shift, diff; - - if (!entry) { - if (!this.chain.tip) - return 1.0; - entry = this.chain.tip; - } - - shift = (entry.bits >>> 24) & 0xff; - diff = 0x0000ffff / (entry.bits & 0x00ffffff); - - while (shift < 29) { - diff *= 256.0; - shift++; - } - - while (shift > 29) { - diff /= 256.0; - shift--; - } - - return diff; -}; - RPC.prototype.getBestBlockHash = co(function* getBestBlockHash(args, help) { if (help || args.length !== 0) throw new RPCError('getbestblockhash'); @@ -585,7 +482,7 @@ RPC.prototype.getBlock = co(function* getBlock(args, help) { throw new RPCError('getblock "hash" ( verbose )'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); @@ -607,86 +504,9 @@ RPC.prototype.getBlock = co(function* getBlock(args, help) { if (!verbose) return block.toRaw().toString('hex'); - return yield this._blockToJSON(entry, block, false); + return yield this.blockToJSON(entry, block, false); }); -RPC.prototype._txToJSON = function _txToJSON(tx, entry) { - var self = this; - var height = entry ? entry.height : -1; - var conf = 0; - - if (height >= this.chain.height) - conf = height + 1 - this.chain.height; - - return { - txid: tx.txid(), - hash: tx.wtxid(), - size: tx.getSize(), - vsize: tx.getVirtualSize(), - version: tx.version, - locktime: tx.locktime, - vin: tx.inputs.map(function(input) { - var out = {}; - if (tx.isCoinbase()) { - out.coinbase = input.script.toJSON(); - } else { - out.txid = input.prevout.txid(); - out.vout = input.prevout.index; - out.scriptSig = { - asm: input.script.toASM(), - hex: input.script.toJSON() - }; - } - if (input.witness.items.length > 0) { - out.txinwitness = input.witness.items.map(function(item) { - return item.toString('hex'); - }); - } - out.sequence = input.sequence; - return out; - }), - vout: tx.outputs.map(function(output, i) { - return { - value: Amount.btc(output.value, true), - n: i, - scriptPubKey: self._scriptToJSON(output.script, true) - }; - }), - blockhash: entry ? entry.rhash() : null, - confirmations: conf, - time: entry ? entry.ts : 0, - blocktime: entry ? entry.ts : 0, - 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: [] - }; - - if (hex) - out.hex = script.toJSON(); - - if (script.isMultisig()) - out.reqSigs = script.getSmall(0); - - if (address) { - address = address.toBase58(this.network); - out.addresses.push(address); - } - - return out; -}; - RPC.prototype.getBlockHash = co(function* getBlockHash(args, help) { var valid = new Validator([args]); var height = valid.u32(0); @@ -716,7 +536,7 @@ RPC.prototype.getBlockHeader = co(function* getBlockHeader(args, help) { throw new RPCError('getblockheader "hash" ( verbose )'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid block hash.'); entry = yield this.chain.db.getEntry(hash); @@ -726,60 +546,7 @@ RPC.prototype.getBlockHeader = co(function* getBlockHeader(args, help) { if (!verbose) return entry.toRaw().toString('hex', 0, 80); - return yield this._headerToJSON(entry); -}); - -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, - merkleroot: util.revHex(entry.merkleRoot), - time: entry.ts, - mediantime: medianTime, - bits: entry.bits, - difficulty: this._getDifficulty(entry), - 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, txDetails) { - var self = this; - var mtp = yield entry.getMedianTime(); - var nextHash = yield this.chain.db.getNextHash(entry.hash); - - 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, - merkleroot: util.revHex(entry.merkleRoot), - tx: block.txs.map(function(tx) { - if (txDetails) - return self._txToJSON(tx, entry); - return tx.txid(); - }), - time: entry.ts, - mediantime: mtp, - bits: entry.bits, - difficulty: this._getDifficulty(entry), - chainwork: entry.chainwork.toString('hex', 64), - previousblockhash: entry.prevBlock !== encoding.NULL_HASH - ? util.revHex(entry.prevBlock) - : null, - nextblockhash: nextHash ? util.revHex(nextHash) : null - }; + return yield this.headerToJSON(entry); }); RPC.prototype.getChainTips = co(function* getChainTips(args, help) { @@ -823,7 +590,7 @@ RPC.prototype.getDifficulty = co(function* getDifficulty(args, help) { if (help || args.length !== 0) throw new RPCError('getdifficulty'); - return this._getDifficulty(); + return this.difficulty(); }); RPC.prototype.getMempoolInfo = co(function* getMempoolInfo(args, help) { @@ -856,7 +623,7 @@ RPC.prototype.getMempoolAncestors = co(function* getMempoolAncestors(args, help) throw new RPCError('No mempool available.'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid TXID.'); entry = this.mempool.getEntry(hash); @@ -868,7 +635,7 @@ RPC.prototype.getMempoolAncestors = co(function* getMempoolAncestors(args, help) if (verbose) { for (i = 0; i < entries.length; i++) { entry = entries[i]; - out.push(this._entryToJSON(entry)); + out.push(this.entryToJSON(entry)); } } else { for (i = 0; i < entries.length; i++) { @@ -894,7 +661,7 @@ RPC.prototype.getMempoolDescendants = co(function* getMempoolDescendants(args, h throw new RPCError('No mempool available.'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid TXID.'); entry = this.mempool.getEntry(hash); @@ -906,7 +673,7 @@ RPC.prototype.getMempoolDescendants = co(function* getMempoolDescendants(args, h if (verbose) { for (i = 0; i < entries.length; i++) { entry = entries[i]; - out.push(this._entryToJSON(entry)); + out.push(this.entryToJSON(entry)); } } else { for (i = 0; i < entries.length; i++) { @@ -930,14 +697,14 @@ RPC.prototype.getMempoolEntry = co(function* getMempoolEntry(args, help) { throw new RPCError('No mempool available.'); if (!hash) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid TXID.'); entry = this.mempool.getEntry(hash); if (!entry) throw new RPCError('Transaction not in mempool.'); - return this._entryToJSON(entry); + return this.entryToJSON(entry); }); RPC.prototype.getRawMempool = co(function* getRawMempool(args, help) { @@ -959,7 +726,7 @@ RPC.prototype.getRawMempool = co(function* getRawMempool(args, help) { if (!entry) continue; - out[entry.txid()] = this._entryToJSON(entry); + out[entry.txid()] = this.entryToJSON(entry); } return out; @@ -970,25 +737,6 @@ RPC.prototype.getRawMempool = co(function* getRawMempool(args, help) { return hashes.map(util.revHex); }); -RPC.prototype._entryToJSON = function _entryToJSON(entry) { - return { - size: entry.size, - fee: Amount.btc(entry.fee, 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: Amount.btc(entry.descFee, true), - ancestorcount: this.mempool.countAncestors(entry), - ancestorsize: 0, - ancestorfees: 0, - depends: this.mempool.getDepends(entry.tx).map(util.revHex) - }; -}; - RPC.prototype.getTXOut = co(function* getTXOut(args, help) { var valid = new Validator([args]); var hash = valid.hash(0); @@ -1006,7 +754,7 @@ RPC.prototype.getTXOut = co(function* getTXOut(args, help) { throw new RPCError('Cannot get coins when pruned.'); if (!hash || index == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid outpoint.'); if (mempool) coin = this.mempool.getCoin(hash, index); @@ -1021,7 +769,7 @@ RPC.prototype.getTXOut = co(function* getTXOut(args, help) { bestblock: this.chain.tip.rhash(), confirmations: coin.getDepth(this.chain.height), value: Amount.btc(coin.value, true), - scriptPubKey: this._scriptToJSON(coin.script, true), + scriptPubKey: this.scriptToJSON(coin.script, true), version: coin.version, coinbase: coin.coinbase }; @@ -1044,7 +792,7 @@ RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { throw new RPCError('Cannot get coins when pruned.'); if (!txids || txids.length === 0) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid TXIDs.'); valid = new Validator([txids]); @@ -1052,7 +800,7 @@ RPC.prototype.getTXOutProof = co(function* getTXOutProof(args, help) { txid = valid.hash(i); if (!txid) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid TXID.'); if (uniq[txid]) throw new RPCError('Duplicate txid.'); @@ -1146,7 +894,7 @@ RPC.prototype.verifyChain = co(function* verifyChain(args, help) { throw new RPCError('verifychain ( checklevel numblocks )'); if (level == null || blocks == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Missing parameters.'); if (this.chain.options.spv) throw new RPCError('Cannot verify chain in SPV mode.'); @@ -1161,31 +909,33 @@ RPC.prototype.verifyChain = co(function* verifyChain(args, help) { * Mining */ -RPC.prototype._submitWork = co(function* _submitWork(data) { +RPC.prototype.submitWork = co(function* submitWork(data) { var unlock = yield this.locker.lock(); try { - return yield this.__submitWork(data); + return yield this._submitWork(data); } finally { unlock(); } }); -RPC.prototype.__submitWork = co(function* __submitWork(data) { +RPC.prototype._submitWork = co(function* _submitWork(data) { var attempt = this.attempt; - var block, entry, header, cb, cur; - - if (data.length !== 128) - throw new RPCError('Invalid parameter.'); + var block, entry, header, nonce, ts, nonces; if (!attempt) return false; + block = attempt.block; + header = Headers.fromAbbr(data); + nonce = header.nonce; + ts = header.ts; + + if (data.length !== 128) + throw new RPCError('Invalid work size.'); + data = data.slice(0, 80); data = swap32(data); - header = Headers.fromAbbr(data); - block = attempt.block; - if (header.prevBlock !== block.prevBlock || header.bits !== block.bits) { return false; @@ -1194,24 +944,13 @@ RPC.prototype.__submitWork = co(function* __submitWork(data) { if (!header.verify()) return false; - cb = this.coinbase[header.merkleRoot]; + nonces = this.nonces[header.merkleRoot]; - if (!cb) + if (!nonces) return false; - cur = block.txs[0]; - block.txs[0] = cb; - attempt.updateMerkle(); - - if (header.merkleRoot !== block.merkleRoot) { - block.txs[0] = cur; - attempt.updateMerkle(); - this.logger.warning('Bad calculated merkle root for submitted work.'); + if (!nonces.verify(attempt, nonce, ts)) return false; - } - - block.ts = header.ts; - attempt.commit(header.nonce); try { entry = yield this.chain.add(block); @@ -1233,17 +972,17 @@ RPC.prototype.__submitWork = co(function* __submitWork(data) { return true; }); -RPC.prototype._createWork = co(function* _createWork(data) { +RPC.prototype.createWork = co(function* createWork(data) { var unlock = yield this.locker.lock(); try { - return yield this.__createWork(data); + return yield this._createWork(data); } finally { unlock(); } }); -RPC.prototype.__createWork = co(function* __createWork() { - var attempt = yield this._getAttempt(true); +RPC.prototype._createWork = co(function* _createWork() { + var attempt = yield this.updateAttempt(); var data, abbr; data = new Buffer(128); @@ -1265,8 +1004,8 @@ RPC.prototype.__createWork = co(function* __createWork() { }); RPC.prototype.getWorkLongpoll = co(function* getWorkLongpoll(args, help) { - yield this._onBlock(); - return yield this._createWork(); + yield this.longpoll(); + return yield this.createWork(); }); RPC.prototype.getWork = co(function* getWork(args, help) { @@ -1278,12 +1017,12 @@ RPC.prototype.getWork = co(function* getWork(args, help) { if (args.length === 1) { if (!data) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid work data.'); - return yield this._submitWork(data); + return yield this.submitWork(data); } - return yield this._creatework(); + return yield this.createWork(); }); RPC.prototype.submitBlock = co(function* submitBlock(args, help) { @@ -1370,7 +1109,7 @@ RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { if (mode === 'proposal') { if (!data) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Missing data parameter.'); block = Block.fromRaw(data); @@ -1408,22 +1147,22 @@ RPC.prototype.getBlockTemplate = co(function* getBlockTemplate(args, help) { } if (lpid) - yield this._poll(lpid); + yield this.handleLongpoll(lpid); - return yield this._template(version, coinbase, rules); + return yield this.createTemplate(version, coinbase, rules); }); -RPC.prototype._template = co(function* _template(version, coinbase, rules) { +RPC.prototype.createTemplate = co(function* createTemplate(version, coinbase, rules) { var unlock = yield this.locker.lock(); try { - return yield this.__template(version, coinbase, rules); + return yield this._createTemplate(version, coinbase, rules); } finally { unlock(); } }); -RPC.prototype.__template = co(function* _template(version, coinbase, rules) { - var attempt = yield this._getAttempt(false); +RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, rules) { + var attempt = yield this.getAttempt(); var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR; var block = attempt.block; var mutable = ['time', 'transactions', 'prevblock']; @@ -1522,7 +1261,7 @@ RPC.prototype.__template = co(function* _template(version, coinbase, rules) { sigoplimit: consensus.MAX_BLOCK_SIGOPS_COST / scale | 0, sizelimit: consensus.MAX_BLOCK_SIZE, weightlimit: undefined, - longpollid: this.chain.tip.rhash() + util.pad32(this._totalTX()), + longpollid: this.chain.tip.rhash() + util.pad32(this.totalTX()), submitold: false, coinbaseaux: { flags: attempt.coinbaseFlags.toString('hex') @@ -1574,96 +1313,6 @@ RPC.prototype.__template = co(function* _template(version, coinbase, rules) { return json; }); -RPC.prototype._poll = co(function* _poll(lpid) { - var watched, lastTX; - - if (lpid.length !== 74) - throw new RPCError('Invalid parameter.'); - - watched = lpid.slice(0, 64); - lastTX = +lpid.slice(64, 74); - - if (!util.isHex(watched) || !util.isNumber(lastTX)) - throw new RPCError('Invalid parameter.'); - - watched = util.revHex(watched); - - if (this.chain.tip.hash !== watched) - return; - - yield this._onBlock(); -}); - -RPC.prototype._onBlock = function _onBlock() { - var self = this; - return new Promise(function(resolve, reject) { - self.once('clear block', resolve); - }); -}; - -RPC.prototype._clearBlock = function _clearBlock() { - this.attempt = null; - this.start = 0; - this.coinbase = {}; - this.emit('clear block'); -}; - -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._clearBlock(); - }); - - if (!this.mempool) - return; - - this.node.on('tx', function() { - if (!self.attempt) - return; - - if (util.now() - self.start > 10) - self._clearBlock(); - }); -}; - -RPC.prototype._getAttempt = co(function* _getAttempt(update) { - var attempt = this.attempt; - var block; - - this._bindChain(); - - if (attempt) { - block = attempt.block; - if (update) { - attempt.updateNonce(); - this.coinbase[block.merkleRoot] = attempt.coinbase.clone(); - } - return attempt; - } - - attempt = yield this.miner.createBlock(); - block = attempt.block; - - this.attempt = attempt; - this.start = util.now(); - this.coinbase[block.merkleRoot] = attempt.coinbase.clone(); - - return attempt; -}); - -RPC.prototype._totalTX = function _totalTX() { - return this.mempool ? this.mempool.totalTX : 0; -}; - RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) { var attempt = this.attempt; var hashps; @@ -1671,17 +1320,17 @@ RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) { if (help || args.length !== 0) throw new RPCError('getmininginfo'); - hashps = yield this._hashps(120); + hashps = yield this.getHashRate(120); return { blocks: this.chain.height, currentblocksize: attempt ? attempt.block.getBaseSize() : 0, currentblocktx: attempt ? attempt.block.txs.length : 0, - difficulty: this._getDifficulty(), + difficulty: this.difficulty(), errors: '', - genproclimit: this.proclimit, + genproclimit: this.procLimit, networkhashps: hashps, - pooledtx: this._totalTX(), + pooledtx: this.totalTX(), testnet: this.network !== Network.main, chain: 'main', generate: this.mining @@ -1696,7 +1345,7 @@ RPC.prototype.getNetworkHashPS = co(function* getNetworkHashPS(args, help) { if (help || args.length > 2) throw new RPCError('getnetworkhashps ( blocks height )'); - return yield this._hashps(lookup, height); + return yield this.getHashRate(lookup, height); }); RPC.prototype.prioritiseTransaction = co(function* prioritiseTransaction(args, help) { @@ -1715,10 +1364,10 @@ RPC.prototype.prioritiseTransaction = co(function* prioritiseTransaction(args, h throw new RPCError('No mempool available.'); if (!hash) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid TXID'); if (pri == null || fee == null) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid fee or priority.'); entry = this.mempool.getEntry(hash); @@ -1737,46 +1386,6 @@ RPC.prototype.prioritiseTransaction = co(function* prioritiseTransaction(args, h return true; }); -RPC.prototype._hashps = co(function* _hashps(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('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.verifyBlock = co(function* verifyBlock(args, help) { var valid = new Validator([args]); var data = valid.buf(0); @@ -1786,7 +1395,7 @@ RPC.prototype.verifyBlock = co(function* verifyBlock(args, help) { throw new RPCError('verifyblock "block-hex"'); if (!data) - throw new RPCError('Invalid parameters.'); + throw new RPCError('Invalid block hex.'); if (this.chain.options.spv) throw new RPCError('Cannot verify block in SPV mode.'); @@ -1823,7 +1432,7 @@ RPC.prototype.setGenerate = co(function* setGenerate(args, help) { throw new RPCError('setgenerate mine ( proclimit )'); this.mining = mine; - this.proclimit = limit; + this.procLimit = limit; if (mine) { this.miner.start().catch(util.nop); @@ -1852,20 +1461,7 @@ RPC.prototype._generate = co(function* generate(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError('generate numblocks ( maxtries )'); - return yield this._generateBlocks(blocks, null, tries); -}); - -RPC.prototype._generateBlocks = co(function* _generateBlocks(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; + return yield this.mineBlocks(blocks, null, tries); }); RPC.prototype.generateToAddress = co(function* generateToAddress(args, help) { @@ -1887,7 +1483,7 @@ RPC.prototype._generateToAddress = co(function* _generateToAddress(args, help) { addr = Address.fromBase58(addr, this.network); - return yield this._generateBlocks(blocks, addr); + return yield this.mineBlocks(blocks, addr); }); /* @@ -1910,7 +1506,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel } if (!inputs || !sendTo) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid parameters (inputs and sendTo).'); tx = new TX(); @@ -1929,7 +1525,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel sequence--; if (!hash || index == null) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid outpoint.'); input = new Input(); input.prevout.hash = hash; @@ -1950,7 +1546,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel value = valid.buf(key); if (!value) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid nulldata..'); output = new Output(); output.value = 0; @@ -1971,7 +1567,7 @@ RPC.prototype.createRawTransaction = co(function* createRawTransaction(args, hel value = valid.btc(key); if (value == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid output value.'); output = new Output(); output.value = value; @@ -1992,11 +1588,11 @@ RPC.prototype.decodeRawTransaction = co(function* decodeRawTransaction(args, hel throw new RPCError('decoderawtransaction "hexstring"'); if (!data) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid hex string.'); tx = TX.fromRaw(data); - return this._txToJSON(tx); + return this.txToJSON(tx); }); RPC.prototype.decodeScript = co(function* decodeScript(args, help) { @@ -2014,7 +1610,7 @@ RPC.prototype.decodeScript = co(function* decodeScript(args, help) { address = Address.fromScripthash(script.hash160()); - script = this._scriptToJSON(script); + script = this.scriptToJSON(script); script.p2sh = address.toBase58(this.network); return script; @@ -2030,7 +1626,7 @@ RPC.prototype.getRawTransaction = co(function* getRawTransaction(args, help) { throw new RPCError('getrawtransaction "txid" ( verbose )'); if (!hash) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid TXID.'); meta = yield this.node.getMeta(hash); @@ -2045,7 +1641,7 @@ RPC.prototype.getRawTransaction = co(function* getRawTransaction(args, help) { if (meta.block) entry = yield this.chain.db.getEntry(meta.block); - json = this._txToJSON(tx, entry); + json = this.txToJSON(tx, entry); json.time = meta.ps; json.hex = tx.toRaw().toString('hex'); @@ -2061,7 +1657,7 @@ RPC.prototype.sendRawTransaction = co(function* sendRawTransaction(args, help) { throw new RPCError('sendrawtransaction "hexstring" ( allowhighfees )'); if (!data) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid hex string.'); tx = TX.fromRaw(data); @@ -2084,7 +1680,7 @@ RPC.prototype.signRawTransaction = co(function* signRawTransaction(args, help) { } if (!data) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid hex string.'); tx = MTX.fromRaw(data); tx.view = yield this.mempool.getSpentView(tx); @@ -2126,7 +1722,7 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { redeem = valid.buf('redeemScript'); if (!hash || index == null || !script || value == null) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid UTXO.'); script = Script.fromRaw(script); @@ -2167,14 +1763,14 @@ RPC.prototype._signRawTransaction = co(function* _signRawTransaction(tx, args) { type = Script.hashType[parts[0]]; if (type == null) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid sighash type.'); if (parts.length > 2) - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid sighash type.'); if (parts.length === 2) { if (parts[1] !== 'ANYONECANPAY') - throw new RPCError('Invalid parameter'); + throw new RPCError('Invalid sighash type.'); type |= Script.hashType.ANYONECANPAY; } } @@ -2202,7 +1798,7 @@ RPC.prototype.createMultisig = co(function* createMultisig(args, help) { throw new RPCError('createmultisig nrequired ["key",...]'); if (m < 1 || n < m || n > 16) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid m and n values.'); valid = new Validator([keys]); @@ -2240,7 +1836,7 @@ RPC.prototype.createWitnessAddress = co(function* createWitnessAddress(args, hel throw new RPCError('createwitnessaddress "script"'); if (!raw) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid script hex.'); script = Script.fromRaw(raw); program = script.forWitness(); @@ -2291,7 +1887,7 @@ RPC.prototype.verifyMessage = co(function* verifyMessage(args, help) { throw new RPCError('verifymessage "bitcoinaddress" "signature" "message"'); if (!hash || !sig || !msg) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid parameters.'); msg = new Buffer(MAGIC_STRING + msg, 'utf8'); msg = crypto.hash256(msg); @@ -2450,7 +2046,7 @@ RPC.prototype.setMockTime = co(function* setMockTime(args, help) { throw new RPCError('setmocktime timestamp'); if (ts == null) - throw new RPCError('Invalid parameter.'); + throw new RPCError('Invalid timestamp.'); this.network.time.offset = 0; @@ -2480,6 +2076,422 @@ RPC.prototype.setLogLevel = co(function* setLogLevel(args, help) { return null; }); +/* + * Helpers + */ + +RPC.prototype.handleLongpoll = co(function* handleLongpoll(lpid) { + var watched, lastTX; + + if (lpid.length !== 74) + throw new RPCError('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.'); + + 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.coinbase = {}; + 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.getAttempt = co(function* getAttempt() { + var attempt = this.attempt; + + this.bindChain(); + + if (!attempt) { + attempt = yield this.miner.createBlock(); + this.attempt = attempt; + this.lastActivity = util.now(); + } + + return attempt; +}); + +RPC.prototype.updateAttempt = co(function* updateAttempt() { + var attempt = this.attempt; + + this.bindChain(); + + if (attempt) { + attempt.updateNonce(); + this.nonces[block.merkleRoot] = attempt.snapshot(); + return attempt; + } + + attempt = yield this.miner.createBlock(); + + this.attempt = attempt; + this.lastActivity = util.now(); + this.nonces[block.merkleRoot] = attempt.snapshot(); + + return attempt; +}); + +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.difficulty = function difficulty(entry) { + var shift, diff; + + if (!entry) + entry = this.chain.tip; + + shift = (entry.bits >>> 24) & 0xff; + diff = 0x0000ffff / (entry.bits & 0x00ffffff); + + while (shift < 29) { + diff *= 256.0; + shift++; + } + + while (shift > 29) { + diff /= 256.0; + shift--; + } + + return diff; +}; + +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('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 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.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: [] + }; + + if (hex) + out.hex = script.toJSON(); + + if (script.isMultisig()) + out.reqSigs = script.getSmall(0); + + if (address) { + address = address.toBase58(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, + merkleroot: util.revHex(entry.merkleRoot), + time: entry.ts, + mediantime: medianTime, + bits: entry.bits, + difficulty: this.difficulty(entry), + 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, + merkleroot: util.revHex(entry.merkleRoot), + tx: txs, + time: entry.ts, + mediantime: mtp, + bits: entry.bits, + difficulty: this.difficulty(entry), + 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.fee, 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: Amount.btc(entry.descFee, true), + ancestorcount: this.mempool.countAncestors(entry), + ancestorsize: 0, + ancestorfees: 0, + depends: this.mempool.getDepends(entry.tx).map(util.revHex) + }; +}; + /* * Helpers */ @@ -2493,6 +2505,31 @@ function swap32(data) { 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 + } + }; +} + /* * Expose */ diff --git a/lib/mining/minerblock.js b/lib/mining/minerblock.js index 1ba90a4f..d7ce7ba9 100644 --- a/lib/mining/minerblock.js +++ b/lib/mining/minerblock.js @@ -560,6 +560,15 @@ MinerBlock.prototype.commit = function commit(nonce) { return this.block; }; +/** + * Snapshot the nonces. + * @returns {Nonces} + */ + +MinerBlock.prototype.snapshot = function snapshot() { + return new Nonces(this.nonce1, this.nonce2); +}; + /** * Send a progress report (emits `status`). */ @@ -647,6 +656,58 @@ BlockEntry.fromEntry = function fromEntry(entry, attempt) { return item; }; +/** + * Nonces + * @constructor + * @ignore + */ + +function Nonces(nonce1, nonce2) { + this.nonce1 = nonce1; + this.nonce2 = nonce2; +} + +/** + * Inject nonces into miner block. + * @param {MinerBlock} attempt + * @param {Number} nonce + * @param {Number} ts + */ + +Nonces.prototype.inject = function inject(attempt, nonce, ts) { + attempt.block.ts = ts; + attempt.block.nonce = nonce; + attempt.nonce1 = this.nonce1; + attempt.nonce2 = this.nonce2; + attempt.updateCoinbase(); + attempt.updateMerkle(); +}; + +/** + * Attempt to verify POW. + * @param {MinerBlock} attempt + * @param {Number} nonce + * @param {Number} ts + */ + +Nonces.prototype.verify = function verify(attempt, nonce, ts) { + var block = attempt.block; + var bts = block.ts; + var bnonce = block.nonce; + var nonce1 = attempt.nonce1; + var nonce2 = attempt.nonce2; + var result; + + this.inject(attempt, nonce, ts); + + if (consensus.verifyPOW(block.hash(), block.bits)) + return true; + + this.inject(attempt, bnonce, bts); + + return false; +}; + /* * Expose */ diff --git a/lib/node/config.js b/lib/node/config.js index a2e42531..84105406 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -351,7 +351,7 @@ Config.prototype.buf = function buf(key, fallback) { * @returns {String[]|null} */ -Config.prototype.list = function list(key, fallback) { +Config.prototype.array = function array(key, fallback) { var value = this.get(key); if (fallback === undefined) @@ -385,18 +385,6 @@ Config.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'string') { - if (!value || typeof value !== 'object') - throw new Error(key + ' must be an object.'); - return value; - } - - try { - value = JSON.parse(value); - } catch (e) { - ; - } - if (!value || typeof value !== 'object') throw new Error(key + ' must be an object.'); @@ -419,13 +407,10 @@ Config.prototype.func = function func(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'string') { - if (!value || typeof value !== 'function') - throw new Error(key + ' must be a function.'); - return value; - } + if (!value || typeof value !== 'function') + throw new Error(key + ' must be a function.'); - throw new Error(key + ' must be a function.'); + return value; }; /** @@ -459,7 +444,7 @@ Config.prototype.path = function path(key, fallback) { }; /** - * Get a config option (as a number). + * Get a config option (in MB). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -477,37 +462,6 @@ Config.prototype.mb = function mb(key, fallback) { return value * 1024; }; -/** - * Get a config option (as a bolean or path). - * @param {String} key - * @param {Object?} fallback - * @returns {String|null} - */ - -Config.prototype.boolpath = function boolpath(key, fallback) { - var value = this.get(key); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - if (typeof value !== 'string') { - if (typeof value !== 'boolean' && typeof value !== 'string') - throw new Error(key + ' must be a boolean or string.'); - return value; - } - - if (value === 'true' || value === '1') - return true; - - if (value === 'false' || value === '0') - return false; - - return this.path(key, fallback); -}; - /** * Grab network type from config data. * @private diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 8fa61a7b..6b485d31 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -103,8 +103,8 @@ function FullNode(options) { proxy: this.config.str('proxy'), onion: this.config.bool('onion'), upnp: this.config.bool('upnp'), - seeds: this.config.list('seeds'), - nodes: this.config.list('nodes'), + seeds: this.config.array('seeds'), + nodes: this.config.array('nodes'), publicHost: this.config.str('public-host'), publicPort: this.config.num('public-port'), host: this.config.str('host'), @@ -119,7 +119,7 @@ function FullNode(options) { chain: this.chain, mempool: this.mempool, fees: this.fees, - address: this.config.list('payout-address'), + address: this.config.array('payout-address'), coinbaseFlags: this.config.str('coinbase-flags'), preverify: this.config.bool('preverify'), maxWeight: this.config.num('max-weight'), diff --git a/lib/node/node.js b/lib/node/node.js index d4bda8d7..0a0e1367 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -351,7 +351,7 @@ Node.prototype.require = function require(name) { */ Node.prototype.loadPlugins = function loadPlugins() { - var plugins = this.config.list('plugins', []); + var plugins = this.config.array('plugins', []); var loader = this.config.func('loader'); var i, name, plugin; diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index 59f90f2b..dc2de83e 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -64,8 +64,8 @@ function SPVNode(options) { proxy: this.config.str('proxy'), onion: this.config.bool('onion'), upnp: this.config.bool('upnp'), - seeds: this.config.list('seeds'), - nodes: this.config.list('nodes'), + seeds: this.config.array('seeds'), + nodes: this.config.array('nodes'), bip151: this.config.bool('bip151'), bip150: this.config.bool('bip150'), identityKey: this.config.buf('identity-key'), diff --git a/lib/utils/validator.js b/lib/utils/validator.js index d6bb3c49..5a45c88d 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -20,9 +20,9 @@ function Validator(data) { } /** - * Test whether a config option is present. - * @param {String} key - * @returns {Boolean} + * Initialize the validator. + * @private + * @param {Object} data */ Validator.prototype.init = function init(data) { @@ -35,7 +35,7 @@ Validator.prototype.init = function init(data) { }; /** - * Test whether a config option is present. + * Test whether value is present. * @param {String} key * @returns {Boolean} */ @@ -57,7 +57,7 @@ Validator.prototype.has = function has(key) { }; /** - * Get a config option. + * Get a value (no type validation). * @param {String} key * @param {Object?} fallback * @returns {Object|null} @@ -99,7 +99,7 @@ Validator.prototype.get = function get(key, fallback) { }; /** - * Get a config option (as a string). + * Get a value (as a string). * @param {String} key * @param {Object?} fallback * @returns {String|null} @@ -115,13 +115,13 @@ Validator.prototype.str = function str(key, fallback) { return fallback; if (typeof value !== 'string') - throw new Error(key + ' must be a string.'); + throw new Error(fmt(key) + ' must be a string.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a number). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -138,23 +138,23 @@ Validator.prototype.num = function num(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); return value; } if (!/^\d+$/.test(value)) - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); value = parseInt(value, 10); if (!isFinite(value)) - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a uint32). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -170,13 +170,13 @@ Validator.prototype.u32 = function u32(key, fallback) { return fallback; if (value % 1 !== 0 || value < 0 || value > 0xffffffff) - throw new Error(key + ' must be a uint32.'); + throw new Error(fmt(key) + ' must be a uint32.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a uint64). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -192,13 +192,13 @@ Validator.prototype.u64 = function u64(key, fallback) { return fallback; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(key + ' must be a uint64.'); + throw new Error(fmt(key) + ' must be a uint64.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a satoshi number or btc string). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -215,28 +215,28 @@ Validator.prototype.amt = function amt(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); return value; } if (!/^\d+(\.\d{0,8})?$/.test(value)) - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); value = parseFloat(value); if (!isFinite(value)) - throw new Error(key + ' must be a number.'); + throw new Error(fmt(key) + ' must be a number.'); value *= 1e8; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(key + ' must be a uint64.'); + throw new Error(fmt(key) + ' must be a uint64.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a btc float). * @param {String} key * @param {Object?} fallback * @returns {Number|null} @@ -254,16 +254,16 @@ Validator.prototype.btc = function btc(key, fallback) { value *= 1e8; if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(key + ' must be a uint64.'); + throw new Error(fmt(key) + ' must be a uint64.'); return value; }; /** - * Get a config option (as a number). + * Get a value (as a reverse hash). * @param {String} key * @param {Object?} fallback - * @returns {Number|null} + * @returns {Hash|null} */ Validator.prototype.hash = function hash(key, fallback) { @@ -279,17 +279,17 @@ Validator.prototype.hash = function hash(key, fallback) { if (typeof value !== 'string') { if (!Buffer.isBuffer(value)) - throw new Error(key + ' must be a buffer.'); + throw new Error(fmt(key) + ' must be a buffer.'); if (value.length !== 32) - throw new Error(key + ' must be a buffer.'); + throw new Error(fmt(key) + ' must be a buffer.'); return value.toString('hex'); } if (value.length !== 64) - throw new Error(key + ' must be a hex string.'); + throw new Error(fmt(key) + ' must be a hex string.'); if (!/^[0-9a-f]+$/i.test(value)) - throw new Error(key + ' must be a hex string.'); + throw new Error(fmt(key) + ' must be a hex string.'); for (i = 0; i < value.length; i += 2) out = value.slice(i, i + 2) + out; @@ -298,10 +298,10 @@ Validator.prototype.hash = function hash(key, fallback) { }; /** - * Get a config option (as a number). + * Get a value (as a number or reverse hash). * @param {String} key * @param {Object?} fallback - * @returns {Number|null} + * @returns {Number|Hash|null} */ Validator.prototype.numhash = function numhash(key, fallback) { @@ -317,10 +317,10 @@ Validator.prototype.numhash = function numhash(key, fallback) { }; /** - * Get a config option (as a number). + * Get a value (as a number or string). * @param {String} key * @param {Object?} fallback - * @returns {Number|null} + * @returns {Number|String|null} */ Validator.prototype.numstr = function numstr(key, fallback) { @@ -335,7 +335,7 @@ Validator.prototype.numstr = function numstr(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(key + ' must be a number or string.'); + throw new Error(fmt(key) + ' must be a number or string.'); return value; } @@ -348,7 +348,7 @@ Validator.prototype.numstr = function numstr(key, fallback) { }; /** - * Get a config option (as a boolean). + * Get a value (as a boolean). * @param {String} key * @param {Object?} fallback * @returns {Boolean|null} @@ -364,8 +364,8 @@ Validator.prototype.bool = function bool(key, fallback) { return fallback; if (typeof value !== 'string') { - assert(typeof value === 'boolean', - 'Passed in config option is of wrong type.'); + if (typeof value !== 'boolean') + throw new Error(fmt(key) + ' must be a boolean.'); return value; } @@ -375,13 +375,14 @@ Validator.prototype.bool = function bool(key, fallback) { if (value === 'false' || value === '0') return false; - throw new Error(key + ' must be a boolean.'); + throw new Error(fmt(key) + ' must be a boolean.'); }; /** - * Get a config option (as a buffer). + * Get a value (as a buffer). * @param {String} key * @param {Object?} fallback + * @param {String?} enc * @returns {Buffer|null} */ @@ -399,24 +400,24 @@ Validator.prototype.buf = function buf(key, fallback, enc) { return fallback; if (typeof value !== 'string') { - assert(Buffer.isBuffer(value), - 'Passed in config option is of wrong type.'); + if (!Buffer.isBuffer(value)) + throw new Error(fmt(key) + ' must be a buffer.'); return value; } data = new Buffer(value, enc); if (data.length !== Buffer.byteLength(value, enc)) - throw new Error(key + ' must be a ' + enc + ' string.'); + throw new Error(fmt(key) + ' must be a ' + enc + ' string.'); return data; }; /** - * Get a config option (as an array of strings). + * Get a value (as an array). * @param {String} key * @param {Object?} fallback - * @returns {String[]|null} + * @returns {Array|String[]|null} */ Validator.prototype.array = function array(key, fallback) { @@ -430,7 +431,7 @@ Validator.prototype.array = function array(key, fallback) { if (typeof value !== 'string') { if (!Array.isArray(value)) - throw new Error(key + ' must be a list/array.'); + throw new Error(fmt(key) + ' must be a list/array.'); return value; } @@ -438,7 +439,7 @@ Validator.prototype.array = function array(key, fallback) { }; /** - * Get a config option (as an object). + * Get a value (as an object). * @param {String} key * @param {Object?} fallback * @returns {Object|null} @@ -453,45 +454,14 @@ Validator.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'string') { - if (!value || typeof value !== 'object') - throw new Error(key + ' must be an object.'); - return value; - } - - try { - value = JSON.parse(value); - } catch (e) { - ; - } - if (!value || typeof value !== 'object') - throw new Error(key + ' must be an object.'); + throw new Error(fmt(key) + ' must be an object.'); return value; }; /** - * Get a config option (as an object). - * @param {String} key - * @param {Object?} fallback - * @returns {Object|null} - */ - -Validator.prototype.next = function next(key, fallback) { - var value = this.obj(key, fallback); - - if (fallback === undefined) - fallback = null; - - if (value === null) - return fallback; - - return new Validator(value); -}; - -/** - * Get a config option (as a function). + * Get a value (as a function). * @param {String} key * @param {Object?} fallback * @returns {Function|null} @@ -506,15 +476,22 @@ Validator.prototype.func = function func(key, fallback) { if (value === null) return fallback; - if (typeof value !== 'string') { - if (typeof value !== 'function') - throw new Error(key + ' must be a function.'); - return value; - } + if (typeof value !== 'function') + throw new Error(fmt(key) + ' must be a function.'); - throw new Error(key + ' must be a function.'); + return value; }; +/* + * Helpers + */ + +function fmt(key) { + if (typeof key === 'number') + return 'Param #' + key; + return key; +} + /* * Expose */ diff --git a/lib/wallet/common.js b/lib/wallet/common.js index 183ddda6..6445b56c 100644 --- a/lib/wallet/common.js +++ b/lib/wallet/common.js @@ -23,15 +23,28 @@ common.isName = function isName(key) { if (typeof key !== 'string') return false; - if (key === '_admin') - return false; - - if (key === '__proto__') + if (key.length === 0) return false; if (!/^[\-\._0-9A-Za-z]+$/.test(key)) return false; + // Prevents __proto__ + // from being used. + switch (key[0]) { + case '_': + case '-': + case '.': + return false; + } + + switch (key[key.length - 1]) { + case '_': + case '-': + case '.': + return false; + } + return key.length >= 1 && key.length <= 40; }; diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index 2f3cea76..5a26a788 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -44,14 +44,12 @@ function RPC(wdb) { assert(wdb, 'RPC requires a WalletDB.'); + this.wdb = wdb; this.network = wdb.network; this.logger = wdb.logger; this.client = wdb.client; - this.wdb = wdb; + this.wallet = null; - - this.locker = new Lock(); - this.feeRate = null; this.init(); @@ -60,55 +58,83 @@ function RPC(wdb) { util.inherits(RPC, RPCBase); RPC.prototype.init = function init() { - this.add('fundrawtransaction', this.fundrawtransaction); - this.add('resendwallettransactions', this.resendwallettransactions); - this.add('abandontransaction', this.abandontransaction); - this.add('addmultisigaddress', this.addmultisigaddress); - this.add('addwitnessaddress', this.addwitnessaddress); - this.add('backupwallet', this.backupwallet); - this.add('dumpprivkey', this.dumpprivkey); - this.add('dumpwallet', this.dumpwallet); - this.add('encryptwallet', this.encryptwallet); - this.add('getaccountaddress', this.getaccountaddress); - this.add('getaccount', this.getaccount); - this.add('getaddressesbyaccount', this.getaddressesbyaccount); - this.add('getbalance', this.getbalance); - this.add('getnewaddress', this.getnewaddress); - this.add('getrawchangeaddress', this.getrawchangeaddress); - this.add('getreceivedbyaccount', this.getreceivedbyaccount); - this.add('getreceivedbyaddress', this.getreceivedbyaddress); - this.add('gettransaction', this.gettransaction); - this.add('getunconfirmedbalance', this.getunconfirmedbalance); - this.add('getwalletinfo', this.getwalletinfo); - this.add('importprivkey', this.importprivkey); - this.add('importwallet', this.importwallet); - this.add('importaddress', this.importaddress); - this.add('importprunedfunds', this.importprunedfunds); - this.add('importpubkey', this.importpubkey); - this.add('keypoolrefill', this.keypoolrefill); - this.add('listaccounts', this.listaccounts); - this.add('listaddressgroupings', this.listaddressgroupings); - this.add('listlockunspent', this.listlockunspent); - this.add('listreceivedbyaccount', this.listreceivedbyaccount); - this.add('listreceivedbyaddress', this.listreceivedbyaddress); - this.add('listsinceblock', this.listsinceblock); - this.add('listtransactions', this.listtransactions); - this.add('listunspent', this.listunspent); - this.add('lockunspent', this.lockunspent); + this.add('help', this.help); + this.add('stop', this.stop); + this.add('fundrawtransaction', this.fundRawTransaction); + this.add('resendwallettransactions', this.resendWalletTransactions); + this.add('abandontransaction', this.abandonTransaction); + this.add('addmultisigaddress', this.addMultisigAddress); + this.add('addwitnessaddress', this.addWitnessAddress); + this.add('backupwallet', this.backupWallet); + this.add('dumpprivkey', this.dumpPrivKey); + this.add('dumpwallet', this.dumpWallet); + this.add('encryptwallet', this.encryptWallet); + this.add('getaccountaddress', this.getAccountAddress); + this.add('getaccount', this.getAccount); + this.add('getaddressesbyaccount', this.getAddressesByAccount); + this.add('getbalance', this.getBalance); + this.add('getnewaddress', this.getNewAddress); + this.add('getrawchangeaddress', this.getRawChangeAddress); + this.add('getreceivedbyaccount', this.getReceivedByAccount); + this.add('getreceivedbyaddress', this.getReceivedByAddress); + this.add('gettransaction', this.getTransaction); + this.add('getunconfirmedbalance', this.getUnconfirmedBalance); + this.add('getwalletinfo', this.getWalletInfo); + this.add('importprivkey', this.importPrivKey); + this.add('importwallet', this.importWallet); + this.add('importaddress', this.importAddress); + this.add('importprunedfunds', this.importPrunedFunds); + this.add('importpubkey', this.importPubkey); + this.add('keypoolrefill', this.keyPoolRefill); + this.add('listaccounts', this.listAccounts); + this.add('listaddressgroupings', this.listAddressGroupings); + this.add('listlockunspent', this.listLockUnspent); + this.add('listreceivedbyaccount', this.listReceivedByAccount); + this.add('listreceivedbyaddress', this.listReceivedByAddress); + this.add('listsinceblock', this.listSinceBlock); + this.add('listtransactions', this.listTransactions); + this.add('listunspent', this.listUnspent); + this.add('lockunspent', this.lockUnspent); this.add('move', this.move); - this.add('sendfrom', this.sendfrom); - this.add('sendmany', this.sendmany); - this.add('sendtoaddress', this.sendtoaddress); - this.add('setaccount', this.setaccount); - this.add('settxfee', this.settxfee); - this.add('signmessage', this.signmessage); - this.add('walletlock', this.walletlock); - this.add('walletpassphrasechange', this.walletpassphrasechange); - this.add('walletpassphrase', this.walletpassphrase); - this.add('removeprunedfunds', this.removeprunedfunds); + this.add('sendfrom', this.sendFrom); + this.add('sendmany', this.sendMany); + this.add('sendtoaddress', this.sendToAddress); + this.add('setaccount', this.setAccount); + this.add('settxfee', this.setTXFee); + this.add('signmessage', this.signMessage); + this.add('walletlock', this.walletLock); + this.add('walletpassphrasechange', this.walletPassphraseChange); + this.add('walletpassphrase', this.walletPassphrase); + this.add('removeprunedfunds', this.removePrunedFunds); + this.add('selectwallet', this.selectWallet); + this.add('getmemory', this.getMemory); + this.add('setloglevel', this.setLogLevel); }; -RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args, help) { +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) { + if (help || args.length !== 0) + throw new RPCError('stop'); + + this.wdb.close(); + + return 'Stopping.'; +}); + +RPC.prototype.fundRawTransaction = co(function* fundRawTransaction(args, help) { var valid = new Validator([args]); var data = valid.buf(0); var options = valid.obj(1); @@ -154,7 +180,7 @@ RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args, help) { * Wallet */ -RPC.prototype.resendwallettransactions = co(function* resendwallettransactions(args, help) { +RPC.prototype.resendWalletTransactions = co(function* resendWalletTransactions(args, help) { var wallet = this.wallet; var hashes = []; var i, tx, txs; @@ -172,7 +198,7 @@ RPC.prototype.resendwallettransactions = co(function* resendwallettransactions(a return hashes; }); -RPC.prototype.addmultisigaddress = co(function* addmultisigaddress(args, help) { +RPC.prototype.addMultisigAddress = co(function* addMultisigAddress(args, help) { if (help || args.length < 2 || args.length > 3) { throw new RPCError('addmultisigaddress' + ' nrequired ["key",...] ( "account" )'); @@ -182,7 +208,7 @@ RPC.prototype.addmultisigaddress = co(function* addmultisigaddress(args, help) { throw new Error('Not implemented.'); }); -RPC.prototype.addwitnessaddress = co(function* addwitnessaddress(args, help) { +RPC.prototype.addWitnessAddress = co(function* addWitnessAddress(args, help) { if (help || args.length < 1 || args.length > 1) throw new RPCError('addwitnessaddress "address"'); @@ -190,7 +216,7 @@ RPC.prototype.addwitnessaddress = co(function* addwitnessaddress(args, help) { throw new Error('Not implemented.'); }); -RPC.prototype.backupwallet = co(function* backupwallet(args, help) { +RPC.prototype.backupWallet = co(function* backupWallet(args, help) { var valid = new Validator([args]); var dest = valid.str(0); @@ -202,7 +228,7 @@ RPC.prototype.backupwallet = co(function* backupwallet(args, help) { return null; }); -RPC.prototype.dumpprivkey = co(function* dumpprivkey(args, help) { +RPC.prototype.dumpPrivKey = co(function* dumpPrivKey(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0, ''); @@ -223,7 +249,7 @@ RPC.prototype.dumpprivkey = co(function* dumpprivkey(args, help) { return ring.toSecret(); }); -RPC.prototype.dumpwallet = co(function* dumpwallet(args, help) { +RPC.prototype.dumpWallet = co(function* dumpWallet(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var file = valid.str(0); @@ -281,7 +307,7 @@ RPC.prototype.dumpwallet = co(function* dumpwallet(args, help) { return null; }); -RPC.prototype.encryptwallet = co(function* encryptwallet(args, help) { +RPC.prototype.encryptWallet = co(function* encryptWallet(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var passphrase = valid.str(0, ''); @@ -300,7 +326,7 @@ RPC.prototype.encryptwallet = co(function* encryptwallet(args, help) { return 'wallet encrypted; we do not need to stop!'; }); -RPC.prototype.getaccountaddress = co(function* getaccountaddress(args, help) { +RPC.prototype.getAccountAddress = co(function* getAccountAddress(args, help) { var valid = new Validator([args]); var wallet = this.wallet; var name = valid.str(0, ''); @@ -320,7 +346,7 @@ RPC.prototype.getaccountaddress = co(function* getaccountaddress(args, help) { return account.receive.getAddress('base58'); }); -RPC.prototype.getaccount = co(function* getaccount(args, help) { +RPC.prototype.getAccount = co(function* getAccount(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0, ''); @@ -341,7 +367,7 @@ RPC.prototype.getaccount = co(function* getaccount(args, help) { return path.name; }); -RPC.prototype.getaddressesbyaccount = co(function* getaddressesbyaccount(args, help) { +RPC.prototype.getAddressesByAccount = co(function* getAddressesByAccount(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0, ''); @@ -366,7 +392,7 @@ RPC.prototype.getaddressesbyaccount = co(function* getaddressesbyaccount(args, h return addrs; }); -RPC.prototype.getbalance = co(function* getbalance(args, help) { +RPC.prototype.getBalance = co(function* getBalance(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -396,7 +422,7 @@ RPC.prototype.getbalance = co(function* getbalance(args, help) { return Amount.btc(value, true); }); -RPC.prototype.getnewaddress = co(function* getnewaddress(args, help) { +RPC.prototype.getNewAddress = co(function* getNewAddress(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -413,7 +439,7 @@ RPC.prototype.getnewaddress = co(function* getnewaddress(args, help) { return address.getAddress('base58'); }); -RPC.prototype.getrawchangeaddress = co(function* getrawchangeaddress(args, help) { +RPC.prototype.getRawChangeAddress = co(function* getRawChangeAddress(args, help) { var wallet = this.wallet; var address; @@ -425,7 +451,7 @@ RPC.prototype.getrawchangeaddress = co(function* getrawchangeaddress(args, help) return address.getAddress('base58'); }); -RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args, help) { +RPC.prototype.getReceivedByAccount = co(function* getReceivedByAccount(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -473,7 +499,7 @@ RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args, hel return Amount.btc(total, true); }); -RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args, help) { +RPC.prototype.getReceivedByAddress = co(function* getReceivedByAddress(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0, ''); @@ -581,7 +607,7 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(wtx) { }; }); -RPC.prototype.gettransaction = co(function* gettransaction(args, help) { +RPC.prototype.getTransaction = co(function* getTransaction(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var hash = valid.hash(0); @@ -602,7 +628,7 @@ RPC.prototype.gettransaction = co(function* gettransaction(args, help) { return yield this._toWalletTX(wtx, watchOnly); }); -RPC.prototype.abandontransaction = co(function* abandontransaction(args, help) { +RPC.prototype.abandonTransaction = co(function* abandonTransaction(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var hash = valid.hash(0); @@ -622,7 +648,7 @@ RPC.prototype.abandontransaction = co(function* abandontransaction(args, help) { return null; }); -RPC.prototype.getunconfirmedbalance = co(function* getunconfirmedbalance(args, help) { +RPC.prototype.getUnconfirmedBalance = co(function* getUnconfirmedBalance(args, help) { var wallet = this.wallet; var balance; @@ -634,7 +660,7 @@ RPC.prototype.getunconfirmedbalance = co(function* getunconfirmedbalance(args, h return Amount.btc(balance.unconfirmed, true); }); -RPC.prototype.getwalletinfo = co(function* getwalletinfo(args, help) { +RPC.prototype.getWalletInfo = co(function* getWalletInfo(args, help) { var wallet = this.wallet; var balance; @@ -658,7 +684,7 @@ RPC.prototype.getwalletinfo = co(function* getwalletinfo(args, help) { }; }); -RPC.prototype.importprivkey = co(function* importprivkey(args, help) { +RPC.prototype.importPrivKey = co(function* importPrivKey(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var secret = valid.str(0); @@ -678,7 +704,7 @@ RPC.prototype.importprivkey = co(function* importprivkey(args, help) { return null; }); -RPC.prototype.importwallet = co(function* importwallet(args, help) { +RPC.prototype.importWallet = co(function* importWallet(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var file = valid.str(0); @@ -732,7 +758,7 @@ RPC.prototype.importwallet = co(function* importwallet(args, help) { return null; }); -RPC.prototype.importaddress = co(function* importaddress(args, help) { +RPC.prototype.importAddress = co(function* importAddress(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0, ''); @@ -765,7 +791,7 @@ RPC.prototype.importaddress = co(function* importaddress(args, help) { return null; }); -RPC.prototype.importpubkey = co(function* importpubkey(args, help) { +RPC.prototype.importPubkey = co(function* importPubkey(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var data = valid.buf(0); @@ -788,13 +814,13 @@ RPC.prototype.importpubkey = co(function* importpubkey(args, help) { return null; }); -RPC.prototype.keypoolrefill = co(function* keypoolrefill(args, help) { +RPC.prototype.keyPoolRefill = co(function* keyPoolRefill(args, help) { if (help || args.length > 1) throw new RPCError('keypoolrefill ( newsize )'); return null; }); -RPC.prototype.listaccounts = co(function* listaccounts(args, help) { +RPC.prototype.listAccounts = co(function* listAccounts(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var minconf = valid.u32(0, 0); @@ -825,13 +851,13 @@ RPC.prototype.listaccounts = co(function* listaccounts(args, help) { return map; }); -RPC.prototype.listaddressgroupings = co(function* listaddressgroupings(args, help) { +RPC.prototype.listAddressGroupings = co(function* listAddressGroupings(args, help) { if (help) throw new RPCError('listaddressgroupings'); throw new Error('Not implemented.'); }); -RPC.prototype.listlockunspent = co(function* listlockunspent(args, help) { +RPC.prototype.listLockUnspent = co(function* listLockUnspent(args, help) { var wallet = this.wallet; var i, outpoints, outpoint, out; @@ -852,7 +878,7 @@ RPC.prototype.listlockunspent = co(function* listlockunspent(args, help) { return out; }); -RPC.prototype.listreceivedbyaccount = co(function* listreceivedbyaccount(args, help) { +RPC.prototype.listReceivedByAccount = co(function* listReceivedByAccount(args, help) { var valid = new Validator([args]); var minconf = valid.u32(0, 0); var includeEmpty = valid.bool(1, false); @@ -866,7 +892,7 @@ RPC.prototype.listreceivedbyaccount = co(function* listreceivedbyaccount(args, h return yield this._listReceived(minconf, includeEmpty, watchOnly, true); }); -RPC.prototype.listreceivedbyaddress = co(function* listreceivedbyaddress(args, help) { +RPC.prototype.listReceivedByAddress = co(function* listReceivedByAddress(args, help) { var valid = new Validator([args]); var minconf = valid.u32(0, 0); var includeEmpty = valid.bool(1, false); @@ -980,7 +1006,7 @@ RPC.prototype._listReceived = co(function* _listReceived(minconf, empty, watchOn return result; }); -RPC.prototype.listsinceblock = co(function* listsinceblock(args, help) { +RPC.prototype.listSinceBlock = co(function* listSinceBlock(args, help) { var wallet = this.wallet; var chainHeight = this.wdb.state.height; var valid = new Validator([args]); @@ -1108,7 +1134,7 @@ RPC.prototype._toListTX = co(function* _toListTX(wtx) { }; }); -RPC.prototype.listtransactions = co(function* listtransactions(args, help) { +RPC.prototype.listTransactions = co(function* listTransactions(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -1145,7 +1171,7 @@ RPC.prototype.listtransactions = co(function* listtransactions(args, help) { return out; }); -RPC.prototype.listunspent = co(function* listunspent(args, help) { +RPC.prototype.listUnspent = co(function* listUnspent(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var minDepth = valid.u32(0, 1); @@ -1221,7 +1247,7 @@ RPC.prototype.listunspent = co(function* listunspent(args, help) { return out; }); -RPC.prototype.lockunspent = co(function* lockunspent(args, help) { +RPC.prototype.lockUnspent = co(function* lockUnspent(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var unlock = valid.bool(0, false); @@ -1271,7 +1297,7 @@ RPC.prototype.move = co(function* move(args, help) { throw new Error('Not implemented.'); }); -RPC.prototype.sendfrom = co(function* sendfrom(args, help) { +RPC.prototype.sendFrom = co(function* sendFrom(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -1310,7 +1336,7 @@ RPC.prototype.sendfrom = co(function* sendfrom(args, help) { return tx.txid(); }); -RPC.prototype.sendmany = co(function* sendmany(args, help) { +RPC.prototype.sendMany = co(function* sendMany(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var name = valid.str(0); @@ -1369,7 +1395,7 @@ RPC.prototype.sendmany = co(function* sendmany(args, help) { return tx.txid(); }); -RPC.prototype.sendtoaddress = co(function* sendtoaddress(args, help) { +RPC.prototype.sendToAddress = co(function* sendToAddress(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0); @@ -1403,7 +1429,7 @@ RPC.prototype.sendtoaddress = co(function* sendtoaddress(args, help) { return tx.txid(); }); -RPC.prototype.setaccount = co(function* setaccount(args, help) { +RPC.prototype.setAccount = co(function* setAccount(args, help) { if (help || args.length < 1 || args.length > 2) throw new RPCError('setaccount "bitcoinaddress" "account"'); @@ -1411,7 +1437,7 @@ RPC.prototype.setaccount = co(function* setaccount(args, help) { throw new Error('Not implemented.'); }); -RPC.prototype.settxfee = co(function* settxfee(args, help) { +RPC.prototype.setTXFee = co(function* setTXFee(args, help) { var valid = new Validator([args]); var rate = valid.btc(0); @@ -1426,7 +1452,7 @@ RPC.prototype.settxfee = co(function* settxfee(args, help) { return true; }); -RPC.prototype.signmessage = co(function* signmessage(args, help) { +RPC.prototype.signMessage = co(function* signMessage(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var addr = valid.str(0, ''); @@ -1457,7 +1483,7 @@ RPC.prototype.signmessage = co(function* signmessage(args, help) { return sig.toString('base64'); }); -RPC.prototype.walletlock = co(function* walletlock(args, help) { +RPC.prototype.walletLock = co(function* walletLock(args, help) { var wallet = this.wallet; if (help || (wallet.master.encrypted && args.length !== 0)) @@ -1471,7 +1497,7 @@ RPC.prototype.walletlock = co(function* walletlock(args, help) { return null; }); -RPC.prototype.walletpassphrasechange = co(function* walletpassphrasechange(args, help) { +RPC.prototype.walletPassphraseChange = co(function* walletPassphraseChange(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var old = valid.str(0, ''); @@ -1493,7 +1519,7 @@ RPC.prototype.walletpassphrasechange = co(function* walletpassphrasechange(args, return null; }); -RPC.prototype.walletpassphrase = co(function* walletpassphrase(args, help) { +RPC.prototype.walletPassphrase = co(function* walletPassphrase(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var passphrase = valid.str(0, ''); @@ -1516,7 +1542,7 @@ RPC.prototype.walletpassphrase = co(function* walletpassphrase(args, help) { return null; }); -RPC.prototype.importprunedfunds = co(function* importprunedfunds(args, help) { +RPC.prototype.importPrunedFunds = co(function* importPrunedFunds(args, help) { var valid = new Validator([args]); var tx = valid.buf(0); var block = valid.buf(1); @@ -1557,7 +1583,7 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args, help) { return null; }); -RPC.prototype.removeprunedfunds = co(function* removeprunedfunds(args, help) { +RPC.prototype.removePrunedFunds = co(function* removePrunedFunds(args, help) { var wallet = this.wallet; var valid = new Validator([args]); var hash = valid.hash(0); @@ -1574,7 +1600,7 @@ RPC.prototype.removeprunedfunds = co(function* removeprunedfunds(args, help) { return null; }); -RPC.prototype.selectwallet = co(function* selectwallet(args, help) { +RPC.prototype.selectWallet = co(function* selectWallet(args, help) { var valid = new Validator([args]); var id = valid.str(0); var wallet; @@ -1592,6 +1618,25 @@ RPC.prototype.selectwallet = co(function* selectwallet(args, help) { return null; }); +RPC.prototype.getMemory = co(function* getMemory(args, help) { + if (help || args.length !== 0) + throw new RPCError('getmemory'); + + 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('setloglevel "level"'); + + this.logger.setLevel(level); + + return null; +}); + /* * Expose */