'use strict'; const assert = require('bsert'); const bio = require('bufio'); const Address = require('../lib/primitives/address'); const Mnemonic = require('../lib/hd/mnemonic'); const Witness = require('../lib/script/witness'); const Script = require('../lib/script/script'); const HDPrivateKey = require('../lib/hd/private'); const Output = require('../lib/primitives/output'); const Coin = require('../lib/primitives/coin'); const MTX = require('../lib/primitives/mtx'); const rules = require('../lib/covenants/rules'); const common = require('./util/common'); const NodeContext = require('./util/node-context'); const pkg = require('../lib/pkg'); const mnemonics = require('./data/mnemonic-english.json'); const consensus = require('../lib/protocol/consensus'); const Outpoint = require('../lib/primitives/outpoint'); const {ZERO_HASH} = consensus; // Commonly used test mnemonic const phrase = mnemonics[0][1]; describe('Node HTTP', function() { describe('Mempool', function() { let nodeCtx, nclient; beforeEach(async () => { nodeCtx = new NodeContext(); await nodeCtx.open(); nclient = nodeCtx.nclient; }); afterEach(async () => { await nodeCtx.close(); nodeCtx = null; }); it('should get mempool rejection filter', async () => { const filterInfo = await nclient.getMempoolRejectionFilter({ verbose: true }); assert.ok('items' in filterInfo); assert.ok('filter' in filterInfo); assert.ok('size' in filterInfo); assert.ok('entries' in filterInfo); assert.ok('n' in filterInfo); assert.ok('limit' in filterInfo); assert.ok('tweak' in filterInfo); assert.equal(filterInfo.entries, 0); }); it('should add an entry to the mempool rejection filter', async () => { const mtx = new MTX(); mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0)); const raw = mtx.toHex(); const txid = await nclient.execute('sendrawtransaction', [raw]); const json = await nclient.checkMempoolRejectionFilter(txid); assert.equal(json.invalid, true); const filterInfo = await nclient.getMempoolRejectionFilter(); assert.equal(filterInfo.entries, 1); }); }); describe('Blockheader', function() { let nodeCtx, nclient; beforeEach(async () => { nodeCtx = new NodeContext(); await nodeCtx.open(); nclient = nodeCtx.nclient; }); afterEach(async () => { await nodeCtx.close(); nodeCtx = null; }); it('should fetch block header by height', async () => { await nclient.execute( 'generatetoaddress', [8, 'rs1q7q3h4chglps004u3yn79z0cp9ed24rfrhvrxnx'] ); // fetch corresponding header and block const height = 7; const header = await nclient.getBlockHeader(height); assert.equal(header.height, height); const properties = [ 'hash', 'version', 'prevBlock', 'merkleRoot', 'time', 'bits', 'nonce', 'height', 'chainwork' ]; for (const property of properties) assert(property in header); const block = await nclient.getBlock(height); assert.equal(block.hash, header.hash); assert.equal(block.height, header.height); assert.equal(block.version, header.version); assert.equal(block.prevBlock, header.prevBlock); assert.equal(block.merkleRoot, header.merkleRoot); assert.equal(block.time, header.time); assert.equal(block.bits, header.bits); assert.equal(block.nonce, header.nonce); }); it('should fetch null for block header that does not exist', async () => { // many blocks in the future const header = await nclient.getBlockHeader(40000); assert.equal(header, null); }); it('should have valid header chain', async () => { await nclient.execute( 'generatetoaddress', [10, 'rs1q7q3h4chglps004u3yn79z0cp9ed24rfrhvrxnx'] ); // starting at the genesis block let prevBlock = '0000000000000000000000000000000000000000000000000000000000000000'; for (let i = 0; i < 10; i++) { const header = await nclient.getBlockHeader(i); assert.equal(prevBlock, header.prevBlock); prevBlock = header.hash; } }); it('should fetch block header by hash', async () => { const info = await nclient.getInfo(); const headerByHash = await nclient.getBlockHeader(info.chain.tip); const headerByHeight = await nclient.getBlockHeader(info.chain.height); assert.deepEqual(headerByHash, headerByHeight); }); }); describe('Chain info', function() { let nodeCtx; afterEach(async () => { await nodeCtx.close(); nodeCtx = null; }); it('should get info', async () => { nodeCtx = new NodeContext(); await nodeCtx.open(); const {node, nclient, network} = nodeCtx; const info = await nclient.getInfo(); assert.strictEqual(info.network, network.type); assert.strictEqual(info.version, pkg.version); assert(info.pool); assert.strictEqual(info.pool.agent, node.pool.options.agent); assert(info.chain); assert.strictEqual(info.chain.height, 0); assert.strictEqual(info.chain.treeRoot, ZERO_HASH.toString('hex')); // state comes from genesis block assert.strictEqual(info.chain.state.tx, 1); assert.strictEqual(info.chain.state.coin, 1); assert.strictEqual(info.chain.state.burned, 0); }); it('should get full node chain info', async () => { nodeCtx = new NodeContext({ network: 'regtest' }); await nodeCtx.open(); const {chain} = await nodeCtx.nclient.getInfo(); assert.strictEqual(chain.height, 0); assert.strictEqual(chain.tip, nodeCtx.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 () => { nodeCtx = new NodeContext({ network: 'regtest', indexAddress: true, indexTX: true }); await nodeCtx.open(); const {chain} = await nodeCtx.nclient.getInfo(); assert.strictEqual(chain.indexers.indexTX, true); assert.strictEqual(chain.indexers.indexAddress, true); }); it('should get fullnode chain info with pruning', async () => { nodeCtx = new NodeContext({ network: 'regtest', prune: true }); await nodeCtx.open(); const {chain} = await nodeCtx.nclient.getInfo(); assert.strictEqual(chain.options.prune, true); }); it('should get fullnode chain info with compact', async () => { nodeCtx = new NodeContext({ network: 'regtest', compactTreeOnInit: true, compactTreeInitInterval: 20000 }); await nodeCtx.open(); const {chain} = await nodeCtx.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 () => { nodeCtx = new NodeContext({ network: 'regtest', spv: true }); await nodeCtx.open(); const {chain} = await nodeCtx.nclient.getInfo(); assert.strictEqual(chain.options.spv, true); }); it('should get next tree update height', async () => { const someAddr = 'rs1q7q3h4chglps004u3yn79z0cp9ed24rfrhvrxnx'; nodeCtx = new NodeContext({ network: 'regtest' }); await nodeCtx.open(); const interval = nodeCtx.network.names.treeInterval; const nclient = nodeCtx.nclient; const node = nodeCtx.node; { // 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() { let nodeCtx = null; afterEach(async () => { if (nodeCtx) await nodeCtx.close(); }); it('should not have public address: regtest', async () => { nodeCtx = new NodeContext({ network: 'regtest' }); await nodeCtx.open(); const {network, nclient} = nodeCtx; const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; assert.strictEqual(pub.listen, false); assert.strictEqual(pub.host, null); assert.strictEqual(pub.port, null); assert.strictEqual(pub.brontidePort, null); }); it('should not have public address: regtest, listen', async () => { nodeCtx = new NodeContext({ network: 'regtest', listen: true }); await nodeCtx.open(); const {network, nclient} = nodeCtx; const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; assert.strictEqual(pub.listen, true); assert.strictEqual(pub.host, null); // we don't discover from external assert.strictEqual(pub.port, null); assert.strictEqual(pub.brontidePort, null); }); it('should not have public address: main', async () => { nodeCtx = new NodeContext({ network: 'main' }); await nodeCtx.open(); const {network, nclient} = nodeCtx; const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; assert.strictEqual(pub.listen, false); assert.strictEqual(pub.host, null); assert.strictEqual(pub.port, null); assert.strictEqual(pub.brontidePort, null); }); it('should not have public address: main, listen', async () => { nodeCtx = new NodeContext({ network: 'main', listen: true }); await nodeCtx.open(); const {network, nclient} = nodeCtx; const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; assert.strictEqual(pub.listen, true); assert.strictEqual(pub.host, null); assert.strictEqual(pub.port, null); assert.strictEqual(pub.brontidePort, null); }); it('should have public address: main, listen, publicHost', async () => { const publicHost = '100.200.11.22'; const publicPort = 11111; const publicBrontidePort = 22222; nodeCtx = new NodeContext({ network: 'main', listen: true, publicHost, publicPort, publicBrontidePort }); await nodeCtx.open(); const {network, nclient} = nodeCtx; const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; assert.strictEqual(pub.listen, true); assert.strictEqual(pub.host, publicHost); assert.strictEqual(pub.port, publicPort); assert.strictEqual(pub.brontidePort, publicBrontidePort); }); }); describe('Websockets', function () { this.timeout(15000); describe('tree commit', () => { const {types} = rules; const nodeCtx = new NodeContext({ apiKey: 'foo', indexTx: true, indexAddress: true, rejectAbsurdFees: false }); nodeCtx.init(); const {network, nclient} = nodeCtx; const {treeInterval} = network.names; let privkey, pubkey; let socketData, mempoolData; let cbAddress; // take into account race conditions async function mineBlocks(count, address) { const blockEvents = common.forEvent( nodeCtx.nclient.socket.events, 'block connect', count ); await nodeCtx.mineBlocks(count, address); await blockEvents; } before(async () => { await nodeCtx.open(); await nclient.call('watch chain'); 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 xkey = key.derive(0).derive(0); socketData = []; mempoolData = {}; pubkey = xkey.publicKey; privkey = xkey.privateKey; cbAddress = Address.fromPubkey(pubkey).toString(network.type); nclient.bind('tree commit', (root, entry, block) => { assert.ok(root); assert.ok(block); assert.ok(entry); socketData.push({root, entry, block}); }); nodeCtx.mempool.on('tx', (tx) => { mempoolData[tx.txid()] = true; }); }); after(async () => { await nodeCtx.close(); }); beforeEach(() => { socketData = []; mempoolData = {}; }); it('should mine 1 tree interval', async () => { await mineBlocks(treeInterval, cbAddress); assert.equal(socketData.length, 1); }); it('should send the correct tree root', async () => { const name = await nclient.execute('grindname', [5]); const rawName = Buffer.from(name, 'ascii'); const nameHash = rules.hashName(rawName); const u32 = Buffer.alloc(4); bio.writeU32(u32, 0, 0); const output = new Output({ address: cbAddress, value: 0, covenant: { type: types.OPEN, items: [nameHash, u32, rawName] } }); const mtx = new MTX(); mtx.addOutput(output); const coins = await nclient.getCoinsByAddresses([cbAddress]); coins.sort((a, b) => a.height - b.height); const coin = Coin.fromJSON(coins[0]); assert.ok(nodeCtx.chain.height > coin.height + network.coinbaseMaturity); mtx.addCoin(coin); const addr = Address.fromPubkey(pubkey); const script = Script.fromPubkeyhash(addr.hash); const sig = mtx.signature(0, script, coin.value, privkey); mtx.inputs[0].witness = Witness.fromItems([sig, pubkey]); const valid = mtx.verify(); assert.ok(valid); const tx = mtx.toTX(); await nodeCtx.node.sendTX(tx); await common.forValue(mempoolData, tx.txid(), true); const pre = await nclient.getInfo(); const mempool = await nclient.getMempool(); assert.equal(mempool[0], mtx.txid()); await mineBlocks(treeInterval, cbAddress); assert.equal(socketData.length, 1); const {root, block, entry} = socketData[0]; assert.bufferEqual(nodeCtx.chain.db.treeRoot(), root); const info = await nclient.getInfo(); assert.notEqual(pre.chain.tip, info.chain.tip); assert.equal(info.chain.tip, block.hash); assert.equal(info.chain.tip, entry.hash); }); }); }); });