Merge pull request #550 from pinheadmz/rpcfixandtest
RPC: fix several wallet RPC calls and add RPC tests
This commit is contained in:
commit
2b2e53d83d
@ -257,19 +257,11 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async addMultisigAddress(args, help) {
|
||||
if (help || args.length < 2 || args.length > 3) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'addmultisigaddress nrequired ["key",...] ( "account" )');
|
||||
}
|
||||
|
||||
// Impossible to implement in bcoin (no address book).
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
async addWitnessAddress(args, help) {
|
||||
if (help || args.length < 1 || args.length > 1)
|
||||
throw new RPCError(errs.MISC_ERROR, 'addwitnessaddress "address"');
|
||||
|
||||
// Unlikely to be implemented.
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
@ -491,7 +483,7 @@ class RPC extends RPCBase {
|
||||
const valid = new Validator(args);
|
||||
let name = valid.str(0);
|
||||
|
||||
if (name === '')
|
||||
if (name === '' || args.length === 0)
|
||||
name = 'default';
|
||||
|
||||
const addr = await wallet.createReceive(name);
|
||||
@ -500,7 +492,7 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async getRawChangeAddress(args, help) {
|
||||
if (help || args.length > 1)
|
||||
if (help || args.length !== 0)
|
||||
throw new RPCError(errs.MISC_ERROR, 'getrawchangeaddress');
|
||||
|
||||
const wallet = this.wallet;
|
||||
@ -750,7 +742,7 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async importWallet(args, help) {
|
||||
if (help || args.length !== 1)
|
||||
if (help || args.length < 1 || args.length > 2)
|
||||
throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )');
|
||||
|
||||
const wallet = this.wallet;
|
||||
@ -839,7 +831,7 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async importPubkey(args, help) {
|
||||
if (help || args.length < 1 || args.length > 4) {
|
||||
if (help || args.length < 1 || args.length > 3) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'importpubkey "pubkey" ( "label" rescan )');
|
||||
}
|
||||
@ -899,8 +891,6 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async listAddressGroupings(args, help) {
|
||||
if (help)
|
||||
throw new RPCError(errs.MISC_ERROR, 'listaddressgroupings');
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
@ -1033,6 +1023,11 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async listSinceBlock(args, help) {
|
||||
if (help || args.length > 3) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'listsinceblock ( "blockhash" target-confirmations includeWatchonly)');
|
||||
}
|
||||
|
||||
const wallet = this.wallet;
|
||||
const chainHeight = this.wdb.state.height;
|
||||
const valid = new Validator(args);
|
||||
@ -1040,11 +1035,6 @@ class RPC extends RPCBase {
|
||||
const minconf = valid.u32(1, 0);
|
||||
const watchOnly = valid.bool(2, false);
|
||||
|
||||
if (help) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'listsinceblock ( "blockhash" target-confirmations includeWatchonly)');
|
||||
}
|
||||
|
||||
if (wallet.watchOnly !== watchOnly)
|
||||
return [];
|
||||
|
||||
@ -1054,10 +1044,12 @@ class RPC extends RPCBase {
|
||||
const entry = await this.client.getEntry(block);
|
||||
if (entry)
|
||||
height = entry.height;
|
||||
else
|
||||
throw new RPCError(errs.MISC_ERROR, 'Block not found');
|
||||
}
|
||||
|
||||
if (height === -1)
|
||||
height = this.chain.height;
|
||||
height = chainHeight;
|
||||
|
||||
const txs = await wallet.getHistory();
|
||||
const out = [];
|
||||
@ -1192,7 +1184,7 @@ class RPC extends RPCBase {
|
||||
if (name === '')
|
||||
name = 'default';
|
||||
|
||||
const txs = await wallet.getHistory();
|
||||
const txs = await wallet.getHistory(name);
|
||||
|
||||
common.sortTX(txs);
|
||||
|
||||
@ -1368,7 +1360,7 @@ class RPC extends RPCBase {
|
||||
if (help || args.length < 2 || args.length > 5) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'sendmany "fromaccount" {"address":amount,...}'
|
||||
+ ' ( minconf "comment" ["address",...] )');
|
||||
+ ' ( minconf "comment" subtractfee )');
|
||||
}
|
||||
|
||||
const wallet = this.wallet;
|
||||
@ -1382,7 +1374,7 @@ class RPC extends RPCBase {
|
||||
name = 'default';
|
||||
|
||||
if (!sendTo)
|
||||
throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
|
||||
throw new RPCError(errs.TYPE_ERROR, 'Invalid send-to address.');
|
||||
|
||||
const to = new Validator(sendTo);
|
||||
const uniq = new BufferSet();
|
||||
@ -1394,10 +1386,11 @@ class RPC extends RPCBase {
|
||||
const hash = addr.getHash();
|
||||
|
||||
if (value == null)
|
||||
throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.');
|
||||
throw new RPCError(errs.INVALID_PARAMETER, 'Invalid amount.');
|
||||
|
||||
if (uniq.has(hash))
|
||||
throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.');
|
||||
throw new RPCError(errs.INVALID_PARAMETER,
|
||||
'Each send-to address must be unique.');
|
||||
|
||||
uniq.add(hash);
|
||||
|
||||
@ -1451,11 +1444,6 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async setAccount(args, help) {
|
||||
if (help || args.length < 1 || args.length > 2) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'setaccount "bitcoinaddress" "account"');
|
||||
}
|
||||
|
||||
// Impossible to implement in bcoin:
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
@ -1576,9 +1564,9 @@ class RPC extends RPCBase {
|
||||
}
|
||||
|
||||
async importPrunedFunds(args, help) {
|
||||
if (help || args.length < 2 || args.length > 3) {
|
||||
if (help || args.length !== 2) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
'importprunedfunds "rawtransaction" "txoutproof" ( "label" )');
|
||||
'importprunedfunds "rawtransaction" "txoutproof"');
|
||||
}
|
||||
|
||||
const valid = new Validator(args);
|
||||
@ -1610,7 +1598,8 @@ class RPC extends RPCBase {
|
||||
};
|
||||
|
||||
if (!await this.wdb.addTX(tx, entry))
|
||||
throw new RPCError(errs.WALLET_ERROR, 'No tracked address for TX.');
|
||||
throw new RPCError(errs.WALLET_ERROR,
|
||||
'Address for TX not in wallet, or TX already in wallet');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1176,6 +1176,15 @@ class TXDB {
|
||||
return this.locked.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock all coins.
|
||||
*/
|
||||
|
||||
unlockCoins() {
|
||||
for (const coin of this.getLocked())
|
||||
this.unlockCoin(coin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked status of a single coin.
|
||||
* @param {Coin|Outpoint} coin
|
||||
|
||||
@ -1955,6 +1955,14 @@ class Wallet extends EventEmitter {
|
||||
return this.txdb.unlockCoin(coin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock all locked coins.
|
||||
*/
|
||||
|
||||
unlockCoins() {
|
||||
return this.txdb.unlockCoins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked status of a single coin.
|
||||
* @param {Coin|Outpoint} coin
|
||||
|
||||
@ -1354,7 +1354,7 @@ class WalletDB extends EventEmitter {
|
||||
async getWalletPaths(wid) {
|
||||
const items = await this.db.range({
|
||||
gte: layout.P.min(wid),
|
||||
lte: layout.P.min(wid)
|
||||
lte: layout.P.max(wid)
|
||||
});
|
||||
|
||||
const paths = [];
|
||||
|
||||
257
test/rpc-test.js
Normal file
257
test/rpc-test.js
Normal file
@ -0,0 +1,257 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint prefer-arrow-callback: "off" */
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('./util/assert');
|
||||
const consensus = require('../lib/protocol/consensus');
|
||||
const Address = require('../lib/primitives/address');
|
||||
const FullNode = require('../lib/node/fullnode');
|
||||
|
||||
const ports = {
|
||||
p2p: 49331,
|
||||
node: 49332,
|
||||
wallet: 49333
|
||||
};
|
||||
|
||||
const node = new FullNode({
|
||||
network: 'regtest',
|
||||
apiKey: 'foo',
|
||||
walletAuth: true,
|
||||
memory: true,
|
||||
workers: true,
|
||||
plugins: [require('../lib/wallet/plugin')],
|
||||
port: ports.p2p,
|
||||
httpPort: ports.node,
|
||||
env: {
|
||||
'BCOIN_WALLET_HTTP_PORT': ports.wallet.toString()
|
||||
}});
|
||||
|
||||
const {NodeClient, WalletClient} = require('bclient');
|
||||
|
||||
const nclient = new NodeClient({
|
||||
port: ports.node,
|
||||
apiKey: 'foo'
|
||||
});
|
||||
|
||||
const wclient = new WalletClient({
|
||||
port: ports.wallet,
|
||||
apiKey: 'foo'
|
||||
});
|
||||
|
||||
const {wdb} = node.require('walletdb');
|
||||
const defaultCoinbaseMaturity = consensus.COINBASE_MATURITY;
|
||||
|
||||
let addressHot = null;
|
||||
let addressMiner = null;
|
||||
let walletHot = null;
|
||||
let walletMiner = null;
|
||||
let blocks = null;
|
||||
let txid = null;
|
||||
let utxo = null;
|
||||
|
||||
describe('RPC', function() {
|
||||
this.timeout(15000);
|
||||
|
||||
before(() => {
|
||||
consensus.COINBASE_MATURITY = 0;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
consensus.COINBASE_MATURITY = defaultCoinbaseMaturity;
|
||||
});
|
||||
|
||||
it('should open node and create wallets', async () => {
|
||||
await node.open();
|
||||
await nclient.open();
|
||||
await wclient.open();
|
||||
|
||||
const walletHotInfo = await wclient.createWallet('hot');
|
||||
walletHot = wclient.wallet('hot', walletHotInfo.token);
|
||||
const walletMinerInfo = await wclient.createWallet('miner');
|
||||
walletMiner = wclient.wallet('miner', walletMinerInfo.token);
|
||||
await walletHot.open();
|
||||
await walletMiner.open();
|
||||
});
|
||||
|
||||
it('should rpc help', async () => {
|
||||
assert(await nclient.execute('help', []));
|
||||
assert(await wclient.execute('help', []));
|
||||
|
||||
await assert.asyncThrows(async () => {
|
||||
await nclient.execute('help', ['getinfo']);
|
||||
}, 'getinfo');
|
||||
|
||||
await assert.asyncThrows(async () => {
|
||||
await wclient.execute('help', ['getbalance']);
|
||||
}, 'getbalance');
|
||||
});
|
||||
|
||||
it('should rpc getinfo', async () => {
|
||||
const info = await nclient.execute('getinfo', []);
|
||||
assert.strictEqual(info.blocks, 0);
|
||||
});
|
||||
|
||||
it('should rpc selectwallet', async () => {
|
||||
const response = await wclient.execute('selectwallet', ['miner']);
|
||||
assert.strictEqual(response, null);
|
||||
});
|
||||
|
||||
it('should rpc getnewaddress from default account', async () => {
|
||||
const acctAddr = await wclient.execute('getnewaddress', []);
|
||||
assert(Address.fromString(acctAddr.toString()));
|
||||
});
|
||||
|
||||
it('should fail rpc getnewaddress from nonexistent account', async () => {
|
||||
await assert.asyncThrows(async () => {
|
||||
await wclient.execute('getnewaddress', ['bad-account-name']);
|
||||
}, 'Account not found.');
|
||||
});
|
||||
|
||||
it('should rpc getaccountaddress', async () => {
|
||||
addressMiner = await wclient.execute('getaccountaddress', ['default']);
|
||||
assert(Address.fromString(addressMiner.toString()));
|
||||
});
|
||||
|
||||
it('should rpc generatetoaddress', async () => {
|
||||
blocks = await nclient.execute('generatetoaddress',
|
||||
[10, addressMiner]);
|
||||
assert.strictEqual(blocks.length, 10);
|
||||
});
|
||||
|
||||
it('should rpc sendtoaddress', async () => {
|
||||
const acctHotDefault = await walletHot.getAccount('default');
|
||||
addressHot = acctHotDefault.receiveAddress;
|
||||
|
||||
txid = await wclient.execute('sendtoaddress', [addressHot, 0.1234]);
|
||||
assert.strictEqual(txid.length, 64);
|
||||
});
|
||||
|
||||
it('should rpc sendmany', async () => {
|
||||
const sendTo = {};
|
||||
sendTo[addressHot] = 1.0;
|
||||
sendTo[addressMiner] = 0.1111;
|
||||
txid = await wclient.execute('sendmany', ['default', sendTo]);
|
||||
assert.strictEqual(txid.length, 64);
|
||||
});
|
||||
|
||||
it('should fail malformed rpc sendmany', async () => {
|
||||
await assert.asyncThrows(async () => {
|
||||
await wclient.execute('sendmany', ['default', null]);
|
||||
}, 'Invalid send-to address');
|
||||
|
||||
const sendTo = {};
|
||||
sendTo[addressHot] = null;
|
||||
await assert.asyncThrows(async () => {
|
||||
await wclient.execute('sendmany', ['default', sendTo]);
|
||||
}, 'Invalid amount.');
|
||||
});
|
||||
|
||||
it('should rpc listreceivedbyaddress', async () => {
|
||||
await wclient.execute('selectwallet', ['hot']);
|
||||
|
||||
const listZeroConf = await wclient.execute('listreceivedbyaddress',
|
||||
[0, false, false]);
|
||||
assert.deepStrictEqual(listZeroConf, [{
|
||||
'involvesWatchonly': false,
|
||||
'address': addressHot,
|
||||
'account': 'default',
|
||||
'amount': 1.1234,
|
||||
'confirmations': 0,
|
||||
'label': ''
|
||||
}]);
|
||||
|
||||
blocks.push(await nclient.execute('generatetoaddress', [1, addressMiner]));
|
||||
await wdb.syncChain();
|
||||
|
||||
const listSomeConf = await wclient.execute('listreceivedbyaddress',
|
||||
[1, false, false]);
|
||||
assert.deepStrictEqual(listSomeConf, [{
|
||||
'involvesWatchonly': false,
|
||||
'address': addressHot,
|
||||
'account': 'default',
|
||||
'amount': 1.1234,
|
||||
'confirmations': 1,
|
||||
'label': ''
|
||||
}]);
|
||||
|
||||
const listTooManyConf = await wclient.execute('listreceivedbyaddress',
|
||||
[100, false, false]);
|
||||
assert.deepStrictEqual(listTooManyConf, []);
|
||||
});
|
||||
|
||||
it('should rpc listtransactions with no args', async () => {
|
||||
const txs = await wclient.execute('listtransactions', []);
|
||||
assert.strictEqual(txs.length, 2);
|
||||
assert.strictEqual(txs[0].amount + txs[1].amount, 1.1234);
|
||||
assert.strictEqual(txs[0].account, 'default');
|
||||
});
|
||||
|
||||
it('should rpc listtransactions from specified account', async () => {
|
||||
const wallet = await wclient.wallet('hot');
|
||||
await wallet.createAccount('foo');
|
||||
|
||||
const txs = await wclient.execute('listtransactions', ['foo']);
|
||||
assert.strictEqual(txs.length, 0);
|
||||
});
|
||||
|
||||
it('should fail rpc listtransactions from nonexistent account', async () => {
|
||||
assert.asyncThrows(async () => {
|
||||
await wclient.execute('listtransactions', ['nonexistent']);
|
||||
}, 'Account not found.');
|
||||
});
|
||||
|
||||
it('should rpc listunspent', async () => {
|
||||
utxo = await wclient.execute('listunspent', []);
|
||||
assert.strictEqual(utxo.length, 2);
|
||||
});
|
||||
|
||||
it('should rpc lockunspent and listlockunspent', async () => {
|
||||
let result = await wclient.execute('listlockunspent', []);
|
||||
assert.deepStrictEqual(result, []);
|
||||
|
||||
// lock one utxo
|
||||
const output = utxo[0];
|
||||
const outputsToLock = [{'txid': output.txid, 'vout': output.vout}];
|
||||
result = await wclient.execute('lockunspent', [false, outputsToLock]);
|
||||
assert(result);
|
||||
|
||||
result = await wclient.execute('listlockunspent', []);
|
||||
assert.deepStrictEqual(result, outputsToLock);
|
||||
|
||||
// unlock all
|
||||
result = await wclient.execute('lockunspent', [true]);
|
||||
assert(result);
|
||||
|
||||
result = await wclient.execute('listlockunspent', []);
|
||||
assert.deepStrictEqual(result, []);
|
||||
});
|
||||
|
||||
it('should rpc listsinceblock', async () => {
|
||||
const listNoBlock = await wclient.execute('listsinceblock', []);
|
||||
assert.strictEqual(listNoBlock.transactions.length, 2);
|
||||
// txs returned in unpredictable order
|
||||
const txids = [
|
||||
listNoBlock.transactions[0].txid,
|
||||
listNoBlock.transactions[1].txid
|
||||
];
|
||||
assert(txids.includes(txid));
|
||||
|
||||
const block5 = blocks[5];
|
||||
const listOldBlock = await wclient.execute('listsinceblock', [block5]);
|
||||
assert.strictEqual(listOldBlock.transactions.length, 2);
|
||||
|
||||
const nonexistentBlock = consensus.ZERO_HASH.toString('hex');
|
||||
await assert.asyncThrows(async () => {
|
||||
await wclient.execute('listsinceblock', [nonexistentBlock]);
|
||||
}, 'Block not found');
|
||||
});
|
||||
|
||||
it('should cleanup', async () => {
|
||||
await walletHot.close();
|
||||
await walletMiner.close();
|
||||
await wclient.close();
|
||||
await nclient.close();
|
||||
await node.close();
|
||||
});
|
||||
});
|
||||
@ -107,6 +107,18 @@ assert.notBufferEqual = function notBufferEqual(actual, expected, message) {
|
||||
}
|
||||
};
|
||||
|
||||
// node V10 implements assert.rejects() but this is compatible with V8
|
||||
assert.asyncThrows = async function asyncThrows(func, expectedError) {
|
||||
let err = null;
|
||||
try {
|
||||
await func();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
const re = new RegExp('^' + expectedError);
|
||||
assert(re.test(err.message));
|
||||
};
|
||||
|
||||
function _isString(value, message, stackStartFunction) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new assert.AssertionError({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user