diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index 2ca497e1..5cbf0689 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -22,7 +22,10 @@ function RPC(node) { this.pool = node.pool; this.fees = node.fees; this.miner = node.miner; + this.wallet = node.wallet; + this.walletdb = node.walletdb; + this.feeRate = null; this.mining = false; this.proclimit = 0; } @@ -159,52 +162,97 @@ RPC.prototype.execute = function execute(json, callback) { return this.verifytxoutproof(json.params, callback); case 'fundrawtransaction': + return this.fundrawtransaction(json.params, callback); case 'resendwallettransactions': + return this.resendwallettransactions(json.params, callback); case 'abandontransaction': + return this.abandontransaction(json.params, callback); case 'addmultisigaddress': + return this.addmultisigaddress(json.params, callback); case 'addwitnessaddress': + return this.addwitnessaddress(json.params, callback); case 'backupwallet': + return this.backupwallet(json.params, callback); case 'dumpprivkey': + return this.dumpprivkey(json.params, callback); case 'dumpwallet': + return this.dumpwallet(json.params, callback); case 'encryptwallet': + return this.encryptwallet(json.params, callback); case 'getaccountaddress': + return this.getaccountaddress(json.params, callback); case 'getaccount': + return this.getaccount(json.params, callback); case 'getaddressesbyaccount': + return this.getaddressesbyaccount(json.params, callback); case 'getbalance': + return this.getbalance(json.params, callback); case 'getnewaddress': + return this.getnewaddress(json.params, callback); case 'getrawchangeaddress': + return this.getrawchangeaddress(json.params, callback); case 'getreceivedbyaccount': + return this.getreceivedbyaccount(json.params, callback); case 'getreceivedbyaddress': + return this.getreceivedbyaddress(json.params, callback); case 'gettransaction': + return this.gettransaction(json.params, callback); case 'getunconfirmedbalance': + return this.getunconfirmedbalance(json.params, callback); case 'getwalletinfo': + return this.getwalletinfo(json.params, callback); case 'importprivkey': + return this.importprivkey(json.params, callback); case 'importwallet': + return this.importwallet(json.params, callback); case 'importaddress': + return this.importaddress(json.params, callback); case 'importprunedfunds': + return this.importprunedfunds(json.params, callback); case 'importpubkey': + return this.importpubkey(json.params, callback); case 'keypoolrefill': + return this.keypoolrefill(json.params, callback); case 'listaccounts': + return this.listaccounts(json.params, callback); case 'listaddressgroupings': + return this.listaddressgroupings(json.params, callback); case 'listlockunspent': + return this.listlockunspent(json.params, callback); case 'listreceivedbyaccount': + return this.listreceivedbyaccount(json.params, callback); case 'listreceivedbyaddress': + return this.listreceivedbyaddress(json.params, callback); case 'listsinceblock': + return this.listsinceblock(json.params, callback); case 'listtransactions': + return this.listtransactions(json.params, callback); case 'listunspent': + return this.listunspent(json.params, callback); case 'lockunspent': + return this.lockunspent(json.params, callback); case 'move': + return this.move(json.params, callback); case 'sendfrom': + return this.sendfrom(json.params, callback); case 'sendmany': + return this.sendmany(json.params, callback); case 'sendtoaddress': + return this.sendtoaddress(json.params, callback); case 'setaccount': + return this.setaccount(json.params, callback); case 'settxfee': + return this.settxfee(json.params, callback); case 'signmessage': + return this.signmessage(json.params, callback); case 'walletlock': + return this.walletlock(json.params, callback); case 'walletpassphrasechange': + return this.walletpassphrasechange(json.params, callback); case 'walletpassphrase': + return this.walletpassphrase(json.params, callback); case 'removeprunedfunds': - return callback(new Error('Method not found.')); + return this.removeprunedfunds(json.params, callback); default: return callback(new Error('Method not found.')); @@ -216,36 +264,52 @@ RPC.prototype.execute = function execute(json, callback) { */ RPC.prototype.getinfo = function getinfo(args, callback) { - return callback(null, { - version: constants.USER_VERSION, - protocolversion: constants.VERSION, - walletversion: 0, - balance: 0, - blocks: this.chain.height, - timeoffset: bcoin.time.offset, - connections: this.pool.peers.all.length, - proxy: '', - difficulty: this._getDifficulty(), - testnet: this.network.type !== 'main', - keypoololdest: 0, - keypoolsize: 0, - unlocked_until: 0, - paytxfee: this.network.getRate() / constants.COIN, - relayfee: this.network.getMinRelay() / constants.COIN, - errors: '' + var self = this; + + if (args.help || args.length !== 0) + return callback(new Error('getinfo')); + + this.wallet.getBalance(function(err, balance) { + if (err) + return callback(err); + + callback(null, { + version: constants.USER_VERSION, + protocolversion: constants.VERSION, + walletversion: 0, + balance: +utils.btc(balance.total), + blocks: self.chain.height, + timeoffset: bcoin.time.offset, + connections: self.pool.peers.all.length, + proxy: '', + difficulty: self._getDifficulty(), + testnet: self.network.type !== 'main', + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: self.wallet.master.until, + paytxfee: +utils.btc(self.network.getRate()), + relayfee: +utils.btc(self.network.getMinRelay()), + errors: '' + }); }); }; RPC.prototype.help = function help(args, callback) { - callback(new Error('Not implemented.')); + if (args.length === 0) + return callback(null, 'Select a command.'); + + args = args.slice(1); + args.help = true; + + this.execute(args, callback); }; RPC.prototype.stop = function stop(args, callback) { - callback(null, 'Stopping.'); + if (args.help || args.length !== 0) + return callback(new Error('stop')); - utils.nextTick(function() { - process.exit(0); - }); + callback(null, 'Stopping.'); + this.node.close(); }; /* @@ -306,14 +370,11 @@ RPC.prototype.addnode = function addnode(args, callback) { case 'remove': for (i = 0; i < this.pool.seeds.length; i++) { seed = this.pool.seeds[i]; - if (seed.host === host.host) + if (seed.host === host.host) { + this.pool.seeds.splice(i, 1); break; + } } - - if (i === this.pool.seeds.length) - break; - - this.pool.seeds.splice(i, 1); break; case 'onetry': break; @@ -573,8 +634,7 @@ RPC.prototype.getblockchaininfo = function getblockchaininfo(args, callback) { difficulty: self._getDifficulty(), mediantime: medianTime, verificationprogress: self.chain.getProgress(), - chainwork: self.chain.tip.chainwork - .toArrayLike(Buffer, 'be', 32).toString('hex'), + chainwork: self.chain.tip.chainwork.toString('hex', 64), pruned: self.chain.db.options.prune, softforks: self._getSoftforks(), bip9_softforks: forks, @@ -622,7 +682,7 @@ RPC.prototype.getblockcount = function getblockcount(args, callback) { if (args.help || args.length !== 0) return callback(new Error('getblockcount')); - callback(null, this.chain.tip.height + 1); + callback(null, this.chain.tip.height); }; RPC.prototype.getblock = function getblock(args, callback) { @@ -653,11 +713,11 @@ RPC.prototype.getblock = function getblock(args, callback) { if (!block) { if (self.chain.db.prune) return callback(new Error('Block not available (pruned data)')); - return callback(new Error('Can"t read block from disk')); + return callback(new Error('Can\'t read block from disk')); } if (!verbose) - return callback(null, block.toRaw('hex')); + return callback(null, block.toRaw().toString('hex')); return self.blockToJSON(entry, block, false, callback); }); @@ -675,13 +735,13 @@ RPC.prototype.txToJSON = function txToJSON(tx) { vin: tx.inputs.map(function(input) { var out = {}; if (tx.isCoinbase()) { - out.coinbase = input.script.toRaw().toString('hex'); + out.coinbase = input.script.toJSON(); } else { out.txid = utils.revHex(input.prevout.hash); out.vout = input.prevout.index; out.scriptSig = { asm: input.script.toASM(), - hex: input.script.toRaw().toString('hex') + hex: input.script.toJSON() }; } if (input.witness.items.length > 0) { @@ -694,7 +754,7 @@ RPC.prototype.txToJSON = function txToJSON(tx) { }), vout: tx.outputs.map(function(output, i) { return { - value: utils.btc(output.value), + value: +utils.btc(output.value), n: i, scriptPubKey: scriptToJSON(output.script, true) }; @@ -713,12 +773,12 @@ function scriptToJSON(script, hex) { out.asm = script.toASM(); if (hex) - out.hex = script.toRaw().toString('hex'); + out.hex = script.toJSON(); type = script.getType(); out.type = bcoin.script.typesByVal[type]; - out.reqSigs = script.isMultisig() ? bcoin.script.getSmall(script.code[0]) : 1; + out.reqSigs = script.isMultisig() ? script.getSmall(0) : 1; address = script.getAddress(); @@ -789,7 +849,7 @@ RPC.prototype.blockheaderToJSON = function blockheaderToJSON(entry, callback) { return callback(null, { hash: utils.revHex(entry.hash), - confirmations: this.chain.height - entry.height + 1, + confirmations: self.chain.height - entry.height + 1, height: entry.height, version: entry.version, merkleroot: utils.revHex(entry.merkleRoot), @@ -797,7 +857,7 @@ RPC.prototype.blockheaderToJSON = function blockheaderToJSON(entry, callback) { mediantime: medianTime, bits: entry.bits, difficulty: self._getDifficulty(entry), - chainwork: entry.chainwork.toString('hex'), + chainwork: entry.chainwork.toString('hex', 64), previousblockhash: entry.prevBlock !== constants.NULL_HASH ? utils.revHex(entry.prevBlock) : null, @@ -813,13 +873,13 @@ RPC.prototype.blockToJSON = function blockToJSON(entry, block, txDetails, callba if (err) return callback(err); - self.chain.db.getNextHash(entry, function(err, nextHash) { + self.chain.db.getNextHash(entry.hash, function(err, nextHash) { if (err) return callback(err); return callback(null, { hash: utils.revHex(entry.hash), - confirmations: this.chain.height - entry.height + 1, + confirmations: self.chain.height - entry.height + 1, strippedsize: block.getBaseSize(), size: block.getSize(), cost: block.getCost(), @@ -835,7 +895,7 @@ RPC.prototype.blockToJSON = function blockToJSON(entry, block, txDetails, callba mediantime: medianTime, bits: entry.bits, difficulty: self._getDifficulty(entry), - chainwork: entry.chainwork.toString('hex'), + chainwork: entry.chainwork.toString('hex', 64), previousblockhash: entry.prevBlock !== constants.NULL_HASH ? utils.revHex(entry.prevBlock) : null, @@ -846,7 +906,15 @@ RPC.prototype.blockToJSON = function blockToJSON(entry, block, txDetails, callba }; RPC.prototype.getchaintips = function getchaintips(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length !== 0) + return callback(new Error('getchaintips')); + + callback(null, [{ + height: this.chain.height, + hash: this.chain.tip.rhash, + branchlen: 0, + status: 'active' + }]); }; RPC.prototype.getdifficulty = function getdifficulty(args, callback) { @@ -875,7 +943,7 @@ RPC.prototype.getrawmempool = function getrawmempool(args, callback) { verbose = false; if (args.length > 0) - verbose = !!args[0]; + verbose = Boolean(args[0]); return this.mempoolToJSON(verbose, callback); }; @@ -959,7 +1027,7 @@ RPC.prototype.gettxout = function gettxout(args, callback) { callback(null, { bestblock: utils.revHex(self.chain.tip.hash), confirmations: coin.getConfirmations(), - value: utils.btc(coin.value), + value: +utils.btc(coin.value), scriptPubKey: scriptToJSON(coin.script, true), version: coin.version, coinbase: coin.coinbase @@ -981,7 +1049,7 @@ RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args, callback) { callback(null, { height: this.chain.height, - bestblock: utils.revHex(this.chain.tip.hash), + bestblock: this.chain.tip.rhash, transactions: 0, txouts: 0, bytes_serialized: 0, @@ -1057,7 +1125,7 @@ RPC.prototype.getblocktemplate = function getblocktemplate(args, callback) { bits: block.bits, height: attempt.height, default_witness_commitment: attempt.witness - ? attempt.coinbase.outputs[1].script.toRaw().toString('hex') + ? attempt.coinbase.outputs[1].script.toJSON() : undefined }); }); @@ -1225,78 +1293,702 @@ RPC.prototype.generatetoaddress = function generatetoaddress(args, callback) { */ RPC.prototype.createrawtransaction = function createrawtransaction(args, callback) { - callback(new Error('Not implemented.')); + var inputs, sendTo, tx, locktime; + var i, input, output, hash, index, sequence; + var keys, addrs, key, value, address, b58; + + if (args.help || args.length < 2 || args.length > 3) { + return callback(new Error('createrawtransaction' + + ' [{"txid":"id","vout":n},...]' + + ' {"address":amount,"data":"hex",...}' + + ' ( locktime )')); + } + + if (!args[0] || !args[1]) + return callback(new Error('Invalid parameter')); + + inputs = args[0]; + sendTo = args[1]; + + if (!Array.isArray(inputs) || typeof sendTo !== 'object') + return callback(new Error('Invalid parameter')); + + tx = bcoin.tx(); + + if (args.length > 2 && args[2] != null) { + locktime = Number(args[2]); + if (locktime < 0 || locktime > 0xffffffff) + return callback(new Error('Invalid parameter, locktime out of range')); + tx.locktime = locktime; + } + + for (i = 0; i < inputs.length; i++) { + input = inputs[i]; + + if (!input) + return callback(new Error('Invalid parameter')); + + hash = input.txid; + index = input.vout; + sequence = 0xffffffff; + + if (tx.locktime) + sequence--; + + if (!utils.isHex(hash) + || hash.length !== 64 + || !utils.isNumber(index) + || index < 0) { + return callback(new Error('Invalid parameter')); + } + + if (utils.isNumber(input.sequence)) { + if (input.sequence < 0 || input.sequence > 0xffffffff) + return callback(new Error('Invalid parameter')); + sequence = input.sequence; + } + + input = new bcoin.input({ + prevout: { + hash: utils.revHex(hash), + index: index + }, + sequence: sequence + }); + + tx.inputs.push(input); + } + + keys = Object.keys(sendTo); + addrs = {}; + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = sendTo[key]; + + if (key === 'data') { + value = new Buffer(value, 'hex'); + output = new bcoin.output({ + value: 0, + script: bcoin.script.fromNulldata(value) + }); + tx.outputs.push(output); + continue; + } + + address = bcoin.address.fromBase58(key); + b58 = address.toBase58(); + + if (addrs[b58]) + return callback(new Error('Duplicate address')); + + addrs[b58] = true; + + output = new bcoin.output({ + value: utils.satoshi(value), + address: address + }); + + tx.outputs.push(output); + } + + callback(null, tx.toRaw().toString('hex')); }; RPC.prototype.decoderawtransaction = function decoderawtransaction(args, callback) { - callback(new Error('Not implemented.')); + var tx; + + if (args.help || args.length !== 1) + return callback(new Error('decoderawtransaction "hexstring"')); + + if (!utils.isHex(args[0])) + return callback(new Error('Invalid parameter')); + + tx = bcoin.tx.fromRaw(args[0], 'hex'); + + callback(null, this.txToJSON(tx)); }; RPC.prototype.decodescript = function decodescript(args, callback) { - callback(new Error('Not implemented.')); + var data, script, hash, address; + + if (args.help || args.length !== 1) + return callback(new Error('decodescript \"hex\"')); + + data = String(args[0]); + script = new bcoin.script(); + + if (data.length > 0) + script.fromRaw(new Buffer(data, 'hex')); + + hash = utils.hash160(script.toRaw()); + address = bcoin.address.fromHash(hash, bcoin.script.types.SCRIPTHASH); + + script = scriptToJSON(script); + script.p2sh = address.toBase58(); + + callback(null, script); }; RPC.prototype.getrawtransaction = function getrawtransaction(args, callback) { + var self = this; + var hash, verbose; + if (args.help || args.length < 1 || args.length > 2) return callback(new Error('getrawtransaction "txid" ( verbose )')); - callback(new Error('Not implemented.')); + + hash = args[0]; + + if (!utils.isHex(hash) || hash.length !== 64) + return callback(new Error('Invalid parameter')); + + verbose = false; + + if (args.length > 1) + verbose = Boolean(args[1]); + + this.node.getTX(utils.revHex(hash), function(err, tx) { + if (err) + return callback(err); + + if (!tx) + return callback(new Error('No information available about transaction')); + + if (!verbose) + return callback(null, tx.toRaw().toString('hex')); + + tx = self.txToJSON(tx); + tx.hex = tx.toRaw().toString('hex'); + + callback(null, tx); + }); }; RPC.prototype.sendrawtransaction = function sendrawtransaction(args, callback) { - callback(new Error('Not implemented.')); + var tx; + + if (args.help || args.length < 1 || args.length > 2) { + return callback(new Error('sendrawtransaction' + + ' "hexstring" ( allowhighfees )')); + } + + if (!utils.isHex(args[0])) + return callback(new Error('Invalid parameter')); + + tx = bcoin.tx.fromRaw(args[0], 'hex'); + + this.node.sendTX(tx); + + callback(null, tx.rhash); }; RPC.prototype.signrawtransaction = function signrawtransaction(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var raw, p, txs, merged; + + if (args.help || args.length < 1 || args.length > 4) { + return callback(new Error('signrawtransaction' + + ' "hexstring" (' + + ' [{"txid":"id","vout":n,"scriptPubKey":"hex",' + + 'redeemScript":"hex"},...] ["privatekey1",...]' + + ' sighashtype )')); + } + + raw = new Buffer(args[0], 'hex'); + p = new bcoin.reader(raw); + txs = []; + + while (p.left()) + txs.push(bcoin.mtx.fromRaw(p)); + + merged = txs[0]; + + this.node.fillCoins(merged, function(err) { + if (err) + return callback(err); + self.wallet.fillCoins(merged, function(err) { + if (err) + return callback(err); + self._signrawtransaction(merged, txs, args, callback); + }); + }); }; +RPC.prototype._signrawtransaction = function signrawtransaction(merged, txs, args, callback) { + var keys = []; + var keyMap = {}; + var k, i, secret, key, addr; + var coins, prevout, prev; + var hash, index, script, value; + var redeem, op, j; + var type, parts, tx; + + if (args.length > 2 && Array.isArray(args[2])) { + k = args[2]; + for (i = 0; i < k.length; i++) { + secret = k[i]; + if (!utils.isBase58(secret)) + return callback(new Error('Invalid parameter')); + key = bcoin.keypair.fromSecret(secret); + addr = new bcoin.keyring({ publicKey: key.getPublicKey() }); + key = { addr: addr, key: key.getPrivateKey() }; + keyMap[addr.getPublicKey('hex')] = key; + keys.push(key); + } + } + + coins = []; + if (args.length > 1 && Array.isArray(args[1])) { + prevout = args[1]; + for (i = 0; i < prevout.length; i++) { + prev = prevout[i]; + if (!prev) + return callback(new Error('Invalid parameter')); + hash = prev.txid; + index = prev.vout; + script = prev.scriptPubKey; + value = utils.satoshi(prev.amount || 0); + if (!utils.isHex(hash) + || hash.length !== 64 + || !utils.isNumber(index) + || index < 0 + || !utils.isHex(script)) { + return callback(new Error('Invalid parameter')); + } + + script = bcoin.script.fromRaw(script, 'hex'); + coins.push(new bcoin.coin({ + hash: utils.revHex(hash), + index: index, + script: script, + value: value, + coinbase: false, + height: -1 + })); + + if (keys.length === 0 || !utils.isHex(prev.redeemScript)) + continue; + + if (script.isScripthash() || script.isWitnessScripthash()) { + redeem = bcoin.script.fromRaw(prev.redeemScript, 'hex'); + if (!redeem.isMultisig()) + continue; + for (j = 1; j < redeem.length - 2; j++) { + op = redeem.get(j); + key = keyMap[op.toString('hex')]; + if (key) { + key.addr.type = 'multisig'; + key.addr.m = redeem.getSmall(0); + key.addr.n = redeem.getSmall(redeem.length - 1); + key.addr.keys = redeem.slice(1, -2); + key.addr.witness = script.isWitnessScripthash(); + break; + } + } + } + } + tx.fillCoins(coins); + } + + type = constants.hashType.ALL; + if (args.length > 3 && typeof args[3] === 'string') { + parts = args[3].split('|'); + type = constants.hashType[parts[0]]; + if (type == null) + return callback(new Error('Invalid parameter')); + if (parts.length > 2) + return callback(new Error('Invalid parameter')); + if (parts.length === 2) { + if (parts[1] !== 'ANYONECANPAY') + return callback(new Error('Invalid parameter')); + type |= constants.hashType.ANYONECANPAY; + } + } + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + key.addr.sign(merged, key.key, null, type); + } + + this.node.wallet.sign(merged, { type: type }, function(err) { + if (err) + return callback(err); + + for (i = 1; i < txs.length; i++) { + tx = txs[i]; + mergeSigs(merged, tx); + } + + callback(null, { + hex: merged.toRaw().toString('hex'), + complete: merged.isSigned() + }); + }); +}; + +function mergeSigs(a, b) { + var map = {}; + var i, input, prev, key, ia, ib; + + for (i = 0; i < b.inputs.length; i++) { + input = b.inputs[i]; + prev = input.prevout; + key = prev.hash + '/' + prev.index; + map[key] = input; + } + + for (i = 0; i < b.inputs.length; i++) { + ia = a.inputs[i]; + if (!ia || ia.length !== 0) + break; + key = prev.hash + '/' + prev.index; + ib = map[key]; + if (ib) + ia.script = ib.script; + } +} + RPC.prototype.fundrawtransaction = function fundrawtransaction(args, callback) { callback(new Error('Not implemented.')); }; +RPC.prototype._createRedeem = function _createRedeem(args, callback) { + var self = this; + var m, n, keys, hash, script; + + if (!utils.isNumber(args[0]) + || !Array.isArray(args[1]) + || args[0] < 1 + || args[1].length < args[0] + || args[1].length > 16) { + return callback(new Error('Invalid parameter.')); + } + + m = args[0]; + n = args[1].length; + keys = args[1]; + + utils.forEachSerial(keys, function(key, next, i) { + if (!utils.isBase58(key)) { + if (!utils.isHex(key)) + return next(new Error('Invalid key.')); + keys[i] = new Buffer(key, 'hex'); + return next(); + } + + hash = bcoin.address.getHash(key, 'hex'); + if (!hash) + return next(new Error('Invalid key.')); + + self.node.wallet.getKeyring(hash, function(err, address) { + if (err) + return next(err); + + if (!address) + return next(new Error('Invalid key.')); + + keys[i] = address.getPublicKey(); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + script = bcoin.script.fromMultisig(m, n, keys); + + if (script.toRaw().length > constants.script.MAX_PUSH) + return callback(new Error('Redeem script exceeds size limit.')); + + callback(null, script); + }); +}; + /* Utility functions */ RPC.prototype.createmultisig = function createmultisig(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length < 2 || args.length > 2) + return callback(new Error('createmultisig nrequired ["key",...]')); + + this._createRedeem(args, function(err, script) { + if (err) + return callback(err); + + callback(null, { + address: script.getAddress().toBase58(), + redeemScript: script.toJSON() + }); + }); +}; + +RPC.prototype._scriptForWitness = function scriptForWitness(script) { + var hash; + + if (script.isPubkey()) { + hash = utils.hash160(script.get(0)); + return bcoin.script.fromProgram(0, hash); + } + + if (script.isPubkeyhash()) { + hash = script.get(2); + return bcoin.script.fromProgram(0, hash); + } + + hash = utils.sha256(script.toRaw()); + return bcoin.script.fromProgram(0, hash); }; RPC.prototype.createwitnessaddress = function createwitnessaddress(args, callback) { - callback(new Error('Not implemented.')); + var raw, script, program; + + if (args.help || args.length !== 1) + return callback(new Error('createwitnessaddress "script"')); + + raw = args[1]; + + if (!utils.isHex(raw)) + return callback(new Error('Invalid parameter')); + + script = bcoin.script.fromRaw(raw, 'hex'); + program = this._scriptForWitness(script); + + callback(null, { + address: program.getAddress().toBase58(), + witnessScript: program.toJSON() + }); }; RPC.prototype.validateaddress = function validateaddress(args, callback) { - callback(new Error('Not implemented.')); + var b58, address, json; + + if (args.help || args.length !== 1) + return callback(new Error('validateaddress "bitcoinaddress"')); + + b58 = String(args[0]); + + try { + address = bcoin.address.fromBase58(b58); + } catch (e) { + return callback(null, { + isvalid: false + }); + } + + this.wallet.getKeyring(address.getHash('hex'), function(err, ring) { + if (err) + return callback(err); + + json = { + isvalid: true, + address: address.toBase58(), + scriptPubKey: address.toScript().toJSON(), + ismine: ring ? true : false, + iswatchonly: false + }; + + if (!ring) + return callback(null, json); + + json.account = ring.name; + json.hdkeypath = ring.account + '/' + ring.change + '/' + ring.index; + + callback(null, json); + }); }; +RPC.magic = 'Bitcoin Signed Message:\n'; + RPC.prototype.verifymessage = function verifymessage(args, callback) { - callback(new Error('Not implemented.')); + var address, sig, msg, key; + + if (args.help || args.length !== 3) { + return callback(new Error('verifymessage' + + ' "bitcoinaddress" "signature" "message"')); + } + + address = String(args[0]); + sig = String(args[1]); + msg = String(args[2]); + + address = bcoin.address.getHash(address); + + if (!address) + return callback(new Error('Invalid address.')); + + sig = new Buffer(sig, 'base64'); + msg = new Buffer(RPC.magic + msg, 'utf8'); + msg = utils.hash256(msg); + + key = bcoin.ec.recover(msg, sig, 0, true); + + if (!key) + return callback(null, false); + + key = utils.hash160(key); + + callback(null, utils.ccmp(key, address)); +}; + +RPC.prototype.signmessagewithprivkey = function signmessagewithprivkey(args, callback) { + var key, msg, sig; + + if (args.help || args.length !== 2) + return callback(new Error('signmessagewithprivkey "privkey" "message"')); + + key = String(args[0]); + msg = String(args[1]); + + key = bcoin.keypair.fromSecret(key).getPrivateKey(); + msg = new Buffer(RPC.magic + msg, 'utf8'); + msg = utils.hash256(msg); + + sig = bcoin.ec.sign(msg, key); + + callback(null, sig.toString('base64')); }; RPC.prototype.estimatefee = function estimatefee(args, callback) { - callback(new Error('Not implemented.')); + var blocks, fee; + + if (args.help || args.length !== 1) + return callback(new Error('estimatefee nblocks')); + + blocks = Number(args[0]); + + if (!utils.isNumber(blocks)) + blocks = -1; + + if (blocks < 1) + blocks = 1; + + fee = this.fees.estimateFee(blocks, false); + + if (fee === 0) + fee = -1; + else + fee = +utils.btc(fee); + + callback(null, fee); }; RPC.prototype.estimatepriority = function estimatepriority(args, callback) { - callback(new Error('Not implemented.')); + var blocks, pri; + + if (args.help || args.length !== 1) + return callback(new Error('estimatepriority nblocks')); + + blocks = Number(args[0]); + + if (!utils.isNumber(blocks)) + blocks = -1; + + if (blocks < 1) + blocks = 1; + + pri = this.fees.estimatePriority(blocks, false); + + callback(null, pri); }; RPC.prototype.estimatesmartfee = function estimatesmartfee(args, callback) { - callback(new Error('Not implemented.')); + var blocks, fee; + + if (args.help || args.length !== 1) + return callback(new Error('estimatesmartfee nblocks')); + + blocks = Number(args[0]); + + if (!utils.isNumber(blocks)) + blocks = -1; + + if (blocks < 1) + blocks = 1; + + fee = this.fees.estimateFee(blocks, true); + + if (fee === 0) + fee = -1; + else + fee = +utils.btc(fee); + + callback(null, { + fee: fee, + blocks: blocks + }); }; RPC.prototype.estimatesmartpriority = function estimatesmartpriority(args, callback) { - callback(new Error('Not implemented.')); + var blocks, pri; + + if (args.help || args.length !== 1) + return callback(new Error('estimatesmartpriority nblocks')); + + blocks = Number(args[0]); + + if (!utils.isNumber(blocks)) + blocks = -1; + + if (blocks < 1) + blocks = 1; + + pri = this.fees.estimatePriority(blocks, true); + + callback(null, { + priority: pri, + blocks: blocks + }); }; RPC.prototype.invalidateblock = function invalidateblock(args, callback) { - callback(new Error('Not implemented.')); + var hash; + + if (args.help || args.length !== 1) + return callback(new Error('invalidateblock "hash"')); + + hash = args[0]; + + if (!utils.isHex(hash) || hash.length !== 64) + return callback(new Error('Block not found.')); + + hash = utils.revHex(hash); + + this.chain.invalid[hash] = true; + + callback(null, null); }; RPC.prototype.reconsiderblock = function reconsiderblock(args, callback) { - callback(new Error('Not implemented.')); + var hash; + + if (args.help || args.length !== 1) + return callback(new Error('reconsiderblock "hash"')); + + hash = args[0]; + + if (!utils.isHex(hash) || hash.length !== 64) + return callback(new Error('Block not found.')); + + hash = utils.revHex(hash); + + delete this.chain.invalid[hash]; + + callback(null, null); }; RPC.prototype.setmocktime = function setmocktime(args, callback) { - callback(new Error('Not implemented.')); + var time, delta; + + if (args.help || args.length !== 1) + return callback(new Error('setmocktime timestamp')); + + time = args[0]; + + if (!utils.isNumber(time)) + return callback(new Error('Invalid parameter.')); + + delta = bcoin.now() - time; + bcoin.time.offset = -delta; + + callback(null, null); }; /* @@ -1304,66 +1996,402 @@ RPC.prototype.setmocktime = function setmocktime(args, callback) { */ RPC.prototype.resendwallettransactions = function resendwallettransactions(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var hashes = []; + var i, tx; + + if (args.help || args.length !== 0) + return callback(new Error('resendwallettransactions')); + + this.walletdb.getUnconfirmed(function(err, txs) { + if (err) + return callback(err); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + hashes.push(tx.rhash); + self.pool.broadcast(tx); + } + + callback(null, hashes); + }); }; RPC.prototype.addmultisigaddress = function addmultisigaddress(args, callback) { + if (args.help || args.length < 2 || args.length > 3) { + return callback(new Error('addmultisigaddress' + + ' nrequired ["key",...] ( "account" )')); + } + // Impossible to implement in bcoin (no address book). callback(new Error('Not implemented.')); }; RPC.prototype.addwitnessaddress = function addwitnessaddress(args, callback) { + if (args.help || args.length < 1 || args.length > 1) + return callback(new Error('addwitnessaddress \"address\"')); + // Unlikely to be implemented. callback(new Error('Not implemented.')); }; RPC.prototype.backupwallet = function backupwallet(args, callback) { + if (args.help || args.length !== 1) + return callback(new Error('backupwallet "destination"')); + // Unlikely to be implemented. callback(new Error('Not implemented.')); }; RPC.prototype.dumpprivkey = function dumpprivkey(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var hash, key; + + if (args.help || args.length !== 1) + return callback(new Error('dumpprivkey "bitcoinaddress"')); + + hash = bcoin.address.getHash(String(args[0]), 'hex'); + + if (!hash) + return callback(new Error('Invalid address.')); + + this.wallet.getKeyring(hash, function(err, ring) { + if (err) + return callback(err); + + if (!ring) + return callback(new Error('Key not found.')); + + key = self.wallet.master.key; + + if (!key) + return callback(new Error('Wallet is locked.')); + + key = key.deriveAccount44(ring.account); + key = key.derive(ring.change).derive(ring.index); + + callback(null, key.toSecret()); + }); }; RPC.prototype.dumpwallet = function dumpwallet(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var file, time, key, address, fmt, str, out; + + if (args.help || args.length !== 1) + return callback(new Error('dumpwallet "filename"')); + + file = utils.normalize(String(args[0])); + time = utils.date(); + out = [ + utils.fmt('# Wallet Dump created by BCoin %s', constants.USER_VERSION), + utils.fmt('# * Created on %s', time), + utils.fmt('# * Best block at time of backup was %d (%s),', + this.chain.height, this.chain.tip.rhash), + utils.fmt('# mined on %s', utils.date(this.chain.tip.ts)), + utils.fmt('# * File: %s', file), + '' + ]; + + this.walletdb.getAddresses(this.wallet.id, function(err, hashes) { + if (err) + return callback(err); + + utils.forEachSerial(hashes, function(hash, next) { + self.wallet.getKeyring(hash, function(err, ring) { + if (err) + return callback(err); + + if (!ring) + return next(); + + key = self.wallet.master.key; + + if (!key) + return callback(new Error('Wallet is locked.')); + + key = key.deriveAccount44(ring.account); + key = key.derive(ring.change).derive(ring.index); + address = ring.getAddress('base58'); + fmt = '%s %s label= addr=%s'; + + if (address.change) + fmt = '%s %s change=1 addr=%s'; + + str = utils.fmt(fmt, key.toSecret(), time, address); + + out.push(str); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + out.push(''); + out.push('# End of dump'); + out.push(''); + + out = out.join('\n'); + + callback(null, out); + }); + }); }; RPC.prototype.encryptwallet = function encryptwallet(args, callback) { - callback(new Error('Not implemented.')); + var passphrase; + + if (!this.wallet.master.encrypted && (args.help || args.help !== 1)) + return callback(new Error('encryptwallet "passphrase"')); + + if (this.wallet.master.encrypted) + return callback(new Error('Already running with an encrypted wallet')); + + passphrase = args[0]; + + if (typeof passphrase !== 'string' || passphrase.length < 1) + return callback(new Error('encryptwallet "passphrase"')); + + this.wallet.setPassphrase(passphrase, function(err) { + if (err) + return callback(err); + callback(null, 'wallet encrypted; we do not need to stop!'); + }); }; RPC.prototype.getaccountaddress = function getaccountaddress(args, callback) { - callback(new Error('Not implemented.')); + var account; + + if (args.help || args.length !== 1) + return callback(new Error('getaccountaddress "account"')); + + account = String(args[0]); + + if (!account) + account = 'default'; + + this.wallet.getAccount(account, function(err, account) { + if (err) + return callback(err); + + if (!account) + return callback(null, ''); + + callback(null, account.receiveAddress.getAddress('base58')); + }); }; RPC.prototype.getaccount = function getaccount(args, callback) { - callback(new Error('Not implemented.')); + var hash; + + if (args.help || args.length !== 1) + return callback(new Error('getaccount "bitcoinaddress"')); + + hash = bcoin.address.getHash(args[0], 'hex'); + + if (!hash) + return callback(new Error('Invalid address.')); + + this.wallet.getKeyring(hash, function(err, address) { + if (err) + return callback(err); + + if (!address) + return callback(null, ''); + + return callback(null, address.name); + }); }; RPC.prototype.getaddressesbyaccount = function getaddressesbyaccount(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var account, addrs; + + if (args.help || args.length !== 1) + return callback(new Error('getaddressesbyaccount "account"')); + + account = String(args[0]); + + if (!account) + account = 'default'; + + addrs = []; + + this.walletdb.getAddresses(this.wallet.id, function(err, hashes) { + if (err) + return callback(err); + + utils.forEachSerial(hashes, function(hash, next) { + self.wallet.getKeyring(hash, function(err, address) { + if (err) + return callback(err); + + if (address && address.name === account) + addrs.push(address.getAddress('base58')); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + callback(null, addrs); + }); + }); }; RPC.prototype.getbalance = function getbalance(args, callback) { - callback(new Error('Not implemented.')); + var minconf = 0; + var account, value; + + if (args.help || args.length > 3) + return callback(new Error('getbalance ( "account" minconf includeWatchonly )')); + + if (args.length >= 1) { + account = String(args[0]); + if (!account) + account = 'default'; + if (account === '*') + account = null; + } + + if (args.length >= 2) + minconf = Number(args[1]); + + this.wallet.getBalance(account, function(err, balance) { + if (err) + return callback(err); + + if (minconf >= 1) + value = balance.confirmed; + else + value = balance.total; + + callback(null, +utils.btc(value)); + }); }; RPC.prototype.getnewaddress = function getnewaddress(args, callback) { - callback(new Error('Not implemented.')); + var account; + + if (args.help || args.length > 1) + return callback(new Error('getnewaddress ( "account" )')); + + if (args.length === 1) + account = String(args[0]); + + if (!account) + account = 'default'; + + this.wallet.createReceive(account, function(err, address) { + if (err) + return callback(err); + callback(null, address.getAddress('base58')); + }); }; RPC.prototype.getrawchangeaddress = function getrawchangeaddress(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length > 1) + return callback(new Error('getrawchangeaddress')); + + this.wallet.createChange(function(err, address) { + if (err) + return callback(err); + callback(null, address.getAddress('base58')); + }); }; RPC.prototype.getreceivedbyaccount = function getreceivedbyaccount(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var minconf = 0; + var total = 0; + var i, j, account, tx, output; + + if (args.help || args.length < 1 || args.length > 2) + return callback(new Error('getreceivedbyaccount "account" ( minconf )')); + + account = String(args[0]); + + if (!account) + account = 'default'; + + if (args.length === 2) + minconf = Number(args[1]); + + this.wallet.getHistory(account, function(err, txs) { + if (err) + return callback(err); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + if (minconf) { + if (tx.height === -1) + continue; + if (!(self.chain.height - tx.height + 1 >= minconf)) + continue; + } + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + total += output.value; + } + } + + callback(null, +utils.btc(total)); + }); }; RPC.prototype.getreceivedbyaddress = function getreceivedbyaddress(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var minconf = 0; + var total = 0; + var i, j, hash, tx, output; + + if (args.help || args.length < 1 || args.length > 2) + return callback(new Error('getreceivedbyaddress "bitcoinaddress" ( minconf )')); + + hash = bcoin.address.getHash(String(args[0]), 'hex'); + + if (!hash) + return callback(new Error('Invalid address')); + + if (args.length === 2) + minconf = Number(args[1]); + + this.wallet.getHistory(function(err, txs) { + if (err) + return callback(err); + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + if (minconf) { + if (tx.height === -1) + continue; + if (!(self.chain.height - tx.height + 1 >= minconf)) + continue; + } + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + if (output.getHash('hex') === hash) + total += output.value; + } + } + + callback(null, +utils.btc(total)); + }); }; RPC.prototype.gettransaction = function gettransaction(args, callback) { + var hash; + + if (args.help || args.length < 1 || args.length > 2) + return callback(new Error('gettransaction "txid" ( includeWatchonly )')); + + hash = String(args[0]); + + if (!utils.isHex(hash) || hash.length !== 64) + return callback(new Error('Invalid parameter')); + + hash = utils.revHex(hash); + + // XXX TODO callback(new Error('Not implemented.')); }; @@ -1372,38 +2400,118 @@ RPC.prototype.abandontransaction = function abandontransaction(args, callback) { }; RPC.prototype.getunconfirmedbalance = function getunconfirmedbalance(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length > 0) + return callback(new Error('getunconfirmedbalance')); + + this.wallet.getBalance(function(err, balance) { + if (err) + return callback(err); + + callback(null, +utils.btc(balance.unconfirmed)); + }); }; RPC.prototype.getwalletinfo = function getwalletinfo(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + + if (args.help || args.length !== 0) + return callback(new Error('getwalletinfo')); + + this.wallet.getBalance(function(err, balance) { + if (err) + return callback(err); + + self.walletdb.tx.getHistoryHashes(self.wallet.id, function(err, hashes) { + if (err) + return callback(err); + + callback(null, { + walletversion: 0, + balance: +utils.btc(balance.total), + unconfirmed_balance: +utils.btc(balance.unconfirmed), + txcount: hashes.length, + keypoololdest: 0, + keypoolsize: 0, + unlocked_until: self.wallet.master.until, + paytxfee: self.feeRate != null + ? +utils.btc(self.feeRate) + : +utils.btc(0) + }); + }); + }); }; RPC.prototype.importprivkey = function importprivkey(args, callback) { + if (args.help || args.length < 1 || args.length > 3) { + return callback(new Error('importprivkey' + + ' "bitcoinprivkey" ( "label" rescan )')); + } + // Impossible to implement in bcoin. callback(new Error('Not implemented.')); }; RPC.prototype.importwallet = function importwallet(args, callback) { + if (args.help || args.length !== 1) + return callback(new Error('importwallet "filename"')); + // Impossible to implement in bcoin. callback(new Error('Not implemented.')); }; RPC.prototype.importaddress = function importaddress(args, callback) { + if (args.help || args.length < 1 || args.length > 4) { + return callback(new Error('importaddress' + + ' "address" ( "label" rescan p2sh )')); + } + // Impossible to implement in bcoin. callback(new Error('Not implemented.')); }; RPC.prototype.importpubkey = function importpubkey(args, callback) { + if (args.help || args.length < 1 || args.length > 4) + return callback(new Error('importpubkey "pubkey" ( "label" rescan )')); + // Impossible to implement in bcoin. callback(new Error('Not implemented.')); }; RPC.prototype.keypoolrefill = function keypoolrefill(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length > 1) + return callback(new Error('keypoolrefill ( newsize )')); + callback(null, null); }; RPC.prototype.listaccounts = function listaccounts(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var map; + + if (args.help || args.length > 2) + return callback(new Error('listaccounts ( minconf includeWatchonly)')); + + map = {}; + + this.walletdb.getAccounts(this.wallet.id, function(err, accounts) { + if (err) + return callback(err); + + utils.forEachSerial(accounts, function(account, next) { + self.wallet.getBalance(account, function(err, balance) { + if (err) + return next(err); + + map[account] = +utils.btc(balance.total); + next(); + }); + }, function(err) { + if (err) + return callback(err); + + callback(null, map); + }); + }); }; RPC.prototype.listaddressgroupings = function listaddressgroupings(args, callback) { + if (args.help) + return callback(new Error('listaddressgroupings')); callback(new Error('Not implemented.')); }; @@ -1439,8 +2547,57 @@ RPC.prototype.move = function move(args, callback) { callback(new Error('Not implemented.')); }; +RPC.prototype._send = function _send(account, address, amount, subtractFee, callback) { + var self = this; + + var options = { + account: account, + subtractFee: subtractFee, + rate: this.feeRate, + outputs: [{ + address: address, + value: amount + }] + }; + + this.wallet.createTX(options, function(err, tx) { + if (err) + return callback(err); + + self.wallet.sign(tx, function(err) { + if (err) + return callback(err); + + self.node.sendTX(tx, function(err) { + if (err) + return callback(err); + callback(null, tx); + }); + }); + }); +}; + RPC.prototype.sendfrom = function sendfrom(args, callback) { - callback(new Error('Not implemented.')); + var account, address, amount; + + if (args.help || args.length < 3 || args.length > 6) { + return callback(new Error('sendfrom' + + ' "fromaccount" "tobitcoinaddress"' + + ' amount ( minconf "comment" "comment-to" )')); + } + + account = String(args[0]); + address = bcoin.address.fromBase58(String(args[1])); + amount = utils.satoshi(String(args[2])); + + if (!account) + account = 'default'; + + this._send(account, address, amount, false, function(err, tx) { + if (err) + return callback(err); + callback(null, tx.rhash); + }); }; RPC.prototype.sendmany = function sendmany(args, callback) { @@ -1448,31 +2605,138 @@ RPC.prototype.sendmany = function sendmany(args, callback) { }; RPC.prototype.sendtoaddress = function sendtoaddress(args, callback) { - callback(new Error('Not implemented.')); + var address, amount, subtractFee; + + if (args.help || args.length < 2 || args.length > 5) { + return callback(new Error('sendtoaddress' + + ' "bitcoinaddress" amount' + + ' ( "comment" "comment-to"' + + ' subtractfeefromamount )')); + } + + address = bcoin.address.fromBase58(String(args[0])); + amount = utils.satoshi(String(args[1])); + subtractFee = Boolean(args[4]); + + this._send(null, address, amount, subtractFee, function(err, tx) { + if (err) + return callback(err); + callback(null, tx.rhash); + }); }; RPC.prototype.setaccount = function setaccount(args, callback) { + if (args.help || args.length < 1 || args.length > 2) + return callback(new Error('setaccount "bitcoinaddress" "account"')); + // Impossible to implement in bcoin: callback(new Error('Not implemented.')); }; RPC.prototype.settxfee = function settxfee(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || args.length < 1 || args.length > 1) + return callback(new Error('settxfee amount')); + + this.feeRate = utils.satoshi(args[0]); + + callback(null, true); }; RPC.prototype.signmessage = function signmessage(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var address, msg, key, sig; + + if (args.help || args.length !== 2) + return callback(new Error('signmessage "bitcoinaddress" "message"')); + + address = String(args[0]); + msg = String(args[1]); + + address = bcoin.address.getHash(address, 'hex'); + + if (!address) + return callback(new Error('Invalid address.')); + + this.wallet.getKeyring(address, function(err, address) { + if (err) + return callback(err); + + if (!address) + return callback(new Error('Address not found.')); + + key = self.wallet.master.key; + + if (!key) + return callback(new Error('Wallet is locked.')); + + key = key.deriveAccount44(address.account); + key = key.derive(address.change).derive(address.index); + + msg = new Buffer(RPC.magic + msg, 'utf8'); + msg = utils.hash256(msg); + + sig = bcoin.ec.sign(msg, key); + + callback(null, sig.toString('base64')); + }); }; RPC.prototype.walletlock = function walletlock(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || (this.wallet.master.encrypted && args.length !== 0)) + return callback(new Error('walletlock')); + + if (!this.wallet.master.encrypted) + return callback(new Error('Wallet is not encrypted.')); + + this.wallet.lock(); + callback(null, null); }; RPC.prototype.walletpassphrasechange = function walletpassphrasechange(args, callback) { - callback(new Error('Not implemented.')); + var old, new_; + + if (args.help || (this.wallet.master.encrypted && args.length !== 2)) { + return callback(new Error('walletpassphrasechange' + + ' "oldpassphrase" "newpassphrase"')); + } + + if (!this.wallet.master.encrypted) + return callback(new Error('Wallet is not encrypted.')); + + if (typeof args[0] !== 'string' || typeof args[1] !== 'string') + return callback(new Error('Invalid parameter')); + + old = args[0]; + new_ = args[1]; + + if (old.length < 1 || new_.length < 1) + return callback(new Error('Invalid parameter')); + + this.wallet.setPassphrase(old, new_, function(err) { + if (err) + return callback(err); + + callback(null, null); + }); }; RPC.prototype.walletpassphrase = function walletpassphrase(args, callback) { - callback(new Error('Not implemented.')); + if (args.help || (this.wallet.master.encrypted && args.length !== 2)) + return callback(new Error('walletpassphrase "passphrase" timeout')); + + if (!this.wallet.master.encrypted) + return callback(new Error('Wallet is not encrypted.')); + + if (typeof args[0] !== 'string' || args[0].length < 1) + return callback(new Error('Invalid parameter')); + + if (!utils.isNumber(args[1]) || args[1] < 0) + return callback(new Error('Invalid parameter')); + + this.wallet.unlock(args[0], args[1], function(err) { + if (err) + return callback(err); + callback(null, null); + }); }; module.exports = RPC; diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index 9ca73318..8e11bd02 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -309,7 +309,10 @@ HTTPServer.prototype._init = function _init() { res.setHeader('WWW-Authenticate', 'Basic realm="rpc"'); send(401, { result: null, - error: 'Bad auth.', + error: { + message: 'Bad auth.', + code: 1 + }, id: req.body.id }); return; @@ -326,7 +329,10 @@ HTTPServer.prototype._init = function _init() { self.logger.error(err); return send(400, { result: null, - error: err.message, + error: { + message: err.message, + code: 1 + }, id: req.body.id }); } diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 072793cd..7e4b3ba8 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -797,6 +797,13 @@ utils.inspectify = function inspectify(obj, color) { : obj; }; +/** + * Format a string. + * @function + */ + +utils.fmt = util.format; + /** * Format a string. * @param {Array} args diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index ffbd810c..0c1bb6e3 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -2481,6 +2481,7 @@ function MasterKey(options) { this.key = null; this.timer = null; + this.until = 0; this._destroy = this.destroy.bind(this); this.locker = new bcoin.locker(this); } @@ -2585,6 +2586,7 @@ MasterKey.prototype.start = function start(timeout) { if (timeout === -1) return; + this.until = utils.now() + (timeout / 1000 | 0); this.timer = setTimeout(this._destroy, timeout); }; @@ -2597,6 +2599,7 @@ MasterKey.prototype.stop = function stop() { if (this.timer != null) { clearTimeout(this.timer); this.timer = null; + this.until = 0; } };