text: /* eslint-env mocha */
  /* eslint prefer-arrow-callback: "off" */
  
  'use strict';
  
  const assert = require('./util/assert');
  const consensus = require('../lib/protocol/consensus');
  const util = require('../lib/utils/util');
  const hash256 = require('bcrypto/lib/hash256');
  const random = require('bcrypto/lib/random');
  const WalletDB = require('../lib/wallet/walletdb');
  const WorkerPool = require('../lib/workers/workerpool');
  const Address = require('../lib/primitives/address');
  const MTX = require('../lib/primitives/mtx');
  const Coin = require('../lib/primitives/coin');
  const KeyRing = require('../lib/primitives/keyring');
  const Input = require('../lib/primitives/input');
  const Outpoint = require('../lib/primitives/outpoint');
  const Script = require('../lib/script/script');
  const HD = require('../lib/hd');
  
  const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
    + 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
  
  const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
    + 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
  
  const enabled = true;
  const workers = new WorkerPool({ enabled });
  const wdb = new WalletDB({ workers });
  
  let currentWallet = null;
  let importedWallet = null;
  let importedKey = null;
  let doubleSpendWallet = null;
  let doubleSpendCoin = null;
  
  function fromU32(num) {
    const data = Buffer.allocUnsafe(4);
    data.writeUInt32LE(num, 0, true);
    return data;
  }
  
  function curBlock(wdb) {
    return fakeBlock(wdb.state.height);
  };
  
  function nextBlock(wdb) {
    return fakeBlock(wdb.state.height + 1);
  }
  
  function fakeBlock(height) {
    const prev = hash256.digest(fromU32((height - 1) >>> 0));
    const hash = hash256.digest(fromU32(height >>> 0));
    const root = hash256.digest(fromU32((height | 0x80000000) >>> 0));
  
    return {
      hash: hash,
      prevBlock: prev,
      merkleRoot: root,
      time: 500000000 + (height * (10 * 60)),
      bits: 0,
      nonce: 0,
      height: height
    };
  }
  
  function dummyInput() {
    const hash = random.randomBytes(32);
    return Input.fromOutpoint(new Outpoint(hash, 0));
  }
  
  async function testP2PKH(witness, nesting) {
    const flags = Script.flags.STANDARD_VERIFY_FLAGS;
    const receiveAddress = nesting ? 'nestedAddress' : 'receiveAddress';
    const type = witness ? Address.types.WITNESS : Address.types.PUBKEYHASH;
    const wallet = await wdb.create({ witness });
  
    const waddr = await wallet.receiveAddress();
    const addr = Address.fromString(waddr.toString(wdb.network), wdb.network);
  
    assert.strictEqual(addr.type, type);
    assert.strictEqual(addr.type, waddr.type);
  
    const src = new MTX();
    src.addInput(dummyInput());
    src.addOutput(await wallet[receiveAddress](), 5460 * 2);
    src.addOutput(new Address(), 2 * 5460);
  
    const mtx = new MTX();
    mtx.addTX(src, 0);
    mtx.addOutput(await wallet.receiveAddress(), 5460);
  
    await wallet.sign(mtx);
  
    const [tx, view] = mtx.commit();
  
    assert(tx.verify(view, flags));
  }
  
  async function testP2SH(witness, nesting) {
    const flags = Script.flags.STANDARD_VERIFY_FLAGS;
    const receiveAddress = nesting ? 'nestedAddress' : 'receiveAddress';
    const receiveDepth = nesting ? 'nestedDepth' : 'receiveDepth';
    const vector = witness ? 'witness' : 'script';
  
    // Create 3 2-of-3 wallets with our pubkeys as "shared keys"
    const options = {
      witness,
      type: 'multisig',
      m: 2,
      n: 3
    };
  
    const alice = await wdb.create(options);
    const bob = await wdb.create(options);
    const carol = await wdb.create(options);
    const recipient = await wdb.create();
  
    await alice.addSharedKey(0, await bob.accountKey(0));
    await alice.addSharedKey(0, await carol.accountKey(0));
  
    await bob.addSharedKey(0, await alice.accountKey(0));
    await bob.addSharedKey(0, await carol.accountKey(0));
  
    await carol.addSharedKey(0, await alice.accountKey(0));
    await carol.addSharedKey(0, await bob.accountKey(0));
  
    // Our p2sh address
    const addr1 = await alice[receiveAddress]();
  
    if (witness) {
      const type = nesting ? Address.types.SCRIPTHASH : Address.types.WITNESS;
      assert.strictEqual(addr1.type, type);
    } else {
      assert.strictEqual(addr1.type, Address.types.SCRIPTHASH);
    }
  
    assert((await alice[receiveAddress]()).equals(addr1));
    assert((await bob[receiveAddress]()).equals(addr1));
    assert((await carol[receiveAddress]()).equals(addr1));
  
    const nestedAddr1 = await alice.nestedAddress();
  
    if (witness) {
      assert(nestedAddr1);
      assert((await alice.nestedAddress()).equals(nestedAddr1));
      assert((await bob.nestedAddress()).equals(nestedAddr1));
      assert((await carol.nestedAddress()).equals(nestedAddr1));
    }
  
    {
      // Add a shared unspent transaction to our wallets
      const fund = new MTX();
      fund.addInput(dummyInput());
      fund.addOutput(nesting ? nestedAddr1 : addr1, 5460 * 10);
  
      // Simulate a confirmation
      assert.strictEqual(await alice[receiveDepth](), 1);
  
      await wdb.addBlock(nextBlock(wdb), [fund.toTX()]);
  
      assert.strictEqual(await alice[receiveDepth](), 2);
      assert.strictEqual(await alice.changeDepth(), 1);
    }
  
    const addr2 = await alice[receiveAddress]();
    assert(!addr2.equals(addr1));
  
    assert((await alice[receiveAddress]()).equals(addr2));
    assert((await bob[receiveAddress]()).equals(addr2));
    assert((await carol[receiveAddress]()).equals(addr2));
  
    // Create a tx requiring 2 signatures
    const send = new MTX();
  
    send.addOutput(await recipient.receiveAddress(), 5460);
  
    assert(!send.verify(flags));
  
    await alice.fund(send, {
      rate: 10000,
      round: true
    });
  
    await alice.sign(send);
  
    assert(!send.verify(flags));
  
    await bob.sign(send);
  
    const [tx, view] = send.commit();
    assert(tx.verify(view, flags));
  
    assert.strictEqual(await alice.changeDepth(), 1);
  
    const change = await alice.changeAddress();
  
    assert((await alice.changeAddress()).equals(change));
    assert((await bob.changeAddress()).equals(change));
    assert((await carol.changeAddress()).equals(change));
  
    // Simulate a confirmation
    {
      await wdb.addBlock(nextBlock(wdb), [tx]);
  
      assert.strictEqual(await alice[receiveDepth](), 2);
      assert.strictEqual(await alice.changeDepth(), 2);
  
      assert((await alice[receiveAddress]()).equals(addr2));
      assert(!(await alice.changeAddress()).equals(change));
    }
  
    const change2 = await alice.changeAddress();
  
    assert((await alice.changeAddress()).equals(change2));
    assert((await bob.changeAddress()).equals(change2));
    assert((await carol.changeAddress()).equals(change2));
  
    const input = tx.inputs[0];
    input[vector].setData(2, Buffer.alloc(73, 0x00));
    input[vector].compile();
  
    assert(!tx.verify(view, flags));
    assert.strictEqual(tx.getFee(view), 10000);
  }
  
  describe('Wallet', function() {
    this.timeout(5000);
  
    before(async () => {
      await wdb.open();
    });
  
    after(async () => {
      await wdb.close();
    });
  
    it('should open walletdb', () => {
      consensus.COINBASE_MATURITY = 0;
    });
  
    it('should generate new key and address', async () => {
      const wallet = await wdb.create();
  
      const addr1 = await wallet.receiveAddress();
      assert(addr1);
  
      const str = addr1.toString(wdb.network);
      const addr2 = Address.fromString(str, wdb.network);
  
      assert(addr2.equals(addr1));
    });
  
    it('should validate existing address', () => {
      assert(Address.fromString('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc', 'main'));
    });
  
    it('should fail to validate invalid address', () => {
      assert.throws(() => {
        Address.fromString('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc', 'main');
      });
    });
  
    it('should create and get wallet', async () => {
      const wallet1 = await wdb.create();
      const wallet2 = await wdb.get(wallet1.id);
      assert(wallet1 === wallet2);
    });
  
    it('should sign/verify p2pkh tx', async () => {
      await testP2PKH(false, false);
    });
  
    it('should sign/verify p2wpkh tx', async () => {
      await testP2PKH(true, false);
    });
  
    it('should sign/verify p2wpkh tx w/ nested bullshit', async () => {
      await testP2PKH(true, true);
    });
  
    it('should multisign/verify TX', async () => {
      const wallet = await wdb.create({
        type: 'multisig',
        m: 1,
        n: 2
      });
  
      const xpriv = HD.PrivateKey.generate();
      const key = xpriv.deriveAccount(44, 0, 0).toPublic();
  
      await wallet.addSharedKey(0, key);
  
      const script = Script.fromMultisig(1, 2, [
        (await wallet.receiveKey()).publicKey,
        key.derivePath('m/0/0').publicKey
      ]);
  
      // Input transaction (bare 1-of-2 multisig)
      const src = new MTX();
      src.addInput(dummyInput());
      src.addOutput(script, 5460 * 2);
      src.addOutput(new Address(), 5460 * 2);
  
      const tx = new MTX();
      tx.addTX(src, 0);
      tx.addOutput(await wallet.receiveAddress(), 5460);
  
      const maxSize = await tx.estimateSize();
  
      await wallet.sign(tx);
  
      assert(tx.toRaw().length <= maxSize);
      assert(tx.verify());
    });
  
    it('should handle missed txs', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      // balance: 51000
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 50000);
      t1.addOutput(await alice.receiveAddress(), 1000);
  
      const t2 = new MTX();
      t2.addTX(t1, 0); // 50000
      t2.addOutput(await alice.receiveAddress(), 24000);
      t2.addOutput(await alice.receiveAddress(), 24000);
  
      // Save for later.
      doubleSpendWallet = alice;
      doubleSpendCoin = Coin.fromTX(t1, 0, -1);
  
      // balance: 49000
      await alice.sign(t2);
  
      const t3 = new MTX();
      t3.addTX(t1, 1); // 1000
      t3.addTX(t2, 0); // 24000
      t3.addOutput(await alice.receiveAddress(), 23000);
  
      // balance: 47000
      await alice.sign(t3);
  
      const t4 = new MTX();
      t4.addTX(t2, 1); // 24000
      t4.addTX(t3, 0); // 23000
      t4.addOutput(await alice.receiveAddress(), 11000);
      t4.addOutput(await alice.receiveAddress(), 11000);
  
      // balance: 22000
      await alice.sign(t4);
  
      const f1 = new MTX();
      f1.addTX(t4, 1); // 11000
      f1.addOutput(await bob.receiveAddress(), 10000);
  
      // balance: 11000
      await alice.sign(f1);
  
      {
        await wdb.addTX(t4.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 22000);
      }
  
      {
        await wdb.addTX(t1.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 73000);
      }
  
      {
        await wdb.addTX(t2.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 71000);
      }
  
      {
        await wdb.addTX(t3.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 69000);
      }
  
      {
        await wdb.addTX(f1.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 58000);
  
        const txs = await alice.getHistory();
        assert(txs.some((wtx) => {
          return wtx.hash.equals(f1.hash());
        }));
      }
  
      {
        const balance = await bob.getBalance();
        assert.strictEqual(balance.unconfirmed, 10000);
  
        const txs = await bob.getHistory();
        assert(txs.some((wtx) => {
          return wtx.tx.hash().equals(f1.hash());
        }));
      }
  
      // Should recover from missed txs on block.
      await wdb.addBlock(nextBlock(wdb), [
        t1.toTX(),
        t2.toTX(),
        t3.toTX(),
        t4.toTX(),
        f1.toTX()
      ]);
  
      {
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 11000);
        assert.strictEqual(balance.confirmed, 11000);
  
        const txs = await alice.getHistory();
        assert(txs.some((wtx) => {
          return wtx.hash.equals(f1.hash());
        }));
      }
  
      {
        const balance = await bob.getBalance();
        assert.strictEqual(balance.unconfirmed, 10000);
        assert.strictEqual(balance.confirmed, 10000);
  
        const txs = await bob.getHistory();
        assert(txs.some((wtx) => {
          return wtx.tx.hash().equals(f1.hash());
        }));
      }
    });
  
    it('should cleanup spenders after double-spend', async () => {
      const wallet = doubleSpendWallet;
  
      // Reorg and unconfirm all previous txs.
      await wdb.removeBlock(curBlock(wdb));
  
      {
        const txs = await wallet.getHistory();
        assert.strictEqual(txs.length, 5);
  
        const total = txs.reduce((t, wtx) => {
          return t + wtx.tx.getOutputValue();
        }, 0);
        assert.strictEqual(total, 154000);
      }
  
      {
        const balance = await wallet.getBalance();
        assert.strictEqual(balance.unconfirmed, 11000);
        assert.strictEqual(balance.confirmed, 0);
      }
  
      {
        const tx = new MTX();
        tx.addCoin(doubleSpendCoin);
        tx.addOutput(await wallet.receiveAddress(), 5000);
  
        await wallet.sign(tx);
  
        await wdb.addTX(tx.toTX());
  
        const balance = await wallet.getBalance();
        assert.strictEqual(balance.unconfirmed, 6000);
      }
  
      {
        const txs = await wallet.getHistory();
        assert.strictEqual(txs.length, 2);
  
        const total = txs.reduce((t, wtx) => {
          return t + wtx.tx.getOutputValue();
        }, 0);
        assert.strictEqual(total, 56000);
      }
    });
  
    it('should handle double-spend (not our input)', async () => {
      const wallet = await wdb.create();
  
      const t1 = new MTX();
      const input = dummyInput();
      t1.addInput(input);
      t1.addOutput(await wallet.receiveAddress(), 50000);
      await wdb.addTX(t1.toTX());
      assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
  
      let conflict = false;
      wallet.on('conflict', () => {
        conflict = true;
      });
  
      const t2 = new MTX();
      t2.addInput(input);
      t2.addOutput(new Address(), 5000);
      await wdb.addTX(t2.toTX());
      assert(conflict);
      assert.strictEqual((await wallet.getBalance()).unconfirmed, 0);
    });
  
    it('should handle more missed txs', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 50000);
      t1.addOutput(await alice.receiveAddress(), 1000);
  
      // balance: 51000
  
      const t2 = new MTX();
      t2.addTX(t1, 0); // 50000
      t2.addOutput(await alice.receiveAddress(), 24000);
      t2.addOutput(await alice.receiveAddress(), 24000);
  
      // balance: 49000
      await alice.sign(t2);
  
      const t3 = new MTX();
      t3.addTX(t1, 1); // 1000
      t3.addTX(t2, 0); // 24000
      t3.addOutput(await alice.receiveAddress(), 23000);
  
      // balance: 47000
      await alice.sign(t3);
  
      const t4 = new MTX();
      t4.addTX(t2, 1); // 24000
      t4.addTX(t3, 0); // 23000
      t4.addOutput(await alice.receiveAddress(), 11000);
      t4.addOutput(await alice.receiveAddress(), 11000);
  
      // balance: 22000
      await alice.sign(t4);
  
      const f1 = new MTX();
      f1.addTX(t4, 1); // 11000
      f1.addOutput(await bob.receiveAddress(), 10000);
  
      // balance: 11000
      await alice.sign(f1);
  
      {
        await wdb.addTX(t4.toTX());
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 22000);
      }
  
      {
        await wdb.addTX(t1.toTX());
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 73000);
      }
  
      {
        await wdb.addTX(t2.toTX());
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 71000);
      }
  
      {
        await wdb.addTX(t3.toTX());
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 69000);
      }
  
      {
        await wdb.addTX(f1.toTX());
  
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 58000);
  
        const txs = await alice.getHistory();
        assert(txs.some((wtx) => {
          return wtx.tx.hash().equals(f1.hash());
        }));
      }
  
      {
        const balance = await bob.getBalance();
        assert.strictEqual(balance.unconfirmed, 10000);
  
        const txs = await bob.getHistory();
        assert(txs.some((wtx) => {
          return wtx.tx.hash().equals(f1.hash());
        }));
      }
  
      // Should recover from missed txs on block.
      await wdb.addBlock(nextBlock(wdb), [
        t1.toTX(),
        t2.toTX(),
        t3.toTX(),
        t4.toTX(),
        f1.toTX()
      ]);
  
      {
        const balance = await alice.getBalance();
        assert.strictEqual(balance.unconfirmed, 11000);
      }
  
      {
        const balance = await bob.getBalance();
        assert.strictEqual(balance.unconfirmed, 10000);
      }
    });
  
    it('should fill tx with inputs', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Create new transaction
      const m2 = new MTX();
      m2.addOutput(await bob.receiveAddress(), 5460);
  
      await alice.fund(m2, {
        rate: 10000,
        round: true
      });
  
      await alice.sign(m2);
  
      const [t2, v2] = m2.commit();
  
      assert(t2.verify(v2));
  
      assert.strictEqual(t2.getInputValue(v2), 16380);
      assert.strictEqual(t2.getOutputValue(), 6380);
      assert.strictEqual(t2.getFee(v2), 10000);
  
      // Create new transaction
      const t3 = new MTX();
      t3.addOutput(await bob.receiveAddress(), 15000);
  
      let err;
      try {
        await alice.fund(t3, {
          rate: 10000,
          round: true
        });
      } catch (e) {
        err = e;
      }
  
      assert(err);
      assert.strictEqual(err.requiredFunds, 25000);
    });
  
    it('should fill tx with inputs with accurate fee', async () => {
      const alice = await wdb.create({
        master: KEY1
      });
  
      const bob = await wdb.create({
        master: KEY2
      });
  
      // Coinbase
      const t1 = new MTX();
      t1.addOutpoint(new Outpoint(consensus.ZERO_HASH, 0));
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Create new transaction
      const m2 = new MTX();
      m2.addOutput(await bob.receiveAddress(), 5460);
  
      await alice.fund(m2, {
        rate: 10000
      });
  
      await alice.sign(m2);
  
      const [t2, v2] = m2.commit();
  
      assert(t2.verify(v2));
  
      assert.strictEqual(t2.getInputValue(v2), 16380);
  
      // Should now have a change output:
      assert.strictEqual(t2.getOutputValue(), 11130);
  
      assert.strictEqual(t2.getFee(v2), 5250);
  
      assert.strictEqual(t2.getWeight(), 2084);
      assert.strictEqual(t2.getBaseSize(), 521);
      assert.strictEqual(t2.getSize(), 521);
      assert.strictEqual(t2.getVirtualSize(), 521);
  
      let balance = null;
      bob.once('balance', (b) => {
        balance = b;
      });
  
      await wdb.addTX(t2);
  
      // Create new transaction
      const t3 = new MTX();
      t3.addOutput(await bob.receiveAddress(), 15000);
  
      let err;
      try {
        await alice.fund(t3, {
          rate: 10000
        });
      } catch (e) {
        err = e;
      }
  
      assert(err);
      assert(balance);
      assert.strictEqual(balance.unconfirmed, 5460);
    });
  
    it('should sign multiple inputs using different keys', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
      const carol = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      // Coinbase
      const t2 = new MTX();
      t2.addInput(dummyInput());
      t2.addOutput(await bob.receiveAddress(), 5460);
      t2.addOutput(await bob.receiveAddress(), 5460);
      t2.addOutput(await bob.receiveAddress(), 5460);
      t2.addOutput(await bob.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
      await wdb.addTX(t2.toTX());
  
      // Create our tx with an output
      const tx = new MTX();
      tx.addOutput(await carol.receiveAddress(), 5460);
  
      const coins1 = await alice.getCoins();
      const coins2 = await bob.getCoins();
  
      // Add our unspent inputs to sign
      tx.addCoin(coins1[0]);
      tx.addCoin(coins1[1]);
      tx.addCoin(coins2[0]);
  
      // Sign transaction
      assert.strictEqual(await alice.sign(tx), 2);
      assert.strictEqual(await bob.sign(tx), 1);
  
      // Verify
      assert.strictEqual(tx.verify(), true);
  
      tx.inputs.length = 0;
      tx.addCoin(coins1[1]);
      tx.addCoin(coins1[2]);
      tx.addCoin(coins2[1]);
  
      assert.strictEqual(await alice.sign(tx), 2);
      assert.strictEqual(await bob.sign(tx), 1);
  
      // Verify
      assert.strictEqual(tx.verify(), true);
    });
  
    it('should verify 2-of-3 p2sh tx', async () => {
      await testP2SH(false, false);
    });
  
    it('should verify 2-of-3 p2wsh tx', async () => {
      await testP2SH(true, false);
    });
  
    it('should verify 2-of-3 p2wsh tx w/ nested bullshit', async () => {
      await testP2SH(true, true);
    });
  
    it('should create account', async () => {
      const wallet = await wdb.create();
      const account = await wallet.createAccount({
        name: 'foo'
      });
  
      assert(account);
      assert(account.initialized);
      assert.strictEqual(account.name, 'foo');
      assert.strictEqual(account.accountIndex, 1);
      assert.strictEqual(account.m, 1);
      assert.strictEqual(account.n, 1);
    });
  
    it('should fail to create duplicate account', async () => {
      const wallet = await wdb.create();
      const name = 'foo';
  
      await wallet.createAccount({ name });
  
      let err;
      try {
        await wallet.createAccount({ name });
      } catch (e) {
        err = e;
      }
  
      assert(err);
      assert.strictEqual(err.message, 'Account already exists.');
    });
  
    it('should fill tx with account 1', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      {
        const account = await alice.createAccount({
          name: 'foo'
        });
        assert.strictEqual(account.name, 'foo');
        assert.strictEqual(account.accountIndex, 1);
      }
  
      const account = await alice.getAccount('foo');
      assert.strictEqual(account.name, 'foo');
      assert.strictEqual(account.accountIndex, 1);
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(account.receiveAddress(), 5460);
      t1.addOutput(account.receiveAddress(), 5460);
      t1.addOutput(account.receiveAddress(), 5460);
      t1.addOutput(account.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Create new transaction
      const t2 = new MTX();
      t2.addOutput(await bob.receiveAddress(), 5460);
  
      await alice.fund(t2, {
        rate: 10000,
        round: true
      });
  
      await alice.sign(t2);
  
      assert(t2.verify());
  
      assert.strictEqual(t2.getInputValue(), 16380);
      assert.strictEqual(t2.getOutputValue(), 6380);
      assert.strictEqual(t2.getFee(), 10000);
  
      // Create new transaction
      const t3 = new MTX();
      t3.addOutput(await bob.receiveAddress(), 15000);
  
      let err;
      try {
        await alice.fund(t3, {
          rate: 10000,
          round: true
        });
      } catch (e) {
        err = e;
      }
  
      assert(err);
      assert.strictEqual(err.requiredFunds, 25000);
  
      const accounts = await alice.getAccounts();
      assert.deepStrictEqual(accounts, ['default', 'foo']);
    });
  
    it('should fail to fill tx with account 1', async () => {
      const wallet = await wdb.create();
  
      {
        const account = await wallet.createAccount({
          name: 'foo'
        });
        assert.strictEqual(account.name, 'foo');
        assert.strictEqual(account.accountIndex, 1);
      }
  
      const account = await wallet.getAccount('foo');
      assert.strictEqual(account.name, 'foo');
      assert.strictEqual(account.accountIndex, 1);
  
      assert(!account.receiveAddress().equals(await wallet.receiveAddress()));
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(account.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Should fill from `foo` and fail
      const t2 = new MTX();
  
      t2.addOutput(await wallet.receiveAddress(), 5460);
  
      let err;
      try {
        await wallet.fund(t2, {
          rate: 10000,
          round: true,
          account: 'foo'
        });
      } catch (e) {
        err = e;
      }
  
      assert(err);
  
      // Should fill from whole wallet and succeed
      const t3 = new MTX();
      t3.addOutput(await wallet.receiveAddress(), 5460);
  
      await wallet.fund(t3, {
        rate: 10000,
        round: true
      });
  
      // Coinbase
      const t4 = new MTX();
      t4.addInput(dummyInput());
      t4.addOutput(await wallet.receiveAddress('foo'), 5460);
      t4.addOutput(await wallet.receiveAddress('foo'), 5460);
      t4.addOutput(await wallet.receiveAddress('foo'), 5460);
  
      await wdb.addTX(t4.toTX());
  
      // Should fill from `foo` and succeed
      const t5 = new MTX();
      t5.addOutput(await wallet.receiveAddress(), 5460);
  
      await wallet.fund(t5, {
        rate: 10000,
        round: true,
        account: 'foo'
      });
  
      currentWallet = wallet;
    });
  
    it('should create two accounts (multiple encryption)', async () => {
      {
        const wallet = await wdb.create({
          id: 'foobar',
          passphrase: 'foo'
        });
        await wallet.destroy();
        wdb.unregister(wallet);
      }
  
      const wallet = await wdb.get('foobar');
      assert(wallet);
  
      const options = {
        name: 'foo1'
      };
  
      const account = await wallet.createAccount(options, 'foo');
  
      assert(account);
  
      await wallet.lock();
    });
  
    it('should fill tx with inputs when encrypted', async () => {
      const wallet = await wdb.create({
        passphrase: 'foo'
      });
  
      wallet.master.stop();
      wallet.master.key = null;
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(await wallet.receiveAddress(), 5460);
      t1.addOutput(await wallet.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Create new transaction
      const t2 = new MTX();
      t2.addOutput(await wallet.receiveAddress(), 5460);
  
      await wallet.fund(t2, {
        rate: 10000,
        round: true
      });
  
      // Should fail
      let err;
      try {
        await wallet.sign(t2, 'bar');
      } catch (e) {
        err = e;
      }
  
      assert(err);
      assert(!t2.verify());
  
      // Should succeed
      await wallet.sign(t2, 'foo');
      assert(t2.verify());
    });
  
    it('should fill tx with inputs with subtract fee (1)', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Create new transaction
      const t2 = new MTX();
      t2.addOutput(await bob.receiveAddress(), 21840);
  
      await alice.fund(t2, {
        rate: 10000,
        round: true,
        subtractFee: true
      });
  
      await alice.sign(t2);
  
      assert(t2.verify());
  
      assert.strictEqual(t2.getInputValue(), 5460 * 4);
      assert.strictEqual(t2.getOutputValue(), 21840 - 10000);
      assert.strictEqual(t2.getFee(), 10000);
    });
  
    it('should fill tx with inputs with subtract fee (2)', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      const options = {
        subtractFee: true,
        rate: 10000,
        round: true,
        outputs: [{ address: await bob.receiveAddress(), value: 21840 }]
      };
  
      // Create new transaction
      const t2 = await alice.createTX(options);
      await alice.sign(t2);
  
      assert(t2.verify());
  
      assert.strictEqual(t2.getInputValue(), 5460 * 4);
      assert.strictEqual(t2.getOutputValue(), 21840 - 10000);
      assert.strictEqual(t2.getFee(), 10000);
    });
  
    it('should fill tx with smart coin selection', async () => {
      const alice = await wdb.create();
      const bob = await wdb.create();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
      t1.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addTX(t1.toTX());
  
      // Coinbase
      const t2 = new MTX();
      t2.addInput(dummyInput());
      t2.addOutput(await alice.receiveAddress(), 5460);
      t2.addOutput(await alice.receiveAddress(), 5460);
      t2.addOutput(await alice.receiveAddress(), 5460);
      t2.addOutput(await alice.receiveAddress(), 5460);
  
      await wdb.addBlock(nextBlock(wdb), [t2.toTX()]);
  
      {
        const coins = await alice.getSmartCoins();
        assert.strictEqual(coins.length, 4);
  
        for (let i = 0; i < coins.length; i++) {
          const coin = coins[i];
          assert.strictEqual(coin.height, wdb.state.height);
        }
      }
  
      // Create a change output for ourselves.
      await alice.send({
        subtractFee: true,
        rate: 1000,
        depth: 1,
        outputs: [{ address: await bob.receiveAddress(), value: 1461 }]
      });
  
      const coins = await alice.getSmartCoins();
      assert.strictEqual(coins.length, 4);
  
      let total = 0;
  
      {
        let found = false;
  
        for (let i = 0; i < coins.length; i++) {
          const coin = coins[i];
          if (coin.height === -1) {
            assert(!found);
            assert(coin.value < 5460);
            found = true;
          } else {
            assert.strictEqual(coin.height, wdb.state.height);
          }
          total += coin.value;
        }
  
        assert(found);
      }
  
      // Use smart selection
      const options = {
        subtractFee: true,
        smart: true,
        rate: 10000,
        outputs: [{
          address: await bob.receiveAddress(),
          value: total
        }]
      };
  
      const t3 = await alice.createTX(options);
      assert.strictEqual(t3.inputs.length, 4);
  
      {
        let found = false;
  
        for (let i = 0; i < t3.inputs.length; i++) {
          const coin = t3.view.getCoinFor(t3.inputs[i]);
          if (coin.height === -1) {
            assert(!found);
            assert(coin.value < 5460);
            found = true;
          } else {
            assert.strictEqual(coin.height, wdb.state.height);
          }
        }
  
        assert(found);
      }
  
      await alice.sign(t3);
  
      assert(t3.verify());
    });
  
    it('should get range of txs', async () => {
      const wallet = currentWallet;
      const txs = await wallet.getRange(null, {
        start: util.now() - 1000
      });
      assert.strictEqual(txs.length, 2);
    });
  
    it('should get range of txs from account', async () => {
      const wallet = currentWallet;
      const txs = await wallet.getRange('foo', {
        start: util.now() - 1000
      });
      assert.strictEqual(txs.length, 2);
    });
  
    it('should not get range of txs from non-existent account', async () => {
      const wallet = currentWallet;
  
      let txs, err;
      try {
        txs = await wallet.getRange('bad', {
          start: 0xdeadbeef - 1000
        });
      } catch (e) {
        err = e;
      }
  
      assert(!txs);
      assert(err);
      assert.strictEqual(err.message, 'Account not found.');
    });
  
    it('should get account balance', async () => {
      const wallet = currentWallet;
      const balance = await wallet.getBalance('foo');
      assert.strictEqual(balance.unconfirmed, 21840);
    });
  
    it('should import privkey', async () => {
      const key = KeyRing.generate();
  
      const wallet = await wdb.create({
        passphrase: 'test'
      });
  
      await wallet.importKey('default', key, 'test');
  
      const wkey = await wallet.getKey(key.getHash());
  
      assert.bufferEqual(wkey.getHash(), key.getHash());
  
      // Coinbase
      const t1 = new MTX();
      t1.addOutput(key.getAddress(), 5460);
      t1.addOutput(key.getAddress(), 5460);
      t1.addOutput(key.getAddress(), 5460);
      t1.addOutput(key.getAddress(), 5460);
  
      t1.addInput(dummyInput());
  
      await wdb.addTX(t1.toTX());
  
      const wtx = await wallet.getTX(t1.hash());
      assert(wtx);
      assert.bufferEqual(t1.hash(), wtx.hash);
  
      const options = {
        rate: 10000,
        round: true,
        outputs: [{
          address: await wallet.receiveAddress(),
          value: 7000
        }]
      };
  
      // Create new transaction
      const t2 = await wallet.createTX(options);
      await wallet.sign(t2);
      assert(t2.verify());
      assert.bufferEqual(t2.inputs[0].prevout.hash, wtx.hash);
  
      importedWallet = wallet;
      importedKey = key;
    });
  
    it('should import pubkey', async () => {
      const key = KeyRing.generate();
      const pub = new KeyRing(key.publicKey);
  
      const wallet = await wdb.create({
        watchOnly: true
      });
  
      await wallet.importKey('default', pub);
  
      const path = await wallet.getPath(pub.getHash());
      assert.bufferEqual(path.hash, pub.getHash());
  
      const wkey = await wallet.getKey(pub.getHash());
      assert(wkey);
    });
  
    it('should import address', async () => {
      const key = KeyRing.generate();
  
      const wallet = await wdb.create({
        watchOnly: true
      });
  
      await wallet.importAddress('default', key.getAddress());
  
      const path = await wallet.getPath(key.getHash());
      assert(path);
      assert.bufferEqual(path.hash, key.getHash());
  
      const wkey = await wallet.getKey(key.getHash());
      assert(!wkey);
    });
  
    it('should get details', async () => {
      const wallet = currentWallet;
  
      const txs = await wallet.getRange('foo', {
        start: util.now() - 1000
      });
  
      const details = await wallet.toDetails(txs);
  
      assert(details.some((tx) => {
        return tx.toJSON(wdb.network).outputs[0].path.name === 'foo';
      }));
    });
  
    it('should rename wallet', async () => {
      const wallet = currentWallet;
  
      await wallet.rename('test');
  
      const txs = await wallet.getRange('foo', {
        start: util.now() - 1000
      });
  
      const details = await wallet.toDetails(txs);
  
      assert(details.length > 0);
      assert.strictEqual(wallet.id, 'test');
    });
  
    it('should change passphrase with encrypted imports', async () => {
      const wallet = importedWallet;
      const addr = importedKey.getAddress();
  
      assert(wallet.master.encrypted);
  
      let data;
      {
        const path = await wallet.getPath(addr);
        assert(path);
        assert(path.data && path.encrypted);
        data = path.data;
      }
  
      await wallet.decrypt('test');
  
      {
        const path = await wallet.getPath(addr);
        assert(path);
        assert(path.data && !path.encrypted);
        assert(await wallet.getKey(addr));
      }
  
      await wallet.encrypt('foo');
  
      {
        const path = await wallet.getPath(addr);
        assert(path);
        assert(path.data && path.encrypted);
        assert(!data.equals(path.data));
        assert(!await wallet.getKey(addr));
      }
  
      await wallet.unlock('foo');
  
      const key = await wallet.getKey(addr);
      assert(key);
      assert.bufferEqual(key.getHash(), addr.getHash());
    });
  
    it('should recover from a missed tx', async () => {
      const wdb = new WalletDB({ workers });
      await wdb.open();
  
      const alice = await wdb.create({
        master: KEY1
      });
  
      const bob = await wdb.create({
        master: KEY1
      });
  
      const addr = await alice.receiveAddress();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(addr, 50000);
  
      await wdb.addTX(t1.toTX());
  
      // Bob misses this tx!
      const t2 = new MTX();
      t2.addTX(t1, 0);
      t2.addOutput(addr, 24000);
      t2.addOutput(addr, 24000);
  
      await alice.sign(t2);
  
      await alice.add(t2.toTX());
  
      assert.notStrictEqual(
        (await alice.getBalance()).unconfirmed,
        (await bob.getBalance()).unconfirmed);
  
      // Bob sees this one.
      const t3 = new MTX();
      t3.addTX(t2, 0);
      t3.addTX(t2, 1);
      t3.addOutput(addr, 30000);
  
      await alice.sign(t3);
  
      assert.strictEqual((await bob.getBalance()).unconfirmed, 50000);
  
      await wdb.addTX(t3.toTX());
  
      assert.strictEqual((await alice.getBalance()).unconfirmed, 30000);
  
      // t1 gets confirmed.
      await wdb.addBlock(nextBlock(wdb), [t1.toTX()]);
  
      // Bob sees t2 on the chain.
      await wdb.addBlock(nextBlock(wdb), [t2.toTX()]);
  
      // Bob sees t3 on the chain.
      await wdb.addBlock(nextBlock(wdb), [t3.toTX()]);
  
      assert.strictEqual((await bob.getBalance()).unconfirmed, 30000);
    });
  
    it('should recover from a missed tx and double spend', async () => {
      const wdb = new WalletDB({ workers });
      await wdb.open();
  
      const alice = await wdb.create({
        master: KEY1
      });
  
      const bob = await wdb.create({
        master: KEY1
      });
  
      const addr = await alice.receiveAddress();
  
      // Coinbase
      const t1 = new MTX();
      t1.addInput(dummyInput());
      t1.addOutput(addr, 50000);
  
      await wdb.addTX(t1.toTX());
  
      // Bob misses this tx!
      const t2a = new MTX();
      t2a.addTX(t1, 0);
      t2a.addOutput(addr, 24000);
      t2a.addOutput(addr, 24000);
  
      await alice.sign(t2a);
  
      await alice.add(t2a.toTX());
  
      assert.notStrictEqual(
        (await alice.getBalance()).unconfirmed,
        (await bob.getBalance()).unconfirmed);
  
      // Bob doublespends.
      const t2b = new MTX();
      t2b.addTX(t1, 0);
      t2b.addOutput(addr, 10000);
      t2b.addOutput(addr, 10000);
  
      await bob.sign(t2b);
  
      await bob.add(t2b.toTX());
  
      // Bob sees this one.
      const t3 = new MTX();
      t3.addTX(t2a, 0);
      t3.addTX(t2a, 1);
      t3.addOutput(addr, 30000);
  
      await alice.sign(t3);
  
      assert.strictEqual((await bob.getBalance()).unconfirmed, 20000);
  
      await wdb.addTX(t3.toTX());
  
      assert.strictEqual((await alice.getBalance()).unconfirmed, 30000);
  
      // t1 gets confirmed.
      await wdb.addBlock(nextBlock(wdb), [t1.toTX()]);
  
      // Bob sees t2a on the chain.
      await wdb.addBlock(nextBlock(wdb), [t2a.toTX()]);
  
      // Bob sees t3 on the chain.
      await wdb.addBlock(nextBlock(wdb), [t3.toTX()]);
  
      assert.strictEqual((await bob.getBalance()).unconfirmed, 30000);
    });
  
    it('should remove a wallet', async () => {
      await wdb.create({
        id: 'alice100'
      });
      assert(await wdb.get('alice100'));
      await wdb.remove('alice100');
      assert(!await wdb.get('alice100'));
    });
  
    const keyTypes = [
      {
        name: 'receive',
        method: 'createReceive',
        branch: 0
      },
      {
        name: 'change',
        method: 'createChange',
        branch: 1
      },
      {
        name: 'nested',
        method: 'createNested',
        branch: 2
      }
    ];
  
    for (const type of keyTypes) {
      it(`should create ${type.name} addresses`, async () => {
        const account = 0;
        const wallet = await wdb.create({
          witness: true
        });
        const addresses = new Set();
  
        for (let i = 0; i < 100; i++) {
          const key = await wallet[type.method](account);
          addresses.add(key.getAddress('string'));
          assert.strictEqual(key.account, account);
          assert.strictEqual(key.branch, type.branch);
          assert.strictEqual(key.index, i + 1);
        }
  
        assert.strictEqual(addresses.size, 100);
      });
  
      it(`should create ${type.name} addresses and get their keys`, async () => {
        const account = 0;
        const wallet = await wdb.create({
          witness: true
        });
  
        const addresses = new Set();
  
        for (let i = 0; i < 100; i++) {
          const key1 = await wallet[type.method](account);
          const address = key1.getAddress();
  
          assert(key1, `Could not get ${type.name}`);
          addresses.add(address);
  
          assert.strictEqual(key1.account, account);
          assert.strictEqual(key1.branch, type.branch);
          assert.strictEqual(key1.index, i + 1);
  
          const key2 = await wallet.getKey(address);
          assert(key2, `Could not get key for ${address.toString()}` +
            `, Key: xpub/${type.branch}/${i+1}`);
  
          assert.strictEqual(key2.name, key1.name);
          assert.strictEqual(key2.account, key1.account);
          assert.strictEqual(key2.branch, key1.branch);
          assert.strictEqual(key2.witness, key1.witness);
          assert.strictEqual(key2.nested, key1.nested);
          assert.bufferEqual(key2.publicKey, key1.publicKey);
          assert.strictEqual(key2.getType(), key1.getType());
        }
  
        assert.strictEqual(addresses.size, 100);
      });
    }
  
    it('should cleanup', async () => {
      consensus.COINBASE_MATURITY = 100;
      // await wdb.close();
    });
  });
  