4031 lines
116 KiB
JavaScript
4031 lines
116 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const WalletClient = require('../lib/client/wallet');
|
|
const consensus = require('../lib/protocol/consensus');
|
|
const Network = require('../lib/protocol/network');
|
|
const util = require('../lib/utils/util');
|
|
const random = require('bcrypto/lib/random');
|
|
const FullNode = require('../lib/node/fullnode');
|
|
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 {Resource} = require('../lib/dns/resource');
|
|
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 Output = require('../lib/primitives/output');
|
|
const Script = require('../lib/script/script');
|
|
const policy = require('../lib/protocol/policy');
|
|
const HDPrivateKey = require('../lib/hd/private');
|
|
const Mnemonic = require('../lib/hd/mnemonic');
|
|
const Wallet = require('../lib/wallet/wallet');
|
|
const rules = require('../lib/covenants/rules');
|
|
const {forValue} = require('./util/common');
|
|
const wutils = require('./util/wallet');
|
|
const {ownership} = require('../lib/covenants/ownership');
|
|
const {CachedStubResolver, STUB_SERVERS} = require('./util/stub');
|
|
const {
|
|
curBlock,
|
|
nextBlock,
|
|
curEntry,
|
|
nextEntry
|
|
} = wutils;
|
|
const {dummyInput} = require('./util/primitives');
|
|
|
|
const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
|
|
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
|
|
|
|
const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
|
|
+ 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
|
|
|
|
const enabled = true;
|
|
const size = 2;
|
|
const network = Network.get('main');
|
|
const workers = new WorkerPool({ enabled, size });
|
|
const wdb = new WalletDB({ network, workers });
|
|
|
|
let currentWallet = null;
|
|
let importedWallet = null;
|
|
let importedKey = null;
|
|
let doubleSpendWallet = null;
|
|
let doubleSpendCoin = null;
|
|
let watchWallet = null;
|
|
|
|
describe('Wallet', function() {
|
|
this.timeout(5000);
|
|
|
|
const originalResolver = ownership.Resolver;
|
|
const originalServers = ownership.servers;
|
|
|
|
before(() => {
|
|
ownership.Resolver = CachedStubResolver;
|
|
ownership.servers = STUB_SERVERS;
|
|
});
|
|
|
|
after(() => {
|
|
ownership.Resolver = originalResolver;
|
|
ownership.servers = originalServers;
|
|
});
|
|
|
|
it('should open walletdb', async () => {
|
|
network.coinbaseMaturity = 1;
|
|
await wdb.open();
|
|
});
|
|
|
|
it('should add blocks to wdb to pass txStart', async () => {
|
|
// Because these tests are written for mainnet.
|
|
while (wdb.height < network.txStart) {
|
|
await wdb.addBlock(nextBlock(wdb), []);
|
|
}
|
|
});
|
|
|
|
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 create and get wallet', async () => {
|
|
const wallet1 = await wdb.create();
|
|
const wallet2 = await wdb.get(wallet1.id);
|
|
assert(wallet1 === wallet2);
|
|
});
|
|
|
|
it('should create wallet with spanish mnemonic', async () => {
|
|
const wallet1 = await wdb.create({language: 'spanish'});
|
|
const phrase = wallet1.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
|
|
const wallet2 = await wdb.create({mnemonic: phrase});
|
|
assert.deepStrictEqual(
|
|
await wallet1.receiveAddress(),
|
|
await wallet2.receiveAddress()
|
|
);
|
|
});
|
|
|
|
it('should sign/verify p2pkh tx', async () => {
|
|
const flags = Script.flags.STANDARD_VERIFY_FLAGS;
|
|
const wallet = await wdb.create();
|
|
|
|
const waddr = await wallet.receiveAddress();
|
|
const addr = Address.fromString(waddr.toString(wdb.network), wdb.network);
|
|
|
|
assert.strictEqual(addr.version, 0);
|
|
assert.strictEqual(addr.version, waddr.version);
|
|
|
|
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));
|
|
});
|
|
|
|
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();
|
|
// UCoins:
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
assert.strictEqual(balance.coin, 2);
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t1.toTX());
|
|
|
|
const balance = await alice.getBalance();
|
|
// UCoins:
|
|
// t1:0 - 50k
|
|
// t1:1 - 1k
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
assert.strictEqual(balance.coin, 4);
|
|
// 22000 + 51000 = 73000
|
|
assert.strictEqual(balance.unconfirmed, 73000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t2.toTX());
|
|
|
|
// t2 spends 50k from t1, but adds 48k from t2
|
|
// 2k less = 71000. BUT t2 output is consumed by t4 so:
|
|
// -24k. 71000 - 24000 = 47000
|
|
// UCoins:
|
|
// t1:0 - 1k (t1:1 - 50k gone)
|
|
// t2:0 - 24k (t2:1 is already consumed by t4)
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 4);
|
|
assert.strictEqual(balance.unconfirmed, 47000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(t3.toTX());
|
|
|
|
// UCoins consumed:
|
|
// t1:1 - 1k
|
|
// t2:0 - 24k
|
|
// t3:0 - 23k - already spent by t4
|
|
// UCoins:
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 2);
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(f1.toTX());
|
|
// Coins consumed:
|
|
// t4:1 - 11k
|
|
// Coins:
|
|
// t4:0 - 11k
|
|
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
|
|
const txs = await alice.listHistory(-1, {
|
|
reverse: false,
|
|
limit: 100
|
|
});
|
|
assert(txs.some((wtx) => {
|
|
return wtx.hash.equals(f1.hash());
|
|
}));
|
|
}
|
|
|
|
{
|
|
const balance = await bob.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 10000);
|
|
|
|
const txs = await bob.listHistory(-1, {
|
|
reverse: false,
|
|
limit: 100
|
|
});
|
|
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.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
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.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
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.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
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.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
assert.strictEqual(txs.length, 2);
|
|
|
|
const total = txs.reduce((t, wtx) => {
|
|
return t + wtx.tx.getOutputValue();
|
|
}, 0);
|
|
assert.strictEqual(total, 56000);
|
|
}
|
|
});
|
|
|
|
it('should remove unconfirmed coinbase tx', async () => {
|
|
const wallet = await wdb.create();
|
|
|
|
const block = nextBlock(wdb);
|
|
const normalTX = new MTX();
|
|
|
|
normalTX.addInput(dummyInput());
|
|
normalTX.addOutput(await wallet.receiveAddress(), 5000);
|
|
|
|
assert(!normalTX.isCoinbase());
|
|
|
|
const cbTX = new MTX();
|
|
|
|
cbTX.addInput({
|
|
prevout: new Outpoint()
|
|
});
|
|
|
|
cbTX.addOutput(await wallet.receiveAddress(), 5000);
|
|
assert(cbTX.isCoinbase());
|
|
|
|
const pendingBefore = await wallet.getPending();
|
|
assert.strictEqual(pendingBefore.length, 0);
|
|
|
|
await wdb.addBlock(block, [cbTX.toTX(), normalTX.toTX()]);
|
|
|
|
const balanceBefore = await wallet.getBalance();
|
|
|
|
assert.strictEqual(balanceBefore.confirmed, balanceBefore.unconfirmed);
|
|
assert.strictEqual(balanceBefore.confirmed, 10000);
|
|
assert.strictEqual(balanceBefore.tx, 2);
|
|
assert.strictEqual(balanceBefore.coin, 2);
|
|
|
|
await wdb.removeBlock(block);
|
|
const pending = await wallet.getPending();
|
|
|
|
assert.strictEqual(pending.length, 1);
|
|
|
|
const balance = await wallet.getBalance();
|
|
|
|
assert.strictEqual(balance.confirmed, 0);
|
|
assert.strictEqual(balance.unconfirmed, 5000);
|
|
assert.strictEqual(balance.tx, 1);
|
|
assert.strictEqual(balance.coin, 1);
|
|
});
|
|
|
|
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 = 0;
|
|
wallet.on('conflict', () => {
|
|
conflict += 1;
|
|
});
|
|
|
|
const t2 = new MTX();
|
|
t2.addInput(input);
|
|
t2.addOutput(new Address(), 5000);
|
|
await wdb.addTX(t2.toTX());
|
|
assert.strictEqual(conflict, 1);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 0);
|
|
});
|
|
|
|
it('should handle double-spend (multiple inputs)', async () => {
|
|
const wallet = await wdb.create();
|
|
const address = await wallet.receiveAddress();
|
|
|
|
const hash = random.randomBytes(32);
|
|
const input0 = Input.fromOutpoint(new Outpoint(hash, 0));
|
|
const input1 = Input.fromOutpoint(new Outpoint(hash, 1));
|
|
|
|
const txa = new MTX();
|
|
txa.addInput(input0);
|
|
txa.addInput(input1);
|
|
txa.addOutput(address, 50000);
|
|
await wdb.addTX(txa.toTX());
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
|
|
|
|
let conflict = 0;
|
|
wallet.on('conflict', () => {
|
|
conflict += 1;
|
|
});
|
|
|
|
const txb = new MTX();
|
|
txb.addInput(input0);
|
|
txb.addInput(input1);
|
|
txb.addOutput(address, 49000);
|
|
await wdb.addTX(txb.toTX());
|
|
|
|
assert.strictEqual(conflict, 1);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 49000);
|
|
});
|
|
|
|
it('should handle double-spend (with block)', async () => {
|
|
const wallet = await wdb.create();
|
|
const address = await wallet.receiveAddress();
|
|
|
|
const hash = random.randomBytes(32);
|
|
const input0 = Input.fromOutpoint(new Outpoint(hash, 0));
|
|
const input1 = Input.fromOutpoint(new Outpoint(hash, 1));
|
|
|
|
const txa = new MTX();
|
|
txa.addInput(input0);
|
|
txa.addInput(input1);
|
|
txa.addOutput(address, 50000);
|
|
await wdb.addTX(txa.toTX());
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
|
|
|
|
let conflict = 0;
|
|
wallet.on('conflict', () => {
|
|
conflict += 1;
|
|
});
|
|
|
|
const txb = new MTX();
|
|
txb.addInput(input0);
|
|
txb.addInput(input1);
|
|
txb.addOutput(address, 49000);
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [txb.toTX()]);
|
|
assert.strictEqual(conflict, 1);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 49000);
|
|
assert.strictEqual((await wallet.getBalance()).confirmed, 49000);
|
|
});
|
|
|
|
it('should recover from interrupt when removing conflict', async () => {
|
|
const wallet = await wdb.create();
|
|
const address = await wallet.receiveAddress();
|
|
|
|
const hash = random.randomBytes(32);
|
|
const input0 = Input.fromOutpoint(new Outpoint(hash, 0));
|
|
const input1 = Input.fromOutpoint(new Outpoint(hash, 1));
|
|
|
|
const txa = new MTX();
|
|
txa.addInput(input0);
|
|
txa.addInput(input1);
|
|
txa.addOutput(address, 50000);
|
|
|
|
await wdb.addTX(txa.toTX());
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
|
|
assert.strictEqual((await wallet.getBalance()).confirmed, 0);
|
|
|
|
let conflict = 0;
|
|
wallet.on('conflict', () => {
|
|
conflict += 1;
|
|
});
|
|
|
|
const txb = new MTX();
|
|
txb.addInput(input0);
|
|
txb.addInput(input1);
|
|
txb.addOutput(address, 49000);
|
|
|
|
const removeConflict = wallet.txdb.removeConflict;
|
|
|
|
wallet.txdb.removeConflict = async () => {
|
|
throw new Error('Unexpected interrupt.');
|
|
};
|
|
|
|
const entry = nextBlock(wdb);
|
|
|
|
await assert.rejects(async () => {
|
|
await wdb.addBlock(entry, [txb.toTX()]);
|
|
}, {
|
|
name: 'Error',
|
|
message: 'Unexpected interrupt.'
|
|
});
|
|
|
|
wallet.txdb.removeConflict = removeConflict;
|
|
|
|
assert.strictEqual(conflict, 0);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
|
|
assert.strictEqual((await wallet.getBalance()).confirmed, 0);
|
|
|
|
await wdb.addBlock(entry, [txb.toTX()]);
|
|
|
|
assert.strictEqual(conflict, 1);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 49000);
|
|
assert.strictEqual((await wallet.getBalance()).confirmed, 49000);
|
|
});
|
|
|
|
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);
|
|
|
|
{
|
|
// Coins:
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
await wdb.addTX(t4.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 2);
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
// Coins:
|
|
// t1:0 - 50k
|
|
// t1:1 - 1k
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
await wdb.addTX(t1.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 4);
|
|
assert.strictEqual(balance.unconfirmed, 73000);
|
|
}
|
|
|
|
{
|
|
// Coins consumed:
|
|
// t1:0 - 50k
|
|
// Coins already spent:
|
|
// t2:1 - 24k
|
|
// Coins:
|
|
// t1:1 - 1k
|
|
// t2:0 - 24k
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
await wdb.addTX(t2.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 4);
|
|
assert.strictEqual(balance.unconfirmed, 47000);
|
|
}
|
|
|
|
{
|
|
// Coins consumed:
|
|
// t1:1 - 1k
|
|
// t2:0 - 24k
|
|
// Coins already spent:
|
|
// t3:0 - 23k
|
|
// Coins:
|
|
// t4:0 - 11k
|
|
// t4:1 - 11k
|
|
await wdb.addTX(t3.toTX());
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.coin, 2);
|
|
assert.strictEqual(balance.unconfirmed, 22000);
|
|
}
|
|
|
|
{
|
|
await wdb.addTX(f1.toTX());
|
|
|
|
// Coins consumed (alice)
|
|
// t4:1 - 11k
|
|
// Coins:
|
|
// t4:0 - 11k
|
|
const balance = await alice.getBalance();
|
|
assert.strictEqual(balance.unconfirmed, 11000);
|
|
|
|
const txs = await alice.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
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.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
|
|
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(), 13660);
|
|
|
|
assert.strictEqual(t2.getFee(v2), 2720);
|
|
|
|
assert.strictEqual(t2.getWeight(), 1079);
|
|
assert.strictEqual(t2.getBaseSize(), 194);
|
|
assert.strictEqual(t2.getSize(), 497);
|
|
assert.strictEqual(t2.getVirtualSize(), 270);
|
|
|
|
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 () => {
|
|
const flags = Script.flags.STANDARD_VERIFY_FLAGS;
|
|
|
|
// Create 3 2-of-3 wallets with our pubkeys as "shared keys"
|
|
const options = {
|
|
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();
|
|
|
|
assert.strictEqual(addr1.version, 0);
|
|
|
|
assert((await alice.receiveAddress()).equals(addr1));
|
|
assert((await bob.receiveAddress()).equals(addr1));
|
|
assert((await carol.receiveAddress()).equals(addr1));
|
|
|
|
{
|
|
// Add a shared unspent transaction to our wallets
|
|
const fund = new MTX();
|
|
fund.addInput(dummyInput());
|
|
fund.addOutput(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.witness.setData(2, Buffer.alloc(65, 0x00));
|
|
input.witness.compile();
|
|
|
|
assert(!tx.verify(view, flags));
|
|
assert.strictEqual(tx.getFee(view), 10000);
|
|
});
|
|
|
|
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 pending range of txs', async () => {
|
|
const wallet = currentWallet;
|
|
const txs = await wallet.listUnconfirmed(null, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
assert.strictEqual(txs.length, 2);
|
|
});
|
|
|
|
it('should get penidng range of txs from account', async () => {
|
|
const wallet = currentWallet;
|
|
const txs = await wallet.listUnconfirmed('foo', {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
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.listUnconfirmed('bad', {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
} 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 require account key to create watch only wallet', async () => {
|
|
try {
|
|
watchWallet = await wdb.create({
|
|
watchOnly: true
|
|
});
|
|
} catch (e) {
|
|
assert.strictEqual(
|
|
e.message,
|
|
'Must add HD public keys to watch only wallet.'
|
|
);
|
|
}
|
|
|
|
const privateKey = HDPrivateKey.generate();
|
|
const xpub = privateKey.xpubkey('main');
|
|
watchWallet = await wdb.create({
|
|
watchOnly: true,
|
|
accountKey: xpub
|
|
});
|
|
});
|
|
|
|
it('should import pubkey', async () => {
|
|
const key = KeyRing.generate();
|
|
const pub = new KeyRing(key.publicKey);
|
|
|
|
await watchWallet.importKey('default', pub);
|
|
|
|
const path = await watchWallet.getPath(pub.getHash());
|
|
assert.bufferEqual(path.hash, pub.getHash());
|
|
|
|
const wkey = await watchWallet.getKey(pub.getHash());
|
|
assert(wkey);
|
|
});
|
|
|
|
it('should import address', async () => {
|
|
const key = KeyRing.generate();
|
|
|
|
await watchWallet.importAddress('default', key.getAddress());
|
|
|
|
const path = await watchWallet.getPath(key.getHash());
|
|
assert(path);
|
|
assert.bufferEqual(path.hash, key.getHash());
|
|
|
|
const wkey = await watchWallet.getKey(key.getHash());
|
|
assert(!wkey);
|
|
});
|
|
|
|
it('should get details', async () => {
|
|
const wallet = currentWallet;
|
|
|
|
const txs = await wallet.listUnconfirmed('foo', {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
|
|
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.listUnconfirmed('foo', {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
|
|
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 yield different keys when bip39Passphrase is supplied', async () => {
|
|
const wallet = await wdb.create();
|
|
|
|
const wallet2 = await wdb.create({
|
|
mnemonic: wallet.master.mnemonic.toString()
|
|
});
|
|
|
|
const wallet3 = await wdb.create({
|
|
mnemonic: wallet.master.mnemonic.toString(),
|
|
bip39Passphrase: 'test'
|
|
});
|
|
|
|
assert.equal(wallet.master.key.xprivkey(), wallet2.master.key.xprivkey());
|
|
assert.equal(wallet.master.mnemonic.toString(), wallet2.master.mnemonic.toString());
|
|
assert.notEqual(wallet.master.key.xprivkey(), wallet3.master.key.xprivkey());
|
|
assert.equal(wallet.master.mnemonic.toString(), wallet3.master.mnemonic.toString());
|
|
});
|
|
|
|
it('should recover from a missed tx', async () => {
|
|
const wdb = new WalletDB({ network, 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({ network, 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 () => {
|
|
const wallet = await wdb.create({
|
|
id: 'alice100'
|
|
});
|
|
const addr1 = await wallet.receiveAddress();
|
|
const b = wdb.db.batch();
|
|
const wid = await wdb.getWID('alice100');
|
|
assert(await wdb.get('alice100'));
|
|
|
|
// Add one single, unconfirmed coin to wallet
|
|
const mtx = new MTX();
|
|
mtx.addInput(dummyInput());
|
|
mtx.addOutput(addr1, 10 * 1e8);
|
|
await wdb.addTXMap(b, mtx.hash(), wid);
|
|
|
|
// Add one name to NameMap
|
|
await wdb.addNameMap(b, rules.hashName('test123'), wid);
|
|
|
|
await b.write();
|
|
|
|
// Should return tx from TX Map
|
|
let wids = await wdb.getWalletsByTX(mtx);
|
|
assert(wids.has(wid));
|
|
|
|
// Should have wid in NameMap
|
|
let map = await wdb.getNameMap(rules.hashName('test123'));
|
|
assert(map.wids.has(wid));
|
|
|
|
// Remove wallet
|
|
await wdb.remove('alice100');
|
|
|
|
// Should not return tx from TX Map after wallet is removed
|
|
wids = await wdb.getWalletsByTX(mtx);
|
|
assert.strictEqual(wids, null);
|
|
|
|
// Should not return wid from NameMap after wid is removed
|
|
map = await wdb.getNameMap(rules.hashName('test123'));
|
|
assert.strictEqual(map, null);
|
|
|
|
// Should not return wallet after it is removed
|
|
assert(!await wdb.get('alice100'));
|
|
});
|
|
|
|
it('should count pending ancestors', async () => {
|
|
// Create wallet and get one address
|
|
const wallet = await wdb.create();
|
|
const addr1 = await wallet.receiveAddress();
|
|
|
|
// Dummy address for outputs
|
|
const recAddr = Address.fromHash(Buffer.alloc(20, 1));
|
|
|
|
// Add one single, unconfirmed coin to wallet
|
|
const mtx = new MTX();
|
|
mtx.addInput(dummyInput());
|
|
mtx.addOutput(addr1, 10 * 1e8);
|
|
const tx0 = mtx.toTX();
|
|
await wallet.txdb.add(tx0, null);
|
|
|
|
let ancs = null;
|
|
ancs = await wallet.getPendingAncestors(tx0);
|
|
assert.strictEqual(ancs.size, 0);
|
|
|
|
// Create one tx
|
|
const tx1 = await wallet.send({
|
|
outputs: [{
|
|
address: recAddr,
|
|
value: 10000
|
|
}]
|
|
});
|
|
ancs = await wallet.getPendingAncestors(tx1);
|
|
assert.strictEqual(ancs.size, 1);
|
|
|
|
// Create a second tx
|
|
const tx2 = await wallet.send({
|
|
outputs: [{
|
|
address: recAddr,
|
|
value: 10000
|
|
}]
|
|
});
|
|
ancs = await wallet.getPendingAncestors(tx2);
|
|
assert.strictEqual(ancs.size, 2);
|
|
|
|
// Confirm tx0 with dummy block
|
|
const block100 = {
|
|
height: 100,
|
|
hash: Buffer.alloc(32, 0),
|
|
time: util.now()
|
|
};
|
|
const wtx0 = await wallet.txdb.getTX(tx0.hash());
|
|
await wallet.txdb.confirm(wtx0, block100, {
|
|
medianTime: await wdb.getMedianTime(99, block100.time),
|
|
txIndex: 0
|
|
});
|
|
|
|
ancs = await wallet.getPendingAncestors(tx2);
|
|
assert.strictEqual(ancs.size, 1);
|
|
|
|
// Confirm tx1 with dummy block
|
|
const block101 = {
|
|
height: 101,
|
|
hash: Buffer.alloc(32, 1),
|
|
time: util.now()
|
|
};
|
|
const wtx1 = await wallet.txdb.getTX(tx1.hash());
|
|
await wallet.txdb.confirm(wtx1, block101, {
|
|
medianTime: await wdb.getMedianTime(100, block101.time),
|
|
txIndex: 0
|
|
});
|
|
|
|
ancs = await wallet.getPendingAncestors(tx2);
|
|
assert.strictEqual(ancs.size, 0);
|
|
});
|
|
|
|
it('should not exceed MEMPOOL_MAX_ANCESTORS policy', async () => {
|
|
// Create wallet and get one address
|
|
const wallet = await wdb.create();
|
|
const addr1 = await wallet.receiveAddress();
|
|
|
|
// Dummy address for outputs
|
|
const recAddr = Address.fromHash(Buffer.alloc(20, 1));
|
|
|
|
// Add one single, unconfirmed coin to wallet
|
|
const mtx1 = new MTX();
|
|
mtx1.addInput(dummyInput());
|
|
mtx1.addOutput(addr1, 10 * 1e8);
|
|
const tx1 = mtx1.toTX();
|
|
await wallet.txdb.add(tx1, null);
|
|
|
|
// Spend unconfirmed change outputs up to the limit
|
|
const limit = policy.MEMPOOL_MAX_ANCESTORS;
|
|
for (let i = 0; i < limit - 1; i++) {
|
|
const tx = await wallet.send({
|
|
outputs: [{
|
|
address: recAddr,
|
|
value: 10000
|
|
}]
|
|
});
|
|
assert(await wallet.txdb.hasPending(tx.hash()));
|
|
}
|
|
|
|
// At the limit
|
|
const pending = await wallet.getPending();
|
|
assert.strictEqual(pending.length, policy.MEMPOOL_MAX_ANCESTORS);
|
|
|
|
// One more unconfirmed change spend would exceed the limit
|
|
await assert.rejects(async () => {
|
|
await wallet.send({
|
|
outputs: [{
|
|
address: recAddr,
|
|
value: 10000
|
|
}]
|
|
});
|
|
}, {
|
|
message: 'TX exceeds maximum unconfirmed ancestors.'
|
|
});
|
|
});
|
|
|
|
it('should create credit if not found during confirmation', async () => {
|
|
// Create wallet and get one address
|
|
const wallet = await wdb.create();
|
|
const addr1 = await wallet.receiveAddress();
|
|
|
|
// Outside the wallet, generate a second private key and address.
|
|
const key2 = HDPrivateKey.generate();
|
|
const ring2 = KeyRing.fromPrivate(key2.privateKey);
|
|
const addr2 = ring2.getAddress();
|
|
|
|
// Build TX to both addresses, known and unknown
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx.addOutput(addr1, 1020304);
|
|
mtx.addOutput(addr2, 4030201);
|
|
const tx = mtx.toTX();
|
|
const hash = tx.hash();
|
|
|
|
// Add unconfirmed TX to txdb (no block provided)
|
|
await wallet.txdb.add(tx, null);
|
|
|
|
// Check
|
|
const bal1 = await wallet.getBalance();
|
|
assert.strictEqual(bal1.tx, 1);
|
|
assert.strictEqual(bal1.coin, 1);
|
|
assert.strictEqual(bal1.confirmed, 0);
|
|
assert.strictEqual(bal1.unconfirmed, 1020304);
|
|
|
|
// Import private key into wallet
|
|
assert(!await wallet.hasAddress(addr2));
|
|
await wallet.importKey('default', ring2);
|
|
assert(await wallet.hasAddress(addr2));
|
|
|
|
// Confirm TX with newly-added output address
|
|
// Create dummy block
|
|
const block = {
|
|
height: 100,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
// Get TX from txdb
|
|
const wtx = await wallet.txdb.getTX(hash);
|
|
// Confirm TX with dummy block in txdb
|
|
const details = await wallet.txdb.confirm(wtx, block, {
|
|
medianTime: await wdb.getMedianTime(99, block.time),
|
|
txIndex: 0
|
|
});
|
|
assert.bufferEqual(details.tx.hash(), hash);
|
|
|
|
// Check balance
|
|
const bal2 = await wallet.getBalance();
|
|
assert.strictEqual(bal2.confirmed, bal2.unconfirmed);
|
|
assert.strictEqual(bal2.confirmed, 5050505);
|
|
assert.strictEqual(bal2.coin, 2);
|
|
assert.strictEqual(bal2.tx, 1);
|
|
|
|
// Check for unconfirmed transactions
|
|
const pending = await wallet.getPending();
|
|
assert.strictEqual(pending.length, 0);
|
|
|
|
// Check history for TX
|
|
const history = await wallet.listHistory(-1, {
|
|
limit: 100,
|
|
reverse: false
|
|
});
|
|
|
|
const wtxs = await wallet.toDetails(history);
|
|
assert.strictEqual(wtxs.length, 1);
|
|
assert.bufferEqual(wtxs[0].hash, hash);
|
|
|
|
// Both old and new credits are not "owned"
|
|
// (created by the wallet spending its own coins)
|
|
for (let i = 0; i < tx.outputs.length; i++) {
|
|
const credit = await wallet.txdb.getCredit(tx.hash(), i);
|
|
assert(!credit.own);
|
|
}
|
|
});
|
|
|
|
it('should create owned credit if not found during confirm', async () => {
|
|
// Create wallet and get one address
|
|
const wallet = await wdb.create();
|
|
const addr1 = await wallet.receiveAddress();
|
|
|
|
// Outside the wallet, generate a second private key and address.
|
|
const key2 = HDPrivateKey.generate();
|
|
const ring2 = KeyRing.fromPrivate(key2.privateKey);
|
|
const addr2 = ring2.getAddress();
|
|
|
|
// Create a confirmed, unspent, wallet-owned credit in txdb
|
|
const mtx1 = new MTX();
|
|
mtx1.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx1.addOutput(addr1, 1 * 1e8);
|
|
const tx1 = mtx1.toTX();
|
|
await wallet.txdb.add(tx1, null);
|
|
|
|
// Create dummy block
|
|
const block1 = {
|
|
height: 99,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
// Get TX from txdb
|
|
const wtx1 = await wallet.txdb.getTX(tx1.hash());
|
|
|
|
// Confirm TX with dummy block in txdb
|
|
await wallet.txdb.confirm(wtx1, block1, {
|
|
medianTime: await wdb.getMedianTime(98, block1.time),
|
|
txIndex: 0
|
|
});
|
|
|
|
// Build TX to both addresses, known and unknown
|
|
const mtx2 = new MTX();
|
|
mtx2.addTX(tx1, 0, 99);
|
|
mtx2.addOutput(addr1, 1020304);
|
|
mtx2.addOutput(addr2, 4030201);
|
|
const tx2 = mtx2.toTX();
|
|
const hash = tx2.hash();
|
|
|
|
// Add unconfirmed TX to txdb (no block provided)
|
|
await wallet.txdb.add(tx2, null);
|
|
|
|
// Check
|
|
const bal1 = await wallet.getBalance();
|
|
assert.strictEqual(bal1.tx, 2);
|
|
assert.strictEqual(bal1.coin, 1);
|
|
assert.strictEqual(bal1.confirmed, 1 * 1e8);
|
|
assert.strictEqual(bal1.unconfirmed, 1020304);
|
|
|
|
// Import private key into wallet
|
|
assert(!await wallet.hasAddress(addr2));
|
|
await wallet.importKey('default', ring2);
|
|
assert(await wallet.hasAddress(addr2));
|
|
|
|
// Confirm TX with newly-added output address
|
|
// Create dummy block
|
|
const block2 = {
|
|
height: 100,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
// Get TX from txdb
|
|
const wtx2 = await wallet.txdb.getTX(hash);
|
|
|
|
// Confirm TX with dummy block in txdb
|
|
const details = await wallet.txdb.confirm(wtx2, block2, {
|
|
medianTime: await wdb.getMedianTime(99, block2.time),
|
|
txIndex: 0
|
|
});
|
|
assert.bufferEqual(details.tx.hash(), hash);
|
|
|
|
// Check balance
|
|
const bal2 = await wallet.getBalance();
|
|
assert.strictEqual(bal2.confirmed, bal2.unconfirmed);
|
|
assert.strictEqual(bal2.confirmed, 5050505);
|
|
assert.strictEqual(bal2.coin, 2);
|
|
assert.strictEqual(bal2.tx, 2);
|
|
|
|
// Check for unconfirmed transactions
|
|
const pending = await wallet.getPending();
|
|
assert.strictEqual(pending.length, 0);
|
|
|
|
// Both old and new credits are "owned"
|
|
// (created by the wallet spending its own coins)
|
|
for (let i = 0; i < tx2.outputs.length; i++) {
|
|
const credit = await wallet.txdb.getCredit(tx2.hash(), i);
|
|
assert(credit.own);
|
|
}
|
|
});
|
|
|
|
it('should throw error with missing outputs', async () => {
|
|
const wallet = new Wallet({ options: {} });
|
|
|
|
let err = null;
|
|
|
|
try {
|
|
await wallet.send({outputs: []});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.equal(err.message, 'At least one output is required.');
|
|
});
|
|
|
|
it('should pass nowFn to the txdb', async () => {
|
|
const NOW = 1;
|
|
const nowFn = () => NOW;
|
|
const wallet = new Wallet({
|
|
options: {
|
|
nowFn
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(wallet.txdb.nowFn(), NOW);
|
|
});
|
|
|
|
it('should cleanup', async () => {
|
|
network.coinbaseMaturity = 2;
|
|
await wdb.close();
|
|
});
|
|
|
|
describe('Disable TXs', function() {
|
|
const network = Network.get('regtest');
|
|
const workers = new WorkerPool({ enabled, size });
|
|
const wdb = new WalletDB({ network, workers });
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
});
|
|
|
|
after(async () => {
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should only send a tx after network txStart', async () => {
|
|
// Create wallet and get one address
|
|
const wallet = await wdb.create();
|
|
const addr1 = await wallet.receiveAddress();
|
|
|
|
// Dummy address for outputs
|
|
const recAddr = Address.fromHash(Buffer.alloc(20, 1));
|
|
|
|
// Add one single, unconfirmed coin to wallet
|
|
const mtx1 = new MTX();
|
|
mtx1.addInput(dummyInput());
|
|
mtx1.addOutput(addr1, 10 * 1e8);
|
|
const tx1 = mtx1.toTX();
|
|
await wallet.txdb.add(tx1, null);
|
|
|
|
const ACTUAL_TXSTART = wallet.network.txStart;
|
|
const ACTUAL_HEIGHT = wdb.height;
|
|
|
|
try {
|
|
wallet.network.txStart = 6;
|
|
wdb.height = 4;
|
|
|
|
await assert.rejects(
|
|
wallet.send({outputs: [{address: recAddr, value: 10000}]}),
|
|
{message: 'Transactions are not allowed on network yet.'}
|
|
);
|
|
} finally {
|
|
wallet.network.txStart = ACTUAL_TXSTART;
|
|
wdb.height = ACTUAL_HEIGHT;
|
|
}
|
|
|
|
try {
|
|
// Transactions are allowed in the NEXT block
|
|
wallet.network.txStart = 6;
|
|
wdb.height = 5;
|
|
|
|
assert(
|
|
await wallet.send({outputs: [{address: recAddr, value: 10000}]})
|
|
);
|
|
} finally {
|
|
wallet.network.txStart = ACTUAL_TXSTART;
|
|
wdb.height = ACTUAL_HEIGHT;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Corruption', function() {
|
|
let workers = null;
|
|
let wdb = null;
|
|
|
|
beforeEach(async () => {
|
|
workers = new WorkerPool({ enabled, size });
|
|
wdb = new WalletDB({ workers });
|
|
await workers.open();
|
|
await wdb.open();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await wdb.close();
|
|
await workers.close();
|
|
});
|
|
|
|
it('should not write tip with error in txs', async () => {
|
|
const alice = await wdb.create();
|
|
const addr = await alice.receiveAddress();
|
|
|
|
const fund = new MTX();
|
|
fund.addInput(dummyInput());
|
|
fund.addOutput(addr, 5460 * 10);
|
|
|
|
wdb._addTX = async () => {
|
|
throw new Error('Some assertion.');
|
|
};
|
|
|
|
await assert.rejects(async () => {
|
|
await wdb.addBlock(nextBlock(wdb), [fund.toTX()]);
|
|
}, {
|
|
message: 'Some assertion.'
|
|
});
|
|
|
|
assert.equal(wdb.height, 0);
|
|
|
|
const bal = await alice.getBalance();
|
|
assert.equal(bal.confirmed, 0);
|
|
assert.equal(bal.unconfirmed, 0);
|
|
});
|
|
|
|
it('should write tip without error in txs', async () => {
|
|
const alice = await wdb.create();
|
|
const addr = await alice.receiveAddress();
|
|
|
|
const fund = new MTX();
|
|
fund.addInput(dummyInput());
|
|
const amount = 5460 * 10;
|
|
fund.addOutput(addr, amount);
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [fund.toTX()]);
|
|
|
|
assert.equal(wdb.height, 1);
|
|
|
|
const bal = await alice.getBalance();
|
|
assert.equal(bal.confirmed, amount);
|
|
assert.equal(bal.unconfirmed, amount);
|
|
});
|
|
});
|
|
|
|
describe('TXDB locked balance', function() {
|
|
const network = Network.get('regtest');
|
|
const workers = new WorkerPool({ enabled, size });
|
|
const wdb = new WalletDB({ network, workers });
|
|
// This test executes a complete auction for this name
|
|
const name = 'satoshi';
|
|
const nameHash = rules.hashName(name);
|
|
// There will be two bids. One from our wallet with a lockup and bid value,
|
|
// the second highest (losing) bid comes from an external wallet.
|
|
const value = 1e6;
|
|
const lockup = 2e6;
|
|
const secondHighest = value - 1;
|
|
// All TXs will have a hard-coded fee to simplify the expected balances,
|
|
// along with counters for OUTGOING (un)confirmed transactions.
|
|
const fee = 10000;
|
|
let uTXCount = 0;
|
|
let cTXCount = 0;
|
|
// Initial wallet funds
|
|
const fund = 10e6;
|
|
// Store height of block confirming the FINALIZE so we can disconnect later.
|
|
let finalizeBlock;
|
|
// Store height of auction OPEN to be used in second bid.
|
|
let start;
|
|
// The main test wallet, and wallet that will receive the FINALIZE.
|
|
let wallet, recip;
|
|
// Store balance data before rescan to ensure rescan was complete
|
|
let recipBalBefore, senderBalBefore;
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
await wdb.connect();
|
|
wallet = await wdb.create();
|
|
recip = await wdb.create();
|
|
network.names.noRollout = true;
|
|
});
|
|
|
|
after(async () => {
|
|
network.names.noRollout = false;
|
|
await wdb.disconnect();
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should fund wallet', async () => {
|
|
const addr = await wallet.receiveAddress();
|
|
|
|
// Fund wallet
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx.addOutput(addr, fund);
|
|
const tx = mtx.toTX();
|
|
|
|
// Dummy block
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
// Add confirmed funding TX to wallet
|
|
await txdbAdd(wallet, tx, block);
|
|
|
|
// Check
|
|
const bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 1);
|
|
assert.strictEqual(bal.coin, 1);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should send and confirm OPEN', async () => {
|
|
const open = await wallet.sendOpen(name, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm OPEN
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, open, block);
|
|
start = wdb.height;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should send and confirm BID', async () => {
|
|
// Advance to bidding
|
|
wdb.height += network.names.treeInterval + 1;
|
|
|
|
const bid = await wallet.sendBid(name, value, lockup, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm BID
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, bid, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
});
|
|
|
|
it('should send and confirm REVEAL', async () => {
|
|
// Advance to reveal
|
|
wdb.height += network.names.biddingPeriod;
|
|
|
|
const reveal = await wallet.sendReveal(name, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
|
|
// Confirm BID
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, reveal, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, value);
|
|
});
|
|
|
|
it('should add external REVEAL to txdb', async () => {
|
|
// The goal is to have a "second-highest" bid
|
|
// so the wallet doesn't win the name for free.
|
|
// We can skip the whole BID/lockup thing for these tests.
|
|
|
|
const output = new Output();
|
|
output.value = secondHighest;
|
|
output.covenant.setReveal(
|
|
nameHash,
|
|
start,
|
|
Buffer.alloc(32)
|
|
);
|
|
|
|
const mtx = new MTX();
|
|
mtx.addInput(dummyInput());
|
|
mtx.outputs.push(output);
|
|
|
|
// Confirm external REVEAL
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, mtx.toTX(), block);
|
|
});
|
|
|
|
it('should send and confirm REGISTER', async () => {
|
|
// Advance to close
|
|
wdb.height += network.names.revealPeriod;
|
|
|
|
const resource = Resource.fromJSON({records: []});
|
|
const register = await wallet.sendUpdate(name, resource, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
// Wallet coin count doesn't change:
|
|
// REVEAL + fee money -> REGISTER + change
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, value);
|
|
|
|
// Confirm REGISTER
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, register, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
});
|
|
|
|
it('should send and confirm TRANSFER', async () => {
|
|
const recipAddr = await recip.receiveAddress();
|
|
const transfer = await wallet.sendTransfer(name, recipAddr, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
const senderBal1 = await wallet.getBalance();
|
|
assert.strictEqual(senderBal1.tx, 6);
|
|
assert.strictEqual(senderBal1.coin, 4);
|
|
assert.strictEqual(senderBal1.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(senderBal1.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(senderBal1.ulocked, secondHighest);
|
|
assert.strictEqual(senderBal1.clocked, secondHighest);
|
|
|
|
const recipBal1 = await recip.getBalance();
|
|
assert.strictEqual(recipBal1.tx, 0);
|
|
assert.strictEqual(recipBal1.coin, 0);
|
|
assert.strictEqual(recipBal1.confirmed, 0);
|
|
assert.strictEqual(recipBal1.unconfirmed, 0);
|
|
assert.strictEqual(recipBal1.ulocked, 0);
|
|
assert.strictEqual(recipBal1.clocked, 0);
|
|
|
|
// Confirm TRANSFER
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, transfer, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
const senderBal2 = await wallet.getBalance();
|
|
assert.strictEqual(senderBal2.tx, 6);
|
|
assert.strictEqual(senderBal2.coin, 4);
|
|
assert.strictEqual(senderBal2.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(senderBal2.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(senderBal2.ulocked, secondHighest);
|
|
assert.strictEqual(senderBal2.clocked, secondHighest);
|
|
|
|
const recipBal2 = await recip.getBalance();
|
|
assert.strictEqual(recipBal2.tx, 0);
|
|
assert.strictEqual(recipBal2.coin, 0);
|
|
assert.strictEqual(recipBal2.confirmed, 0);
|
|
assert.strictEqual(recipBal2.unconfirmed, 0);
|
|
assert.strictEqual(recipBal2.ulocked, 0);
|
|
assert.strictEqual(recipBal2.clocked, 0);
|
|
});
|
|
|
|
it('should send and confirm FINALIZE', async () => {
|
|
// Advance past lockup period
|
|
wdb.height += network.names.transferLockup;
|
|
|
|
const finalize = await wallet.sendFinalize(name, {hardFee: fee});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
const senderBal3 = await wallet.getBalance();
|
|
assert.strictEqual(senderBal3.tx, 7);
|
|
// One less wallet coin because name UTXO belongs to recip now
|
|
assert.strictEqual(senderBal3.coin, 3);
|
|
assert.strictEqual(senderBal3.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(senderBal3.unconfirmed, fund - secondHighest - (uTXCount * fee));
|
|
assert.strictEqual(senderBal3.ulocked, 0);
|
|
assert.strictEqual(senderBal3.clocked, secondHighest);
|
|
|
|
// The name and its locked value now belong to recipient
|
|
const recipBal3 = await recip.getBalance();
|
|
assert.strictEqual(recipBal3.tx, 1);
|
|
assert.strictEqual(recipBal3.coin, 1);
|
|
assert.strictEqual(recipBal3.confirmed, 0);
|
|
assert.strictEqual(recipBal3.unconfirmed, secondHighest);
|
|
assert.strictEqual(recipBal3.ulocked, secondHighest);
|
|
assert.strictEqual(recipBal3.clocked, 0);
|
|
|
|
// Confirm FINALIZE
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
await txdbAdd(wallet, finalize, block);
|
|
await txdbAdd(recip, finalize, block);
|
|
|
|
finalizeBlock = block.height;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
senderBalBefore = await wallet.getBalance();
|
|
assert.strictEqual(senderBalBefore.tx, 7);
|
|
assert.strictEqual(senderBalBefore.coin, 3);
|
|
assert.strictEqual(senderBalBefore.confirmed, fund - secondHighest - (cTXCount * fee));
|
|
assert.strictEqual(senderBalBefore.unconfirmed, fund - secondHighest - (uTXCount * fee));
|
|
assert.strictEqual(senderBalBefore.ulocked, 0);
|
|
assert.strictEqual(senderBalBefore.clocked, 0);
|
|
|
|
recipBalBefore = await recip.getBalance();
|
|
assert.strictEqual(recipBalBefore.tx, 1);
|
|
assert.strictEqual(recipBalBefore.coin, 1);
|
|
assert.strictEqual(recipBalBefore.confirmed, secondHighest);
|
|
assert.strictEqual(recipBalBefore.unconfirmed, secondHighest);
|
|
assert.strictEqual(recipBalBefore.ulocked, secondHighest);
|
|
assert.strictEqual(recipBalBefore.clocked, secondHighest);
|
|
});
|
|
|
|
it('should have correct balance after rescan', async () => {
|
|
await wdb.rescan(0);
|
|
|
|
const senderBalAfter = await wallet.getBalance();
|
|
const recipBalAfter = await recip.getBalance();
|
|
|
|
assert.deepStrictEqual(senderBalAfter, senderBalBefore);
|
|
assert.deepStrictEqual(recipBalAfter, recipBalBefore);
|
|
});
|
|
|
|
it('should disconnect FINALIZE', async () => {
|
|
const walletBlock = await wallet.txdb.getBlock(finalizeBlock);
|
|
const walletHashes = walletBlock.toArray();
|
|
assert.strictEqual(walletHashes.length, 1);
|
|
await wallet.txdb.unconfirm(walletHashes[0], finalizeBlock, {
|
|
medianTime: walletBlock.time,
|
|
txIndex: 0
|
|
});
|
|
|
|
const recipBlock = await recip.txdb.getBlock(finalizeBlock);
|
|
const recipHashes = recipBlock.toArray();
|
|
assert.strictEqual(recipHashes.length, 1);
|
|
await recip.txdb.unconfirm(recipHashes[0], finalizeBlock, {
|
|
medianTime: recipBlock.time,
|
|
txIndex: 0
|
|
});
|
|
cTXCount--;
|
|
|
|
// Check
|
|
const senderBal4 = await wallet.getBalance();
|
|
assert.strictEqual(senderBal4.tx, 7);
|
|
assert.strictEqual(senderBal4.coin, 3);
|
|
assert.strictEqual(senderBal4.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(senderBal4.unconfirmed, fund - secondHighest - (uTXCount * fee));
|
|
assert.strictEqual(senderBal4.ulocked, 0);
|
|
assert.strictEqual(senderBal4.clocked, secondHighest);
|
|
|
|
const recipBal4 = await recip.getBalance();
|
|
assert.strictEqual(recipBal4.tx, 1);
|
|
assert.strictEqual(recipBal4.coin, 1);
|
|
assert.strictEqual(recipBal4.confirmed, 0);
|
|
assert.strictEqual(recipBal4.unconfirmed, secondHighest);
|
|
assert.strictEqual(recipBal4.ulocked, secondHighest);
|
|
assert.strictEqual(recipBal4.clocked, 0);
|
|
});
|
|
});
|
|
|
|
describe('TXDB locked balance after simulated rescan', function() {
|
|
const network = Network.get('regtest');
|
|
const workers = new WorkerPool({ enabled, size });
|
|
const wdb = new WalletDB({ network, workers });
|
|
const name = 'satoshi';
|
|
const nameHash = rules.hashName(name);
|
|
const value = 1e6;
|
|
const lockup = 2e6;
|
|
const secondHighest = value - 1;
|
|
const fee = 10000;
|
|
const fund = 10e6;
|
|
let uTXCount = 0;
|
|
let cTXCount = 0;
|
|
let start;
|
|
let wallet;
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
wallet = await wdb.create();
|
|
// rollout all names
|
|
network.names.noRollout = true;
|
|
});
|
|
|
|
after(async () => {
|
|
network.names.noRollout = false;
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should fund wallet', async () => {
|
|
const addr = await wallet.receiveAddress();
|
|
|
|
// Fund wallet
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx.addOutput(addr, fund);
|
|
const tx = mtx.toTX();
|
|
|
|
// Dummy block
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
// Add confirmed funding TX to wallet
|
|
await txdbAdd(wallet, tx, block);
|
|
|
|
// Check
|
|
const bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 1);
|
|
assert.strictEqual(bal.coin, 1);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should confirm new OPEN', async () => {
|
|
const open = await wallet.createOpen(name, { hardFee: fee });
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 1);
|
|
assert.strictEqual(bal.coin, 1);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm OPEN
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
await txdbAdd(wallet, open.toTX(), block);
|
|
start = wdb.height;
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should confirm new BID', async () => {
|
|
// Advance to bidding
|
|
wdb.height += network.names.treeInterval + 1;
|
|
|
|
const bid = await wallet.createBid(name, value, lockup, {hardFee: fee});
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm BID
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
await txdbAdd(wallet, bid.toTX(), block);
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
});
|
|
|
|
it('should confirm new REVEAL', async () => {
|
|
// Advance to reveal
|
|
wdb.height += network.names.biddingPeriod;
|
|
|
|
const reveal = await wallet.createReveal(name, {hardFee: fee});
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
|
|
// Confirm BID
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, reveal.toTX(), block);
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, value);
|
|
});
|
|
|
|
it('should add external REVEAL to txdb', async () => {
|
|
// The goal is to have a "second-highest" bid
|
|
// so the wallet doesn't win the name for free.
|
|
// We can skip the whole BID/lockup thing for these tests.
|
|
|
|
const output = new Output();
|
|
output.value = secondHighest;
|
|
output.covenant.setReveal(
|
|
nameHash,
|
|
start,
|
|
Buffer.alloc(32)
|
|
);
|
|
|
|
const mtx = new MTX();
|
|
mtx.addInput(dummyInput());
|
|
mtx.outputs.push(output);
|
|
|
|
// Confirm external REVEAL
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, mtx.toTX(), block);
|
|
});
|
|
|
|
it('should confirm new REGISTER', async () => {
|
|
// Advance to close
|
|
wdb.height += network.names.revealPeriod;
|
|
|
|
const resource = Resource.fromJSON({records: []});
|
|
const register = await wallet.createUpdate(name, resource, {hardFee: fee});
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, value);
|
|
|
|
// Confirm REGISTER
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
await txdbAdd(wallet, register.toTX(), block);
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
// Wallet coin count doesn't change:
|
|
// REVEAL + fee money -> REGISTER + change
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
});
|
|
|
|
it('should confirm new TRANSFER', async () => {
|
|
const addr = new Address({
|
|
version: 0,
|
|
hash: Buffer.alloc(20, 0x88)
|
|
});
|
|
const transfer = await wallet.createTransfer(name, addr, {hardFee: fee});
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
|
|
// Confirm TRANSFER
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, transfer.toTX(), block);
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 6);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
});
|
|
|
|
it('should confirm new FINALIZE', async () => {
|
|
// Advance past lockup
|
|
wdb.height += network.names.transferLockup + 1;
|
|
|
|
const finalize = await wallet.createFinalize(name, {hardFee: fee});
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 6);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
|
|
// Confirm FINALIZE
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, finalize.toTX(), block);
|
|
uTXCount++;
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 7);
|
|
// Coin count reduced by giving away name UTXO
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - secondHighest - (cTXCount * fee));
|
|
assert.strictEqual(bal.unconfirmed, fund - secondHighest - (uTXCount * fee));
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
});
|
|
|
|
describe('Node Integration', function() {
|
|
const ports = {p2p: 49331, node: 49332, wallet: 49333};
|
|
let node, chain, miner, wdb = null;
|
|
|
|
beforeEach(async () => {
|
|
node = new FullNode({
|
|
memory: true,
|
|
network: 'regtest',
|
|
workers: true,
|
|
workersSize: 2,
|
|
plugins: [require('../lib/wallet/plugin')],
|
|
port: ports.p2p,
|
|
httpPort: ports.node,
|
|
env: {
|
|
'HSD_WALLET_HTTP_PORT': ports.wallet.toString()
|
|
}
|
|
});
|
|
|
|
chain = node.chain;
|
|
miner = node.miner;
|
|
wdb = node.require('walletdb').wdb;
|
|
await node.open();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await node.close();
|
|
});
|
|
|
|
async function createBlock(tip) {
|
|
const job = await miner.createJob(tip);
|
|
const block = await job.mineAsync();
|
|
return block;
|
|
}
|
|
|
|
async function mineBlock(tip) {
|
|
const block = await createBlock(tip);
|
|
return chain.add(block);
|
|
}
|
|
|
|
it('should not stack in-memory block queue (oom)', async () => {
|
|
let height = 0;
|
|
|
|
const addBlock = wdb.addBlock.bind(wdb);
|
|
wdb.addBlock = async (entry, txs) => {
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
await addBlock(entry, txs);
|
|
};
|
|
|
|
async function raceForward() {
|
|
await mineBlock();
|
|
|
|
await forValue(node.chain, 'height', height + 1);
|
|
assert.equal(wdb.height, height + 1);
|
|
|
|
height += 1;
|
|
}
|
|
|
|
for (let i = 0; i < 10; i++)
|
|
await raceForward();
|
|
});
|
|
|
|
it('should emit details with correct confirmation', async () => {
|
|
const wclient = new WalletClient({port: ports.wallet});
|
|
await wclient.open();
|
|
|
|
const info = await wclient.createWallet('test');
|
|
const wallet = wclient.wallet('test', info.token);
|
|
await wallet.open();
|
|
|
|
const acct = await wallet.getAccount('default');
|
|
const waddr = acct.receiveAddress;
|
|
|
|
miner.addresses.length = 0;
|
|
miner.addAddress(waddr);
|
|
|
|
let txCount = 0;
|
|
let txConfirmedCount = 0;
|
|
let confirmedCount = 0;
|
|
|
|
wallet.on('tx', (details) => {
|
|
if (details.confirmations === 1)
|
|
txConfirmedCount += 1;
|
|
else if (details.confirmations === 0)
|
|
txCount += 1;
|
|
});
|
|
|
|
wallet.on('confirmed', (details) => {
|
|
assert.equal(details.confirmations, 1);
|
|
confirmedCount += 1;
|
|
});
|
|
|
|
for (let i = 0; i < 101; i++)
|
|
await mineBlock();
|
|
|
|
await wallet.send({outputs: [{address: waddr, value: 1 * 1e8}]});
|
|
await mineBlock();
|
|
|
|
await wclient.close();
|
|
|
|
assert.equal(txConfirmedCount, 102);
|
|
assert.equal(txCount, 1);
|
|
assert.equal(confirmedCount, 1);
|
|
});
|
|
|
|
it('should emit conflict event (multiple inputs)', async () => {
|
|
const wallet = await wdb.create({id: 'test2'});
|
|
const address = await wallet.receiveAddress();
|
|
|
|
const wclient = new WalletClient({port: ports.wallet});
|
|
await wclient.open();
|
|
|
|
const cwallet = wclient.wallet(wallet.id, wallet.token);
|
|
await cwallet.open();
|
|
|
|
try {
|
|
const hash = random.randomBytes(32);
|
|
const input0 = Input.fromOutpoint(new Outpoint(hash, 0));
|
|
const input1 = Input.fromOutpoint(new Outpoint(hash, 1));
|
|
|
|
const txa = new MTX();
|
|
txa.addInput(input0);
|
|
txa.addInput(input1);
|
|
txa.addOutput(address, 50000);
|
|
await wdb.addTX(txa.toTX());
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 50000);
|
|
|
|
let conflict = 0;
|
|
cwallet.on('conflict', () => {
|
|
conflict += 1;
|
|
});
|
|
|
|
const txb = new MTX();
|
|
txb.addInput(input0);
|
|
txb.addInput(input1);
|
|
txb.addOutput(address, 49000);
|
|
await wdb.addTX(txb.toTX());
|
|
|
|
assert.strictEqual(conflict, 1);
|
|
assert.strictEqual((await wallet.getBalance()).unconfirmed, 49000);
|
|
} finally {
|
|
await wclient.close();
|
|
}
|
|
});
|
|
|
|
it('should get same mtp for chain and wallet', async () => {
|
|
const assertSameMTP = async (mtp) => {
|
|
assert.strictEqual(wdb.state.height, chain.tip.height);
|
|
|
|
const chainMTP = await node.chain.getMedianTime(chain.tip);
|
|
const walletMTP = await wdb.getMedianTime(wdb.state.height);
|
|
|
|
assert.strictEqual(walletMTP, chainMTP);
|
|
if (mtp)
|
|
assert.strictEqual(mtp, chainMTP);
|
|
};
|
|
|
|
await assertSameMTP();
|
|
|
|
const times = [];
|
|
const mtp = await node.chain.getMedianTime(chain.tip);
|
|
times[chain.tip.height] = mtp;
|
|
for (let i = 0; i < 40; i++) {
|
|
const block = await createBlock(chain.tip);
|
|
const futureMTP = await wdb.getMedianTime(chain.tip.height, block.time);
|
|
await chain.add(block);
|
|
await assertSameMTP(futureMTP);
|
|
const mtp = await node.chain.getMedianTime(chain.tip);
|
|
times[chain.tip.height] = mtp;
|
|
}
|
|
|
|
// revert all
|
|
for (let i = 0; i < 40; i++) {
|
|
const entry = chain.tip;
|
|
const mtp = await chain.getMedianTime(entry);
|
|
const tipMtp = await wdb.getMedianTime(entry.height - 1, entry.time);
|
|
await assertSameMTP(times[entry.height]);
|
|
assert.strictEqual(tipMtp, times[entry.height]);
|
|
assert.strictEqual(tipMtp, mtp);
|
|
|
|
await chain.disconnect(entry);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Wallet Name Claims', function() {
|
|
// 'it' blocks in this 'describe' create state
|
|
// that later 'it' blocks depend on.
|
|
let wallet, update;
|
|
const network = Network.get('regtest');
|
|
const workers = new WorkerPool({enabled: false, size});
|
|
const wdb = new WalletDB({network, workers});
|
|
// Cloudflare's "custom value" plus the standard "name value".
|
|
// Verifiable with reserved-browser.js and names.json
|
|
const lockup = 6800000000000 + 503513487;
|
|
// By setting the fee rate to zero when we create the claim,
|
|
// we can ensure deterministic wallet values even if the size of
|
|
// claim changes due to external factors like Cloudflare updating
|
|
// their real world DNS zone, which is retrieved by the wallet in this test.
|
|
const fee = 0;
|
|
const name = 'cloudflare';
|
|
const nameHash = rules.hashString(name);
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
await wdb.connect();
|
|
wallet = await wdb.create();
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
const entry = nextEntry(wdb);
|
|
await wdb.addBlock(entry, []);
|
|
}
|
|
});
|
|
|
|
after(async () => {
|
|
await wdb.disconnect();
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should not have any cloudflare state', async () => {
|
|
const nameinfo = await wallet.getNameState(nameHash);
|
|
assert.deepEqual(nameinfo, null);
|
|
});
|
|
|
|
it('should confirm cloudflare CLAIM', async () => {
|
|
this.timeout(10000);
|
|
|
|
// Use a fresh wallet.
|
|
const pre = await wallet.getBalance();
|
|
assert.equal(pre.tx, 0);
|
|
assert.equal(pre.coin, 0);
|
|
assert.equal(pre.unconfirmed, 0);
|
|
assert.equal(pre.confirmed, 0);
|
|
assert.equal(pre.ulocked, 0);
|
|
assert.equal(pre.clocked, 0);
|
|
|
|
const claim = await wallet.sendFakeClaim('cloudflare', {fee});
|
|
assert(claim);
|
|
|
|
const tx = claim.toTX(network, wdb.state.height + 1);
|
|
const entry = nextEntry(wdb);
|
|
await wdb.addBlock(entry, [tx]);
|
|
|
|
const ns = await wallet.getNameState(nameHash);
|
|
const json = ns.getJSON(wdb.state.height, network);
|
|
assert.equal(json.name, 'cloudflare');
|
|
assert.equal(json.state, 'LOCKED');
|
|
|
|
const post = await wallet.getBalance();
|
|
assert.equal(post.tx, 1);
|
|
assert.equal(post.coin, 1);
|
|
assert.equal(post.unconfirmed, lockup);
|
|
assert.equal(post.confirmed, lockup);
|
|
assert.equal(post.ulocked, lockup);
|
|
assert.equal(post.clocked, lockup);
|
|
});
|
|
|
|
it('should advance past lockup period', async () => {
|
|
const ns = await wallet.getNameState(nameHash);
|
|
const json = ns.getJSON(wdb.state.height, network);
|
|
const {blocksUntilClosed} = json.stats;
|
|
|
|
for (let i = 0; i < blocksUntilClosed; i++) {
|
|
const entry = nextEntry(wdb);
|
|
await wdb.addBlock(entry, []);
|
|
}
|
|
|
|
{
|
|
const ns = await wallet.getNameState(nameHash);
|
|
const json = ns.getJSON(wdb.state.height, network);
|
|
assert.equal(json.name, 'cloudflare');
|
|
assert.equal(json.state, 'CLOSED');
|
|
}
|
|
});
|
|
|
|
it('should send an update for cloudflare', async () => {
|
|
const pre = await wallet.getBalance();
|
|
assert.equal(pre.tx, 1);
|
|
assert.equal(pre.coin, 1);
|
|
assert.equal(pre.unconfirmed, lockup);
|
|
assert.equal(pre.confirmed, lockup);
|
|
assert.equal(pre.ulocked, lockup);
|
|
assert.equal(pre.clocked, lockup);
|
|
|
|
const records = Resource.fromJSON({
|
|
records: [{type: 'NS', ns: 'ns1.easyhandshake.com.'}]
|
|
});
|
|
|
|
update = await wallet.sendUpdate('cloudflare', records);
|
|
const entry = nextEntry(wdb);
|
|
await wdb.addBlock(entry, [update]);
|
|
|
|
const ns = await wallet.getNameState(nameHash);
|
|
const json = ns.getJSON(wdb.state.height, network);
|
|
assert.equal(json.name, 'cloudflare');
|
|
|
|
const resource = Resource.decode(ns.data);
|
|
assert.deepEqual(records.toJSON(), resource.toJSON());
|
|
|
|
// The unconfirmed and confirmed values should
|
|
// take into account the transaction fee. Assert
|
|
// against the value of the newly created output.
|
|
const val = update.output(1).value;
|
|
const post = await wallet.getBalance();
|
|
assert.equal(post.tx, 2);
|
|
assert.equal(post.coin, 2);
|
|
assert.equal(post.unconfirmed, val);
|
|
assert.equal(post.confirmed, val);
|
|
assert.equal(post.ulocked, 0);
|
|
assert.equal(post.clocked, 0);
|
|
});
|
|
|
|
it('should remove a block and update balances correctly', async () => {
|
|
const val = update.output(1).value;
|
|
const pre = await wallet.getBalance();
|
|
assert.equal(pre.tx, 2);
|
|
assert.equal(pre.coin, 2);
|
|
assert.equal(pre.unconfirmed, val);
|
|
assert.equal(pre.confirmed, val);
|
|
assert.equal(pre.ulocked, 0);
|
|
assert.equal(pre.clocked, 0);
|
|
|
|
const cur = curEntry(wdb);
|
|
await wdb.removeBlock(cur);
|
|
|
|
const post = await wallet.getBalance();
|
|
assert.equal(post.tx, 2);
|
|
assert.equal(post.coin, 2);
|
|
// The unconfirmed balance includes value in the mempool
|
|
// and the chain itself. The reorg'd tx can be included
|
|
// in another block so the unconfirmed total does not
|
|
// include the tx fee. That value has been effectively
|
|
// spent already.
|
|
assert.equal(post.unconfirmed, val);
|
|
assert.equal(post.confirmed, lockup);
|
|
assert.equal(post.ulocked, 0);
|
|
assert.equal(post.clocked, lockup);
|
|
});
|
|
|
|
it('should update balances correctly after abandon', async () => {
|
|
const val = update.output(1).value;
|
|
const pre = await wallet.getBalance();
|
|
assert.equal(pre.tx, 2);
|
|
assert.equal(pre.coin, 2);
|
|
assert.equal(pre.unconfirmed, val);
|
|
assert.equal(pre.confirmed, lockup);
|
|
assert.equal(pre.ulocked, 0);
|
|
assert.equal(pre.clocked, lockup);
|
|
|
|
assert(await wallet.txdb.hasTX(update.hash()));
|
|
await wallet.abandon(update.hash());
|
|
|
|
// The UPDATE was abandoned and now the wallet
|
|
// reflects only the CLAIM, so these values
|
|
// should match the wallet balance post
|
|
// 'should confirm cloudflare CLAIM'
|
|
const post = await wallet.getBalance();
|
|
assert.equal(post.tx, 1);
|
|
assert.equal(post.coin, 1);
|
|
assert.equal(post.unconfirmed, lockup);
|
|
assert.equal(post.confirmed, lockup);
|
|
assert.equal(post.ulocked, lockup);
|
|
assert.equal(post.clocked, lockup);
|
|
|
|
const coins = await wallet.getCoins();
|
|
assert.equal(coins.length, 1);
|
|
const [claim] = coins;
|
|
assert.equal(claim.covenant.isClaim(), true);
|
|
});
|
|
});
|
|
|
|
describe('Create auction-related TX in advance', function () {
|
|
const network = Network.get('regtest');
|
|
const workers = new WorkerPool({ enabled, size });
|
|
const wdb = new WalletDB({ network, workers });
|
|
// This test executes a complete auction for this name
|
|
const name = 'satoshi-in-advance';
|
|
// There will be two bids. Both from our wallet, one made in advance and
|
|
// another created and broadcasted right away
|
|
const value = 1e6;
|
|
const lockup = 2e6;
|
|
const secondHighest = value - 1;
|
|
// All TXs will have a hard-coded fee to simplify the expected balances,
|
|
// along with counters for OUTGOING (un)confirmed transactions.
|
|
const fee = 10000;
|
|
let uTXCount = 0;
|
|
let cTXCount = 0;
|
|
// Initial wallet funds
|
|
const fund = 10e6;
|
|
// Store height of auction OPEN to be used in second bid.
|
|
// The main test wallet, and wallet that will receive the FINALIZE.
|
|
/** @type {Wallet} */
|
|
let wallet;
|
|
let unsentReveal;
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
wallet = await wdb.create();
|
|
// rollout all names
|
|
network.names.noRollout = true;
|
|
});
|
|
|
|
after(async () => {
|
|
network.names.noRollout = false;
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should fund wallet', async () => {
|
|
const addr = await wallet.receiveAddress();
|
|
|
|
// Fund wallet
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx.addOutput(addr, fund);
|
|
const tx = mtx.toTX();
|
|
|
|
// Dummy block
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
// Add confirmed funding TX to wallet
|
|
await txdbAdd(wallet, tx, block);
|
|
|
|
// Check
|
|
const bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 1);
|
|
assert.strictEqual(bal.coin, 1);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should send and confirm OPEN', async () => {
|
|
const open = await wallet.sendOpen(name, { hardFee: fee });
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm OPEN
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, open, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 2);
|
|
assert.strictEqual(bal.coin, 2);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, 0);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
});
|
|
|
|
it('should send and confirm BID', async () => {
|
|
// Advance to bidding
|
|
wdb.height += network.names.treeInterval + 1;
|
|
const losingBid = await wallet.sendBid(name, secondHighest, lockup, {
|
|
hardFee: fee
|
|
});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, 0);
|
|
|
|
// Confirm BID
|
|
let block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, losingBid, block);
|
|
cTXCount++;
|
|
|
|
const losingBlindFromMtx = losingBid.outputs
|
|
.find(o => o.covenant.isBid())
|
|
.covenant.getHash(3);
|
|
let allBids = await wallet.getBids();
|
|
assert.strictEqual(allBids.length, 1);
|
|
const losingBlindFromWallet = allBids.find(b =>
|
|
b.blind.equals(losingBlindFromMtx)
|
|
);
|
|
assert(losingBlindFromWallet);
|
|
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 3);
|
|
assert.strictEqual(bal.coin, 3);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, lockup);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
|
|
const auctionsTXs = await wallet.createAuctionTXs(name, value, lockup, {
|
|
hardFee: fee
|
|
});
|
|
const winningBidUnsent = auctionsTXs.bid;
|
|
unsentReveal = auctionsTXs.reveal;
|
|
|
|
const winningBid = await wallet.sendMTX(winningBidUnsent, null);
|
|
uTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, 2 * lockup);
|
|
assert.strictEqual(bal.clocked, lockup);
|
|
|
|
// Confirm BID
|
|
block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
|
|
await txdbAdd(wallet, winningBid, block);
|
|
cTXCount++;
|
|
|
|
const winningBlindFromMtx = winningBid.outputs
|
|
.find(o => o.covenant.isBid())
|
|
.covenant.getHash(3);
|
|
allBids = await wallet.getBids();
|
|
assert.strictEqual(allBids.length, 2);
|
|
const winningBlindFromWallet = allBids.find(b =>
|
|
b.blind.equals(winningBlindFromMtx)
|
|
);
|
|
assert(winningBlindFromWallet);
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 4);
|
|
assert.strictEqual(bal.coin, 4);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, 2 * lockup);
|
|
assert.strictEqual(bal.clocked, 2 * lockup);
|
|
});
|
|
|
|
it('should send and confirm REVEAL', async () => {
|
|
// Advance to reveal
|
|
wdb.height += network.names.biddingPeriod;
|
|
const reveal = await wallet.sendMTX(unsentReveal, null);
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
assert.strictEqual(bal.coin, 5);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, lockup + value);
|
|
assert.strictEqual(bal.clocked, 2 * lockup);
|
|
|
|
// Confirm REVEAL
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, reveal, block);
|
|
cTXCount++;
|
|
|
|
const revealValueFromMtx = reveal.outputs.find(o => o.covenant.isReveal())
|
|
.value;
|
|
let allReveals = await wallet.getReveals();
|
|
assert.strictEqual(allReveals.length, 1);
|
|
const revealFromWallet = allReveals.find(
|
|
b => b.value === revealValueFromMtx
|
|
);
|
|
assert(revealFromWallet);
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 5);
|
|
assert.strictEqual(bal.coin, 5);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, lockup + value);
|
|
assert.strictEqual(bal.clocked, lockup + value);
|
|
|
|
const reveal2 = await wallet.sendReveal(name, { hardFee: fee });
|
|
uTXCount++;
|
|
|
|
// Confirm REVEAL
|
|
const block2 = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, reveal2, block2);
|
|
cTXCount++;
|
|
|
|
const reveal2ValueFromMtx = reveal.outputs.find(o =>
|
|
o.covenant.isReveal()
|
|
).value;
|
|
allReveals = await wallet.getReveals();
|
|
assert.strictEqual(allReveals.length, 2);
|
|
const reveal2FromWallet = allReveals.find(
|
|
b => b.value === reveal2ValueFromMtx
|
|
);
|
|
assert(reveal2FromWallet);
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 6);
|
|
assert.strictEqual(bal.coin, 6);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, value + secondHighest);
|
|
assert.strictEqual(bal.clocked, value + secondHighest);
|
|
});
|
|
|
|
it('should send and confirm REDEEM', async () => {
|
|
// Advance to close
|
|
wdb.height += network.names.revealPeriod;
|
|
const redeem = await wallet.sendRedeem(name, { hardFee: fee });
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 7);
|
|
// Wallet coin count doesn't change:
|
|
// REVEAL + fee money -> REDEEM + change
|
|
assert.strictEqual(bal.coin, 6);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, value + secondHighest);
|
|
|
|
// Confirm REDEEM
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, redeem, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 7);
|
|
assert.strictEqual(bal.coin, 6);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, value);
|
|
assert.strictEqual(bal.clocked, value);
|
|
});
|
|
|
|
it('should send and confirm REGISTER', async () => {
|
|
const resource = Resource.fromJSON({ records: [] });
|
|
const register = await wallet.sendUpdate(name, resource, {
|
|
hardFee: fee
|
|
});
|
|
uTXCount++;
|
|
|
|
// Check
|
|
let bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 8);
|
|
// Wallet coin count doesn't change:
|
|
// REVEAL + fee money -> REGISTER + change
|
|
assert.strictEqual(bal.coin, 6);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, value);
|
|
|
|
// Confirm REGISTER
|
|
const block = {
|
|
height: wdb.height + 1,
|
|
hash: Buffer.alloc(32),
|
|
time: util.now()
|
|
};
|
|
await txdbAdd(wallet, register, block);
|
|
cTXCount++;
|
|
|
|
// Check
|
|
bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.tx, 8);
|
|
assert.strictEqual(bal.coin, 6);
|
|
assert.strictEqual(bal.confirmed, fund - cTXCount * fee);
|
|
assert.strictEqual(bal.unconfirmed, fund - uTXCount * fee);
|
|
assert.strictEqual(bal.ulocked, secondHighest);
|
|
assert.strictEqual(bal.clocked, secondHighest);
|
|
});
|
|
});
|
|
|
|
describe('Nonce and blind generation', function () {
|
|
const name = 'random-name';
|
|
const nameHash = rules.hashString(name);
|
|
const value = 1e6;
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
});
|
|
|
|
after(async () => {
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should generate nonce (pubkeyhash account)', async () => {
|
|
const wallet = await wdb.create({
|
|
master: KEY1
|
|
});
|
|
|
|
const expectedNonce = Buffer.from(
|
|
'63d757a0e55b99db90b47fbfae81e3779801ec4d390c021ee30eafb6108e08ad',
|
|
'hex'
|
|
);
|
|
|
|
const addr = await wallet.receiveAddress();
|
|
assert.strictEqual(
|
|
addr.toString(network),
|
|
'hs1qyz8n88g6v5033r6fyj4jxkz29mqk5dgkdv7md7'
|
|
);
|
|
|
|
// Generate nonce (single nonce)
|
|
const nonce = await wallet.generateNonce(nameHash, addr, value);
|
|
assert.bufferEqual(nonce, expectedNonce);
|
|
|
|
// Generate all nonces (include all keys in multisig)
|
|
const nonces = await wallet.generateNonces(nameHash, addr, value);
|
|
assert.strictEqual(nonces.length, 1);
|
|
assert.bufferEqual(nonces[0], expectedNonce);
|
|
});
|
|
|
|
it('should generate nonce (multisig accounts)', async () => {
|
|
const alice = await wdb.create({
|
|
master: KEY1,
|
|
type: 'multisig',
|
|
m: 2,
|
|
n: 2
|
|
});
|
|
const bob = await wdb.create({
|
|
master: KEY2,
|
|
type: 'multisig',
|
|
m: 2,
|
|
n: 2
|
|
});
|
|
|
|
// Based on alice's and bob's public keys
|
|
const expectedNonces = {
|
|
alice: Buffer.from(
|
|
'3c6fcdeca8a5c4cd76cb84af2fbc47737f6a0dc1d135a9aefb20786944ea081a',
|
|
'hex'
|
|
),
|
|
bob: Buffer.from(
|
|
'097eca5a6d1a9ade3ef20ba3feab60b2dd1858f1b5888fb736917ec6ef0567e9',
|
|
'hex'
|
|
)
|
|
};
|
|
const expectedBlinds = {
|
|
alice: rules.blind(value, expectedNonces.alice),
|
|
bob: rules.blind(value, expectedNonces.bob)
|
|
};
|
|
|
|
// Initialize both multisig wallets
|
|
await alice.addSharedKey(0, await bob.accountKey(0));
|
|
await bob.addSharedKey(0, await alice.accountKey(0));
|
|
|
|
// P2SH address common for alice and bob
|
|
const addr = await alice.receiveAddress();
|
|
assert.strictEqual(
|
|
addr.toString(network),
|
|
'hs1q8nnrenk92qdmm32zfv56shvhj6n6g8kq3ermpz50k5zxh4qjq34qk7v4d2'
|
|
);
|
|
|
|
// Generate Nonce
|
|
// --------------
|
|
|
|
// Generate nonce for same inputs from both wallets
|
|
const aliceNonce = await alice.generateNonce(nameHash, addr, value);
|
|
const bobNonce = await bob.generateNonce(nameHash, addr, value);
|
|
|
|
// Same nonce is generated by alice and bob
|
|
// and is based on the smallest public key (alice's)
|
|
assert.bufferEqual(aliceNonce, expectedNonces.alice);
|
|
assert.bufferEqual(bobNonce, expectedNonces.alice);
|
|
|
|
// Generate nonces for all multisig participants
|
|
const aliceNonces = await alice.generateNonces(nameHash, addr, value);
|
|
const bobNonces = await bob.generateNonces(nameHash, addr, value);
|
|
|
|
// Both alice and bob get the same N nonces
|
|
assert.deepStrictEqual(aliceNonces, bobNonces);
|
|
assert.strictEqual(aliceNonces.length, 2);
|
|
assert.deepStrictEqual(
|
|
aliceNonces,
|
|
[expectedNonces.alice, expectedNonces.bob]
|
|
);
|
|
|
|
// Generate Blind
|
|
// --------------
|
|
|
|
// sanity check: no blinds saved as of this point
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), false);
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), false);
|
|
|
|
// 1) Generate single blind:
|
|
const bobBlind = await bob.generateBlind(nameHash, addr, value);
|
|
// smallest public key (alice's) is used for blind
|
|
assert.bufferEqual(bobBlind, expectedBlinds.alice);
|
|
// bob's public key blind isn't stored
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), true);
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), false);
|
|
|
|
// 2) Generate all blinds:
|
|
const bobBlinds = await bob.generateBlinds(nameHash, addr, value);
|
|
// smallest public key (alice's) is used for blind
|
|
assert.deepStrictEqual(
|
|
bobBlinds,
|
|
[expectedBlinds.alice, expectedBlinds.bob]
|
|
);
|
|
// but blinds for all keys are saved in db
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.alice), true);
|
|
assert.strictEqual(await bob.txdb.hasBlind(expectedBlinds.bob), true);
|
|
});
|
|
});
|
|
|
|
describe('Bid and Reveal by Reveal and Bid', function () {
|
|
const network = Network.get('regtest');
|
|
const wdb = new WalletDB({ network });
|
|
|
|
const mineBlocks = async (count) => {
|
|
for (let i = 0; i < count; i++) {
|
|
await wdb.addBlock(nextEntry(wdb), []);
|
|
}
|
|
};
|
|
|
|
const NAME = rules.grindName(10, 1, network);
|
|
const NAMEHASH = rules.hashString(NAME);
|
|
let wallet;
|
|
|
|
const BASE_BID = 1e6;
|
|
const BASE_LOCKUP = 2e6;
|
|
const BID_COUNT = 5;
|
|
const bids = [];
|
|
let revealMTX;
|
|
|
|
before(async () => {
|
|
await wdb.open();
|
|
await wdb.connect();
|
|
|
|
wallet = await wdb.create();
|
|
});
|
|
|
|
after(async () => {
|
|
await wdb.disconnect();
|
|
await wdb.close();
|
|
});
|
|
|
|
it('should fund wallet', async () => {
|
|
const addr = await wallet.receiveAddress();
|
|
|
|
const txs = [];
|
|
for (let i = 0; i < BID_COUNT; i++) {
|
|
const mtx = new MTX();
|
|
mtx.addOutpoint(new Outpoint(Buffer.alloc(32), 0));
|
|
mtx.addOutput(addr, 20e6);
|
|
txs.push(mtx.toTX());
|
|
}
|
|
|
|
await wdb.addBlock(nextEntry(wdb), txs);
|
|
});
|
|
|
|
it('should open names', async () => {
|
|
const open = await wallet.createOpen(NAME);
|
|
await wdb.addBlock(nextEntry(wdb), [open.toTX()]);
|
|
await mineBlocks(network.names.treeInterval + 1);
|
|
});
|
|
|
|
it('should create and get bid', async () => {
|
|
for (let i = 0; i < BID_COUNT; i++) {
|
|
const bid = await wallet.createBid(NAME, BASE_BID + i, BASE_LOCKUP + i);
|
|
await wdb.addTX(bid.toTX());
|
|
bids.push(bid);
|
|
}
|
|
|
|
const txs = bids.map(b => b.toTX());
|
|
await wdb.addBlock(nextEntry(wdb), txs);
|
|
|
|
for (const [index, bid] of bids.entries()) {
|
|
const bidOut = bid.outpoint(0);
|
|
const blindBid = await wallet.getBid(NAMEHASH, bidOut);
|
|
assert.ok(blindBid);
|
|
assert.bufferEqual(blindBid.nameHash, NAMEHASH);
|
|
assert.bufferEqual(blindBid.prevout.hash, bid.hash());
|
|
assert.strictEqual(blindBid.prevout.index, 0);
|
|
assert.strictEqual(blindBid.lockup, BASE_LOCKUP + index);
|
|
assert.strictEqual(blindBid.height, wdb.state.height);
|
|
assert.strictEqual(blindBid.own, true);
|
|
}
|
|
|
|
await mineBlocks(network.names.biddingPeriod);
|
|
});
|
|
|
|
it('should create and get reveal', async () => {
|
|
revealMTX = await wallet.createReveal(NAME);
|
|
await wdb.addBlock(nextEntry(wdb), [revealMTX.toTX()]);
|
|
|
|
for (const [index, out] of revealMTX.outputs.entries()) {
|
|
if (!out.covenant.isReveal())
|
|
continue;
|
|
|
|
const revealOut = revealMTX.outpoint(index);
|
|
const bidReveal = await wallet.getReveal(NAMEHASH, revealOut);
|
|
|
|
assert.ok(bidReveal);
|
|
assert.bufferEqual(bidReveal.prevout.hash, revealMTX.hash());
|
|
assert.strictEqual(bidReveal.prevout.index, index);
|
|
assert.bufferEqual(bidReveal.nameHash, NAMEHASH);
|
|
assert.strictEqual(bidReveal.height, wdb.state.height);
|
|
assert.strictEqual(bidReveal.own, true);
|
|
}
|
|
});
|
|
|
|
it('should get reveal by bid', async () => {
|
|
for (const [index, bidTX] of bids.entries()) {
|
|
const bidOut = bidTX.outpoint(0);
|
|
const bidReveal = await wallet.getRevealByBid(NAMEHASH, bidOut);
|
|
assert.ok(bidReveal);
|
|
|
|
assert.bufferEqual(bidReveal.bidPrevout.hash, bidOut.hash);
|
|
assert.strictEqual(bidReveal.bidPrevout.index, bidOut.index);
|
|
assert.strictEqual(bidReveal.value, BASE_BID + index);
|
|
}
|
|
});
|
|
|
|
it('should get bid by reveal', async () => {
|
|
for (const bidTX of bids) {
|
|
const bidOut = bidTX.outpoint(0);
|
|
const bidReveal = await wallet.getRevealByBid(NAMEHASH, bidOut);
|
|
assert.ok(bidReveal);
|
|
|
|
const origBlindBid = await wallet.getBid(NAMEHASH, bidOut);
|
|
const blindBid = await wallet.getBidByReveal(NAMEHASH,
|
|
new Outpoint(bidReveal.prevout.hash, bidReveal.prevout.index));
|
|
|
|
assert.deepStrictEqual(blindBid, origBlindBid);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Wallet Zap', function () {
|
|
const DEFAULT = 'default';
|
|
const ALT = 'alt';
|
|
|
|
let workers = null;
|
|
/** @type {WalletDB} */
|
|
let wdb = null;
|
|
/** @type {Wallet} */
|
|
let wallet;
|
|
|
|
beforeEach(async () => {
|
|
workers = new WorkerPool({ enabled, size });
|
|
wdb = new WalletDB({ workers });
|
|
await workers.open();
|
|
await wdb.open();
|
|
|
|
wallet = wdb.primary;
|
|
|
|
const altAccount = await wallet.createAccount({
|
|
name: ALT
|
|
});
|
|
|
|
assert(altAccount);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await wdb.close();
|
|
await workers.close();
|
|
});
|
|
|
|
it('should zap all txs (wallet)', async () => {
|
|
const hashes = [];
|
|
|
|
for (const account of [DEFAULT, ALT]) {
|
|
for (let i = 0; i < 5; i++) {
|
|
const mtx = await dummyTX(wallet, account);
|
|
await wdb.addTX(mtx.toTX());
|
|
hashes.push(mtx.hash());
|
|
}
|
|
}
|
|
|
|
const txs = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txs.length, hashes.length);
|
|
|
|
// zap all
|
|
await wallet.zap(-1, 0);
|
|
|
|
const txsAfterZap = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txsAfterZap.length, 0);
|
|
});
|
|
|
|
it('should zap all txs (account)', async () => {
|
|
for (const account of [DEFAULT, ALT]) {
|
|
for (let i = 0; i < 5; i++) {
|
|
const mtx = await dummyTX(wallet, account);
|
|
await wdb.addTX(mtx.toTX());
|
|
}
|
|
}
|
|
|
|
const txs = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txs.length, 10);
|
|
|
|
// zap all
|
|
await wallet.zap(DEFAULT, 0);
|
|
|
|
const txsAfterZapAll = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txsAfterZapAll.length, 5);
|
|
|
|
const txsAfterZapAlt = await wallet.listUnconfirmed(ALT, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txsAfterZapAlt.length, 5);
|
|
});
|
|
|
|
it('should zap last 2 txs (all)', async () => {
|
|
let time = 0;
|
|
wallet.txdb.nowFn = () => time++;
|
|
const hashes = [];
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
for (const account of [DEFAULT, ALT]) {
|
|
const mtx = await dummyTX(wallet, account);
|
|
// this increments/calls nowFn once.
|
|
await wdb.addTX(mtx.toTX());
|
|
hashes.push(mtx.hash());
|
|
}
|
|
}
|
|
|
|
// time will be 4 (4 txs), if we want to zap oldest 2 txs,
|
|
// zap will call nowFn once more, so time will be 5.
|
|
// First 2 txs have time 0 and 1. Zap accepts second argument
|
|
// age, which is time - age. So, we need to pass time - 1.
|
|
// e.g. time - 1 = 4. Internal timer will be 5 (nowFn increment).
|
|
// Age becomes: 5 - 4 = 1. So, zap will zap all txs with age 1
|
|
// - so first 2 txs.
|
|
const zapped = await wallet.zap(-1, time - 1);
|
|
assert.strictEqual(zapped, 2);
|
|
|
|
const txsAfterZap = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txsAfterZap.length, 2);
|
|
assert.deepStrictEqual(txsAfterZap.map(tx => tx.hash), hashes.slice(2));
|
|
});
|
|
|
|
it('should zap last 2 txs (account)', async () => {
|
|
let time = 0;
|
|
wallet.txdb.nowFn = () => time++;
|
|
const hashes = [];
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
for (const account of [DEFAULT, ALT]) {
|
|
const mtx = await dummyTX(wallet, account);
|
|
await wdb.addTX(mtx.toTX());
|
|
hashes.push(mtx.hash());
|
|
}
|
|
}
|
|
|
|
// two transactions from default (calculation above.)
|
|
const zapped = await wallet.zap(DEFAULT, time - 3);
|
|
assert.strictEqual(zapped, 2);
|
|
|
|
const txsAfterZap = await wallet.listUnconfirmed(DEFAULT, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
const txsAfterZapAll = await wallet.listUnconfirmed(-1, {
|
|
limit: 20,
|
|
reverse: false
|
|
});
|
|
|
|
assert.strictEqual(txsAfterZap.length, 2);
|
|
assert.strictEqual(txsAfterZapAll.length, 6);
|
|
});
|
|
});
|
|
});
|
|
|
|
async function txdbAdd(wallet, tx, block, txIndex = 0) {
|
|
return wallet.txdb.add(tx, block, {
|
|
medianTime: block.time,
|
|
txIndex
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {Wallet} wallet
|
|
* @param {(String|Number)} [account]
|
|
* @param {Number} [value=10000]
|
|
* @returns {Promise<MTX>}
|
|
*/
|
|
|
|
async function dummyTX(wallet, account = 'default', value = 10000) {
|
|
const addr = await wallet.receiveAddress(account);
|
|
const mtx = new MTX();
|
|
mtx.addInput(dummyInput());
|
|
mtx.addOutput(addr, value);
|
|
return mtx;
|
|
};
|