Skip to content

Commit

Permalink
Merge pull request bitpay#1711 from bitjson/spent-height-indicators
Browse files Browse the repository at this point in the history
refactor(coin): identify, document SpentHeightIndicators
  • Loading branch information
micahriggan authored Sep 20, 2018
2 parents a304b65 + 01b96a2 commit d99bab1
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 76 deletions.
4 changes: 2 additions & 2 deletions packages/bitcore-node/src/models/block.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CoinModel } from './coin';
import { CoinModel, SpentHeightIndicators } from './coin';
import { TransactionModel } from './transaction';
import { TransformOptions } from '../types/TransformOptions';
import { LoggifyClass } from '../decorators/Loggify';
Expand Down Expand Up @@ -149,7 +149,7 @@ export class Block extends BaseModel<IBlock> {
await CoinModel.collection.deleteMany({ chain, network, mintHeight: { $gte: localTip.height } });
await CoinModel.collection.updateMany(
{ chain, network, spentHeight: { $gte: localTip.height } },
{ $set: { spentTxid: null, spentHeight: -1 } }
{ $set: { spentTxid: null, spentHeight: SpentHeightIndicators.pending } }
);

logger.debug('Removed data from above blockHeight: ', localTip.height);
Expand Down
19 changes: 19 additions & 0 deletions packages/bitcore-node/src/models/coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ export type ICoin = {
spentHeight: number;
};

/**
* Number values less than 0 which indicate the spent state of a coin.
*/
export enum SpentHeightIndicators {
/**
* The value below which numbers are simply used as indicators.
*/
minimum = 0,
/**
* The coin is spent by a transaction currently in the mempool but not yet
* included in a block.
*/
pending = -1,
/**
* The coin is unspent, and no transactions spending it have been seen.
*/
unspent = -2
}

@LoggifyClass
class Coin extends BaseModel<ICoin> {
constructor() {
Expand Down
16 changes: 11 additions & 5 deletions packages/bitcore-node/src/models/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CoinModel, ICoin } from './coin';
import { CoinModel, ICoin, SpentHeightIndicators } from './coin';
import { WalletAddressModel } from './walletAddress';
import { partition } from '../utils/partition';
import { ObjectID } from 'bson';
Expand Down Expand Up @@ -184,7 +184,7 @@ export class Transaction extends BaseModel<ITransaction> {
chain: parentChain,
network,
mintHeight: height,
spentHeight: { $gt: -2, $lt: forkHeight }
spentHeight: { $gt: SpentHeightIndicators.unspent, $lt: forkHeight }
})
.toArray();
}
Expand All @@ -211,7 +211,13 @@ export class Transaction extends BaseModel<ITransaction> {

mintOps.push({
updateOne: {
filter: { mintTxid: txid, mintIndex: index, spentHeight: { $lt: 0 }, chain, network },
filter: {
mintTxid: txid,
mintIndex: index,
spentHeight: { $lt: SpentHeightIndicators.minimum },
chain,
network
},
update: {
$set: {
chain,
Expand All @@ -221,7 +227,7 @@ export class Transaction extends BaseModel<ITransaction> {
value: output.satoshis,
address,
script: scriptBuffer,
spentHeight: -2,
spentHeight: SpentHeightIndicators.unspent,
wallets: []
}
},
Expand Down Expand Up @@ -291,7 +297,7 @@ export class Transaction extends BaseModel<ITransaction> {
filter: {
mintTxid: inputObj.prevTxId,
mintIndex: inputObj.outputIndex,
spentHeight: { $lt: 0 },
spentHeight: { $lt: SpentHeightIndicators.minimum },
chain,
network
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import through2 from 'through2';

import { MongoBound } from '../../../models/base';
import { ObjectId } from 'mongodb';
import { CoinModel, ICoin } from '../../../models/coin';
import { CoinModel, ICoin, SpentHeightIndicators } from '../../../models/coin';
import { BlockModel, IBlock } from '../../../models/block';
import { WalletModel, IWallet } from '../../../models/wallet';
import { WalletAddressModel } from '../../../models/walletAddress';
Expand Down Expand Up @@ -40,7 +40,7 @@ export class InternalStateProvider implements CSP.IChainStateService {
}
const query = { chain: chain, network: network.toLowerCase(), address } as any;
if (args.unspent) {
query.spentHeight = { $lt: 0 };
query.spentHeight = { $lt: SpentHeightIndicators.minimum };
}
return query;
}
Expand Down Expand Up @@ -233,7 +233,7 @@ export class InternalStateProvider implements CSP.IChainStateService {
const { chain, network, pubKey, stream } = params;
const wallet = await WalletModel.collection.findOne({ pubKey });
const walletId = wallet!._id;
const query = { chain, network, wallets: walletId, spentHeight: { $gte: 0 } };
const query = { chain, network, wallets: walletId, spentHeight: { $gte: SpentHeightIndicators.minimum } };
const cursor = CoinModel.collection.find(query);
const seen = {};
const stringifyWallets = (wallets: Array<ObjectId>) => wallets.map(w => w.toHexString());
Expand Down Expand Up @@ -318,7 +318,7 @@ export class InternalStateProvider implements CSP.IChainStateService {
const { wallet, limit, args = {}, stream } = params;
let query: any = { wallets: wallet._id };
if (args.includeSpent !== 'true') {
query.spentHeight = { $lt: -1 };
query.spentHeight = { $lt: SpentHeightIndicators.pending };
}
const tip = await this.getLocalTip(params);
const tipHeight = tip ? tip.height : 0;
Expand Down
3 changes: 2 additions & 1 deletion packages/bitcore-node/src/services/p2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ChainStateProvider } from '../providers/chain-state';
import { TransactionModel } from '../models/transaction';
import { Bitcoin } from '../types/namespaces/Bitcoin';
import { StateModel } from '../models/state';
import { SpentHeightIndicators } from '../models/coin';
const Chain = require('../chain');
const LRU = require('lru-cache');

Expand Down Expand Up @@ -235,7 +236,7 @@ export class P2pService {
chain: this.chain,
network: this.network,
txs: [tx],
height: -1,
height: SpentHeightIndicators.pending,
mempoolTime: now,
blockTime: now,
blockTimeNormalized: now,
Expand Down
131 changes: 72 additions & 59 deletions packages/bitcore-node/test/integration/models/block.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { expect } from 'chai';
import { resetDatabase } from '../../helpers';
import { BlockModel } from '../../../src/models/block';
import { TransactionModel } from '../../../src/models/transaction';
import { CoinModel } from '../../../src/models/coin';
import { CoinModel, SpentHeightIndicators } from '../../../src/models/coin';
import { TEST_BLOCK } from '../../data/test-block';
import logger from '../../../src/logger';

describe('Block Model', function () {
describe('Block Model', function() {
beforeEach(async () => {
await resetDatabase();
});
Expand Down Expand Up @@ -53,7 +53,7 @@ describe('Block Model', function () {
previousBlockHash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
size: 264,
bits: parseInt('207fffff', 16).toString(),
processed: true,
processed: true
});
await BlockModel.collection.insertOne({
chain: 'BTC',
Expand All @@ -68,12 +68,15 @@ describe('Block Model', function () {
previousBlockHash: '3279069d22ce5af68ef38332d5b40e79e1964b154d466e7fa233015a34c27312',
size: 264,
bits: parseInt('207fffff', 16).toString(),
processed: true,
processed: true
});

await BlockModel.addBlock({ block: TEST_BLOCK, chain: 'BTC', network: 'regtest', initialSyncComplete: false });

const blocks = await BlockModel.collection.find({ chain: 'BTC', network: 'regtest' }).sort({ height: 1 }).toArray();
const blocks = await BlockModel.collection
.find({ chain: 'BTC', network: 'regtest' })
.sort({ height: 1 })
.toArray();
expect(blocks.length).to.equal(5);
const ownBlock = blocks[4];
expect(ownBlock.chain).to.equal('BTC');
Expand All @@ -93,11 +96,13 @@ describe('Block Model', function () {

logger.info(`new block was successfully added with hash`, ownBlock.hash);

const transaction = await TransactionModel.collection.find({
chain: 'BTC',
network: 'regtest',
blockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929'
}).toArray();
const transaction = await TransactionModel.collection
.find({
chain: 'BTC',
network: 'regtest',
blockHash: '64bfb3eda276ae4ae5b64d9e36c9c0b629bc767fb7ae66f9d55d2c5c8103a929'
})
.toArray();
expect(transaction.length).to.equal(1);
expect(transaction[0].chain).to.equal('BTC');
expect(transaction[0].network).to.equal('regtest');
Expand All @@ -111,13 +116,11 @@ describe('Block Model', function () {
expect(transaction[0].wallets.length).to.equal(0);

logger.info(`tx: ${transaction[0].txid} was successfully stored in the TX model`);

});
});

describe('handleReorg', () => {
it('should not reorg if the incoming block\'s prevHash matches the block hash of the current highest block', async () => {

it("should not reorg if the incoming block's prevHash matches the block hash of the current highest block", async () => {
await BlockModel.collection.insertOne({
chain: 'BTC',
network: 'regtest',
Expand Down Expand Up @@ -158,7 +161,7 @@ describe('Block Model', function () {
previousBlockHash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
size: 264,
bits: parseInt('207fffff', 16).toString(),
processed: true,
processed: true
});

await BlockModel.handleReorg({
Expand All @@ -177,10 +180,8 @@ describe('Block Model', function () {

const result = await BlockModel.collection.find({ chain: 'BTC', network: 'regtest' }).toArray();
expect(result.length).to.equal(3);

});
it('should not reorg if localTip height is zero', async () => {

await BlockModel.handleReorg({
header: {
prevHash: '12c719927ce18f9a61d7c5a7af08d3110cacfa43671aa700956c3c05ed38bdaa',
Expand All @@ -197,7 +198,6 @@ describe('Block Model', function () {

const result = await BlockModel.collection.find({ chain: 'BTC', network: 'regtest' }).toArray();
expect(result.length).to.equal(0);

});
it('should successfully handle reorg', async () => {
// setting the Block model
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('Block Model', function () {
previousBlockHash: '2a883ff89c7d6e9302bb4a4634cd580319a4fd59d69e979b344972b0ba042b86',
size: 264,
bits: parseInt('207fffff', 16).toString(),
processed: true,
processed: true
});

// setting TX model
Expand Down Expand Up @@ -328,60 +328,74 @@ describe('Block Model', function () {
});

// check for removed block after Reorg in db
const blocks = await BlockModel.collection.find({
chain: 'BTC',
network: 'regtest'
}).toArray();
const blocks = await BlockModel.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(blocks.length).to.equal(2);

const removedBlock = await BlockModel.collection.find({
chain: 'BTC',
network: 'regtest',
height: {
$gte: 7
}
}).toArray();
const removedBlock = await BlockModel.collection
.find({
chain: 'BTC',
network: 'regtest',
height: {
$gte: 7
}
})
.toArray();
expect(removedBlock.length).to.equal(0);

// check for removed tx after Reorg in db
const transaction = await TransactionModel.collection.find({
chain: 'BTC',
network: 'regtest'
}).toArray();
const transaction = await TransactionModel.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(transaction.length).to.equal(1);

const removedTransaction = await TransactionModel.collection.find({
chain: 'BTC',
network: 'regtest',
blockHeight: {
$gte: 7
}
}).toArray();
const removedTransaction = await TransactionModel.collection
.find({
chain: 'BTC',
network: 'regtest',
blockHeight: {
$gte: 7
}
})
.toArray();
expect(removedTransaction.length).to.equal(0);

// check for removed coin after Reorg in db
const coinModel = await CoinModel.collection.find({
chain: 'BTC',
network: 'regtest',
}).toArray();
const coinModel = await CoinModel.collection
.find({
chain: 'BTC',
network: 'regtest'
})
.toArray();
expect(coinModel.length).to.equal(1);

const removedCoin = await CoinModel.collection.find({
chain: 'BTC',
network: 'regtest',
mintHeight: {
$gte: 7
}
}).toArray();
const removedCoin = await CoinModel.collection
.find({
chain: 'BTC',
network: 'regtest',
mintHeight: {
$gte: 7
}
})
.toArray();
expect(removedCoin.length).to.equal(0);

// check for unspent coins in the db
const unspentCoins = await CoinModel.collection.find({
chain: 'BTC',
network: 'regtest',
spentTxid: null,
spentHeight: -1
}).toArray();
const unspentCoins = await CoinModel.collection
.find({
chain: 'BTC',
network: 'regtest',
spentTxid: null,
spentHeight: SpentHeightIndicators.pending
})
.toArray();
expect(unspentCoins.length).equal(1);
expect(unspentCoins[0].chain).to.equal('BTC');
expect(unspentCoins[0].network).to.equal('regtest');
Expand All @@ -392,8 +406,7 @@ describe('Block Model', function () {
expect(unspentCoins[0].value).to.equal(500.0);
expect(unspentCoins[0].address).to.equal('mkjB6LmjiNfJWgH4aP4v1GkFjRcQTfDSfj');
expect(unspentCoins[0].spentTxid).to.equal(null);
expect(unspentCoins[0].spentHeight).to.equal(-1);

expect(unspentCoins[0].spentHeight).to.equal(SpentHeightIndicators.pending);
});
});
});
Loading

0 comments on commit d99bab1

Please sign in to comment.