Skip to content

Commit

Permalink
Merge PR #869 from 'nodech/chain-info'
Browse files Browse the repository at this point in the history
  • Loading branch information
nodech committed Dec 4, 2023
2 parents b6778c4 + 919cf24 commit aac7fa7
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 4 deletions.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@
**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`
- `compacted` - whethere tree is compacted or not.
- `compactOnInit` - is tree compaction on init enabled.
- `compactInterval` - what is the current compaction interval config.
- `nextCompaction` - when will the next compaction trigger after restart.
- `lastCompaction` - when was the last compaction run.

### 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
Expand Down
24 changes: 21 additions & 3 deletions lib/blockchain/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ class Chain extends AsyncEmitter {
return this.db.close();
}

/**
* Get compaction heights.
* @returns {Promise<Object>}
*/

async getCompactionHeights() {
if (this.options.spv)
return null;

const {keepBlocks} = this.network.block;
const {compactionHeight} = await this.db.getTreeState();
const {compactTreeInitInterval} = this.options;
const compactFrom = compactionHeight + keepBlocks + compactTreeInitInterval;

return {
compactionHeight,
compactFrom
};
}

/**
* Check if we need to compact tree data.
* @returns {Promise<Boolean>} - Should we sync
Expand All @@ -150,9 +170,7 @@ class Chain extends AsyncEmitter {
if (this.height <= startFrom)
return true;

const {compactionHeight} = await this.db.getTreeState();
const {compactTreeInitInterval} = this.options;
const compactFrom = compactionHeight + keepBlocks + compactTreeInitInterval;
const {compactFrom} = await this.getCompactionHeights();

if (compactFrom > this.height) {
this.logger.debug(
Expand Down
2 changes: 2 additions & 0 deletions lib/node/fullnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ class FullNode extends Node {
}

this.logger.info('Node is loaded.');
this.emit('open');
}

/**
Expand Down Expand Up @@ -348,6 +349,7 @@ class FullNode extends Node {

this.logger.info('Node is closed.');
this.emit('closed');
this.emit('close');
}

/**
Expand Down
43 changes: 43 additions & 0 deletions lib/node/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,57 @@ 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,
chain: {
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,
Expand Down
2 changes: 2 additions & 0 deletions lib/node/spvnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class SPVNode extends Node {
await this.handleOpen();

this.logger.info('Node is loaded.');
this.emit('open');
}

/**
Expand Down Expand Up @@ -218,6 +219,7 @@ class SPVNode extends Node {

this.logger.info('Node is closed.');
this.emit('closed');
this.emit('close');
}

/**
Expand Down
142 changes: 142 additions & 0 deletions test/node-http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand Down

0 comments on commit aac7fa7

Please sign in to comment.