diff --git a/lib/node/http.js b/lib/node/http.js index 380c47745..568da349e 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -121,11 +121,17 @@ class HTTP extends Server { if (!addr) addr = this.pool.hosts.address; + const treeInterval = this.network.names.treeInterval; + const blocksUntilNextTreeRoot = + treeInterval - (this.chain.height % treeInterval); + res.json(200, { version: pkg.version, network: this.network.type, chain: { height: this.chain.height, + nextTreeRootHeight: this.chain.height + blocksUntilNextTreeRoot, + blocksUntilNextTreeRoot: blocksUntilNextTreeRoot, tip: this.chain.tip.hash.toString('hex'), treeRoot: this.chain.tip.treeRoot.toString('hex'), progress: this.chain.getProgress(), diff --git a/test/node-http-test.js b/test/node-http-test.js new file mode 100644 index 000000000..aa618ec27 --- /dev/null +++ b/test/node-http-test.js @@ -0,0 +1,108 @@ +/*! + * test/node-http-test.js - test for wallet http endoints + * Copyright (c) 2019, Mark Tyneway (MIT License). + * https://github.com/handshake-org/hsd + */ + +/* eslint-env mocha */ +/* eslint prefer-arrow-callback: "off" */ +/* eslint no-return-assign: "off" */ + +'use strict'; + +const assert = require('bsert'); +const {NodeClient} = require('hs-client'); + +const Network = require('../lib/protocol/network'); +const FullNode = require('../lib/node/fullnode'); +const Address = require('../lib/primitives/address'); +const Mnemonic = require('../lib/hd/mnemonic'); +const HDPrivateKey = require('../lib/hd/private'); +const common = require('./util/common'); +const mnemonics = require('./data/mnemonic-english.json'); +// Commonly used test mnemonic +const phrase = mnemonics[0][1]; + +const network = Network.get('regtest'); + +const node = new FullNode({ + network: 'regtest', + apiKey: 'foo', + walletAuth: true, + memory: true, + workers: true +}); + +const nclient = new NodeClient({ + port: network.rpcPort, + apiKey: 'foo' +}); + +let cbAddress; + +const { + treeInterval +} = network.names; + +describe('Node HTTP', function() { + this.timeout(15000); + + before(async () => { + await node.open(); + await nclient.open(); + + 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); + const xpub = key.toPublic(); + const pubkey = xpub.derive(0).derive(0).publicKey; + + cbAddress = Address.fromPubkey(pubkey).toString(network.type); + }); + + after(async () => { + await nclient.close(); + await node.close(); + }); + + describe('get info', function () { + it('should set intervals properly at height 0', async () => { + const info = await nclient.getInfo(); + assert.equal(info.chain.height, 0); + assert.equal(info.chain.nextTreeRootHeight, treeInterval); + assert.equal(info.chain.blocksUntilNextTreeRoot, treeInterval); + }); + + it('should test for off by one errors with the intervals', async () => { + await mineBlocks(treeInterval - 1, cbAddress); + + { + const info = await nclient.getInfo(); + assert.equal(info.chain.height, treeInterval - 1); + assert.equal(info.chain.nextTreeRootHeight, treeInterval); + } + + await mineBlocks(1, cbAddress); + + { + const info = await nclient.getInfo(); + assert.equal(info.chain.height, treeInterval); + assert.equal(info.chain.nextTreeRootHeight, info.chain.height + treeInterval); + assert.equal(info.chain.blocksUntilNextTreeRoot, treeInterval); + } + }); + }); +}); + +// take into account race conditions +async function mineBlocks(count, address) { + for (let i = 0; i < count; i++) { + const obj = { complete: false }; + node.once('block', () => { + obj.complete = true; + }); + await nclient.execute('generatetoaddress', [1, address]); + await common.forValue(obj, 'complete', true); + } +}