fcoin/test/http-test.js

506 lines
14 KiB
JavaScript

/* eslint-env mocha */
/* eslint prefer-arrow-callback: "off" */
'use strict';
const {BloomFilter} = require('bfilter');
const assert = require('bsert');
const consensus = require('../lib/protocol/consensus');
const Address = require('../lib/primitives/address');
const Script = require('../lib/script/script');
const Outpoint = require('../lib/primitives/outpoint');
const MTX = require('../lib/primitives/mtx');
const FullNode = require('../lib/node/fullnode');
const ChainEntry = require('../lib/blockchain/chainentry');
const pkg = require('../lib/pkg');
if (process.browser)
return;
const ports = {
p2p: 49331,
node: 49332,
wallet: 49333
};
const node = new FullNode({
network: 'regtest',
apiKey: 'foo',
walletAuth: true,
memory: true,
workers: true,
workersSize: 2,
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',
timeout: 15000
});
const wclient = new WalletClient({
port: ports.wallet,
apiKey: 'foo'
});
let wallet = null;
const {wdb} = node.require('walletdb');
let addr = null;
let hash = null;
let blocks = 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();
await nclient.open();
await wclient.open();
});
it('should create wallet', async () => {
const info = await wclient.createWallet('test');
assert.strictEqual(info.id, 'test');
wallet = wclient.wallet('test', info.token);
await wallet.open();
});
it('should get info', async () => {
const info = await nclient.getInfo();
assert.strictEqual(info.network, node.network.type);
assert.strictEqual(info.version, pkg.version);
assert(typeof info.pool === 'object');
assert.strictEqual(info.pool.agent, node.pool.options.agent);
assert(typeof info.chain === 'object');
assert.strictEqual(info.chain.height, 0);
assert(typeof info.indexes === 'object');
assert(typeof info.indexes.addr === 'object');
assert.equal(info.indexes.addr.enabled, false);
assert.equal(info.indexes.addr.height, 0);
assert(typeof info.indexes.tx === 'object');
assert.equal(info.indexes.addr.enabled, false);
assert.equal(info.indexes.tx.height, 0);
});
it('should get wallet info', async () => {
const info = await wallet.getInfo();
assert.strictEqual(info.id, 'test');
const acct = await wallet.getAccount('default');
const str = acct.receiveAddress;
assert(typeof str === 'string');
addr = Address.fromString(str, node.network);
});
it('should fill with funds', async () => {
const mtx = new MTX();
mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0));
mtx.addOutput(addr, 50460);
mtx.addOutput(addr, 50460);
mtx.addOutput(addr, 50460);
mtx.addOutput(addr, 50460);
const tx = mtx.toTX();
let balance = null;
wallet.once('balance', (b) => {
balance = b;
});
let receive = null;
wallet.once('address', (r) => {
receive = r[0];
});
let details = null;
wallet.once('tx', (d) => {
details = d;
});
await wdb.addTX(tx);
await new Promise(r => setTimeout(r, 300));
assert(receive);
assert.strictEqual(receive.name, 'default');
assert.strictEqual(receive.type, 'pubkeyhash');
assert.strictEqual(receive.branch, 0);
assert(balance);
assert.strictEqual(balance.confirmed, 0);
assert.strictEqual(balance.unconfirmed, 201840);
assert(details);
assert.strictEqual(details.hash, tx.txid());
});
it('should get balance', async () => {
const balance = await wallet.getBalance();
assert.strictEqual(balance.confirmed, 0);
assert.strictEqual(balance.unconfirmed, 201840);
});
it('should send a tx', async () => {
const options = {
rate: 10000,
outputs: [{
value: 10000,
address: addr.toString(node.network)
}]
};
const tx = await wallet.send(options);
assert(tx);
assert.strictEqual(tx.inputs.length, 1);
assert.strictEqual(tx.outputs.length, 2);
let value = 0;
value += tx.outputs[0].value;
value += tx.outputs[1].value;
assert.strictEqual(value, 48190);
hash = tx.hash;
});
it('should get a tx', async () => {
const tx = await wallet.getTX(hash);
assert(tx);
assert.strictEqual(tx.hash, hash);
});
it('should generate new api key', async () => {
const old = wallet.token.toString('hex');
const result = await wallet.retoken(null);
assert.strictEqual(result.token.length, 64);
assert.notStrictEqual(result.token, old);
});
it('should get balance', async () => {
const balance = await wallet.getBalance();
assert.strictEqual(balance.unconfirmed, 199570);
});
it('should execute an rpc call', async () => {
const info = await nclient.execute('getblockchaininfo', []);
assert.strictEqual(info.blocks, 0);
});
it('should execute an rpc call with bool parameter', async () => {
const info = await nclient.execute('getrawmempool', [true]);
assert.deepStrictEqual(info, {});
});
it('should create account', async () => {
const info = await wallet.createAccount('foo1');
assert(info);
assert(info.initialized);
assert.strictEqual(info.name, 'foo1');
assert.strictEqual(info.accountIndex, 1);
assert.strictEqual(info.m, 1);
assert.strictEqual(info.n, 1);
});
it('should create account', async () => {
const info = await wallet.createAccount('foo2', {
type: 'multisig',
m: 1,
n: 2
});
assert(info);
assert(!info.initialized);
assert.strictEqual(info.name, 'foo2');
assert.strictEqual(info.accountIndex, 2);
assert.strictEqual(info.m, 1);
assert.strictEqual(info.n, 2);
});
it('should get a block template', async () => {
const json = await nclient.execute('getblocktemplate', [{
rules: ['segwit']
}]);
assert.deepStrictEqual(json, {
capabilities: ['proposal'],
mutable: ['time', 'transactions', 'prevblock'],
version: 536870912,
rules: ['!segwit'],
vbavailable: {},
vbrequired: 0,
height: 1,
previousblockhash:
'530827f38f93b43ed12af0b3ad25a288dc02ed74d6d7857862df51fc56c416f9',
target:
'7fffff0000000000000000000000000000000000000000000000000000000000',
bits: '207fffff',
noncerange: '00000000ffffffff',
curtime: json.curtime,
mintime: 1296688603,
maxtime: json.maxtime,
expires: json.expires,
sigoplimit: 80000,
sizelimit: 4000000,
weightlimit: 4000000,
longpollid:
'530827f38f93b43ed12af0b3ad25a288dc02ed74d6d7857862df51fc56c416f9'
+ '0000000000',
submitold: false,
coinbaseaux: { flags: '6d696e65642062792062636f696e' },
coinbasevalue: 5000000000,
transactions: [],
default_witness_commitment:
'6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48beb'
+ 'd836974e8cf9'
});
});
it('should send a block template proposal', async () => {
const attempt = await node.miner.createBlock();
const block = attempt.toBlock();
const hex = block.toRaw().toString('hex');
const json = await nclient.execute('getblocktemplate', [{
mode: 'proposal',
data: hex
}]);
assert.strictEqual(json, null);
});
it('should validate an address', async () => {
const json = await nclient.execute('validateaddress', [
addr.toString(node.network)
]);
assert.deepStrictEqual(json, {
isvalid: true,
address: addr.toString(node.network),
scriptPubKey: Script.fromAddress(addr).toRaw().toString('hex'),
iswitness: false,
isscript: false
});
});
it('should not validate invalid address', async () => {
// Valid Mainnet P2WPKH from
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
const json = await nclient.execute('validateaddress', [
'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 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: address,
isscript: addr.isScripthash(),
scriptPubKey: script.toJSON(),
witness_version: addr.version,
witness_program: addr.hash.toString('hex')
});
});
it('should validate a p2sh address', async () => {
const script = Script.fromMultisig(2, 2, pubkeys);
const address = Address.fromScript(script);
// Test the valid case - render the address to the
// correct network
{
const json = await nclient.execute('validateaddress', [
address.toString(node.network)
]);
assert.deepEqual(json, {
isvalid: true,
address: address.toString(node.network),
scriptPubKey: Script.fromAddress(address).toJSON(),
isscript: true,
iswitness: false
});
}
// 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')
]);
assert.deepEqual(json, {
isvalid: false
});
}
});
it('should validate a p2wsh address', async () => {
const script = Script.fromMultisig(2, 2, pubkeys);
const scriptPubKey = script.forWitness();
const program = script.sha256();
const address = Address.fromProgram(0, program);
const json = await nclient.execute('validateaddress', [
address.toString(node.network)
]);
assert.deepEqual(json, {
isvalid: true,
address: address.toString(node.network),
scriptPubKey: scriptPubKey.toJSON(),
isscript: true,
iswitness: true,
witness_version: 0,
witness_program: program.toString('hex')
});
});
for (const template of [true, false]) {
const suffix = template ? 'with template' : 'without template';
it(`should create and sign transaction ${suffix}`, async () => {
const change = await wallet.createChange('default');
const tx = await wallet.createTX({
template: template, // should not matter, sign = true
sign: true,
outputs: [{
address: change.address,
value: 50000
}]
});
const mtx = MTX.fromJSON(tx);
for (const input of tx.inputs) {
const script = input.script;
assert.notStrictEqual(script, '',
'Input must be signed.');
}
assert.strictEqual(mtx.verify(), true,
'Transaction must be signed.');
});
}
it('should create transaction without template', async () => {
const change = await wallet.createChange('default');
const tx = await wallet.createTX({
sign: false,
outputs: [{
address: change.address,
value: 50000
}]
});
for (const input of tx.inputs) {
const script = input.script;
assert.strictEqual(script.length, 0,
'Input must not be templated.');
}
});
it('should create transaction with template', async () => {
const change = await wallet.createChange('default');
const tx = await wallet.createTX({
sign: false,
template: true,
outputs: [{
address: change.address,
value: 20000
}]
});
for (const input of tx.inputs) {
const script = Buffer.from(input.script, 'hex');
// p2pkh
// 1 (OP_0 placeholder) + 1 (length) + 33 (pubkey)
assert.strictEqual(script.length, 35);
assert.strictEqual(script[0], 0x00,
'First item in stack must be a placeholder OP_0');
}
});
it('should generate 10 blocks from RPC call', async () => {
blocks = await nclient.execute(
'generatetoaddress',
[10, addr.toString('regtest')]
);
assert.strictEqual(blocks.length, 10);
});
it('should initiate rescan from socket without a bloom filter', async () => {
// Rescan from height 5. Without a filter loaded = no response, but no error
const response = await nclient.call('rescan', 5);
assert.strictEqual(null, response);
});
it('should initiate rescan from socket WITH a bloom filter', async () => {
// Create an SPV-standard Bloom filter and add one of our wallet addresses
const filter = BloomFilter.fromRate(20000, 0.001, BloomFilter.flags.ALL);
const walletAddr = addr.toString('regtest');
filter.add(walletAddr, 'ascii');
// Send Bloom filter to server
await nclient.call('set filter', filter.filter);
// `rescan` commands the node server to check blocks against a bloom filter.
// When the server matches a transaction in a block to the filter, it
// sends a socket call BACK to the client with the ChainEntry of the block,
// and an array of matched transactions. Because of this callback, the
// CLIENT MUST have a `block rescan` hook in place or the server will throw.
const matchingBlocks = [];
nclient.hook('block rescan', (entry, txs) => {
// Coinbase transactions were mined to our watch address, matching filter.
assert.strictEqual(txs.length, 1);
const cbtx = MTX.fromRaw(txs[0]);
assert.strictEqual(
cbtx.outputs[0].getAddress().toString('regtest'),
walletAddr
);
// Blocks are returned as raw ChainEntry
matchingBlocks.push(
ChainEntry.fromRaw(entry).rhash().toString('hex')
);
});
// Rescan from height 5 -- should return blocks 5 through 10, inclusive.
await nclient.call('rescan', 5);
assert.deepStrictEqual(matchingBlocks, blocks.slice(4));
});
it('should cleanup', async () => {
consensus.COINBASE_MATURITY = 100;
await wallet.close();
await wclient.close();
await nclient.close();
await node.close();
});
});