fcoin/test/mempool-test.js
Nodar Chkuaselidze 32cba1bf4a
mempool: reflect spent coins in mempool coinview.
Currently coinview does not account for spent coins in the mempool,
This does not create problems because we have additional checks in
right places which detect double spends, but technically
coinview should help you detect double spent in the mempool as well.
This way it will be compatible with chain.getCoinView.

getSpentView will still return all outputs that are available
in the mempool. (This could also return spentView from indexers if
available, this method is used by `signrawtransaction`.)
2018-11-25 23:32:13 +04:00

410 lines
9.7 KiB
JavaScript

/* eslint-env mocha */
/* eslint prefer-arrow-callback: "off" */
'use strict';
const assert = require('./util/assert');
const random = require('bcrypto/lib/random');
const MempoolEntry = require('../lib/mempool/mempoolentry');
const Mempool = require('../lib/mempool/mempool');
const WorkerPool = require('../lib/workers/workerpool');
const Chain = require('../lib/blockchain/chain');
const MTX = require('../lib/primitives/mtx');
const Coin = require('../lib/primitives/coin');
const KeyRing = require('../lib/primitives/keyring');
const Address = require('../lib/primitives/address');
const Outpoint = require('../lib/primitives/outpoint');
const Script = require('../lib/script/script');
const Witness = require('../lib/script/witness');
const MemWallet = require('./util/memwallet');
const ALL = Script.hashType.ALL;
const ONE_HASH = Buffer.alloc(32, 0x00);
ONE_HASH[0] = 0x01;
const workers = new WorkerPool({
enabled: true
});
const chain = new Chain({
memory: true,
workers
});
const mempool = new Mempool({
chain,
memory: true,
workers
});
const wallet = new MemWallet();
let cachedTX = null;
function dummyInput(script, hash) {
const coin = new Coin();
coin.height = 0;
coin.value = 0;
coin.script = script;
coin.hash = hash;
coin.index = 0;
const fund = new MTX();
fund.addCoin(coin);
fund.addOutput(script, 70000);
const [tx, view] = fund.commit();
const entry = MempoolEntry.fromTX(tx, view, 0);
mempool.trackEntry(entry, view);
return Coin.fromTX(fund, 0, -1);
}
describe('Mempool', function() {
this.timeout(5000);
it('should open mempool', async () => {
await workers.open();
await chain.open();
await mempool.open();
chain.state.flags |= Script.flags.VERIFY_WITNESS;
});
it('should handle incoming orphans and TXs', async () => {
const key = KeyRing.generate();
const t1 = new MTX();
t1.addOutput(wallet.getAddress(), 50000);
t1.addOutput(wallet.getAddress(), 10000);
const script = Script.fromPubkey(key.publicKey);
t1.addCoin(dummyInput(script, ONE_HASH));
const sig = t1.signature(0, script, 70000, key.privateKey, ALL, 0);
t1.inputs[0].script = Script.fromItems([sig]);
// balance: 51000
wallet.sign(t1);
const t2 = new MTX();
t2.addTX(t1, 0); // 50000
t2.addOutput(wallet.getAddress(), 20000);
t2.addOutput(wallet.getAddress(), 20000);
// balance: 49000
wallet.sign(t2);
const t3 = new MTX();
t3.addTX(t1, 1); // 10000
t3.addTX(t2, 0); // 20000
t3.addOutput(wallet.getAddress(), 23000);
// balance: 47000
wallet.sign(t3);
const t4 = new MTX();
t4.addTX(t2, 1); // 24000
t4.addTX(t3, 0); // 23000
t4.addOutput(wallet.getAddress(), 11000);
t4.addOutput(wallet.getAddress(), 11000);
// balance: 22000
wallet.sign(t4);
const f1 = new MTX();
f1.addTX(t4, 1); // 11000
f1.addOutput(new Address(), 9000);
// balance: 11000
wallet.sign(f1);
const fake = new MTX();
fake.addTX(t1, 1); // 1000 (already redeemed)
fake.addOutput(wallet.getAddress(), 6000); // 6000 instead of 500
// Script inputs but do not sign
wallet.template(fake);
// Fake signature
const input = fake.inputs[0];
input.script.setData(0, Buffer.alloc(73, 0x00));
input.script.compile();
// balance: 11000
{
await mempool.addTX(fake.toTX());
await mempool.addTX(t4.toTX());
const balance = mempool.getBalance();
assert.strictEqual(balance, 70000);
}
{
await mempool.addTX(t1.toTX());
const balance = mempool.getBalance();
assert.strictEqual(balance, 60000);
}
{
await mempool.addTX(t2.toTX());
const balance = mempool.getBalance();
assert.strictEqual(balance, 50000);
}
{
await mempool.addTX(t3.toTX());
const balance = mempool.getBalance();
assert.strictEqual(balance, 22000);
}
{
await mempool.addTX(f1.toTX());
const balance = mempool.getBalance();
assert.strictEqual(balance, 20000);
}
const txs = mempool.getHistory();
assert(txs.some((tx) => {
return tx.hash().equals(f1.hash());
}));
});
it('should get spend coins and reflect in coinview', async () => {
const wallet = new MemWallet();
const script = Script.fromAddress(wallet.getAddress());
const dummyCoin = dummyInput(script, random.randomBytes(32));
// spend first output
const mtx1 = new MTX();
mtx1.addOutput(wallet.getAddress(), 50000);
mtx1.addCoin(dummyCoin);
wallet.sign(mtx1);
// spend second tx
const tx1 = mtx1.toTX();
const coin1 = Coin.fromTX(tx1, 0, -1);
const mtx2 = new MTX();
mtx2.addOutput(wallet.getAddress(), 10000);
mtx2.addOutput(wallet.getAddress(), 30000); // 10k fee..
mtx2.addCoin(coin1);
wallet.sign(mtx2);
const tx2 = mtx2.toTX();
await mempool.addTX(tx1);
{
const view = await mempool.getCoinView(tx2);
assert(view.hasEntry(coin1));
}
await mempool.addTX(tx2);
// we should not have coins available in the mempool for these txs.
{
const view = await mempool.getCoinView(tx1);
const sview = await mempool.getSpentView(tx1);
assert(!view.hasEntry(dummyCoin));
assert(sview.hasEntry(dummyCoin));
}
{
const view = await mempool.getCoinView(tx2);
const sview = await mempool.getSpentView(tx2);
assert(!view.hasEntry(coin1));
assert(sview.hasEntry(coin1));
}
});
it('should handle locktime', async () => {
const key = KeyRing.generate();
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromPubkey(key.publicKey);
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
tx.setLocktime(200);
chain.tip.height = 200;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = Script.fromItems([sig]);
await mempool.addTX(tx.toTX());
chain.tip.height = 0;
});
it('should handle invalid locktime', async () => {
const key = KeyRing.generate();
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromPubkey(key.publicKey);
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
tx.setLocktime(200);
chain.tip.height = 200 - 1;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = Script.fromItems([sig]);
let err;
try {
await mempool.addTX(tx.toTX());
} catch (e) {
err = e;
}
assert(err);
chain.tip.height = 0;
});
it('should not cache a malleated wtx with mutated sig', async () => {
const key = KeyRing.generate();
key.witness = true;
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromProgram(0, key.getKeyHash());
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
const prevs = Script.fromPubkeyhash(key.getKeyHash());
const sig = tx.signature(0, prevs, 70000, key.privateKey, ALL, 1);
sig[sig.length - 1] = 0;
tx.inputs[0].witness = new Witness([sig, key.publicKey]);
let err;
try {
await mempool.addTX(tx.toTX());
} catch (e) {
err = e;
}
assert(err);
assert(!mempool.hasReject(tx.hash()));
});
it('should not cache a malleated tx with unnecessary witness', async () => {
const key = KeyRing.generate();
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromPubkey(key.publicKey);
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = Script.fromItems([sig]);
tx.inputs[0].witness.push(Buffer.alloc(0));
let err;
try {
await mempool.addTX(tx.toTX());
} catch (e) {
err = e;
}
assert(err);
assert(!mempool.hasReject(tx.hash()));
});
it('should not cache a malleated wtx with wit removed', async () => {
const key = KeyRing.generate();
key.witness = true;
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromProgram(0, key.getKeyHash());
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
let err;
try {
await mempool.addTX(tx.toTX());
} catch (e) {
err = e;
}
assert(err);
assert(err.malleated);
assert(!mempool.hasReject(tx.hash()));
});
it('should cache non-malleated tx without sig', async () => {
const key = KeyRing.generate();
const tx = new MTX();
tx.addOutput(wallet.getAddress(), 50000);
tx.addOutput(wallet.getAddress(), 10000);
const prev = Script.fromPubkey(key.publicKey);
const prevHash = random.randomBytes(32);
tx.addCoin(dummyInput(prev, prevHash));
let err;
try {
await mempool.addTX(tx.toTX());
} catch (e) {
err = e;
}
assert(err);
assert(!err.malleated);
assert(mempool.hasReject(tx.hash()));
cachedTX = tx;
});
it('should clear reject cache', async () => {
const tx = new MTX();
tx.addOutpoint(new Outpoint());
tx.addOutput(wallet.getAddress(), 50000);
assert(mempool.hasReject(cachedTX.hash()));
await mempool.addBlock({ height: 1 }, [tx.toTX()]);
assert(!mempool.hasReject(cachedTX.hash()));
});
it('should destroy mempool', async () => {
await mempool.close();
await chain.close();
await workers.close();
});
});