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`.)
This commit is contained in:
Nodar Chkuaselidze 2018-11-25 03:28:26 +04:00
parent e305d900d6
commit 32cba1bf4a
No known key found for this signature in database
GPG Key ID: 8E1B4DC29040BD90
2 changed files with 104 additions and 2 deletions

View File

@ -502,6 +502,28 @@ class Mempool extends EventEmitter {
return Coin.fromTX(entry.tx, index, -1);
}
/**
* Check whether coin is still unspent.
* @param {Hash} hash
* @param {Number} index
* @returns {boolean}
*/
hasCoin(hash, index) {
const entry = this.map.get(hash);
if (!entry)
return false;
if (this.isSpent(hash, index))
return false;
if (index >= entry.tx.outputs.length)
return false;
return true;
}
/**
* Check to see if a coin has been spent. This differs from
* {@link ChainDB#isSpent} in that it actually maintains a
@ -1689,6 +1711,8 @@ class Mempool extends EventEmitter {
/**
* Get coin viewpoint (lock).
* Note: this does not return
* historical view of coins from the indexers.
* @method
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}.
@ -1697,12 +1721,40 @@ class Mempool extends EventEmitter {
async getSpentView(tx) {
const unlock = await this.locker.lock();
try {
return await this.getCoinView(tx);
return await this._getSpentView(tx);
} finally {
unlock();
}
}
/**
* Get coin viewpoint
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}
*/
async _getSpentView(tx) {
const view = new CoinView();
for (const {prevout} of tx.inputs) {
const {hash, index} = prevout;
const tx = this.getTX(hash);
if (tx) {
if (index < tx.outputs.length)
view.addIndex(tx, index, -1);
continue;
}
const coin = await this.chain.readCoin(prevout);
if (coin)
view.addEntry(prevout, coin);
}
return view;
}
/**
* Get coin viewpoint (no lock).
* @method
@ -1718,7 +1770,7 @@ class Mempool extends EventEmitter {
const tx = this.getTX(hash);
if (tx) {
if (index < tx.outputs.length)
if (this.hasCoin(hash, index))
view.addIndex(tx, index, -1);
continue;
}

View File

@ -177,6 +177,56 @@ describe('Mempool', function() {
}));
});
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();