diff --git a/CHANGELOG.md b/CHANGELOG.md index 3300a92f0..5bcb60a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,24 @@ **When upgrading to this version of hsd, you must pass `--wallet-migrate=3` when you run it for the first time.** -### Wallet Changes: +### Node Changes +#### Node HTTP API + - `GET /` or `getInfo()` now has more properties: + - `treeRootHeight` - height at which the block txns are accumulated + in the current branch. + - `indexers` + - `indexTX` - is tx indexer enabled. + - `indexAddress` - is addr indexer enabled. + - `options` + - `spv` is the Node SPV? + - `prune` does node have pruning enabled. + - `treeCompaction` + - `compact` - is tree compaction on init enabled. + - `compactInterval` - what is the current compaction interval config. + - `lastCompaction` - when was the last compaction run. + - `nextCompaction` - when will the next compaction trigger after restart. + +### Wallet Changes #### Configuration Wallet now has option `wallet-migrate-no-rescan`/`migrate-no-rescan` if you want to disable rescan when migration recommends it. It may result in the diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index dbb69e56c..ff47269fa 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -140,9 +140,6 @@ class Chain extends AsyncEmitter { if (this.options.spv) return null; - if (!this.options.compactTreeOnInit) - return null; - const {keepBlocks} = this.network.block; const {compactionHeight} = await this.db.getTreeState(); const {compactTreeInitInterval} = this.options; diff --git a/lib/node/http.js b/lib/node/http.js index f54c49483..23b6621cb 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -132,6 +132,39 @@ class HTTP extends Server { pub.brontidePort = brontide.port; } + const treeInterval = this.network.names.treeInterval; + const prevHeight = this.chain.height - 1; + const treeRootHeight = this.chain.height === 0 ? 0 : + prevHeight - (prevHeight % treeInterval) + 1; + + const treeCompaction = { + compacted: false, + compactOnInit: false, + compactInterval: null, + lastCompaction: null, + nextCompaction: null + }; + + if (!this.chain.options.spv) { + const chainOptions = this.chain.options; + const { + compactionHeight, + compactFrom + } = await this.chain.getCompactionHeights(); + + treeCompaction.compactOnInit = chainOptions.compactTreeOnInit; + + if (chainOptions.compactTreeOnInit) { + treeCompaction.compactInterval = chainOptions.compactTreeInitInterval; + treeCompaction.nextCompaction = compactFrom; + } + + if (compactionHeight > 0) { + treeCompaction.compacted = true; + treeCompaction.lastCompaction = compactionHeight; + } + } + res.json(200, { version: pkg.version, network: this.network.type, @@ -139,7 +172,17 @@ class HTTP extends Server { height: this.chain.height, tip: this.chain.tip.hash.toString('hex'), treeRoot: this.chain.tip.treeRoot.toString('hex'), + treeRootHeight: treeRootHeight, progress: this.chain.getProgress(), + indexers: { + indexTX: this.chain.options.indexTX, + indexAddress: this.chain.options.indexAddress + }, + options: { + spv: this.chain.options.spv, + prune: this.chain.options.prune + }, + treeCompaction: treeCompaction, state: { tx: this.chain.db.state.tx, coin: this.chain.db.state.coin, diff --git a/test/node-http-test.js b/test/node-http-test.js index ac626e2ee..148ecc2f3 100644 --- a/test/node-http-test.js +++ b/test/node-http-test.js @@ -5,6 +5,7 @@ const bio = require('bufio'); const NodeClient = require('../lib/client/node'); const Network = require('../lib/protocol/network'); const FullNode = require('../lib/node/fullnode'); +const SPVNode = require('../lib/node/spvnode'); const Address = require('../lib/primitives/address'); const Mnemonic = require('../lib/hd/mnemonic'); const Witness = require('../lib/script/witness'); @@ -16,10 +17,151 @@ const MTX = require('../lib/primitives/mtx'); const rules = require('../lib/covenants/rules'); const common = require('./util/common'); const mnemonics = require('./data/mnemonic-english.json'); +const {forEvent} = common; // Commonly used test mnemonic const phrase = mnemonics[0][1]; describe('Node HTTP', function() { + describe('Chain info', function() { + const network = Network.get('regtest'); + const nclient = new NodeClient({ + port: network.rpcPort + }); + + let node; + + afterEach(async () => { + if (node && node.opened) { + const close = forEvent(node, 'close'); + await node.close(); + await close; + } + + node = null; + }); + + it('should get full node chain info', async () => { + node = new FullNode({ + network: network.type + }); + + await node.open(); + + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.height, 0); + assert.strictEqual(chain.tip, network.genesis.hash.toString('hex')); + assert.strictEqual(chain.treeRoot, Buffer.alloc(32, 0).toString('hex')); + assert.strictEqual(chain.progress, 0); + assert.strictEqual(chain.indexers.indexTX, false); + assert.strictEqual(chain.indexers.indexAddress, false); + assert.strictEqual(chain.options.spv, false); + assert.strictEqual(chain.options.prune, false); + assert.strictEqual(chain.treeCompaction.compacted, false); + assert.strictEqual(chain.treeCompaction.compactOnInit, false); + assert.strictEqual(chain.treeCompaction.compactInterval, null); + assert.strictEqual(chain.treeCompaction.nextCompaction, null); + assert.strictEqual(chain.treeCompaction.lastCompaction, null); + }); + + it('should get fullnode chain info with indexers', async () => { + node = new FullNode({ + network: network.type, + indexAddress: true, + indexTX: true + }); + + await node.open(); + + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.indexers.indexTX, true); + assert.strictEqual(chain.indexers.indexAddress, true); + }); + + it('should get fullnode chain info with pruning', async () => { + node = new FullNode({ + network: network.type, + prune: true + }); + + await node.open(); + + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.options.prune, true); + }); + + it('should get fullnode chain info with compact', async () => { + node = new FullNode({ + network: network.type, + compactTreeOnInit: true, + compactTreeInitInterval: 20000 + }); + + await node.open(); + + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeCompaction.compacted, false); + assert.strictEqual(chain.treeCompaction.compactOnInit, true); + assert.strictEqual(chain.treeCompaction.compactInterval, 20000); + assert.strictEqual(chain.treeCompaction.lastCompaction, null); + // last compaction height + keepBlocks + compaction interval + // regtest: 0 + 10000 + 20000 + assert.strictEqual(chain.treeCompaction.nextCompaction, 30000); + }); + + it('should get spv node chain info', async () => { + node = new SPVNode({ + network: network.type + }); + + await node.open(); + + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.options.spv, true); + }); + + it('should get next tree update height', async () => { + const someAddr = 'rs1q7q3h4chglps004u3yn79z0cp9ed24rfrhvrxnx'; + node = new FullNode({ + network: network.type + }); + const interval = network.names.treeInterval; + + await node.open(); + + { + // 0th block will be 0. + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeRootHeight, 0); + } + + // blocks from 1 - 4 will be 1. + // last block commits the tree root. + for (let i = 0; i < interval - 1; i++) { + await node.rpc.generateToAddress([1, someAddr]); + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeRootHeight, 1); + } + + { + // block 5 is also 1 and it commits the new root. + await node.rpc.generateToAddress([1, someAddr]); + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeRootHeight, 1); + } + + for (let i = 0; i < interval; i++) { + await node.rpc.generateToAddress([1, someAddr]); + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeRootHeight, interval + 1); + } + + // This block will be part of the new tree batch. + await node.rpc.generateToAddress([1, someAddr]); + const {chain} = await nclient.getInfo(); + assert.strictEqual(chain.treeRootHeight, interval * 2 + 1); + }); + }); + describe('Networking info', function() { it('should not have public address: regtest', async () => { const network = Network.get('regtest');