wallet rpc: add getaddressinfo rpc command
update the rpc command to better match bitcoind. add the ismine and iswatchonly fields and segwit related fields such as witness_version and witness_program.
This commit is contained in:
parent
26a2000b01
commit
a28ffa272a
@ -129,6 +129,7 @@ class RPC extends RPCBase {
|
||||
this.add('dumpprivkey', this.dumpPrivKey);
|
||||
this.add('dumpwallet', this.dumpWallet);
|
||||
this.add('encryptwallet', this.encryptWallet);
|
||||
this.add('getaddressinfo', this.getAddressInfo);
|
||||
this.add('getaccountaddress', this.getAccountAddress);
|
||||
this.add('getaccount', this.getAccount);
|
||||
this.add('getaddressesbyaccount', this.getAddressesByAccount);
|
||||
@ -443,6 +444,40 @@ class RPC extends RPCBase {
|
||||
return addrs;
|
||||
}
|
||||
|
||||
async getAddressInfo(args, help) {
|
||||
if (help || args.length !== 1)
|
||||
throw new RPCError(errs.MISC_ERROR, 'getaddressinfo "address"');
|
||||
|
||||
const valid = new Validator(args);
|
||||
const addr = valid.str(0, '');
|
||||
|
||||
const address = parseAddress(addr, this.network);
|
||||
const script = Script.fromAddress(address);
|
||||
const wallet = this.wallet.toJSON();
|
||||
|
||||
const path = await this.wallet.getPath(address);
|
||||
|
||||
const isScript = script.isScripthash() || script.isWitnessScripthash();
|
||||
const isWitness = address.isProgram();
|
||||
|
||||
const result = {
|
||||
address: address.toString(this.network),
|
||||
scriptPubKey: script ? script.toJSON() : undefined,
|
||||
ismine: path != null,
|
||||
ischange: path ? path.branch === 1 : false,
|
||||
iswatchonly: wallet.watchOnly,
|
||||
isscript: isScript,
|
||||
iswitness: isWitness
|
||||
};
|
||||
|
||||
if (isWitness) {
|
||||
result.witness_version = address.version;
|
||||
result.witness_program = address.hash.toString('hex');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getBalance(args, help) {
|
||||
if (help || args.length > 3) {
|
||||
throw new RPCError(errs.MISC_ERROR,
|
||||
|
||||
@ -56,6 +56,14 @@ let hash = null;
|
||||
describe('HTTP', function() {
|
||||
this.timeout(15000);
|
||||
|
||||
// m/44'/1'/0'/0/{0,1}
|
||||
const pubkeys = [
|
||||
Buffer.from('02a7451395735369f2ecdfc829c0f'
|
||||
+ '774e88ef1303dfe5b2f04dbaab30a535dfdd6', 'hex'),
|
||||
Buffer.from('03589ae7c835ce76e23cf8feb32f1a'
|
||||
+ 'df4a7f2ba0ed2ad70801802b0bcd70e99c1c', 'hex')
|
||||
];
|
||||
|
||||
it('should open node', async () => {
|
||||
consensus.COINBASE_MATURITY = 0;
|
||||
await node.open();
|
||||
@ -273,29 +281,32 @@ describe('HTTP', function() {
|
||||
});
|
||||
|
||||
it('should not validate invalid address', async () => {
|
||||
// send an address for the wrong network
|
||||
// Valid Mainnet P2WPKH from
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||
const json = await nclient.execute('validateaddress', [
|
||||
addr.toString('main')
|
||||
'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
|
||||
]);
|
||||
|
||||
// Sending an address from the incorrect network
|
||||
// should result in an invalid address
|
||||
assert.deepStrictEqual(json, {
|
||||
isvalid: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate a p2wpkh address', async () => {
|
||||
const info = await wallet.createAccount('foo3', {
|
||||
witness: true
|
||||
});
|
||||
const json = await nclient.execute('validateaddress', [
|
||||
info.receiveAddress
|
||||
]);
|
||||
const addr = Address.fromString(info.receiveAddress);
|
||||
const address = 'bcrt1q8gk5z3dy7zv9ywe7synlrk58elz4hrnegvpv6m';
|
||||
const addr = Address.fromString(address);
|
||||
const script = Script.fromAddress(addr);
|
||||
|
||||
const json = await nclient.execute('validateaddress', [
|
||||
address
|
||||
]);
|
||||
|
||||
assert.deepStrictEqual(json, {
|
||||
isvalid: true,
|
||||
iswitness: true,
|
||||
address: info.receiveAddress,
|
||||
address: address,
|
||||
isscript: addr.isScripthash(),
|
||||
scriptPubKey: script.toJSON(),
|
||||
witness_version: addr.version,
|
||||
@ -304,18 +315,11 @@ describe('HTTP', function() {
|
||||
});
|
||||
|
||||
it('should validate a p2sh address', async () => {
|
||||
await wallet.createAccount('foo4');
|
||||
|
||||
const pubkeys = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const result = await wallet.createAddress('foo4', 'default');
|
||||
pubkeys.push(Buffer.from(result.publicKey, 'hex'));
|
||||
}
|
||||
|
||||
const script = Script.fromMultisig(2, 2, pubkeys);
|
||||
const address = Address.fromScript(script);
|
||||
|
||||
// test the valid case
|
||||
// Test the valid case - render the address to the
|
||||
// correct network
|
||||
{
|
||||
const json = await nclient.execute('validateaddress', [
|
||||
address.toString(node.network)
|
||||
@ -330,7 +334,8 @@ describe('HTTP', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// test the invalid case
|
||||
// Test the invalid case - render the address to the
|
||||
// incorrect network, making it an invalid address
|
||||
{
|
||||
const json = await nclient.execute('validateaddress', [
|
||||
address.toString('main')
|
||||
@ -343,15 +348,6 @@ describe('HTTP', function() {
|
||||
});
|
||||
|
||||
it('should validate a p2wsh address', async () => {
|
||||
await wallet.createAccount('foo5', {
|
||||
witness: true
|
||||
});
|
||||
|
||||
const pubkeys = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const result = await wallet.createAddress('foo5', 'default');
|
||||
pubkeys.push(Buffer.from(result.publicKey, 'hex'));
|
||||
}
|
||||
const script = Script.fromMultisig(2, 2, pubkeys);
|
||||
const scriptPubKey = script.forWitness();
|
||||
const program = script.sha256();
|
||||
|
||||
275
test/wallet-rpc-test.js
Normal file
275
test/wallet-rpc-test.js
Normal file
@ -0,0 +1,275 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
'use strict';
|
||||
|
||||
const {NodeClient, WalletClient} = require('bclient');
|
||||
const assert = require('./util/assert');
|
||||
const FullNode = require('../lib/node/fullnode');
|
||||
const Network = require('../lib/protocol/network');
|
||||
const Mnemonic = require('../lib/hd/mnemonic');
|
||||
const HDPrivateKey = require('../lib/hd/private');
|
||||
const Script = require('../lib/script/script');
|
||||
const Address = require('../lib/primitives/address');
|
||||
const network = Network.get('regtest');
|
||||
const mnemonics = require('./data/mnemonic-english.json');
|
||||
// Commonly used test mnemonic
|
||||
const phrase = mnemonics[0][1];
|
||||
|
||||
const ports = {
|
||||
p2p: 49331,
|
||||
node: 49332,
|
||||
wallet: 49333
|
||||
};
|
||||
|
||||
const node = new FullNode({
|
||||
network: network.type,
|
||||
apiKey: 'bar',
|
||||
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 nclient = new NodeClient({
|
||||
port: ports.node,
|
||||
apiKey: 'bar'
|
||||
});
|
||||
|
||||
const wclient = new WalletClient({
|
||||
port: ports.wallet,
|
||||
apiKey: 'bar'
|
||||
});
|
||||
|
||||
describe('Wallet RPC Methods', function() {
|
||||
this.timeout(15000);
|
||||
|
||||
// Define an account level hd extended public key to be
|
||||
// used to derive addresses throughout the test suite
|
||||
let xpub;
|
||||
|
||||
before(async () => {
|
||||
await node.open();
|
||||
await nclient.open();
|
||||
await wclient.open();
|
||||
|
||||
// Derive the xpub using the well known
|
||||
// mnemonic and network's coin type
|
||||
const mnemonic = Mnemonic.fromPhrase(phrase);
|
||||
const priv = HDPrivateKey.fromMnemonic(mnemonic);
|
||||
const type = network.keyPrefix.coinType;
|
||||
const key = priv.derive(44, true).derive(type, true).derive(0, true);
|
||||
|
||||
xpub = key.toPublic();
|
||||
|
||||
// Assert that the expected test phrase was
|
||||
// read from disk
|
||||
assert.equal(phrase, [
|
||||
'abandon', 'abandon', 'abandon', 'abandon',
|
||||
'abandon', 'abandon', 'abandon', 'abandon',
|
||||
'abandon', 'abandon', 'abandon', 'about'
|
||||
].join(' '));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await nclient.close();
|
||||
await wclient.close();
|
||||
await node.close();
|
||||
});
|
||||
|
||||
describe('getaddressinfo', () => {
|
||||
const watchOnlyWalletId = 'foo';
|
||||
const standardWalletId = 'bar';
|
||||
|
||||
// m/44'/1'/0'/0/{0,1}
|
||||
const pubkeys = [
|
||||
Buffer.from('02a7451395735369f2ecdfc829c0f'
|
||||
+ '774e88ef1303dfe5b2f04dbaab30a535dfdd6', 'hex'),
|
||||
Buffer.from('03589ae7c835ce76e23cf8feb32f1a'
|
||||
+ 'df4a7f2ba0ed2ad70801802b0bcd70e99c1c', 'hex')
|
||||
];
|
||||
|
||||
// set up the initial testing state
|
||||
before(async () => {
|
||||
{
|
||||
// Set up the testing environment
|
||||
// by creating a wallet and a watch
|
||||
// only wallet
|
||||
const info = await nclient.getInfo();
|
||||
assert.equal(info.chain.height, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Create a watch only wallet using the path
|
||||
// m/44'/1'/0' and assert that the wallet
|
||||
// was properly created
|
||||
const accountKey = xpub.xpubkey(network.type);
|
||||
const response = await wclient.createWallet(watchOnlyWalletId, {
|
||||
watchOnly: true,
|
||||
accountKey: accountKey
|
||||
});
|
||||
|
||||
assert.equal(response.id, watchOnlyWalletId);
|
||||
|
||||
const wallet = wclient.wallet(watchOnlyWalletId);
|
||||
const info = await wallet.getAccount('default');
|
||||
assert.equal(info.accountKey, accountKey);
|
||||
assert.equal(info.watchOnly, true);
|
||||
}
|
||||
|
||||
{
|
||||
// Create a wallet that manages the private keys itself
|
||||
const response = await wclient.createWallet(standardWalletId);
|
||||
assert.equal(response.id, standardWalletId);
|
||||
|
||||
const info = await wclient.getAccount(standardWalletId, 'default');
|
||||
assert.equal(info.watchOnly, false);
|
||||
};
|
||||
});
|
||||
|
||||
// The rpc interface requires the wallet to be selected first
|
||||
it('should return iswatchonly correctly', async () => {
|
||||
// m/44'/1'/0'/0/0
|
||||
const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV';
|
||||
|
||||
{
|
||||
await wclient.execute('selectwallet', [standardWalletId]);
|
||||
const response = await wclient.execute('getaddressinfo', [receive]);
|
||||
assert.equal(response.iswatchonly, false);
|
||||
}
|
||||
{
|
||||
await wclient.execute('selectwallet', [watchOnlyWalletId]);
|
||||
const response = await wclient.execute('getaddressinfo', [receive]);
|
||||
assert.equal(response.iswatchonly, true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the correct address', async () => {
|
||||
// m/44'/1'/0'/0/0
|
||||
const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV';
|
||||
|
||||
await wclient.execute('selectwallet', [watchOnlyWalletId]);
|
||||
const response = await wclient.execute('getaddressinfo', [receive]);
|
||||
assert.equal(response.address, receive);
|
||||
});
|
||||
|
||||
it('should detect owned address', async () => {
|
||||
// m/44'/1'/0'/0/0
|
||||
const receive = 'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV';
|
||||
{
|
||||
await wclient.execute('selectwallet', [watchOnlyWalletId]);
|
||||
const response = await wclient.execute('getaddressinfo', [receive]);
|
||||
assert.equal(response.ismine, true);
|
||||
}
|
||||
{
|
||||
await wclient.execute('selectwallet', [standardWalletId]);
|
||||
const response = await wclient.execute('getaddressinfo', [receive]);
|
||||
assert.equal(response.ismine, false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should detect a p2sh address', async () => {
|
||||
const script = Script.fromMultisig(2, 2, pubkeys);
|
||||
const address = Address.fromScript(script);
|
||||
const addr = address.toString(network);
|
||||
const response = await wclient.execute('getaddressinfo', [addr]);
|
||||
|
||||
assert.equal(response.isscript, true);
|
||||
assert.equal(response.iswitness, false);
|
||||
assert.equal(response.witness_program, undefined);
|
||||
});
|
||||
|
||||
it('should return the correct program for a p2wpkh address', async () => {
|
||||
// m/44'/1'/0'/0/5
|
||||
const receive = 'bcrt1q53724q6cywuzsvq5e3nvdeuwrepu69jsc6ulmx';
|
||||
const addr = Address.fromString(receive);
|
||||
|
||||
await wclient.execute('selectwallet', [watchOnlyWalletId]);
|
||||
const str = addr.toString(network);
|
||||
const response = await wclient.execute('getaddressinfo', [str]);
|
||||
assert.equal(response.witness_program, addr.hash.toString('hex'));
|
||||
});
|
||||
|
||||
it('should detect p2wsh program', async () => {
|
||||
const script = Script.fromMultisig(2, 2, pubkeys);
|
||||
const address = Address.fromWitnessScripthash(script.sha256());
|
||||
const addr = address.toString(network);
|
||||
const response = await wclient.execute('getaddressinfo', [addr]);
|
||||
|
||||
assert.equal(response.isscript, true);
|
||||
assert.equal(response.iswitness, true);
|
||||
assert.equal(response.witness_program, address.hash.toString('hex'));
|
||||
});
|
||||
|
||||
it('should detect ismine up to the lookahead', async () => {
|
||||
const info = await wclient.getAccount(watchOnlyWalletId, 'default');
|
||||
await wclient.execute('selectwallet', [watchOnlyWalletId]);
|
||||
|
||||
// m/44'/1'/0'
|
||||
const addresses = [
|
||||
'mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV', // /0/0
|
||||
'mzpbWabUQm1w8ijuJnAof5eiSTep27deVH', // /0/1
|
||||
'mnTkxhNkgx7TsZrEdRcPti564yQTzynGJp', // /0/2
|
||||
'mpW3iVi2Td1vqDK8Nfie29ddZXf9spmZkX', // /0/3
|
||||
'n2BMo5arHDyAK2CM8c56eoEd18uEkKnRLC', // /0/4
|
||||
'mvWgTTtQqZohUPnykucneWNXzM5PLj83an', // /0/5
|
||||
'muTU2Av1EwnsyhieQhyPL7hgEf883LR4xg', // /0/6
|
||||
'mwduZ8Ksa563v7rWdSPmqyKR4y2FeB5g8p', // /0/7
|
||||
'miyBE85ro5zt9RseSzYVEbB3TfzkxgSm8C', // /0/8
|
||||
'mnYwW7mU3jajB11vrpDZwZDrXwVfE5Jc31', // /0/9
|
||||
'mx3YNRT8Vg8QwFq5Z5MAVDDVHp4ihHsffn' // /0/10
|
||||
];
|
||||
|
||||
// Assert that the lookahead is configured as expected
|
||||
// subtract one from addresses.length, it is 0 indexed
|
||||
assert.equal(addresses.length - 1, info.lookahead);
|
||||
|
||||
// Each address through the lookahead number should
|
||||
// be recognized as an owned address
|
||||
for (let i = 0; i <= info.lookahead; i++) {
|
||||
const address = addresses[i];
|
||||
const response = await wclient.execute('getaddressinfo', [address]);
|
||||
assert.equal(response.ismine, true);
|
||||
}
|
||||
|
||||
// m/44'/1'/0'/0/11
|
||||
// This address is outside of the lookahead range
|
||||
const failed = 'myHL2QuECVYkx9Y94gyC6RSweLNnteETsB';
|
||||
|
||||
const response = await wclient.execute('getaddressinfo', [failed]);
|
||||
assert.equal(response.ismine, false);
|
||||
});
|
||||
|
||||
it('should detect change addresses', async () => {
|
||||
// m/44'/1'/0'/1/0
|
||||
const address = 'mi8nhzZgGZQthq6DQHbru9crMDerUdTKva';
|
||||
const info = await wclient.execute('getaddressinfo', [address]);
|
||||
|
||||
assert.equal(info.ischange, true);
|
||||
});
|
||||
|
||||
it('should throw for the wrong network', async () => {
|
||||
// m/44'/1'/0'/0/0
|
||||
const failed = '16JcQVoL61QsLCPS6ek8UJZ52eRfaFqLJt';
|
||||
|
||||
// Match the bitcoind response when sending the incorrect
|
||||
// network. Expect an RPC error
|
||||
const fn = async () => await wclient.execute('getaddressinfo', [failed]);
|
||||
await assert.asyncThrows(fn, 'Invalid address.');
|
||||
});
|
||||
|
||||
it('should fail for invalid address', async () => {
|
||||
// m/44'/1'/0'/0/0
|
||||
let failed = '16JcQVoL61QsLCPS6ek8UJZ52eRfaFqLJt';
|
||||
// remove the first character
|
||||
failed = failed.slice(1, failed.length);
|
||||
|
||||
const fn = async () => await wclient.execute('getaddressinfo', [failed]);
|
||||
await assert.asyncThrows(fn, 'Invalid address.');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user