diff --git a/lib/primitives/address.js b/lib/primitives/address.js index 7b6b83370..f555a8d80 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -173,7 +173,7 @@ class Address extends bio.Struct { /** * Compile the address object to a bech32 address. - * @param {{NetworkType|Network)?} network + * @param {(NetworkType|Network)?} network * @returns {String} * @throws Error on bad hash/prefix. */ diff --git a/test/auction-rpc-test.js b/test/auction-rpc-test.js index e479e5e31..9a8092987 100644 --- a/test/auction-rpc-test.js +++ b/test/auction-rpc-test.js @@ -12,7 +12,7 @@ const { Path } = require('..'); const {forValue} = require('./util/common'); -const NodeContext = require('./util/node'); +const NodeContext = require('./util/node-context'); class TestUtil { constructor() { @@ -52,7 +52,7 @@ class TestUtil { await this.nodeCtx.open(); - this.node.plugins.walletdb.wdb.on('confirmed', ((details, tx) => { + this.nodeCtx.wdb.on('confirmed', ((details, tx) => { const txid = tx.txid(); if (!this.txs[txid]) @@ -416,8 +416,6 @@ describe('Auction RPCs', function() { describe('SPV', function () { const spvCtx = new NodeContext({ - port: 10000, - brontidePort: 20000, httpPort: 30000, only: '127.0.0.1', noDns: true, diff --git a/test/http-test.js b/test/http-test.js deleted file mode 100644 index fdb1c49d3..000000000 --- a/test/http-test.js +++ /dev/null @@ -1,409 +0,0 @@ -'use strict'; - -const assert = require('bsert'); -const consensus = require('../lib/protocol/consensus'); -const Address = require('../lib/primitives/address'); -const Outpoint = require('../lib/primitives/outpoint'); -const MTX = require('../lib/primitives/mtx'); -const Script = require('../lib/script/script'); -const FullNode = require('../lib/node/fullnode'); -const pkg = require('../lib/pkg'); -const Network = require('../lib/protocol/network'); -const network = Network.get('regtest'); -const {ZERO_HASH} = consensus; - -const node = new FullNode({ - network: 'regtest', - apiKey: 'foo', - walletAuth: true, - memory: true, - workers: true, - plugins: [require('../lib/wallet/plugin')] -}); - -const {NodeClient, WalletClient} = require('../lib/client'); - -const nclient = new NodeClient({ - port: network.rpcPort, - apiKey: 'foo' -}); - -const wclient = new WalletClient({ - port: network.walletPort, - apiKey: 'foo' -}); - -let wallet = null; - -const {wdb} = node.require('walletdb'); - -let addr = null; -let hash = null; - -describe('HTTP', function() { - this.timeout(20000); - - it('should open node', async () => { - 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(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 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.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, 49060); - - 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, 200440); - }); - - 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', []); - assert.deepStrictEqual(json, { - capabilities: ['proposal'], - mutable: ['time', 'transactions', 'prevblock'], - version: 0, - rules: [], - vbavailable: {}, - vbrequired: 0, - height: 1, - previousblockhash: network.genesis.hash.toString('hex'), - treeroot: network.genesis.treeRoot.toString('hex'), - reservedroot: consensus.ZERO_HASH.toString('hex'), - mask: json.mask, - target: - '7fffff0000000000000000000000000000000000000000000000000000000000', - bits: '207fffff', - noncerange: '' - + '000000000000000000000000000000000000000000000000' - + 'ffffffffffffffffffffffffffffffffffffffffffffffff', - curtime: json.curtime, - mintime: 1580745081, - maxtime: json.maxtime, - expires: json.expires, - sigoplimit: 80000, - sizelimit: 1000000, - weightlimit: 4000000, - longpollid: node.chain.tip.hash.toString('hex') + '00000000', - submitold: false, - coinbaseaux: { flags: '6d696e656420627920687364' }, - coinbasevalue: 2000000000, - claims: [], - airdrops: [], - transactions: [] - }); - }); - - it('should send a block template proposal', async () => { - const attempt = await node.miner.createBlock(); - const block = attempt.toBlock(); - const hex = block.toHex(); - 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, - isscript: false, - isspendable: true, - address: addr.toString(node.network), - witness_program: addr.hash.toString('hex'), - witness_version: addr.version - }); - }); - - it('should not validate invalid address', async () => { - const json = await nclient.execute('validateaddress', [ - addr.toString('main') - ]); - assert.deepStrictEqual(json, { - isvalid: false - }); - }); - - it('should validate a p2wsh address', async () => { - const pubkeys = []; - for (let i = 0; i < 2; i++) { - const result = await wallet.createAddress('default'); - pubkeys.push(Buffer.from(result.publicKey, 'hex')); - } - - const script = Script.fromMultisig(2, 2, pubkeys); - const address = Address.fromScript(script); - - const json = await nclient.execute('validateaddress', [ - address.toString(node.network) - ]); - - assert.deepStrictEqual(json, { - address: address.toString(node.network), - isscript: true, - isspendable: true, - isvalid: true, - witness_version: address.version, - witness_program: address.hash.toString('hex') - }); - }); - - it('should validate a null address', async () => { - const data = Buffer.from('foobar', 'ascii'); - const nullAddr = Address.fromNulldata(data); - - const json = await nclient.execute('validateaddress', [ - nullAddr.toString(node.network) - ]); - - assert.deepStrictEqual(json, { - address: nullAddr.toString(node.network), - isscript: false, - isspendable: false, - isvalid: true, - witness_version: nullAddr.version, - witness_program: nullAddr.hash.toString('hex') - }); - }); - - it('should get mempool rejection filter', async () => { - const filterInfo = await nclient.get('/mempool/invalid', { 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.get(`/mempool/invalid/${txid}`); - assert.equal(json.invalid, true); - - const filterInfo = await nclient.get('/mempool/invalid'); - assert.equal(filterInfo.entries, 1); - }); - - it('should generate 10 blocks from RPC call', async () => { - const blocks = await nclient.execute('generatetoaddress', [10, addr.toString(network)]); - assert.strictEqual(blocks.length, 10); - }); - - // depends on the previous test to generate blocks - it('should fetch block header by height', async () => { - // fetch corresponding header and block - const height = 7; - const header = await nclient.get(`/header/${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.get(`/header/${40000}`); - assert.equal(header, null); - }); - - it('should have valid header chain', async () => { - // starting at the genesis block - let prevBlock = '0000000000000000000000000000000000000000000000000000000000000000'; - for (let i = 0; i < 10; i++) { - const header = await nclient.get(`/header/${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.get(`/header/${info.chain.tip}`); - const headerByHeight = await nclient.get(`/header/${info.chain.height}`); - - assert.deepEqual(headerByHash, headerByHeight); - }); - - it('should cleanup', async () => { - await wallet.close(); - await wclient.close(); - await nclient.close(); - await node.close(); - }); -}); diff --git a/test/mempool-invalidation-test.js b/test/mempool-invalidation-test.js index fa4e302bd..6caff479a 100644 --- a/test/mempool-invalidation-test.js +++ b/test/mempool-invalidation-test.js @@ -9,7 +9,7 @@ const {states} = require('../lib/covenants/namestate'); const {Resource} = require('../lib/dns/resource'); const {forEvent} = require('./util/common'); const {CachedStubResolver} = require('./util/stub'); -const NodeContext = require('./util/node'); +const NodeContext = require('./util/node-context'); const network = Network.get('regtest'); const { diff --git a/test/node-http-test.js b/test/node-http-test.js index eedb06c13..a27679186 100644 --- a/test/node-http-test.js +++ b/test/node-http-test.js @@ -2,7 +2,6 @@ const assert = require('bsert'); const bio = require('bufio'); -const Network = require('../lib/protocol/network'); const Address = require('../lib/primitives/address'); const Mnemonic = require('../lib/hd/mnemonic'); const Witness = require('../lib/script/witness'); @@ -13,23 +12,169 @@ 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'); +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(); + nclient = nodeCtx.nclient; + + await nodeCtx.open(); + }); + + afterEach(async () => { + await nodeCtx.close(); + nodeCtx = null; + }); + + it('should get mempool rejection filter', async () => { + const filterInfo = await nclient.get('/mempool/invalid', { 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.get(`/mempool/invalid/${txid}`); + assert.equal(json.invalid, true); + + const filterInfo = await nclient.get('/mempool/invalid'); + assert.equal(filterInfo.entries, 1); + }); + }); + + describe('Blockheader', function() { + let nodeCtx, nclient; + + beforeEach(async () => { + nodeCtx = new NodeContext(); + nclient = nodeCtx.nclient; + + await nodeCtx.open(); + }); + + 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.get(`/header/${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.get(`/header/${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.get(`/header/${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.get(`/header/${info.chain.tip}`); + const headerByHeight = await nclient.get(`/header/${info.chain.height}`); + + assert.deepEqual(headerByHash, headerByHeight); + }); + }); + describe('Chain info', function() { let nodeCtx; afterEach(async () => { - if (nodeCtx && nodeCtx.opened) - await nodeCtx.close(); - + 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' @@ -156,16 +301,26 @@ describe('Node HTTP', function() { }); describe('Networking info', function() { + let nodeCtx = null; + + afterEach(async () => { + if (nodeCtx) + await nodeCtx.close(); + }); + it('should not have public address: regtest', async () => { - const nodeCtx = new NodeContext(); + nodeCtx = new NodeContext({ + network: 'regtest' + }); + + const {network, nclient} = nodeCtx; await nodeCtx.open(); - const {pool} = await nodeCtx.nclient.getInfo(); - await nodeCtx.close(); + const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); - assert.strictEqual(pool.port, nodeCtx.network.port); - assert.strictEqual(pool.brontidePort, nodeCtx.network.brontidePort); + assert.strictEqual(pool.port, network.port); + assert.strictEqual(pool.brontidePort, network.brontidePort); const {public: pub} = pool; @@ -176,15 +331,14 @@ describe('Node HTTP', function() { }); it('should not have public address: regtest, listen', async () => { - const network = Network.get('regtest'); - - const nodeCtx = new NodeContext({ + nodeCtx = new NodeContext({ + network: 'regtest', listen: true }); + const {network, nclient} = nodeCtx; await nodeCtx.open(); - const {pool} = await nodeCtx.nclient.getInfo(); - await nodeCtx.close(); + const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); @@ -199,15 +353,14 @@ describe('Node HTTP', function() { }); it('should not have public address: main', async () => { - const nodeCtx = new NodeContext({ + nodeCtx = new NodeContext({ network: 'main' }); - const network = nodeCtx.network; + const {network, nclient} = nodeCtx; await nodeCtx.open(); - const {pool} = await nodeCtx.nclient.getInfo(); - await nodeCtx.close(); + const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); @@ -222,16 +375,15 @@ describe('Node HTTP', function() { }); it('should not have public address: main, listen', async () => { - const nodeCtx = new NodeContext({ + nodeCtx = new NodeContext({ network: 'main', listen: true }); - const network = nodeCtx.network; + const {network, nclient} = nodeCtx; await nodeCtx.open(); - const {pool} = await nodeCtx.nclient.getInfo(); - await nodeCtx.close(); + const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); @@ -250,7 +402,7 @@ describe('Node HTTP', function() { const publicPort = 11111; const publicBrontidePort = 22222; - const nodeCtx = new NodeContext({ + nodeCtx = new NodeContext({ network: 'main', listen: true, publicHost, @@ -258,11 +410,10 @@ describe('Node HTTP', function() { publicBrontidePort }); - const network = nodeCtx.network; + const {network, nclient} = nodeCtx; await nodeCtx.open(); - const {pool} = await nodeCtx.nclient.getInfo(); - await nodeCtx.close(); + const {pool} = await nclient.getInfo(); assert.strictEqual(pool.host, '0.0.0.0'); assert.strictEqual(pool.port, network.port); @@ -289,10 +440,9 @@ describe('Node HTTP', function() { indexAddress: true, rejectAbsurdFees: false }); - const network = nodeCtx.network; + const {network, nclient} = nodeCtx; const {treeInterval} = network.names; - const nclient = nodeCtx.nclient; let privkey, pubkey; let socketData, mempoolData; @@ -300,14 +450,13 @@ describe('Node HTTP', function() { // take into account race conditions async function mineBlocks(count, address) { - for (let i = 0; i < count; i++) { - const obj = { complete: false }; - nodeCtx.node.once('block', () => { - obj.complete = true; - }); - await nclient.execute('generatetoaddress', [1, address]); - await common.forValue(obj, 'complete', true); - } + const blockEvents = common.forEvent( + nodeCtx.nclient.socket.events, + 'block connect', + count + ); + await nodeCtx.mineBlocks(count, address); + await blockEvents; } before(async () => { diff --git a/test/node-rescan-test.js b/test/node-rescan-test.js index 75783b523..c651a3e57 100644 --- a/test/node-rescan-test.js +++ b/test/node-rescan-test.js @@ -6,7 +6,7 @@ const {BloomFilter} = require('@handshake-org/bfilter'); const TX = require('../lib/primitives/tx'); const nodeCommon = require('../lib/blockchain/common'); const {scanActions} = nodeCommon; -const NodeContext = require('./util/node'); +const NodeContext = require('./util/node-context'); const {forEvent, sleep} = require('./util/common'); const MemWallet = require('./util/memwallet'); const rules = require('../lib/covenants/rules'); diff --git a/test/node-rpc-test.js b/test/node-rpc-test.js index f61442f2f..0b1428010 100644 --- a/test/node-rpc-test.js +++ b/test/node-rpc-test.js @@ -1,15 +1,14 @@ 'use strict'; const assert = require('bsert'); -const FullNode = require('../lib/node/fullnode'); -const SPVNode = require('../lib/node/spvnode'); const Network = require('../lib/protocol/network'); const consensus = require('../lib/protocol/consensus'); const MemWallet = require('./util/memwallet'); const TX = require('../lib/primitives/tx'); -const NodeClient = require('../lib/client/node'); +const NodeContext = require('./util/node-context'); +const Address = require('../lib/primitives/address'); +const Script = require('../lib/script/script'); -const TIMEOUT = 15000; const API_KEY = 'foo'; const NETWORK = 'regtest'; @@ -22,7 +21,6 @@ const ports = { const nodeOptions = { network: NETWORK, apiKey: API_KEY, - walletAuth: true, memory: true, workers: true, workersSize: 2, @@ -30,12 +28,6 @@ const nodeOptions = { httpPort: ports.node }; -const clientOptions = { - port: ports.node, - apiKey: API_KEY, - timeout: TIMEOUT -}; - const errs = { MISC_ERROR: -1 }; @@ -43,18 +35,63 @@ const errs = { describe('RPC', function() { this.timeout(15000); + describe('getblockchaininfo', function() { + const nodeCtx = new NodeContext(nodeOptions); + const nclient = nodeCtx.nclient; + + before(async () => { + await nodeCtx.open(); + }); + + after(async () => { + await nodeCtx.close(); + }); + + it('should get blockchain info', async () => { + const info = await nclient.execute('getblockchaininfo', []); + assert.strictEqual(info.chain, NETWORK); + assert.strictEqual(info.blocks, 0); + assert.strictEqual(info.headers, 0); + assert.strictEqual(info.pruned, false); + }); + }); + + describe('getrawmempool', function() { + const nodeCtx = new NodeContext(nodeOptions); + const nclient = nodeCtx.nclient; + + before(async () => { + await nodeCtx.open(); + }); + + after(async () => { + await nodeCtx.close(); + }); + + it('should get raw mempool', async () => { + const hashes = await nclient.execute('getrawmempool', [true]); + assert.deepEqual(hashes, {}); + }); + }); + describe('getblock', function () { - const node = new FullNode(nodeOptions); - const nclient = new NodeClient(clientOptions); + let nodeCtx, nclient, node; before(async () => { - await node.open(); - await nclient.open(); + nodeCtx = new NodeContext({ + ...nodeOptions, + name: 'node-rpc-test' + }); + + nclient = nodeCtx.nclient; + node = nodeCtx.node; + + await nodeCtx.open(); }); after(async () => { - await nclient.close(); - await node.close(); + await nodeCtx.close(); + nodeCtx = null; }); it('should rpc getblock', async () => { @@ -83,7 +120,7 @@ describe('RPC', function() { const address = 'rs1qjjpnmnrzfvxgqlqf5j48j50jmq9pyqjz0a7ytz'; // Mine two blocks. - await nclient.execute('generatetoaddress', [2, address]); + await nodeCtx.mineBlocks(2, address); const {chain} = await nclient.getInfo(); const info = await nclient.execute('getblock', [chain.tip]); @@ -138,100 +175,6 @@ describe('RPC', function() { const info = await nclient.execute('getblock', [hash]); assert.deepEqual(info.confirmations, -1); }); - - it('should validateresource (valid)', async () => { - const records = [ - [{type: 'NS', ns: 'ns1.handshake.org.'}], - [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(32)}], - [{type: 'TXT', txt: ['i like turtles', 'then who phone']}], - [{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '192.168.0.1'}], - [{type: 'GLUE6', ns: 'ns2.nam.ek.', address: '::'}], - [{type: 'SYNTH4', address: '192.168.0.1'}], - [{type: 'SYNTH6', address: '::'}] - ]; - - for (const record of records) { - const data = {records: record}; - const info = await nclient.execute('validateresource', [data]); - assert.deepEqual(info, data); - } - }); - - it('should validateresource (invalid)', async () => { - const records = [ - [ - // No trailing dot - [{type: 'NS', ns: 'ns1.handshake.org'}], - 'Invalid NS record. ns must be a valid name.' - ], - [ - [{type: 'DS', keyTag: 0xffffff}], - 'Invalid DS record. KeyTag must be a uint16.' - ], - [ - [{type: 'DS', keyTag: 0xffff, algorithm: 0xffff}], - 'Invalid DS record. Algorithm must be a uint8.' - ], - [ - [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xffff}], - 'Invalid DS record. DigestType must be a uint8.' - ], - [ - [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: Buffer.alloc(0)}], - 'Invalid DS record. Digest must be a String.' - ], - [ - [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(256)}], - 'Invalid DS record. Digest is too large.' - ], - [ - [{type: 'TXT', txt: 'foobar'}], - 'Invalid TXT record. txt must be an Array.' - ], - [ - [{type: 'TXT', txt: [{}]}], - 'Invalid TXT record. Entries in txt Array must be type String.' - ], - [ - [{type: 'TXT', txt: ['0'.repeat(256)]}], - 'Invalid TXT record. Entries in txt Array must be <= 255 in length.' - ], - [ - [{type: 'GLUE4', ns: 'ns1.nam.ek', address: '192.168.0.1'}], - 'Invalid GLUE4 record. ns must be a valid name.' - ], - [ - [{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '::'}], - 'Invalid GLUE4 record. Address must be a valid IPv4 address.' - ], - [ - [{type: 'GLUE6', ns: 'ns1.nam.ek', address: '::'}], - 'Invalid GLUE6 record. ns must be a valid name.' - ], - [ - [{type: 'GLUE6', ns: 'ns1.nam.ek.', address: '0.0.0.0'}], - 'Invalid GLUE6 record. Address must be a valid IPv6 address.' - ], - [ - [{type: 'SYNTH4', address: '::'}], - 'Invalid SYNTH4 record. Address must be a valid IPv4 address.' - ], - [ - [{type: 'SYNTH6', address: '127.0.0.1'}], - 'Invalid SYNTH6 record. Address must be a valid IPv6 address.' - ] - ]; - - for (const [record, reason] of records) { - try { - const data = {records: record}; - await nclient.execute('validateresource', [data]); - assert.fail(); - } catch (e) { - assert.equal(e.message, reason); - } - } - }); }); describe('pruneblockchain', function() { @@ -243,7 +186,7 @@ describe('RPC', function() { const TEST_PRUNED_BLOCKS = 10; const TEST_PRUNE_AFTER_HEIGHT = 10; - let nclient, node; + let nodeCtx; before(() => { network.block.pruneAfterHeight = TEST_PRUNE_AFTER_HEIGHT; @@ -256,91 +199,77 @@ describe('RPC', function() { }); afterEach(async () => { - if (nclient && nclient.opened) - await nclient.close(); - - if (node && node.opened) - await node.close(); + if (nodeCtx) + await nodeCtx.close(); }); it('should fail with wrong arguments', async () => { - node = new FullNode(nodeOptions); - nclient = new NodeClient(clientOptions); - - await node.open(); + nodeCtx = new NodeContext(nodeOptions); + await nodeCtx.open(); await assert.rejects(async () => { - await nclient.execute('pruneblockchain', [1]); + await nodeCtx.nclient.execute('pruneblockchain', [1]); }, { code: errs.MISC_ERROR, type: 'RPCError', message: 'pruneblockchain' }); - - await node.close(); }); it('should not work for spvnode', async () => { - node = new SPVNode(nodeOptions); - nclient = new NodeClient(clientOptions); - - await node.open(); + nodeCtx = new NodeContext({ + ...nodeOptions, + spv: true + }); + await nodeCtx.open(); await assert.rejects(async () => { - await nclient.execute('pruneblockchain'); + await nodeCtx.nclient.execute('pruneblockchain'); }, { type: 'RPCError', message: 'Cannot prune chain in SPV mode.', code: errs.MISC_ERROR }); - - await node.close(); }); it('should fail for pruned node', async () => { - node = new FullNode({ + nodeCtx = new NodeContext({ ...nodeOptions, prune: true }); - - await node.open(); + await nodeCtx.open(); await assert.rejects(async () => { - await nclient.execute('pruneblockchain'); + await nodeCtx.nclient.execute('pruneblockchain'); }, { type: 'RPCError', code: errs.MISC_ERROR, message: 'Chain is already pruned.' }); - - await node.close(); }); it('should fail for short chain', async () => { - node = new FullNode(nodeOptions); - - await node.open(); + nodeCtx = new NodeContext(nodeOptions); + await nodeCtx.open(); await assert.rejects(async () => { - await nclient.execute('pruneblockchain'); + await nodeCtx.nclient.execute('pruneblockchain'); }, { type: 'RPCError', code: errs.MISC_ERROR, message: 'Chain is too short for pruning.' }); - - await node.close(); }); it('should prune chain', async () => { // default - prune: false - node = new FullNode(nodeOptions); - nclient = new NodeClient(clientOptions); + nodeCtx = new NodeContext(nodeOptions); + await nodeCtx.open(); - await node.open(); + const {miner, nclient} = nodeCtx; const addr = 'rs1q4rvs9pp9496qawp2zyqpz3s90fjfk362q92vq8'; - node.miner.addAddress(addr); + miner.addAddress(addr); let genBlocks = TEST_PRUNE_AFTER_HEIGHT; genBlocks += TEST_PRUNED_BLOCKS; @@ -386,14 +315,18 @@ describe('RPC', function() { const block = await nclient.execute('getblock', [blocks[i]]); assert(block, `block ${i} was pruned.`); } - - await node.close(); }); }); describe('mining', function() { - const node = new FullNode(nodeOptions); - const nclient = new NodeClient(clientOptions); + const nodeCtx = new NodeContext(nodeOptions); + const { + miner, + chain, + mempool, + nodeRPC, + nclient + } = nodeCtx; const wallet = new MemWallet({ network: NETWORK @@ -402,32 +335,80 @@ describe('RPC', function() { let mtx1, mtx2; before(async () => { - await node.open(); - await nclient.open(); + await nodeCtx.open(); }); after(async () => { - await nclient.close(); - await node.close(); + await nodeCtx.close(); + }); + + it('should get a block template', async () => { + const {network, chain} = nodeCtx; + const json = await nclient.execute('getblocktemplate', []); + assert.deepStrictEqual(json, { + capabilities: ['proposal'], + mutable: ['time', 'transactions', 'prevblock'], + version: 0, + rules: [], + vbavailable: {}, + vbrequired: 0, + height: 1, + previousblockhash: network.genesis.hash.toString('hex'), + treeroot: network.genesis.treeRoot.toString('hex'), + reservedroot: consensus.ZERO_HASH.toString('hex'), + mask: json.mask, + target: + '7fffff0000000000000000000000000000000000000000000000000000000000', + bits: '207fffff', + noncerange: '' + + '000000000000000000000000000000000000000000000000' + + 'ffffffffffffffffffffffffffffffffffffffffffffffff', + curtime: json.curtime, + mintime: 1580745081, + maxtime: json.maxtime, + expires: json.expires, + sigoplimit: 80000, + sizelimit: 1000000, + weightlimit: 4000000, + longpollid: chain.tip.hash.toString('hex') + '00000000', + submitold: false, + coinbaseaux: { flags: '6d696e656420627920687364' }, + coinbasevalue: 2000000000, + claims: [], + airdrops: [], + transactions: [] + }); + }); + + it('should send a block template proposal', async () => { + const {node} = nodeCtx; + const attempt = await node.miner.createBlock(); + const block = attempt.toBlock(); + const hex = block.toHex(); + const json = await nclient.execute('getblocktemplate', [{ + mode: 'proposal', + data: hex + }]); + assert.strictEqual(json, null); }); it('should submit a block', async () => { - const block = await node.miner.mineBlock(); + const block = await miner.mineBlock(); const hex = block.toHex(); const result = await nclient.execute('submitblock', [hex]); assert.strictEqual(result, null); - assert.bufferEqual(node.chain.tip.hash, block.hash()); + assert.bufferEqual(chain.tip.hash, block.hash()); }); it('should add transactions to mempool', async () => { // Fund MemWallet - node.miner.addresses.length = 0; - node.miner.addAddress(wallet.getReceive()); + miner.addresses.length = 0; + miner.addAddress(wallet.getReceive()); for (let i = 0; i < 10; i++) { - const block = await node.miner.mineBlock(); - const entry = await node.chain.add(block); + const block = await miner.mineBlock(); + const entry = await chain.add(block); wallet.addBlock(entry, block.txs); } @@ -439,7 +420,7 @@ describe('RPC', function() { address: wallet.getReceive() }] }); - await node.mempool.addTX(mtx1.toTX()); + await mempool.addTX(mtx1.toTX()); // Low fee mtx2 = await wallet.send({ @@ -449,13 +430,13 @@ describe('RPC', function() { address: wallet.getReceive() }] }); - await node.mempool.addTX(mtx2.toTX()); + await mempool.addTX(mtx2.toTX()); - assert.strictEqual(node.mempool.map.size, 2); + assert.strictEqual(mempool.map.size, 2); }); it('should get a block template', async () => { - node.rpc.refreshBlock(); + nodeRPC.refreshBlock(); const result = await nclient.execute( 'getblocktemplate', @@ -491,7 +472,7 @@ describe('RPC', function() { let fees = 0; let weight = 0; - node.rpc.refreshBlock(); + nodeRPC.refreshBlock(); const result = await nclient.execute( 'getblocktemplate', @@ -513,15 +494,24 @@ describe('RPC', function() { }); it('should mine a block', async () => { - const block = await node.miner.mineBlock(); + const block = await miner.mineBlock(); assert(block); - await node.chain.add(block); + await chain.add(block); }); }); describe('transactions', function() { - const node = new FullNode({...nodeOptions, indexTx: true}); - const nclient = new NodeClient(clientOptions); + const nodeCtx = new NodeContext({ + ...nodeOptions, + indexTX: true + }); + + const { + miner, + chain, + mempool, + nclient + } = nodeCtx; const wallet = new MemWallet({ network: NETWORK @@ -530,22 +520,20 @@ describe('RPC', function() { let tx1; before(async () => { - await node.open(); - await nclient.open(); + await nodeCtx.open(); }); after(async () => { - await nclient.close(); - await node.close(); + await nodeCtx.close(); }); it('should confirm a transaction in a block', async () => { // Fund MemWallet - node.miner.addresses.length = 0; - node.miner.addAddress(wallet.getReceive()); + miner.addresses.length = 0; + miner.addAddress(wallet.getReceive()); for (let i = 0; i < 10; i++) { - const block = await node.miner.mineBlock(); - const entry = await node.chain.add(block); + const block = await miner.mineBlock(); + const entry = await chain.add(block); wallet.addBlock(entry, block.txs); } @@ -557,14 +545,15 @@ describe('RPC', function() { }] }); tx1 = mtx1.toTX(); - await node.mempool.addTX(tx1); - assert.strictEqual(node.mempool.map.size, 1); + await mempool.addTX(tx1); + + assert.strictEqual(mempool.map.size, 1); - const block = await node.miner.mineBlock(); + const block = await miner.mineBlock(); assert(block); assert.strictEqual(block.txs.length, 2); - await node.chain.add(block); + await chain.add(block); }); it('should get raw transaction', async () => { @@ -591,24 +580,22 @@ describe('RPC', function() { for (const [i, vout] of result.vout.entries()) { const output = tx.output(i); assert.equal(vout.address.version, output.address.version); - assert.equal(vout.address.string, output.address.toString(node.network)); + assert.equal(vout.address.string, output.address.toString(nodeCtx.network)); assert.equal(vout.address.hash, output.address.hash.toString('hex')); } }); }); describe('networking', function() { - const node = new FullNode({...nodeOptions, bip37: true}); - const nclient = new NodeClient(clientOptions); + const nodeCtx = new NodeContext({ ...nodeOptions, bip37: true }); + const nclient = nodeCtx.nclient; before(async () => { - await node.open(); - await nclient.open(); + await nodeCtx.open(); }); after(async () => { - await nclient.close(); - await node.close(); + await nodeCtx.close(); }); it('should get service names for rpc getnetworkinfo', async () => { @@ -618,18 +605,16 @@ describe('RPC', function() { }); }); - describe('utility', function() { - const node = new FullNode({...nodeOptions}); - const nclient = new NodeClient(clientOptions); + describe('DNS Utility', function() { + const nodeCtx = new NodeContext(nodeOptions); + const nclient = nodeCtx.nclient; before(async () => { - await node.open(); - await nclient.open(); + await nodeCtx.open(); }); after(async () => { - await nclient.close(); - await node.close(); + await nodeCtx.close(); }); it('should decode resource', async () => { @@ -675,5 +660,190 @@ describe('RPC', function() { } ); }); + + it('should validateresource (valid)', async () => { + const records = [ + [{type: 'NS', ns: 'ns1.handshake.org.'}], + [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(32)}], + [{type: 'TXT', txt: ['i like turtles', 'then who phone']}], + [{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '192.168.0.1'}], + [{type: 'GLUE6', ns: 'ns2.nam.ek.', address: '::'}], + [{type: 'SYNTH4', address: '192.168.0.1'}], + [{type: 'SYNTH6', address: '::'}] + ]; + + for (const record of records) { + const data = {records: record}; + const info = await nclient.execute('validateresource', [data]); + assert.deepEqual(info, data); + } + }); + + it('should validateresource (invalid)', async () => { + const records = [ + [ + // No trailing dot + [{type: 'NS', ns: 'ns1.handshake.org'}], + 'Invalid NS record. ns must be a valid name.' + ], + [ + [{type: 'DS', keyTag: 0xffffff}], + 'Invalid DS record. KeyTag must be a uint16.' + ], + [ + [{type: 'DS', keyTag: 0xffff, algorithm: 0xffff}], + 'Invalid DS record. Algorithm must be a uint8.' + ], + [ + [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xffff}], + 'Invalid DS record. DigestType must be a uint8.' + ], + [ + [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: Buffer.alloc(0)}], + 'Invalid DS record. Digest must be a String.' + ], + [ + [{type: 'DS', keyTag: 0xffff, algorithm: 0xff, digestType: 0xff, digest: '00'.repeat(256)}], + 'Invalid DS record. Digest is too large.' + ], + [ + [{type: 'TXT', txt: 'foobar'}], + 'Invalid TXT record. txt must be an Array.' + ], + [ + [{type: 'TXT', txt: [{}]}], + 'Invalid TXT record. Entries in txt Array must be type String.' + ], + [ + [{type: 'TXT', txt: ['0'.repeat(256)]}], + 'Invalid TXT record. Entries in txt Array must be <= 255 in length.' + ], + [ + [{type: 'GLUE4', ns: 'ns1.nam.ek', address: '192.168.0.1'}], + 'Invalid GLUE4 record. ns must be a valid name.' + ], + [ + [{type: 'GLUE4', ns: 'ns1.nam.ek.', address: '::'}], + 'Invalid GLUE4 record. Address must be a valid IPv4 address.' + ], + [ + [{type: 'GLUE6', ns: 'ns1.nam.ek', address: '::'}], + 'Invalid GLUE6 record. ns must be a valid name.' + ], + [ + [{type: 'GLUE6', ns: 'ns1.nam.ek.', address: '0.0.0.0'}], + 'Invalid GLUE6 record. Address must be a valid IPv6 address.' + ], + [ + [{type: 'SYNTH4', address: '::'}], + 'Invalid SYNTH4 record. Address must be a valid IPv4 address.' + ], + [ + [{type: 'SYNTH6', address: '127.0.0.1'}], + 'Invalid SYNTH6 record. Address must be a valid IPv6 address.' + ] + ]; + + for (const [record, reason] of records) { + try { + const data = {records: record}; + await nclient.execute('validateresource', [data]); + assert.fail(); + } catch (e) { + assert.equal(e.message, reason); + } + } + }); + }); + + describe('Address Utility', function() { + const nodeCtx = new NodeContext({ + ...nodeOptions, + wallet: true + }); + + const { + node, + nclient, + wdb + } = nodeCtx; + + let wallet, addr; + + before(async () => { + await nodeCtx.open(); + wallet = await wdb.create({ id: 'test'}); + }); + + after(async () => { + await nodeCtx.close(); + }); + + it('should validate an address', async () => { + addr = await wallet.receiveAddress('default'); + const json = await nclient.execute('validateaddress', [ + addr.toString(nodeCtx.network) + ]); + + assert.deepStrictEqual(json, { + isvalid: true, + isscript: false, + isspendable: true, + address: addr.toString(node.network), + witness_program: addr.hash.toString('hex'), + witness_version: addr.version + }); + }); + + it('should not validate invalid address', async () => { + const json = await nclient.execute('validateaddress', [ + addr.toString('main') + ]); + assert.deepStrictEqual(json, { + isvalid: false + }); + }); + + it('should validate a p2wsh address', async () => { + const pubkeys = []; + for (let i = 0; i < 2; i++) { + const result = await wallet.receiveKey('default'); + pubkeys.push(Buffer.from(result.publicKey, 'hex')); + } + + const script = Script.fromMultisig(2, 2, pubkeys); + const address = Address.fromScript(script); + + const json = await nclient.execute('validateaddress', [ + address.toString(node.network) + ]); + + assert.deepStrictEqual(json, { + address: address.toString(node.network), + isscript: true, + isspendable: true, + isvalid: true, + witness_version: address.version, + witness_program: address.hash.toString('hex') + }); + }); + + it('should validate a null address', async () => { + const data = Buffer.from('foobar', 'ascii'); + const nullAddr = Address.fromNulldata(data); + + const json = await nclient.execute('validateaddress', [ + nullAddr.toString(node.network) + ]); + + assert.deepStrictEqual(json, { + address: nullAddr.toString(node.network), + isscript: false, + isspendable: false, + isvalid: true, + witness_version: nullAddr.version, + witness_program: nullAddr.hash.toString('hex') + }); + }); }); }); diff --git a/test/util/node.js b/test/util/node-context.js similarity index 76% rename from test/util/node.js rename to test/util/node-context.js index fd11010cd..fce35076c 100644 --- a/test/util/node.js +++ b/test/util/node-context.js @@ -28,18 +28,21 @@ class NodeContext { this.clients = []; this.fromOptions(options); + this.init(); } fromOptions(options) { const fnodeOptions = { ...options, memory: true, - workers: true, network: 'regtest', listen: false, wallet: false, spv: false, - logger: this.logger + logger: this.logger, + + // wallet plugin options + walletHttpPort: null }; if (options.network != null) @@ -69,11 +72,6 @@ class NodeContext { if (options.wallet != null) fnodeOptions.wallet = options.wallet; - if (options.apiKey != null) { - assert(typeof options.apiKey === 'string'); - fnodeOptions.apiKey = options.apiKey; - } - if (options.spv != null) { assert(typeof options.spv === 'boolean'); fnodeOptions.spv = options.spv; @@ -84,60 +82,33 @@ class NodeContext { fnodeOptions.httpPort = options.httpPort; } - if (options.indexTX != null) { - assert(typeof options.indexTX === 'boolean'); - fnodeOptions.indexTX = options.indexTX; - } - - if (options.indexAddress != null) { - assert(typeof options.indexAddress === 'boolean'); - fnodeOptions.indexAddress = options.indexAddress; - } - - if (options.prune != null) { - assert(typeof options.prune === 'boolean'); - fnodeOptions.prune = options.prune; + if (options.walletHttpPort != null) { + assert(typeof options.walletHttpPort === 'number'); + fnodeOptions.walletHttpPort = options.walletHttpPort; } - if (options.compactOnInit != null) { - assert(typeof options.compactOnInit === 'boolean'); - fnodeOptions.compactOnInit = options.compactOnInit; + if (options.timeout != null) { + assert(typeof options.timeout === 'number'); + fnodeOptions.timeout = options.timeout; } - if (options.compactTreeInitInterval != null) { - assert(typeof options.compactTreeInitInterval === 'number'); - fnodeOptions.compactTreeInitInterval = options.compactTreeInitInterval; - } + this.options = fnodeOptions; + } - if (fnodeOptions.spv) - this.node = new SPVNode(fnodeOptions); + init() { + if (this.options.spv) + this.node = new SPVNode(this.options); else - this.node = new FullNode(fnodeOptions); - - if (options.timeout != null) - fnodeOptions.timeout = options.timeout; + this.node = new FullNode(this.options); - if (fnodeOptions.wallet) + if (this.options.wallet) this.node.use(plugin); - this.nclient = new NodeClient({ - timeout: fnodeOptions.timeout, - apiKey: fnodeOptions.apiKey, - port: fnodeOptions.httpPort || this.node.network.rpcPort - }); - - this.clients.push(this.nclient); - - if (fnodeOptions.wallet) { - this.wclient = new WalletClient({ - timeout: fnodeOptions.timeout, - port: this.node.network.walletPort - }); + // Initial wallets. + this.nclient = this.nodeClient(); - this.clients.push(this.wclient); - } - - this.options = fnodeOptions; + if (this.options.wallet) + this.wclient = this.walletClient(); } get network() { @@ -156,6 +127,10 @@ class NodeContext { return this.node.chain; } + get nodeRPC() { + return this.node.rpc; + } + get height() { return this.chain.tip.height; } @@ -193,6 +168,9 @@ class NodeContext { */ async open() { + if (this.opened) + return; + if (this.prefix) await fs.mkdirp(this.prefix); @@ -298,7 +276,8 @@ class NodeContext { walletClient(options = {}) { const client = new WalletClient({ timeout: this.options.timeout, - port: this.network.walletPort, + apiKey: this.options.apiKey, + port: this.options.walletHttpPort || this.network.walletPort, ...options }); @@ -306,6 +285,23 @@ class NodeContext { return client; } + + /** + * Mine blocks and wait for connect. + * @param {Number} count + * @param {Address} address + * @returns {Promise} - Block hashes + */ + + async mineBlocks(count, address) { + assert(this.open); + + const blockEvents = common.forEvent(this.node, 'block', count); + const hashes = await this.nodeRPC.generateToAddress([count, address]); + await blockEvents; + + return hashes; + } } module.exports = NodeContext; diff --git a/test/util/nodes-context.js b/test/util/nodes-context.js index 1de3b98a7..0be0f325c 100644 --- a/test/util/nodes-context.js +++ b/test/util/nodes-context.js @@ -1,60 +1,61 @@ 'use strict'; const assert = require('assert'); -const FullNode = require('../../lib/node/fullnode'); const Network = require('../../lib/protocol/network'); -const Logger = require('blgr'); +const NodeContext = require('./node-context'); class NodesContext { - constructor(network, size) { + constructor(network, size = 1) { this.network = Network.get(network); - this.size = size || 4; - this.nodes = []; + this.size = size; + this.nodeCtxs = []; - this.init(); + assert(this.size > 0); } - init() { - for (let i = 0; i < this.size; i++) { - const port = this.network.port + i; - - let last = port - 1; - - if (last < this.network.port) - last = port; - - const node = new FullNode({ - network: this.network, - memory: true, - logger: new Logger({ - level: 'debug', - file: false, - console: false - }), - listen: true, - publicHost: '127.0.0.1', - publicPort: port, - httpPort: port + 100, - host: '127.0.0.1', - port: port, - seeds: [ - `127.0.0.1:${last}` - ] - }); - - node.on('error', (err) => { - node.logger.error(err); - }); - - this.nodes.push(node); - } + init(options) { + for (let i = 0; i < this.size; i++) + this.addNode(options); + } + + addNode(options = {}) { + const index = this.nodeCtxs.length; + + let seedPort = this.network.port + index - 1; + + if (seedPort < this.network.port) + seedPort = this.network.port; + + const port = this.network.port + index; + const brontidePort = this.network.brontidePort + index; + const httpPort = this.network.rpcPort + index + 100; + const walletHttpPort = this.network.walletPort + index + 200; + + const nodeCtx = new NodeContext({ + ...options, + + // override + name: `node-${index}`, + network: this.network, + listen: true, + publicHost: '127.0.0.1', + publicPort: port, + brontidePort: brontidePort, + httpPort: httpPort, + walletHttpPort: walletHttpPort, + seeds: [ + `127.0.0.1:${seedPort}` + ] + }); + + this.nodeCtxs.push(nodeCtx); } open() { const jobs = []; - for (const node of this.nodes) - jobs.push(node.open()); + for (const nodeCtx of this.nodeCtxs) + jobs.push(nodeCtx.open()); return Promise.all(jobs); } @@ -62,61 +63,61 @@ class NodesContext { close() { const jobs = []; - for (const node of this.nodes) - jobs.push(node.close()); + for (const nodeCtx of this.nodeCtxs) + jobs.push(nodeCtx.close()); return Promise.all(jobs); } async connect() { - for (const node of this.nodes) { - await node.connect(); + for (const nodeCtx of this.nodeCtxs) { + await nodeCtx.node.connect(); await new Promise(r => setTimeout(r, 1000)); } } async disconnect() { - for (let i = this.nodes.length - 1; i >= 0; i--) { - const node = this.nodes[i]; + for (let i = this.nodeCtxs.length - 1; i >= 0; i--) { + const node = this.nodeCtxs[i]; await node.disconnect(); await new Promise(r => setTimeout(r, 1000)); } } startSync() { - for (const node of this.nodes) { - node.chain.synced = true; - node.chain.emit('full'); - node.startSync(); + for (const nodeCtx of this.nodeCtxs) { + nodeCtx.chain.synced = true; + nodeCtx.chain.emit('full'); + nodeCtx.node.startSync(); } } stopSync() { - for (const node of this.nodes) - node.stopSync(); + for (const nodeCtx of this.nodeCtxs) + nodeCtx.stopSync(); } async generate(index, blocks) { - const node = this.nodes[index]; + const nodeCtx = this.nodeCtxs[index]; - assert(node); + assert(nodeCtx); for (let i = 0; i < blocks; i++) { - const block = await node.miner.mineBlock(); - await node.chain.add(block); + const block = await nodeCtx.miner.mineBlock(); + await nodeCtx.chain.add(block); } } - height(index) { - const node = this.nodes[index]; - + context(index) { + const node = this.nodeCtxs[index]; assert(node); - - return node.chain.height; + return node; } - async sync() { - return new Promise(r => setTimeout(r, 3000)); + height(index) { + const nodeCtx = this.nodeCtxs[index]; + assert(nodeCtx); + return nodeCtx.height; } } diff --git a/test/wallet-http-test.js b/test/wallet-http-test.js index a6e81b409..6f1dd4d92 100644 --- a/test/wallet-http-test.js +++ b/test/wallet-http-test.js @@ -1,8 +1,6 @@ 'use strict'; -const {NodeClient, WalletClient} = require('../lib/client'); const Network = require('../lib/protocol/network'); -const FullNode = require('../lib/node/fullnode'); const MTX = require('../lib/primitives/mtx'); const {isSignatureEncoding, isKeyEncoding} = require('../lib/script/common'); const {Resource} = require('../lib/dns/resource'); @@ -17,34 +15,9 @@ const network = Network.get('regtest'); const assert = require('bsert'); const {BufferSet} = require('buffer-map'); const common = require('./util/common'); - -const node = new FullNode({ - network: 'regtest', - apiKey: 'foo', - walletAuth: true, - memory: true, - workers: true, - plugins: [require('../lib/wallet/plugin')] -}); - -const nclient = new NodeClient({ - port: network.rpcPort, - apiKey: 'foo' -}); - -const wclient = new WalletClient({ - port: network.walletPort, - apiKey: 'foo' -}); - -const {wdb} = node.require('walletdb'); -const wallet = wclient.wallet('primary'); -const wallet2 = wclient.wallet('secondary'); - -let name, cbAddress; -const accountTwo = 'foobar'; -const ownedNames = []; -const allNames = []; +const Outpoint = require('../lib/primitives/outpoint'); +const consensus = require('../lib/protocol/consensus'); +const NodeContext = require('./util/node-context'); const { treeInterval, @@ -56,1585 +29,1788 @@ const { describe('Wallet HTTP', function() { this.timeout(20000); - before(async () => { - await node.open(); - await nclient.open(); - await wclient.open(); + /** @type {NodeContext} */ + let nodeCtx; + let wclient, nclient; + + // primary wallet client. + let wallet, cbAddress; - await wclient.createWallet('secondary'); + const beforeAll = async () => { + nodeCtx = new NodeContext({ + apiKey: 'foo', + network: 'regtest', + walletAuth: true, + wallet: true + }); + + await nodeCtx.open(); + + wclient = nodeCtx.wclient; + nclient = nodeCtx.nclient; + + wallet = nodeCtx.wclient.wallet('primary'); cbAddress = (await wallet.createAddress('default')).address; - await wallet.createAccount(accountTwo); - }); + }; - after(async () => { - await nclient.close(); - await wclient.close(); - await node.close(); - }); + const afterAll = async () => { + await nodeCtx.close(); + }; - beforeEach(async () => { - name = await nclient.execute('grindname', [5]); - }); + describe('Create wallet', function() { + before(beforeAll); + after(afterAll); + + it('should create wallet', async () => { + const info = await wclient.createWallet('test'); + assert.strictEqual(info.id, 'test'); + const wallet = wclient.wallet('test', info.token); + await wallet.open(); + }); + + it('should create wallet with spanish mnemonic', async () => { + await wclient.createWallet( + 'cartera1', + {language: 'spanish'} + ); + const master = await wclient.getMaster('cartera1'); + const phrase = master.mnemonic.phrase; + for (const word of phrase.split(' ')) { + const language = Mnemonic.getLanguage(word); + assert.strictEqual(language, 'spanish'); + // Comprobar la cordura: + assert.notStrictEqual(language, 'english'); + } - afterEach(async () => { - await node.mempool.reset(); + // Verificar + await wclient.createWallet( + 'cartera2', + {mnemonic: phrase} + ); + assert.deepStrictEqual( + await wclient.getAccount('cartera1', 'default'), + await wclient.getAccount('cartera2', 'default') + ); + }); }); - it('should create wallet with spanish mnemonic', async () => { - await wclient.createWallet( - 'cartera1', - {language: 'spanish'} - ); - const master = await wclient.getMaster('cartera1'); - const phrase = master.mnemonic.phrase; - for (const word of phrase.split(' ')) { - const language = Mnemonic.getLanguage(word); - assert.strictEqual(language, 'spanish'); - // Comprobar la cordura: - assert.notStrictEqual(language, 'english'); - } - - // Verificar - await wclient.createWallet( - 'cartera2', - {mnemonic: phrase} - ); - assert.deepStrictEqual( - await wclient.getAccount('cartera1', 'default'), - await wclient.getAccount('cartera2', 'default') - ); - }); + describe('Lookahead', function() { + before(beforeAll); + after(afterAll); - it('should create wallet with default account 1000 lookahead', async () => { - const wname = 'lookahead'; - await wclient.createWallet(wname, { - lookahead: 1000 - }); + it('should create wallet with default account 1000 lookahead', async () => { + const wname = 'lookahead'; + await wclient.createWallet(wname, { + lookahead: 1000 + }); - const defAccount = await wclient.getAccount(wname, 'default'); - assert.strictEqual(defAccount.lookahead, 1000); + const defAccount = await wclient.getAccount(wname, 'default'); + assert.strictEqual(defAccount.lookahead, 1000); - const newAccount = await wclient.createAccount(wname, 'newaccount', { - lookahead: 1001 - }); - assert.strictEqual(newAccount.lookahead, 1001); - const getNewAccount = await wclient.getAccount(wname, 'newaccount', { - lookahead: 1001 + const newAccount = await wclient.createAccount(wname, 'newaccount', { + lookahead: 1001 + }); + assert.strictEqual(newAccount.lookahead, 1001); + const getNewAccount = await wclient.getAccount(wname, 'newaccount', { + lookahead: 1001 + }); + + assert.strictEqual(getNewAccount.lookahead, 1001); }); - assert.strictEqual(getNewAccount.lookahead, 1001); + it('should modify account lookahead to 1000', async () => { + const wname = 'lookahead2'; + await wclient.createWallet(wname); + + const defAccount = await wclient.getAccount(wname, 'default'); + assert.strictEqual(defAccount.lookahead, 200); + + const modified = await wclient.modifyAccount(wname, 'default', { + lookahead: 1000 + }); + assert.strictEqual(modified.lookahead, 1000); + }); }); - it('should modify account lookahead to 1000', async () => { - const wname = 'lookahead2'; - await wclient.createWallet(wname); + describe('Wallet info', function() { + let wallet; + + before(async () => { + await beforeAll(); - const defAccount = await wclient.getAccount(wname, 'default'); - assert.strictEqual(defAccount.lookahead, 200); + await wclient.createWallet('test'); + wallet = wclient.wallet('test'); + }); + after(afterAll); - const modified = await wclient.modifyAccount(wname, 'default', { - lookahead: 1000 + 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'); }); - assert.strictEqual(modified.lookahead, 1000); }); - it('should get key by address from watch-only', async () => { - const phrase = 'abandon abandon abandon abandon abandon abandon ' - + 'abandon abandon abandon abandon abandon about'; - const master = HD.HDPrivateKey.fromPhrase(phrase); - const xprv = master.deriveAccount(44, 5355, 5); - const xpub = xprv.toPublic(); - const pubkey = xpub.derive(0).derive(0); - const addr = Address.fromPubkey(pubkey.publicKey); - const wallet = wclient.wallet('watchonly'); - await wclient.createWallet('watchonly', { - watchOnly: true, - accountKey: xpub.xpubkey('regtest') - }); - const key = await wallet.getKey(addr.toString('regtest')); - assert.equal(xpub.childIndex ^ HD.common.HARDENED, key.account); - assert.equal(0, key.branch); - assert.equal(0, key.index); + describe('Key/Address', function() { + before(beforeAll); + after(afterAll); + + it('should get key by address from watch-only', async () => { + const phrase = 'abandon abandon abandon abandon abandon abandon ' + + 'abandon abandon abandon abandon abandon about'; + const master = HD.HDPrivateKey.fromPhrase(phrase); + const xprv = master.deriveAccount(44, 5355, 5); + const xpub = xprv.toPublic(); + const pubkey = xpub.derive(0).derive(0); + const addr = Address.fromPubkey(pubkey.publicKey); + const wallet = wclient.wallet('watchonly'); + await wclient.createWallet('watchonly', { + watchOnly: true, + accountKey: xpub.xpubkey('regtest') + }); + const key = await wallet.getKey(addr.toString('regtest')); + assert.equal(xpub.childIndex ^ HD.common.HARDENED, key.account); + assert.equal(0, key.branch); + assert.equal(0, key.index); + }); }); - it('should mine to the primary/default wallet', async () => { - const height = 20; + describe('Mine/Fund', function() { + before(beforeAll); + after(afterAll); - await mineBlocks(height, cbAddress); + it('should mine to the primary/default wallet', async () => { + const height = 20; - const info = await nclient.getInfo(); - assert.equal(info.chain.height, height); + await nodeCtx.mineBlocks(height, cbAddress); - const accountInfo = await wallet.getAccount('default'); - // each coinbase output was indexed - assert.equal(accountInfo.balance.coin, height); + const info = await nclient.getInfo(); + assert.equal(info.chain.height, height); - const coins = await wallet.getCoins(); - // the wallet has no previous history besides - // what it has mined - assert.ok(coins.every(coin => coin.coinbase === true)); - }); + const accountInfo = await wallet.getAccount('default'); + // each coinbase output was indexed + assert.equal(accountInfo.balance.coin, height); - it('should create a transaction', async () => { - const tx = await wallet.createTX({ - outputs: [{ address: cbAddress, value: 1e4 }] + const coins = await wallet.getCoins(); + // the wallet has no previous history besides + // what it has mined + assert.ok(coins.every(coin => coin.coinbase === true)); }); - - assert.ok(tx); - assert.equal(tx.outputs.length, 1 + 1); // send + change - assert.equal(tx.locktime, 0); }); - it('should create self-send transaction with HD paths', async () => { - const tx = await wallet.createTX({ - paths: true, - outputs: [{ address: cbAddress, value: 1e4 }] - }); + describe('Events', function() { + before(beforeAll); + after(afterAll); - assert.ok(tx); - assert.ok(tx.inputs); + it('balance address and tx events', async () => { + await wclient.createWallet('test'); + const testWallet = wclient.wallet('test'); + await testWallet.open(); + const {address} = await testWallet.createAddress('default'); - for (let i = 0; i < tx.inputs.length; i++) { - const path = tx.inputs[i].path; + const mtx = new MTX(); + mtx.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0)); + mtx.addOutput(address, 50460); + mtx.addOutput(address, 50460); + mtx.addOutput(address, 50460); + mtx.addOutput(address, 50460); - assert.ok(typeof path.name === 'string'); - assert.ok(typeof path.account === 'number'); - assert.ok(typeof path.change === 'boolean'); - assert.ok(typeof path.derivation === 'string'); - } + const tx = mtx.toTX(); - // cbAddress is a self-send - // so all output paths including change should be known - for (let i = 0; i < tx.outputs.length; i++) { - const path = tx.outputs[i].path; + let balance = null; + testWallet.once('balance', (b) => { + balance = b; + }); - assert.ok(typeof path.name === 'string'); - assert.ok(typeof path.account === 'number'); - assert.ok(typeof path.change === 'boolean'); - assert.ok(typeof path.derivation === 'string'); - } - }); + let receive = null; + testWallet.once('address', (r) => { + receive = r[0]; + }); - it('should create a transaction with HD paths', async () => { - const tx = await wallet.createTX({ - paths: true, - outputs: [{ - address: 'rs1qlf5se77y0xlg5940slyf00djvveskcsvj9sdrd', - value: 1e4 - }] - }); - - assert.ok(tx); - assert.ok(tx.inputs); - - for (let i = 0; i < tx.inputs.length; i++) { - const path = tx.inputs[i].path; - - assert.ok(typeof path.name === 'string'); - assert.ok(typeof path.account === 'number'); - assert.ok(typeof path.change === 'boolean'); - assert.ok(typeof path.derivation === 'string'); - } - { - const path = tx.outputs[1].path; // change - assert.ok(typeof path.name === 'string'); - assert.ok(typeof path.account === 'number'); - assert.ok(typeof path.change === 'boolean'); - assert.ok(typeof path.derivation === 'string'); - } - { - const path = tx.outputs[0].path; // receiver - assert(!path); - } + let details = null; + testWallet.once('tx', (d) => { + details = d; + }); + + await nodeCtx.wdb.addTX(tx); + await new Promise(r => setTimeout(r, 300)); + + assert(receive); + assert.strictEqual(receive.name, 'default'); + 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 create a transaction with a locktime', async () => { - const locktime = 8e6; + describe('Create/Send transaction', function() { + let wallet2; - const tx = await wallet.createTX({ - locktime: locktime, - outputs: [{ address: cbAddress, value: 1e4 }] + before(async () => { + await beforeAll(); + await nodeCtx.mineBlocks(20, cbAddress); + await wclient.createWallet('secondary'); + wallet2 = wclient.wallet('secondary'); }); - assert.equal(tx.locktime, locktime); - }); + after(afterAll); - it('should create a transaction that is not bip 69 sorted', async () => { - // create a list of outputs that descend in value - // bip 69 sorts in ascending order based on the value - const outputs = []; - for (let i = 0; i < 5; i++) { - const addr = await wallet.createAddress('default'); - outputs.push({ address: addr.address, value: (5 - i) * 1e5 }); - } + it('should create a transaction', async () => { + const tx = await wallet.createTX({ + outputs: [{ address: cbAddress, value: 1e4 }] + }); - const tx = await wallet.createTX({ - outputs: outputs, - sort: false + assert.ok(tx); + assert.equal(tx.outputs.length, 1 + 1); // send + change + assert.equal(tx.locktime, 0); }); - // assert outputs in the same order that they were sent from the client - for (const [i, output] of outputs.entries()) { - assert.equal(tx.outputs[i].value, output.value); - assert.equal(tx.outputs[i].address.toString(network), output.address); - } + it('should create self-send transaction with HD paths', async () => { + const tx = await wallet.createTX({ + paths: true, + outputs: [{ address: cbAddress, value: 1e4 }] + }); + + assert.ok(tx); + assert.ok(tx.inputs); - const mtx = MTX.fromJSON(tx); - mtx.sortMembers(); + for (let i = 0; i < tx.inputs.length; i++) { + const path = tx.inputs[i].path; - // the order changes after sorting - assert.ok(tx.outputs[0].value !== mtx.outputs[0].value); - }); + assert.ok(typeof path.name === 'string'); + assert.ok(typeof path.account === 'number'); + assert.ok(typeof path.change === 'boolean'); + assert.ok(typeof path.derivation === 'string'); + } - it('should create a transaction that is bip 69 sorted', async () => { - const outputs = []; - for (let i = 0; i < 5; i++) { - const addr = await wallet.createAddress('default'); - outputs.push({ address: addr.address, value: (5 - i) * 1e5 }); - } + // cbAddress is a self-send + // so all output paths including change should be known + for (let i = 0; i < tx.outputs.length; i++) { + const path = tx.outputs[i].path; - const tx = await wallet.createTX({ - outputs: outputs + assert.ok(typeof path.name === 'string'); + assert.ok(typeof path.account === 'number'); + assert.ok(typeof path.change === 'boolean'); + assert.ok(typeof path.derivation === 'string'); + } }); - const mtx = MTX.fromJSON(tx); - mtx.sortMembers(); + it('should create a transaction with HD paths', async () => { + const tx = await wallet.createTX({ + paths: true, + outputs: [{ + address: 'rs1qlf5se77y0xlg5940slyf00djvveskcsvj9sdrd', + value: 1e4 + }] + }); - // assert the ordering of the outputs is the - // same after sorting the response client side - for (const [i, output] of tx.outputs.entries()) { - assert.equal(output.value, mtx.outputs[i].value); - assert.equal(output.address, mtx.outputs[i].address.toString(network)); - } - }); + assert.ok(tx); + assert.ok(tx.inputs); - it('should mine to the secondary/default wallet', async () => { - const height = 5; + for (let i = 0; i < tx.inputs.length; i++) { + const path = tx.inputs[i].path; - const {address} = await wallet2.createAddress('default'); - await mineBlocks(height, address); + assert.ok(typeof path.name === 'string'); + assert.ok(typeof path.account === 'number'); + assert.ok(typeof path.change === 'boolean'); + assert.ok(typeof path.derivation === 'string'); + } + { + const path = tx.outputs[1].path; // change + assert.ok(typeof path.name === 'string'); + assert.ok(typeof path.account === 'number'); + assert.ok(typeof path.change === 'boolean'); + assert.ok(typeof path.derivation === 'string'); + } + { + const path = tx.outputs[0].path; // receiver + assert(!path); + } + }); - const accountInfo = await wallet2.getAccount('default'); - assert.equal(accountInfo.balance.coin, height); - }); + it('should create a transaction with a locktime', async () => { + const locktime = 8e6; - it('should have no name state indexed initially', async () => { - const names = await wallet.getNames(); + const tx = await wallet.createTX({ + locktime: locktime, + outputs: [{ address: cbAddress, value: 1e4 }] + }); - assert.strictEqual(names.length, 0); - }); + assert.equal(tx.locktime, locktime); + }); - it('should allow covenants with create tx', async () => { - const {address} = await wallet.createChange('default'); + it('should create a transaction that is not bip 69 sorted', async () => { + // create a list of outputs that descend in value + // bip 69 sorts in ascending order based on the value + const outputs = []; + for (let i = 0; i < 5; i++) { + const addr = await wallet.createAddress('default'); + outputs.push({ address: addr.address, value: (5 - i) * 1e5 }); + } - const output = openOutput(name, address); + const tx = await wallet.createTX({ + outputs: outputs, + sort: false + }); - const tx = await wallet.createTX({outputs: [output]}); - assert.equal(tx.outputs[0].covenant.type, types.OPEN); - }); + // assert outputs in the same order that they were sent from the client + for (const [i, output] of outputs.entries()) { + assert.equal(tx.outputs[i].value, output.value); + assert.equal(tx.outputs[i].address.toString(network), output.address); + } - it('should allow covenants with send tx', async () => { - const {address} = await wallet.createChange('default'); + const mtx = MTX.fromJSON(tx); + mtx.sortMembers(); - const output = openOutput(name, address); + // the order changes after sorting + assert.ok(tx.outputs[0].value !== mtx.outputs[0].value); + }); - const tx = await wallet.send({outputs: [output]});; - assert.equal(tx.outputs[0].covenant.type, types.OPEN); - }); + it('should create a transaction that is bip 69 sorted', async () => { + const outputs = []; + for (let i = 0; i < 5; i++) { + const addr = await wallet.createAddress('default'); + outputs.push({ address: addr.address, value: (5 - i) * 1e5 }); + } - it('should create an open and broadcast the tx', async () => { - let emitted = 0; - const handler = () => emitted++; - node.mempool.on('tx', handler); + const tx = await wallet.createTX({ + outputs: outputs + }); - const json = await wallet.createOpen({ - name: name + const mtx = MTX.fromJSON(tx); + mtx.sortMembers(); + + // assert the ordering of the outputs is the + // same after sorting the response client side + for (const [i, output] of tx.outputs.entries()) { + assert.equal(output.value, mtx.outputs[i].value); + assert.equal(output.address, mtx.outputs[i].address.toString(network)); + } }); - // wait for tx event on mempool - await common.forEvent(node.mempool, 'tx'); + it('should mine to the secondary/default wallet', async () => { + const height = 5; - const mempool = await nclient.getMempool(); + const {address} = await wallet2.createAddress('default'); + await nodeCtx.mineBlocks(height, address); - assert.ok(mempool.includes(json.hash)); + const accountInfo = await wallet2.getAccount('default'); + assert.equal(accountInfo.balance.coin, height); + }); + }); - const opens = json.outputs.filter(output => output.covenant.type === types.OPEN); - assert.equal(opens.length, 1); + describe('Get balance', function() { + before(async () => { + await beforeAll(); + await nodeCtx.mineBlocks(20, cbAddress); + }); - assert.equal(emitted, 1); + after(afterAll); - // reset for next test - node.mempool.removeListener('tx', handler); + it('should get balance', async () => { + const balance = await wallet.getBalance(); + assert.equal(balance.tx, 20); + assert.equal(balance.coin, 20); + }); }); - it('should create an open and not broadcast the transaction', async () => { - let entered = false; - const handler = () => entered = true; - node.mempool.on('tx', handler); + describe('Get TX', function() { + let hash; + + before(async () => { + await beforeAll(); + + await nodeCtx.mineBlocks(10, cbAddress); + const {address} = await wallet.createAddress('default'); + const tx = await wallet.send({outputs: [{address, value: 1e4}]}); - const json = await wallet.createOpen({ - name: name, - broadcast: false + hash = tx.hash; }); - await sleep(500); + after(afterAll); - // tx is not in the mempool - assert.equal(entered, false); - const mempool = await nclient.getMempool(); - assert.ok(!mempool.includes(json.hash)); + it('should fail to get TX that does not exist', async () => { + const hash = consensus.ZERO_HASH; + const tx = await wallet.getTX(hash.toString('hex')); + assert.strictEqual(tx, null); + }); - const mtx = MTX.fromJSON(json); - assert.ok(mtx.hasWitness()); + it('should get TX', async () => { + const tx = await wallet.getTX(hash.toString('hex')); + assert(tx); + assert.strictEqual(tx.hash, hash); + }); + }); - // the signature and pubkey are templated correctly - const sig = mtx.inputs[0].witness.get(0); - assert.ok(isSignatureEncoding(sig)); - const pubkey = mtx.inputs[0].witness.get(1); - assert.ok(isKeyEncoding(pubkey)); - assert.ok(secp256k1.publicKeyVerify(pubkey)); + describe('Create account (Integration)', function() { + before(beforeAll); + after(afterAll); + + it('should create an account', async () => { + const info = await wallet.createAccount('foo'); + assert(info); + assert(info.initialized); + assert.strictEqual(info.name, 'foo'); + 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('foo1'); + assert(info); + assert(info.initialized); + assert.strictEqual(info.name, 'foo1'); + assert.strictEqual(info.accountIndex, 2); + 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, 3); + assert.strictEqual(info.m, 1); + assert.strictEqual(info.n, 2); + }); + }); - // transaction is valid - assert.ok(mtx.verify()); + describe('Wallet auction (Integration)', function() { + const accountTwo = 'foobar'; - const opens = mtx.outputs.filter(output => output.covenant.type === types.OPEN); - assert.equal(opens.length, 1); + let name, wallet2; - // reset for next test - node.mempool.removeListener('tx', handler); - }); + const ownedNames = []; + const allNames = []; - it('should create an open and not sign the transaction', async () => { - let entered = false; - const handler = () => entered = true; - node.mempool.on('tx', handler); + before(async () => { + await beforeAll(); + + await nodeCtx.mineBlocks(20, cbAddress); + await wallet.createAccount(accountTwo); - const json = await wallet.createOpen({ - name: name, - broadcast: false, - sign: false + await wclient.createWallet('secondary'); + wallet2 = wclient.wallet('secondary'); + const saddr = (await wallet2.createAddress('default')).address; + await nodeCtx.mineBlocks(5, saddr); }); - await sleep(500); + after(afterAll); - // tx is not in the mempool - assert.equal(entered, false); - const mempool = await nclient.getMempool(); - assert.ok(!mempool.includes(json.hash)); + beforeEach(async () => { + name = await nclient.execute('grindname', [5]); + }); - // the signature is templated as an - // empty buffer - const mtx = MTX.fromJSON(json); - const sig = mtx.inputs[0].witness.get(0); - assert.bufferEqual(Buffer.from(''), sig); - assert.ok(!isSignatureEncoding(sig)); + afterEach(async () => { + await nodeCtx.mempool.reset(); + }); - // the pubkey is properly templated - const pubkey = mtx.inputs[0].witness.get(1); - assert.ok(isKeyEncoding(pubkey)); - assert.ok(secp256k1.publicKeyVerify(pubkey)); + it('should have no name state indexed initially', async () => { + const names = await wallet.getNames(); + assert.strictEqual(names.length, 0); + }); - // transaction not valid - assert.equal(mtx.verify(), false); + it('should allow covenants with create tx', async () => { + const {address} = await wallet.createChange('default'); - // reset for next test - node.mempool.removeListener('tx', handler); - }); + const output = openOutput(name, address); - it('should throw error with incompatible broadcast and sign options', async () => { - const fn = async () => await (wallet.createOpen({ - name: name, - broadcast: true, - sign: false - })); + const tx = await wallet.createTX({outputs: [output]}); + assert.equal(tx.outputs[0].covenant.type, types.OPEN); + }); - await assert.rejects(fn, {message: 'Must sign when broadcasting.'}); - }); + it('should allow covenants with send tx', async () => { + const {address} = await wallet.createChange('default'); - it('should fail to create open for account with no monies', async () => { - const info = await wallet.getAccount(accountTwo); - assert.equal(info.balance.tx, 0); - assert.equal(info.balance.coin, 0); + const output = openOutput(name, address); - const fn = async () => (await wallet.createOpen({ - name: name, - account: accountTwo - })); + const tx = await wallet.send({outputs: [output]});; + assert.equal(tx.outputs[0].covenant.type, types.OPEN); + }); - await assert.rejects(fn, {message: /Not enough funds./}); - }); + it('should create an open and broadcast the tx', async () => { + let emitted = 0; + const handler = () => emitted++; + nodeCtx.mempool.on('tx', handler); - it('should mine to the account with no monies', async () => { - const height = 5; + const mempoolTXEvent = common.forEvent(nodeCtx.mempool, 'tx'); + const json = await wallet.createOpen({ + name: name + }); + await mempoolTXEvent; - const {receiveAddress} = await wallet.getAccount(accountTwo); + const mempool = await nodeCtx.nclient.getMempool(); - await mineBlocks(height, receiveAddress); + assert.ok(mempool.includes(json.hash)); - const info = await wallet.getAccount(accountTwo); - assert.equal(info.balance.tx, height); - assert.equal(info.balance.coin, height); - }); + const opens = json.outputs.filter(output => output.covenant.type === types.OPEN); + assert.equal(opens.length, 1); - it('should create open for specific account', async () => { - const json = await wallet.createOpen({ - name: name, - account: accountTwo + assert.equal(emitted, 1); + + // reset for next test + nodeCtx.mempool.removeListener('tx', handler); }); - const info = await wallet.getAccount(accountTwo); + it('should create an open and not broadcast the transaction', async () => { + let entered = false; + const handler = () => entered = true; + nodeCtx.mempool.on('tx', handler); - // assert that each of the inputs belongs to the account - for (const {address} of json.inputs) { - const keyInfo = await wallet.getKey(address); - assert.equal(keyInfo.name, info.name); - } - }); + const json = await wallet.createOpen({ + name: name, + broadcast: false + }); - it('should open an auction', async () => { - await wallet.createOpen({ - name: name - }); + await sleep(200); + + // tx is not in the mempool + assert.equal(entered, false); + const mempool = await nclient.getMempool(); + assert.ok(!mempool.includes(json.hash)); - // save chain height for later comparison - const info = await nclient.getInfo(); + const mtx = MTX.fromJSON(json); + assert.ok(mtx.hasWitness()); - await mineBlocks(treeInterval + 1, cbAddress); + // the signature and pubkey are templated correctly + const sig = mtx.inputs[0].witness.get(0); + assert.ok(isSignatureEncoding(sig)); + const pubkey = mtx.inputs[0].witness.get(1); + assert.ok(isKeyEncoding(pubkey)); + assert.ok(secp256k1.publicKeyVerify(pubkey)); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + // transaction is valid + assert.ok(mtx.verify()); - const json = await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 + const opens = mtx.outputs.filter(output => output.covenant.type === types.OPEN); + assert.equal(opens.length, 1); + + // reset for next test + nodeCtx.mempool.removeListener('tx', handler); }); - const bids = json.outputs.filter(output => output.covenant.type === types.BID); - assert.equal(bids.length, 1); + it('should create an open and not sign the transaction', async () => { + let entered = false; + const handler = () => entered = true; + nodeCtx.mempool.on('tx', handler); - const [bid] = bids; - assert.equal(bid.covenant.items.length, 4); + const json = await wallet.createOpen({ + name: name, + broadcast: false, + sign: false + }); - const [nameHash, start, rawName, blind] = bid.covenant.items; - assert.equal(nameHash, rules.hashName(name).toString('hex')); + await sleep(200); - // initially opened in the first block mined, so chain.height + 1 - const hex = Buffer.from(start, 'hex').reverse().toString('hex'); - assert.equal(parseInt(hex, 16), info.chain.height + 1); + // tx is not in the mempool + assert.equal(entered, false); + const mempool = await nclient.getMempool(); + assert.ok(!mempool.includes(json.hash)); - assert.equal(rawName, Buffer.from(name, 'ascii').toString('hex')); + // the signature is templated as an + // empty buffer + const mtx = MTX.fromJSON(json); + const sig = mtx.inputs[0].witness.get(0); + assert.bufferEqual(Buffer.from(''), sig); + assert.ok(!isSignatureEncoding(sig)); - // blind is type string, so 32 * 2 - assert.equal(blind.length, 32 * 2); - }); + // the pubkey is properly templated + const pubkey = mtx.inputs[0].witness.get(1); + assert.ok(isKeyEncoding(pubkey)); + assert.ok(secp256k1.publicKeyVerify(pubkey)); - it('should be able to get nonce', async () => { - const bid = 100; + // transaction not valid + assert.equal(mtx.verify(), false); - const response = await wallet.getNonce(name, { - address: cbAddress, - bid: bid + // reset for next test + nodeCtx.mempool.removeListener('tx', handler); }); - const address = Address.fromString(cbAddress, network.type); - const nameHash = rules.hashName(name); + it('should throw error with incompatible broadcast and sign options', async () => { + const fn = async () => await (wallet.createOpen({ + name: name, + broadcast: true, + sign: false + })); - const primary = node.plugins.walletdb.wdb.primary; - const nonces = await primary.generateNonces(nameHash, address, bid); - const blinds = nonces.map(nonce => rules.blind(bid, nonce)); + await assert.rejects(fn, {message: 'Must sign when broadcasting.'}); + }); + + it('should fail to create open for account with no monies', async () => { + const info = await wallet.getAccount(accountTwo); + assert.equal(info.balance.tx, 0); + assert.equal(info.balance.coin, 0); + + const fn = async () => (await wallet.createOpen({ + name: name, + account: accountTwo + })); - assert.deepStrictEqual(response, { - address: address.toString(network.type), - blinds: blinds.map(blind => blind.toString('hex')), - nonces: nonces.map(nonce => nonce.toString('hex')), - bid: bid, - name: name, - nameHash: nameHash.toString('hex') + await assert.rejects(fn, {message: /Not enough funds./}); }); - }); - it('should be able to get nonce for bid=0', async () => { - const bid = 0; + it('should mine to the account with no monies', async () => { + const height = 5; + + const {receiveAddress} = await wallet.getAccount(accountTwo); + + await nodeCtx.mineBlocks(height, receiveAddress); - const response = await wallet.getNonce(name, { - address: cbAddress, - bid: bid + const info = await wallet.getAccount(accountTwo); + assert.equal(info.balance.tx, height); + assert.equal(info.balance.coin, height); }); - const address = Address.fromString(cbAddress, network.type); - const nameHash = rules.hashName(name); + it('should create open for specific account', async () => { + const json = await wallet.createOpen({ + name: name, + account: accountTwo + }); - const primary = node.plugins.walletdb.wdb.primary; - const nonces = await primary.generateNonces(nameHash, address, bid); - const blinds = nonces.map(nonce => rules.blind(bid, nonce)); + const info = await wallet.getAccount(accountTwo); - assert.deepStrictEqual(response, { - address: address.toString(network.type), - blinds: blinds.map(blind => blind.toString('hex')), - nonces: nonces.map(nonce => nonce.toString('hex')), - bid: bid, - name: name, - nameHash: nameHash.toString('hex') + // assert that each of the inputs belongs to the account + for (const {address} of json.inputs) { + const keyInfo = await wallet.getKey(address); + assert.equal(keyInfo.name, info.name); + } }); - }); - it('should get name info', async () => { - const names = await wallet.getNames(); + it('should open an auction', async () => { + await wallet.createOpen({ + name: name + }); - assert.strictEqual(allNames.length, names.length); + // save chain height for later comparison + const info = await nclient.getInfo(); - assert(names.length > 0); - const [ns] = names; + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const nameInfo = await wallet.getName(ns.name); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - assert.deepEqual(ns, nameInfo); - }); + const json = await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - it('should fail to open a bid without a bid value', async () => { - const fn = async () => (await wallet.createBid({ - name: name - })); + const bids = json.outputs.filter(output => output.covenant.type === types.BID); + assert.equal(bids.length, 1); - await assert.rejects(fn, {message: 'Bid is required.'}); - }); + const [bid] = bids; + assert.equal(bid.covenant.items.length, 4); - it('should fail to open a bid without a lockup value', async () => { - const fn = async () => (await wallet.createBid({ - name: name, - bid: 1000 - })); + const [nameHash, start, rawName, blind] = bid.covenant.items; + assert.equal(nameHash, rules.hashName(name).toString('hex')); - await assert.rejects(fn, {message: 'Lockup is required.'}); - }); + // initially opened in the first block mined, so chain.height + 1 + const hex = Buffer.from(start, 'hex').reverse().toString('hex'); + assert.equal(parseInt(hex, 16), info.chain.height + 1); + + assert.equal(rawName, Buffer.from(name, 'ascii').toString('hex')); - it('should send bid with 0 value and non-dust lockup', async () => { - await wallet.createOpen({ - name: name + // blind is type string, so 32 * 2 + assert.equal(blind.length, 32 * 2); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should be able to get nonce', async () => { + const bid = 100; - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + const response = await wallet.getNonce(name, { + address: cbAddress, + bid: bid + }); - await wallet.createBid({ - name: name, - bid: 0, - lockup: 1000 - }); - }); + const address = Address.fromString(cbAddress, network.type); + const nameHash = rules.hashName(name); + + const primary = nodeCtx.wdb.primary; + const nonces = await primary.generateNonces(nameHash, address, bid); + const blinds = nonces.map(nonce => rules.blind(bid, nonce)); - it('should fail to send bid with 0 value and 0 lockup', async () => { - await wallet.createOpen({ - name: name + assert.deepStrictEqual(response, { + address: address.toString(network.type), + blinds: blinds.map(blind => blind.toString('hex')), + nonces: nonces.map(nonce => nonce.toString('hex')), + bid: bid, + name: name, + nameHash: nameHash.toString('hex') + }); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should be able to get nonce for bid=0', async () => { + const bid = 0; - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + const response = await wallet.getNonce(name, { + address: cbAddress, + bid: bid + }); - const fn = async () => await wallet.createBid({ - name: name, - bid: 0, - lockup: 0 - }); + const address = Address.fromString(cbAddress, network.type); + const nameHash = rules.hashName(name); - await assert.rejects(fn, {message: 'Output is dust.'}); - }); + const primary = nodeCtx.wdb.primary; + const nonces = await primary.generateNonces(nameHash, address, bid); + const blinds = nonces.map(nonce => rules.blind(bid, nonce)); - it('should get all bids (single player)', async () => { - await wallet.createOpen({ - name: name + assert.deepStrictEqual(response, { + address: address.toString(network.type), + blinds: blinds.map(blind => blind.toString('hex')), + nonces: nonces.map(nonce => nonce.toString('hex')), + bid: bid, + name: name, + nameHash: nameHash.toString('hex') + }); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get name info', async () => { + const names = await wallet.getNames(); + + assert.strictEqual(allNames.length, names.length); + + assert(names.length > 0); + const [ns] = names; - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + const nameInfo = await wallet.getName(ns.name); - const tx1 = await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 + assert.deepEqual(ns, nameInfo); }); - const tx2 = await wallet.createBid({ - name: name, - bid: 2000, - lockup: 3000 + it('should fail to open a bid without a bid value', async () => { + const fn = async () => (await wallet.createBid({ + name: name + })); + + await assert.rejects(fn, {message: 'Bid is required.'}); }); - const tx3 = await wallet.createBid({ - name: name, - bid: 4000, - lockup: 5000 + it('should fail to open a bid without a lockup value', async () => { + const fn = async () => (await wallet.createBid({ + name: name, + bid: 1000 + })); + + await assert.rejects(fn, {message: 'Lockup is required.'}); }); - await mineBlocks(1, cbAddress); + it('should send bid with 0 value and non-dust lockup', async () => { + await wallet.createOpen({ + name: name + }); - // this method gets all bids for all names - const bids = await wallet.getBids(); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - // this depends on this it block creating - // the first bids of this test suite - assert.equal(bids.length, 3); - assert.ok(bids.every(bid => bid.name === name)); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - // tx1 - assert.ok(bids.find(bid => - (bid.value === 1000 - && bid.lockup === 2000 - && bid.prevout.hash === tx1.hash) - )); + await wallet.createBid({ + name: name, + bid: 0, + lockup: 1000 + }); + }); - // tx2 - assert.ok(bids.find(bid => - (bid.value === 2000 - && bid.lockup === 3000 - && bid.prevout.hash === tx2.hash) - )); + it('should fail to send bid with 0 value and 0 lockup', async () => { + await wallet.createOpen({ + name: name + }); - // tx3 - assert.ok(bids.find(bid => - (bid.value === 4000 - && bid.lockup === 5000 - && bid.prevout.hash === tx3.hash) - )); - }); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); + + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); + + const fn = async () => await wallet.createBid({ + name: name, + bid: 0, + lockup: 0 + }); - it('should get all bids (two players)', async () => { - await wallet.createOpen({ - name: name + await assert.rejects(fn, {message: 'Output is dust.'}); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get all bids (single player)', async () => { + await wallet.createOpen({ + name: name + }); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const tx1 = await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - const tx2 = await wallet2.createBid({ - name: name, - bid: 2000, - lockup: 3000 - }); + const tx1 = await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - await mineBlocks(1, cbAddress); + const tx2 = await wallet.createBid({ + name: name, + bid: 2000, + lockup: 3000 + }); + + const tx3 = await wallet.createBid({ + name: name, + bid: 4000, + lockup: 5000 + }); + + await nodeCtx.mineBlocks(1, cbAddress); - { - await sleep(100); - // fetch all bids for the name - const bids = await wallet.getBidsByName(name); - assert.equal(bids.length, 2); + // this method gets all bids for all names + const bids = await wallet.getBids(); - // there is no value property on bids - // from other wallets + // this depends on this it block creating + // the first bids of this test suite + assert.equal(bids.length, 3); + assert.ok(bids.every(bid => bid.name === name)); + + // tx1 assert.ok(bids.find(bid => - (bid.lockup === 2000 + (bid.value === 1000 + && bid.lockup === 2000 && bid.prevout.hash === tx1.hash) )); + // tx2 assert.ok(bids.find(bid => - (bid.lockup === 3000 + (bid.value === 2000 + && bid.lockup === 3000 && bid.prevout.hash === tx2.hash) )); - } - - { - // fetch only own bids for the name - const bids = await wallet.getBidsByName(name, {own: true}); - assert.equal(bids.length, 1); - const [bid] = bids; - assert.equal(bid.prevout.hash, tx1.hash); - } - }); - it('should create a reveal', async () => { - await wallet.createOpen({ - name: name + // tx3 + assert.ok(bids.find(bid => + (bid.value === 4000 + && bid.lockup === 5000 + && bid.prevout.hash === tx3.hash) + )); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get all bids (two players)', async () => { + await wallet.createOpen({ + name: name + }); + + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 + const tx1 = await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); + + const tx2 = await wallet2.createBid({ + name: name, + bid: 2000, + lockup: 3000 + }); + + await nodeCtx.mineBlocks(1, cbAddress); + + { + await sleep(100); + // fetch all bids for the name + const bids = await wallet.getBidsByName(name); + assert.equal(bids.length, 2); + + // there is no value property on bids + // from other wallets + assert.ok(bids.find(bid => + (bid.lockup === 2000 + && bid.prevout.hash === tx1.hash) + )); + + assert.ok(bids.find(bid => + (bid.lockup === 3000 + && bid.prevout.hash === tx2.hash) + )); + } + + { + // fetch only own bids for the name + const bids = await wallet.getBidsByName(name, {own: true}); + assert.equal(bids.length, 1); + const [bid] = bids; + assert.equal(bid.prevout.hash, tx1.hash); + } }); - await mineBlocks(biddingPeriod + 1, cbAddress); + it('should create a reveal', async () => { + await wallet.createOpen({ + name: name + }); - const {info} = await nclient.execute('getnameinfo', [name]); - assert.equal(info.name, name); - assert.equal(info.state, 'REVEAL'); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const json = await wallet.createReveal({ - name: name + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); + + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); + + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); + + const {info} = await nclient.execute('getnameinfo', [name]); + assert.equal(info.name, name); + assert.equal(info.state, 'REVEAL'); + + const json = await wallet.createReveal({ + name: name + }); + + const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL); + assert.equal(reveals.length, 1); }); - const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL); - assert.equal(reveals.length, 1); - }); + it('should create all reveals', async () => { + await wallet.createOpen({ + name: name + }); + + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); + + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); + + for (let i = 0; i < 3; i++) { + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); + } + + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - it('should create all reveals', async () => { - await wallet.createOpen({ - name: name + const {info} = await nclient.execute('getnameinfo', [name]); + assert.equal(info.name, name); + assert.equal(info.state, 'REVEAL'); + + const json = await wallet.createReveal(); + + const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL); + assert.equal(reveals.length, 3); }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get all reveals (single player)', async () => { + await wallet.createOpen({ + name: name + }); + + const name2 = await nclient.execute('grindname', [5]); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + await wallet.createOpen({ + name: name2 + }); + + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); + + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); + allNames.push(name2); - for (let i = 0; i < 3; i++) { await wallet.createBid({ name: name, bid: 1000, lockup: 2000 }); - } - await mineBlocks(biddingPeriod + 1, cbAddress); + await wallet.createBid({ + name: name2, + bid: 2000, + lockup: 3000 + }); - const {info} = await nclient.execute('getnameinfo', [name]); - assert.equal(info.name, name); - assert.equal(info.state, 'REVEAL'); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - const json = await wallet.createReveal(); + await wallet.createReveal({ + name: name + }); - const reveals = json.outputs.filter(output => output.covenant.type === types.REVEAL); - assert.equal(reveals.length, 3); - }); + await wallet.createReveal({ + name: name2 + }); - it('should get all reveals (single player)', async () => { - await wallet.createOpen({ - name: name - }); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - const name2 = await nclient.execute('grindname', [5]); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); + ownedNames.push(name2); - await wallet.createOpen({ - name: name2 + { + const reveals = await wallet.getReveals(); + assert.equal(reveals.length, 2); + } + + { + // a single reveal per name + const reveals = await wallet.getRevealsByName(name); + assert.equal(reveals.length, 1); + } }); - await mineBlocks(treeInterval + 1, cbAddress); + // this test creates namestate to use duing the + // next test, hold on to the name being used. + const state = { + name: '', + bids: [], + reveals: [] + }; - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); - allNames.push(name2); + it('should get own reveals (two players)', async () => { + state.name = name; - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + await wallet.createOpen({ + name: name + }); - await wallet.createBid({ - name: name2, - bid: 2000, - lockup: 3000 - }); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - await mineBlocks(biddingPeriod + 1, cbAddress); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await wallet.createReveal({ - name: name - }); + const b1 = await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - await wallet.createReveal({ - name: name2 - }); + const b2 = await wallet2.createBid({ + name: name, + bid: 2000, + lockup: 3000 + }); - await mineBlocks(revealPeriod + 1, cbAddress); + state.bids.push(b1); + state.bids.push(b2); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); - ownedNames.push(name2); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - { - const reveals = await wallet.getReveals(); - assert.equal(reveals.length, 2); - } + const r1 = await wallet.createReveal({ + name: name + }); - { - // a single reveal per name - const reveals = await wallet.getRevealsByName(name); - assert.equal(reveals.length, 1); - } - }); + const r2 = await wallet2.createReveal({ + name: name + }); - // this test creates namestate to use duing the - // next test, hold on to the name being used. - const state = { - name: '', - bids: [], - reveals: [] - }; + state.reveals.push(r1); + state.reveals.push(r2); + + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); + + // wallet did not win this auction so name is not pushed to ownedNames[] + + { + const reveals = await wallet.getRevealsByName(name, {own: true}); + assert.equal(reveals.length, 1); + const [reveal] = reveals; + assert.equal(reveal.own, true); + assert.equal(reveal.prevout.hash, r1.hash); + } + + { + const reveals = await wallet.getRevealsByName(name); + assert.equal(reveals.length, 2); - it('should get own reveals (two players)', async () => { - state.name = name; + assert.ok(reveals.find(reveal => + reveal.prevout.hash === r1.hash + )); - await wallet.createOpen({ - name: name + assert.ok(reveals.find(reveal => + reveal.prevout.hash === r2.hash + )); + } }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get auction info', async () => { + const ns = await wallet.getName(state.name); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); - - const b1 = await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + const auction = await wallet.getAuctionByName(ns.name); - const b2 = await wallet2.createBid({ - name: name, - bid: 2000, - lockup: 3000 - }); + // auction info returns a list of bids + // and a list of reveals for the name + assert.ok(Array.isArray(auction.bids)); + assert.ok(Array.isArray(auction.reveals)); - state.bids.push(b1); - state.bids.push(b2); + // 2 bids and 2 reveals in the previous test + assert.equal(auction.bids.length, 2); + assert.equal(auction.reveals.length, 2); - await mineBlocks(biddingPeriod + 1, cbAddress); + // ordering can be nondeterministic + function matchTxId(namestates, target) { + assert.ok(namestates.find(ns => ns.prevout.hash === target)); + } - const r1 = await wallet.createReveal({ - name: name + matchTxId(auction.bids, state.bids[0].hash); + matchTxId(auction.bids, state.bids[1].hash); + matchTxId(auction.reveals, state.reveals[0].hash); + matchTxId(auction.reveals, state.reveals[1].hash); }); - const r2 = await wallet2.createReveal({ - name: name - }); + it('should create a bid and a reveal (reveal in advance)', async () => { + const balanceBeforeTest = await wallet.getBalance(); + const lockConfirmedBeforeTest = balanceBeforeTest.lockedConfirmed; + const lockUnconfirmedBeforeTest = balanceBeforeTest.lockedUnconfirmed; - state.reveals.push(r1); - state.reveals.push(r2); + await wallet.createOpen({ name: name }); - await mineBlocks(revealPeriod + 1, cbAddress); + await nodeCtx.mineBlocks(treeInterval + 2, cbAddress); - // wallet did not win this auction so name is not pushed to ownedNames[] + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - { - const reveals = await wallet.getRevealsByName(name, {own: true}); - assert.equal(reveals.length, 1); - const [reveal] = reveals; - assert.equal(reveal.own, true); - assert.equal(reveal.prevout.hash, r1.hash); - } + const balanceBeforeBid = await wallet.getBalance(); + assert.equal(balanceBeforeBid.lockedConfirmed - lockConfirmedBeforeTest, 0); + assert.equal( + balanceBeforeBid.lockedUnconfirmed - lockUnconfirmedBeforeTest, + 0 + ); - { - const reveals = await wallet.getRevealsByName(name); - assert.equal(reveals.length, 2); + const bidValue = 1000000; + const lockupValue = 5000000; - assert.ok(reveals.find(reveal => - reveal.prevout.hash === r1.hash - )); + const auctionTxs = await wallet.client.post( + `/wallet/${wallet.id}/auction`, + { + name: name, + bid: 1000000, + lockup: 5000000, + broadcastBid: true + } + ); - assert.ok(reveals.find(reveal => - reveal.prevout.hash === r2.hash - )); - } - }); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - it('should get auction info', async () => { - const ns = await wallet.getName(state.name); + let walletAuction = await wallet.getAuctionByName(name); + const bidFromWallet = walletAuction.bids.find( + b => b.prevout.hash === auctionTxs.bid.hash + ); + assert(bidFromWallet); - const auction = await wallet.getAuctionByName(ns.name); + const { info } = await nclient.execute('getnameinfo', [name]); + assert.equal(info.name, name); + assert.equal(info.state, 'REVEAL'); - // auction info returns a list of bids - // and a list of reveals for the name - assert.ok(Array.isArray(auction.bids)); - assert.ok(Array.isArray(auction.reveals)); + const b5 = await wallet.getBalance(); + assert.equal(b5.lockedConfirmed - lockConfirmedBeforeTest, lockupValue); + assert.equal(b5.lockedUnconfirmed - lockUnconfirmedBeforeTest, lockupValue); - // 2 bids and 2 reveals in the previous test - assert.equal(auction.bids.length, 2); - assert.equal(auction.reveals.length, 2); + await nclient.broadcast(auctionTxs.reveal.hex); + await nodeCtx.mineBlocks(1, cbAddress); - // ordering can be nondeterministic - function matchTxId(namestates, target) { - assert.ok(namestates.find(ns => ns.prevout.hash === target)); - } + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - matchTxId(auction.bids, state.bids[0].hash); - matchTxId(auction.bids, state.bids[1].hash); - matchTxId(auction.reveals, state.reveals[0].hash); - matchTxId(auction.reveals, state.reveals[1].hash); - }); + walletAuction = await wallet.getAuctionByName(name); + const revealFromWallet = walletAuction.reveals.find( + b => b.prevout.hash === auctionTxs.reveal.hash + ); + assert(revealFromWallet); - it('should create a bid and a reveal (reveal in advance)', async () => { - const balanceBeforeTest = await wallet.getBalance(); - const lockConfirmedBeforeTest = balanceBeforeTest.lockedConfirmed; - const lockUnconfirmedBeforeTest = balanceBeforeTest.lockedUnconfirmed; + const b6 = await wallet.getBalance(); + assert.equal(b6.lockedConfirmed - lockConfirmedBeforeTest, bidValue); + assert.equal(b6.lockedUnconfirmed - lockUnconfirmedBeforeTest, bidValue); - await wallet.createOpen({ name: name }); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - await mineBlocks(treeInterval + 2, cbAddress); + const ns = await nclient.execute('getnameinfo', [name]); + const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index); + assert.ok(coin); + }); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + it('should create a redeem', async () => { + await wallet.createOpen({ + name: name + }); - const balanceBeforeBid = await wallet.getBalance(); - assert.equal(balanceBeforeBid.lockedConfirmed - lockConfirmedBeforeTest, 0); - assert.equal( - balanceBeforeBid.lockedUnconfirmed - lockUnconfirmedBeforeTest, - 0 - ); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const bidValue = 1000000; - const lockupValue = 5000000; + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - const auctionTxs = await wallet.client.post( - `/wallet/${wallet.id}/auction`, - { + // wallet2 wins the auction, wallet can submit redeem + await wallet.createBid({ name: name, - bid: 1000000, - lockup: 5000000, - broadcastBid: true - } - ); - - await mineBlocks(biddingPeriod + 1, cbAddress); + bid: 1000, + lockup: 2000 + }); - let walletAuction = await wallet.getAuctionByName(name); - const bidFromWallet = walletAuction.bids.find( - b => b.prevout.hash === auctionTxs.bid.hash - ); - assert(bidFromWallet); + await wallet2.createBid({ + name: name, + bid: 2000, + lockup: 3000 + }); - const { info } = await nclient.execute('getnameinfo', [name]); - assert.equal(info.name, name); - assert.equal(info.state, 'REVEAL'); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - const b5 = await wallet.getBalance(); - assert.equal(b5.lockedConfirmed - lockConfirmedBeforeTest, lockupValue); - assert.equal(b5.lockedUnconfirmed - lockUnconfirmedBeforeTest, lockupValue); + await wallet.createReveal({ + name: name + }); - await nclient.broadcast(auctionTxs.reveal.hex); - await mineBlocks(1, cbAddress); + await wallet2.createReveal({ + name: name + }); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - walletAuction = await wallet.getAuctionByName(name); - const revealFromWallet = walletAuction.reveals.find( - b => b.prevout.hash === auctionTxs.reveal.hash - ); - assert(revealFromWallet); + // wallet did not win this auction so name is not pushed to ownedNames[] - const b6 = await wallet.getBalance(); - assert.equal(b6.lockedConfirmed - lockConfirmedBeforeTest, bidValue); - assert.equal(b6.lockedUnconfirmed - lockUnconfirmedBeforeTest, bidValue); + // wallet2 is the winner, therefore cannot redeem + const fn = async () => (await wallet2.createRedeem({ + name: name + })); - await mineBlocks(revealPeriod + 1, cbAddress); + await assert.rejects( + fn, + {message: `No reveals to redeem for name: ${name}.`} + ); - const ns = await nclient.execute('getnameinfo', [name]); - const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index); - assert.ok(coin); - }); + const json = await wallet.createRedeem({ + name: name + }); - it('should create a redeem', async () => { - await wallet.createOpen({ - name: name + const redeem = json.outputs.filter(({covenant}) => covenant.type === types.REDEEM); + assert.ok(redeem.length > 0); }); - await mineBlocks(treeInterval + 1, cbAddress); - - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + it('should create an update', async () => { + await wallet.createOpen({ + name: name + }); - // wallet2 wins the auction, wallet can submit redeem - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - await wallet2.createBid({ - name: name, - bid: 2000, - lockup: 3000 - }); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await mineBlocks(biddingPeriod + 1, cbAddress); + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - await wallet.createReveal({ - name: name - }); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - await wallet2.createReveal({ - name: name - }); + await wallet.createReveal({ + name: name + }); - await mineBlocks(revealPeriod + 1, cbAddress); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - // wallet did not win this auction so name is not pushed to ownedNames[] + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - // wallet2 is the winner, therefore cannot redeem - const fn = async () => (await wallet2.createRedeem({ - name: name - })); + { + const json = await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['foobar'] + } + ] + } + }); - await assert.rejects( - fn, - {message: `No reveals to redeem for name: ${name}.`} - ); + // register directly after reveal + const registers = json.outputs.filter(({covenant}) => covenant.type === types.REGISTER); + assert.equal(registers.length, 1); + } - const json = await wallet.createRedeem({ - name: name - }); + // mine a block + await nodeCtx.mineBlocks(1, cbAddress); - const redeem = json.outputs.filter(({covenant}) => covenant.type === types.REDEEM); - assert.ok(redeem.length > 0); - }); + { + const json = await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['barfoo'] + } + ] + } + }); - it('should create an update', async () => { - await wallet.createOpen({ - name: name + // update after register or update + const updates = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE); + assert.equal(updates.length, 1); + } }); - await mineBlocks(treeInterval + 1, cbAddress); + it('should get name resource', async () => { + const names = await wallet.getNames(); + // filter out names that have data + // this test depends on the previous test + const [ns] = names.filter(n => n.data.length > 0); + assert(ns); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + const state = Resource.decode(Buffer.from(ns.data, 'hex')); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 + const resource = await wallet.getResource(ns.name); + assert(resource); + const res = Resource.fromJSON(resource); + + assert.deepEqual(state, res); }); - await mineBlocks(biddingPeriod + 1, cbAddress); + it('should fail to get name resource for non existent name', async () => { + const name = await nclient.execute('grindname', [10]); - await wallet.createReveal({ - name: name + const resource = await wallet.getResource(name); + assert.equal(resource, null); }); - await mineBlocks(revealPeriod + 1, cbAddress); + it('should create a renewal', async () => { + await wallet.createOpen({ + name: name + }); + + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - { - const json = await wallet.createUpdate({ + await wallet.createBid({ name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] - } + bid: 1000, + lockup: 2000 }); - // register directly after reveal - const registers = json.outputs.filter(({covenant}) => covenant.type === types.REGISTER); - assert.equal(registers.length, 1); - } + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); + + await wallet.createReveal({ + name: name + }); + + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - // mine a block - await mineBlocks(1, cbAddress); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - { - const json = await wallet.createUpdate({ + await wallet.createUpdate({ name: name, data: { records: [ { type: 'TXT', - txt: ['barfoo'] + txt: ['foobar'] } ] } }); - // update after register or update - const updates = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE); - assert.equal(updates.length, 1); - } - }); - - it('should get name resource', async () => { - const names = await wallet.getNames(); - // filter out names that have data - // this test depends on the previous test - const [ns] = names.filter(n => n.data.length > 0); - assert(ns); + // mine up to the earliest point in which a renewal + // can be submitted, a treeInterval into the future + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const state = Resource.decode(Buffer.from(ns.data, 'hex')); - - const resource = await wallet.getResource(ns.name); - assert(resource); - const res = Resource.fromJSON(resource); + const json = await wallet.createRenewal({ + name + }); - assert.deepEqual(state, res); - }); + const updates = json.outputs.filter(({covenant}) => covenant.type === types.RENEW); + assert.equal(updates.length, 1); + }); - it('should fail to get name resource for non existent name', async () => { - const name = await nclient.execute('grindname', [10]); + it('should create a transfer', async () => { + await wallet.createOpen({ + name: name + }); - const resource = await wallet.getResource(name); - assert.equal(resource, null); - }); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - it('should create a renewal', async () => { - await wallet.createOpen({ - name: name - }); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await mineBlocks(treeInterval + 1, cbAddress); + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + await wallet.createReveal({ + name: name + }); - await mineBlocks(biddingPeriod + 1, cbAddress); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - await wallet.createReveal({ - name: name - }); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - await mineBlocks(revealPeriod + 1, cbAddress); + await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['foobar'] + } + ] + } + }); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - await wallet.createUpdate({ - name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] - } - }); + const {receiveAddress} = await wallet.getAccount(accountTwo); - // mine up to the earliest point in which a renewal - // can be submitted, a treeInterval into the future - await mineBlocks(treeInterval + 1, cbAddress); + const json = await wallet.createTransfer({ + name, + address: receiveAddress + }); - const json = await wallet.createRenewal({ - name + const xfer = json.outputs.filter(({covenant}) => covenant.type === types.TRANSFER); + assert.equal(xfer.length, 1); }); - const updates = json.outputs.filter(({covenant}) => covenant.type === types.RENEW); - assert.equal(updates.length, 1); - }); - - it('should create a transfer', async () => { - await wallet.createOpen({ - name: name - }); + it('should create a finalize', async () => { + await wallet.createOpen({ + name: name + }); - await mineBlocks(treeInterval + 1, cbAddress); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - await mineBlocks(biddingPeriod + 1, cbAddress); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - await wallet.createReveal({ - name: name - }); + await wallet.createReveal({ + name: name + }); - await mineBlocks(revealPeriod + 1, cbAddress); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - await wallet.createUpdate({ - name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] - } - }); + await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['foobar'] + } + ] + } + }); - await mineBlocks(treeInterval + 1, cbAddress); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const {receiveAddress} = await wallet.getAccount(accountTwo); + const {receiveAddress} = await wallet2.getAccount('default'); - const json = await wallet.createTransfer({ - name, - address: receiveAddress - }); + await wallet.createTransfer({ + name, + address: receiveAddress + }); - const xfer = json.outputs.filter(({covenant}) => covenant.type === types.TRANSFER); - assert.equal(xfer.length, 1); - }); + await nodeCtx.mineBlocks(transferLockup + 1, cbAddress); - it('should create a finalize', async () => { - await wallet.createOpen({ - name: name - }); + const json = await wallet.createFinalize({ + name + }); - await mineBlocks(treeInterval + 1, cbAddress); + const final = json.outputs.filter(({covenant}) => covenant.type === types.FINALIZE); + assert.equal(final.length, 1); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + await nodeCtx.mineBlocks(1, cbAddress); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + // Confirmed FINALIZE means this wallet is not the owner anymore! + ownedNames.splice(ownedNames.indexOf(name), 1); - await mineBlocks(biddingPeriod + 1, cbAddress); + const ns = await nclient.execute('getnameinfo', [name]); + const coin = await nclient.getCoin(ns.info.owner.hash, ns.info.owner.index); - await wallet.createReveal({ - name: name + assert.equal(coin.address, receiveAddress); }); - await mineBlocks(revealPeriod + 1, cbAddress); + it('should create a cancel', async () => { + await wallet.createOpen({ + name: name + }); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - await wallet.createUpdate({ - name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] - } - }); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await mineBlocks(treeInterval + 1, cbAddress); + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - const {receiveAddress} = await wallet2.getAccount('default'); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - await wallet.createTransfer({ - name, - address: receiveAddress - }); + await wallet.createReveal({ + name: name + }); - await mineBlocks(transferLockup + 1, cbAddress); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - const json = await wallet.createFinalize({ - name - }); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - const final = json.outputs.filter(({covenant}) => covenant.type === types.FINALIZE); - assert.equal(final.length, 1); + await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['foobar'] + } + ] + } + }); - await mineBlocks(1, cbAddress); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - // Confirmed FINALIZE means this wallet is not the owner anymore! - ownedNames.splice(ownedNames.indexOf(name), 1); + const {receiveAddress} = await wallet.getAccount(accountTwo); - const ns = await nclient.execute('getnameinfo', [name]); - const coin = await nclient.getCoin(ns.info.owner.hash, ns.info.owner.index); + await wallet.createTransfer({ + name, + address: receiveAddress + }); - assert.equal(coin.address, receiveAddress); - }); + await nodeCtx.mineBlocks(transferLockup + 1, cbAddress); - it('should create a cancel', async () => { - await wallet.createOpen({ - name: name - }); + const json = await wallet.createCancel({name}); - await mineBlocks(treeInterval + 1, cbAddress); + const cancel = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE); + assert.equal(cancel.length, 1); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + await nodeCtx.mineBlocks(1, cbAddress); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 - }); + const ns = await nclient.execute('getnameinfo', [name]); + assert.equal(ns.info.name, name); - await mineBlocks(biddingPeriod + 1, cbAddress); + const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index); + assert.ok(coin); - await wallet.createReveal({ - name: name + const keyInfo = await wallet.getKey(coin.address); + assert.ok(keyInfo); }); - await mineBlocks(revealPeriod + 1, cbAddress); - - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); - - await wallet.createUpdate({ - name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] - } - }); + it('should create a revoke', async () => { + await wallet.createOpen({ + name: name + }); - await mineBlocks(treeInterval + 1, cbAddress); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const {receiveAddress} = await wallet.getAccount(accountTwo); + // Confirmed OPEN adds name to wallet's namemap + allNames.push(name); - await wallet.createTransfer({ - name, - address: receiveAddress - }); + await wallet.createBid({ + name: name, + bid: 1000, + lockup: 2000 + }); - await mineBlocks(transferLockup + 1, cbAddress); + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); - const json = await wallet.createCancel({name}); + await wallet.createReveal({ + name: name + }); - const cancel = json.outputs.filter(({covenant}) => covenant.type === types.UPDATE); - assert.equal(cancel.length, 1); + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); - await mineBlocks(1, cbAddress); + // Confirmed REVEAL with highest bid makes wallet the owner + ownedNames.push(name); - const ns = await nclient.execute('getnameinfo', [name]); - assert.equal(ns.info.name, name); + await wallet.createUpdate({ + name: name, + data: { + records: [ + { + type: 'TXT', + txt: ['foobar'] + } + ] + } + }); - const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index); - assert.ok(coin); + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); - const keyInfo = await wallet.getKey(coin.address); - assert.ok(keyInfo); - }); + const json = await wallet.createRevoke({name}); - it('should create a revoke', async () => { - await wallet.createOpen({ - name: name - }); + const final = json.outputs.filter(({covenant}) => covenant.type === types.REVOKE); + assert.equal(final.length, 1); - await mineBlocks(treeInterval + 1, cbAddress); + await nodeCtx.mineBlocks(1, cbAddress); - // Confirmed OPEN adds name to wallet's namemap - allNames.push(name); + // Confirmed REVOKE means no one owns this name anymore + ownedNames.splice(ownedNames.indexOf(name), 1); - await wallet.createBid({ - name: name, - bid: 1000, - lockup: 2000 + const ns = await nclient.execute('getnameinfo', [name]); + assert.equal(ns.info.name, name); + assert.equal(ns.info.state, 'REVOKED'); }); - await mineBlocks(biddingPeriod + 1, cbAddress); + it('should require passphrase for auction TXs', async () => { + const passphrase = 'BitDNS!5353'; + await wclient.createWallet('lockedWallet', {passphrase}); + const lockedWallet = await wclient.wallet('lockedWallet'); - await wallet.createReveal({ - name: name + // Fast-forward through the default 60-second unlock timeout + async function lock() { + const wallet = await nodeCtx.wdb.get('lockedWallet'); + return wallet.lock(); + } + await lock(); + + // Wallet is created and encrypted + const info = await lockedWallet.getInfo(); + assert(info); + assert(info.master.encrypted); + + // Fund + const addr = await lockedWallet.createAddress('default'); + await nodeCtx.mineBlocks(10, addr.address); + await common.forValue(nodeCtx.wdb, 'height', nodeCtx.chain.height); + const bal = await lockedWallet.getBalance(); + assert(bal.confirmed > 0); + + // Open + await assert.rejects( + lockedWallet.createOpen({name}), + {message: 'No passphrase.'} + ); + + await lockedWallet.createOpen({name, passphrase}); + await lock(); + + await nodeCtx.mineBlocks(treeInterval + 1, cbAddress); + + // Bid + await assert.rejects( + lockedWallet.createBid({name, lockup: 1, bid: 1}), + {message: 'No passphrase.'} + ); + + // Send multiple bids, wallet remains unlocked for 60 seconds (all 3 bids) + await lockedWallet.createBid( + {name, lockup: 1000000, bid: 1000000, passphrase} + ); + await lockedWallet.createBid({name, lockup: 2000000, bid: 2000000}); + await lockedWallet.createBid({name, lockup: 3000000, bid: 3000000}); + await lock(); + + await nodeCtx.mineBlocks(biddingPeriod + 1, cbAddress); + + // Reveal + await assert.rejects( + lockedWallet.createReveal({name}), + {message: 'No passphrase.'} + ); + const revealAll = await lockedWallet.createReveal({name, passphrase}); + await lock(); + + // All 3 bids are revealed + const reveals = revealAll.outputs.filter( + output => output.covenant.type === types.REVEAL + ); + assert.equal(reveals.length, 3); + + await nodeCtx.mineBlocks(revealPeriod + 1, cbAddress); + + // Redeem all by not passing specific name + await assert.rejects( + lockedWallet.createRedeem(), + {message: 'No passphrase.'} + ); + const redeemAll = await lockedWallet.createRedeem({passphrase}); + await lock(); + + // Only 2 reveals are redeemed (because the third one is the winner) + const redeems = redeemAll.outputs.filter( + output => output.covenant.type === types.REDEEM + ); + assert.equal(redeems.length, 2); + + // Register + await assert.rejects( + lockedWallet.createUpdate({name, data: {records: []}}), + {message: 'No passphrase.'} + ); + const register = await lockedWallet.createUpdate( + {name, data: {records: []}, passphrase} + ); + await lock(); + + // Only 1 register, only 1 winner! + const registers = register.outputs.filter( + output => output.covenant.type === types.REGISTER + ); + assert.equal(registers.length, 1); }); - await mineBlocks(revealPeriod + 1, cbAddress); + it('should get all wallet names', async () => { + const names = await wallet.getNames(); - // Confirmed REVEAL with highest bid makes wallet the owner - ownedNames.push(name); + assert.equal(allNames.length, names.length); - await wallet.createUpdate({ - name: name, - data: { - records: [ - { - type: 'TXT', - txt: ['foobar'] - } - ] + for (const {name} of names) { + assert(allNames.includes(name)); } }); - await mineBlocks(treeInterval + 1, cbAddress); - - const json = await wallet.createRevoke({name}); - - const final = json.outputs.filter(({covenant}) => covenant.type === types.REVOKE); - assert.equal(final.length, 1); - - await mineBlocks(1, cbAddress); - - // Confirmed REVOKE means no one owns this name anymore - ownedNames.splice(ownedNames.indexOf(name), 1); - - const ns = await nclient.execute('getnameinfo', [name]); - assert.equal(ns.info.name, name); - assert.equal(ns.info.state, 'REVOKED'); - }); - - it('should require passphrase for auction TXs', async () => { - const passphrase = 'BitDNS!5353'; - await wclient.createWallet('lockedWallet', {passphrase}); - const lockedWallet = await wclient.wallet('lockedWallet'); - - // Fast-forward through the default 60-second unlock timeout - async function lock() { - const wallet = await wdb.get('lockedWallet'); - return wallet.lock(); - } - await lock(); - - // Wallet is created and encrypted - const info = await lockedWallet.getInfo(); - assert(info); - assert(info.master.encrypted); - - // Fund - const addr = await lockedWallet.createAddress('default'); - await mineBlocks(10, addr.address); - await common.forValue(wdb, 'height', node.chain.height); - const bal = await lockedWallet.getBalance(); - assert(bal.confirmed > 0); - - // Open - await assert.rejects( - lockedWallet.createOpen({name}), - {message: 'No passphrase.'} - ); - - await lockedWallet.createOpen({name, passphrase}); - await lock(); - - await mineBlocks(treeInterval + 1, cbAddress); - - // Bid - await assert.rejects( - lockedWallet.createBid({name, lockup: 1, bid: 1}), - {message: 'No passphrase.'} - ); - - // Send multiple bids, wallet remains unlocked for 60 seconds (all 3 bids) - await lockedWallet.createBid( - {name, lockup: 1000000, bid: 1000000, passphrase} - ); - await lockedWallet.createBid({name, lockup: 2000000, bid: 2000000}); - await lockedWallet.createBid({name, lockup: 3000000, bid: 3000000}); - await lock(); - - await mineBlocks(biddingPeriod + 1, cbAddress); - - // Reveal - await assert.rejects( - lockedWallet.createReveal({name}), - {message: 'No passphrase.'} - ); - const revealAll = await lockedWallet.createReveal({name, passphrase}); - await lock(); - - // All 3 bids are revealed - const reveals = revealAll.outputs.filter( - output => output.covenant.type === types.REVEAL - ); - assert.equal(reveals.length, 3); - - await mineBlocks(revealPeriod + 1, cbAddress); - - // Redeem all by not passing specific name - await assert.rejects( - lockedWallet.createRedeem(), - {message: 'No passphrase.'} - ); - const redeemAll = await lockedWallet.createRedeem({passphrase}); - await lock(); - - // Only 2 reveals are redeemed (because the third one is the winner) - const redeems = redeemAll.outputs.filter( - output => output.covenant.type === types.REDEEM - ); - assert.equal(redeems.length, 2); - - // Register - await assert.rejects( - lockedWallet.createUpdate({name, data: {records: []}}), - {message: 'No passphrase.'} - ); - const register = await lockedWallet.createUpdate( - {name, data: {records: []}, passphrase} - ); - await lock(); - - // Only 1 register, only 1 winner! - const registers = register.outputs.filter( - output => output.covenant.type === types.REGISTER - ); - assert.equal(registers.length, 1); - }); - - it('should get all wallet names', async () => { - const names = await wallet.getNames(); - - assert.equal(allNames.length, names.length); - - for (const {name} of names) { - assert(allNames.includes(name)); - } - }); - - it('should only get wallet-owned names', async () => { - // TODO: convert to using hs-client method - // when wallet.getNames() allows `options` - const names = await wallet.client.get(`/wallet/${wallet.id}/name`, {own: true}); + it('should only get wallet-owned names', async () => { + // TODO: convert to using hs-client method + // when wallet.getNames() allows `options` + const names = await wallet.client.get(`/wallet/${wallet.id}/name`, {own: true}); - assert.equal(names.length, ownedNames.length); + assert.equal(names.length, ownedNames.length); - for (const {name} of names) { - assert(ownedNames.includes(name)); - } + for (const {name} of names) { + assert(ownedNames.includes(name)); + } + }); }); describe('HTTP tx races (Integration)', function() { const WNAME1 = 'racetest-1'; const WNAME2 = 'racetest-2'; - const rcwallet1 = wclient.wallet(WNAME1); - const rcwallet2 = wclient.wallet(WNAME2); const FUND_VALUE = 1e6; const HARD_FEE = 1e4; const NAMES = []; const PASSPHRASE1 = 'racetest-passphrase-1'; const PASSPHRASE2 = 'racetest-passphrase-2'; + let rcwallet1, rcwallet2, wclient; let w1addr; - const fundNcoins = async (recvWallet, n, value = FUND_VALUE) => { - assert(typeof n === 'number'); - for (let i = 0; i < n; i++) { - const addr = (await recvWallet.createAddress('default')).address; - - await wallet.send({ - hardFee: HARD_FEE, - outputs: [{ - address: addr, - value: value - }] - }); - } - - const blockConnects = common.forEvent(wdb, 'block connect', 1); - await mineBlocks(1, w1addr); - await blockConnects; - }; - const checkDoubleSpends = (txs) => { const spentCoins = new BufferSet(); @@ -1651,12 +1827,35 @@ describe('Wallet HTTP', function() { }; const wMineBlocks = async (n = 1) => { - const forConnect = common.forEvent(wdb, 'block connect', n); - await mineBlocks(n, w1addr); + const forConnect = common.forEvent(nodeCtx.wdb, 'block connect', n); + await nodeCtx.mineBlocks(n, w1addr); await forConnect; }; + const fundNcoins = async (recvWallet, n, value = FUND_VALUE) => { + assert(typeof n === 'number'); + for (let i = 0; i < n; i++) { + const addr = (await recvWallet.createAddress('default')).address; + + await wallet.send({ + hardFee: HARD_FEE, + outputs: [{ + address: addr, + value: value + }] + }); + } + + await wMineBlocks(1); + }; + before(async () => { + await beforeAll(); + + wclient = nodeCtx.wclient; + rcwallet1 = wclient.wallet(WNAME1); + rcwallet2 = wclient.wallet(WNAME2); + w1addr = (await wallet.createAddress('default')).address; const winfo1 = await wclient.createWallet(WNAME1, { passphrase: PASSPHRASE1 @@ -1673,6 +1872,8 @@ describe('Wallet HTTP', function() { await wMineBlocks(5); }); + after(afterAll); + beforeEach(async () => { await rcwallet1.lock(); await rcwallet2.lock(); @@ -1683,7 +1884,7 @@ describe('Wallet HTTP', function() { await fundNcoins(rcwallet1, 3); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.send({ @@ -1715,11 +1916,11 @@ describe('Wallet HTTP', function() { await fundNcoins(rcwallet1, 3); for (let i = 0; i < 3; i++) - NAMES.push(rules.grindName(10, node.chain.tip.height, network)); + NAMES.push(rules.grindName(10, nodeCtx.chain.tip.height, network)); const promises = []; - const forMemTX = common.forEvent(node.mempool, 'tx', 4); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createOpen({ @@ -1762,7 +1963,7 @@ describe('Wallet HTTP', function() { // this is 2 blocks ahead, but does not matter for this test. await wMineBlocks(network.names.treeInterval + 1); - const forMemTX = common.forEvent(node.mempool, 'tx', 3 + 3 * 2); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3 + 3 * 2); for (let i = 0; i < 3; i++) { // make sure we use ALL coins, no NONE left. @@ -1814,14 +2015,14 @@ describe('Wallet HTTP', function() { it('should reveal 3 times and reveal all', async () => { // Now we don't have fees to reveal. Fund these fees. - fundNcoins(rcwallet1, 3, HARD_FEE); - fundNcoins(rcwallet2, 1, HARD_FEE); + await fundNcoins(rcwallet1, 3, HARD_FEE); + await fundNcoins(rcwallet2, 1, HARD_FEE); const promises = []; await wMineBlocks(network.names.biddingPeriod); - const forMemTX = common.forEvent(node.mempool, 'tx', 4); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 4); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createReveal({ @@ -1866,7 +2067,7 @@ describe('Wallet HTTP', function() { // double spend. await fundNcoins(rcwallet1, 3, HARD_FEE); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createUpdate({ @@ -1897,7 +2098,7 @@ describe('Wallet HTTP', function() { await fundNcoins(rcwallet2, 3, HARD_FEE); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 2; i++) { promises.push(rcwallet2.createRedeem({ @@ -1923,7 +2124,7 @@ describe('Wallet HTTP', function() { await wMineBlocks(network.names.treeInterval); await fundNcoins(rcwallet1, 3, HARD_FEE); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createRenewal({ @@ -1946,7 +2147,7 @@ describe('Wallet HTTP', function() { await fundNcoins(rcwallet1, 3, HARD_FEE); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); const addrs = [ (await rcwallet2.createAddress('default')).address, @@ -1975,7 +2176,7 @@ describe('Wallet HTTP', function() { await fundNcoins(rcwallet1, 3, HARD_FEE); - const forMemTX = common.forEvent(node.mempool, 'tx', 3); + const forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createCancel({ @@ -1995,7 +2196,7 @@ describe('Wallet HTTP', function() { it('should finalize 3 names', async () => { await fundNcoins(rcwallet1, 6, HARD_FEE); - let forMemTX = common.forEvent(node.mempool, 'tx', 3); + let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); const addrs = [ (await rcwallet2.createAddress('default')).address, @@ -2018,7 +2219,7 @@ describe('Wallet HTTP', function() { // Now we finalize all. const promises = []; - forMemTX = common.forEvent(node.mempool, 'tx', 3); + forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); for (let i = 0; i < 3; i++) { promises.push(rcwallet1.createFinalize({ @@ -2040,7 +2241,7 @@ describe('Wallet HTTP', function() { // send them back await fundNcoins(rcwallet2, 6, HARD_FEE); - let forMemTX = common.forEvent(node.mempool, 'tx', 3); + let forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); const addrs = [ (await rcwallet1.createAddress('default')).address, @@ -2060,7 +2261,7 @@ describe('Wallet HTTP', function() { await forMemTX; await wMineBlocks(network.names.transferLockup); - forMemTX = common.forEvent(node.mempool, 'tx', 3); + forMemTX = common.forEvent(nodeCtx.mempool, 'tx', 3); const promises = []; for (let i = 0; i < 3; i++) { @@ -2083,18 +2284,6 @@ async function sleep(time) { return new Promise(resolve => setTimeout(resolve, time)); } -// 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); - } -} - // create an OPEN output function openOutput(name, address) { const nameHash = rules.hashName(name); diff --git a/test/wallet-rescan-test.js b/test/wallet-rescan-test.js index 3e0b997d8..0be8a1e1a 100644 --- a/test/wallet-rescan-test.js +++ b/test/wallet-rescan-test.js @@ -1,10 +1,26 @@ 'use strict'; const assert = require('bsert'); -const FullNode = require('../lib/node/fullnode'); -const WalletPlugin = require('../lib/wallet/plugin'); const Network = require('../lib/protocol/network'); -const {forEvent} = require('./util/common'); +const NodeContext = require('./util/node-context'); + +// Setup: +// - Standalone Node (no wallet) responsible for progressing network. +// - Wallet Node (with wallet) responsible for rescanning. +// - Wallet SPV Node (with wallet) responsible for rescanning. +// - Wallet Standalone Node responsible for rescanning. +// - Wallet SPV Standalone Node responsible for rescanning. +// +// Test cases: +// - TX deeper depth -> TX shallower depth for derivation (Second tx is discovered first) +// - TX with outputs -> deeper, deep, shallow - derivation depths. +// (Outputs are discovered from shallower to deeper) +// - Replicate both transactions in the same block on rescan. +// - Replicate both transactions when receiving tip. +// +// If per block derivation lookahead is higher than wallet lookahed +// recovery is impossible. This tests situation where in block +// derivation depth is lower than wallet lookahead. // TODO: Rewrite using util/node from the interactive rescan test. // TODO: Add the standalone Wallet variation. @@ -13,42 +29,26 @@ const {forEvent} = require('./util/common'); describe('Wallet rescan', function() { const network = Network.get('regtest'); - let node, wdb; - - const beforeAll = async () => { - node = new FullNode({ + describe('Deadlock', function() { + const nodeCtx = new NodeContext({ memory: true, network: 'regtest', - plugins: [WalletPlugin] + wallet: true }); - node.on('error', (err) => { - assert(false, err); - }); - - wdb = node.require('walletdb').wdb; - const wdbSynced = forEvent(wdb, 'sync done'); - - await node.open(); - await wdbSynced; - }; - - const afterAll = async () => { - await node.close(); - node = null; - wdb = null; - }; - - describe('Deadlock', function() { - let address; + let address, node, wdb; before(async () => { - await beforeAll(); + node = nodeCtx.node; + wdb = nodeCtx.wdb; + await nodeCtx.open(); address = await wdb.primary.receiveAddress(); }); - after(afterAll); + after(async () => { + await nodeCtx.close(); + }); it('should generate 10 blocks', async () => { await node.rpc.generateToAddress([10, address.toString(network)]);