bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
238 lines • 10.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const crypto = __importStar(require("crypto"));
const crypto_wallet_core_1 = require("crypto-wallet-core");
const coin_1 = require("../../../src/models/coin");
const transaction_1 = require("../../../src/models/transaction");
const helpers_1 = require("../../helpers");
const integration_1 = require("../../helpers/integration");
function createNewTxid() {
const seed = (Math.random() * 10000).toString();
return crypto
.createHash('sha256')
.update(seed + 1)
.digest()
.toString('hex');
}
async function addTx(tx, outputs) {
await transaction_1.TransactionStorage.collection.insertOne(tx);
await coin_1.CoinStorage.collection.insertMany(outputs);
}
async function makeMempoolTxChain(chain, network, startingTxid, chainLength = 1) {
let txid = startingTxid;
let nextTxid = createNewTxid();
let allTxids = new Array();
for (let i = 1; i <= chainLength; i++) {
const badMempoolTx = {
chain,
network,
blockHeight: -1,
txid
};
const badMempoolOutputs = [
{
chain,
network,
mintHeight: -1,
mintTxid: txid,
spentTxid: i != chainLength ? nextTxid : '',
mintIndex: 0,
spentHeight: -1
}
];
await addTx(badMempoolTx, badMempoolOutputs);
allTxids.push(txid);
txid = nextTxid;
nextTxid = createNewTxid();
}
return allTxids;
}
describe('Coin Model', function () {
const suite = this;
this.timeout(30000);
before(integration_1.intBeforeHelper);
after(async () => (0, integration_1.intAfterHelper)(suite));
beforeEach(async () => {
await (0, helpers_1.resetDatabase)();
});
const chain = 'BTC';
const network = 'integration';
const blockTx = {
chain,
network,
blockHeight: 1,
txid: '01234'
};
const blockTxOutputs = {
chain,
network,
mintHeight: 1,
mintTxid: '01234',
mintIndex: 0,
spentHeight: -1,
spentTxid: '12345'
};
const block2TxOutputs = {
chain,
network,
mintHeight: 2,
mintTxid: '123456',
mintIndex: 0,
spentHeight: -1
};
it('should appropriately mark coins related to transactions that are in mempool, but no longer valid', async () => {
// insert a valid tx, with a valid output
await transaction_1.TransactionStorage.collection.insertOne(blockTx);
await coin_1.CoinStorage.collection.insertOne(blockTxOutputs);
const chainLength = 5;
const txids = await makeMempoolTxChain(chain, network, blockTxOutputs.spentTxid, chainLength);
const allRelatedCoins = await transaction_1.TransactionStorage.findAllRelatedOutputs(blockTxOutputs.spentTxid);
(0, chai_1.expect)(allRelatedCoins.length).to.eq(chainLength);
const spentOps = new Array();
spentOps.push({
updateOne: {
filter: {
chain,
network,
mintIndex: blockTxOutputs.mintIndex,
mintTxid: blockTxOutputs.mintTxid,
spentHeight: { $lt: 0 }
},
update: { $set: { spentHeight: block2TxOutputs.mintHeight, spentTxid: block2TxOutputs.mintTxid } }
}
});
await transaction_1.TransactionStorage.pruneMempool({
chain,
network,
initialSyncComplete: true,
spendOps: spentOps
});
const badTxs = await transaction_1.TransactionStorage.collection.find({ chain, network, txid: { $in: txids } }).toArray();
(0, chai_1.expect)(badTxs.length).to.eq(chainLength);
// the replaced tx is marked as conflicting, all the rest still pending to be cleaned up by pruning service
(0, chai_1.expect)(badTxs[0].blockHeight).to.eq(-3 /* SpentHeightIndicators.conflicting */);
(0, chai_1.expect)(badTxs[0].replacedByTxid).to.exist;
(0, chai_1.expect)(badTxs.slice(1).every(tx => tx.blockHeight === -1 /* SpentHeightIndicators.pending */)).to.equal(true);
const goodTxs = await transaction_1.TransactionStorage.collection.find({ chain, network, txid: blockTx.txid }).toArray();
(0, chai_1.expect)(goodTxs.length).to.eq(1);
(0, chai_1.expect)(goodTxs[0].txid).to.eq(blockTx.txid);
(0, chai_1.expect)(goodTxs[0].blockHeight).to.eq(blockTx.blockHeight);
// Coins
const badNewCoins = await coin_1.CoinStorage.collection.find({ chain, network, mintTxid: { $in: txids } }).toArray();
(0, chai_1.expect)(badNewCoins.length).to.equal(badNewCoins.filter(c => c.spentHeight == -1 /* SpentHeightIndicators.pending */).length);
const goodNewCoins = await coin_1.CoinStorage.collection.find({ chain, network, mintTxid: blockTx.txid }).toArray();
(0, chai_1.expect)(goodNewCoins.length).to.equal(goodNewCoins.filter(c => c.spentHeight == -2 /* SpentHeightIndicators.unspent */).length);
});
it('should appropriately mark coins related to transactions that are RBFed', async () => {
const privateKey = new crypto_wallet_core_1.BitcoreLib.PrivateKey('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy');
const utxo1 = {
txId: createNewTxid(),
outputIndex: 0,
address: '17XBj6iFEsf8kzDMGQk5ghZipxX49VXuaV',
script: '76a91447862fe165e6121af80d5dde1ecb478ed170565b88ac',
satoshis: 50000
};
// create tx with mutliple outputs
const tx1 = new crypto_wallet_core_1.BitcoreLib.Transaction()
.from(utxo1)
.to('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
.to('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 13000)
.to('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 11000)
.sign(privateKey);
// import transaction in block 1
await transaction_1.TransactionStorage.batchImport({
txs: [tx1],
height: 1,
initialSyncComplete: true,
chain: 'BTC',
network: 'integration'
});
// insert mempool tx using all outputs from last tx
const mempoolTxid = createNewTxid();
const mempoolTx = {
chain,
network,
blockHeight: -1, // pending
txid: mempoolTxid
};
const mempoolOutputs = Array.from({ length: 3 }, (_v, i) => i).map(i => {
return {
chain,
network,
mintHeight: -1,
mintTxid: mempoolTxid,
mintIndex: i,
spentHeight: -2 /* SpentHeightIndicators.unspent */
};
});
await addTx(mempoolTx, mempoolOutputs);
// update existing outputs to be spent by mempool tx
await coin_1.CoinStorage.collection.updateMany({ chain, network, mintTxid: tx1.hash }, { $set: { spentTxid: mempoolTxid, spentHeight: -1 /* SpentHeightIndicators.pending */ } });
// create new tx that uses one of the inputs
const utxo2 = [
{
txId: tx1.hash,
outputIndex: 0,
address: '1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK',
script: '76a91447862fe165e6121af80d5dde1ecb478ed170565b88ac',
satoshis: 15000
}
];
const tx2 = new crypto_wallet_core_1.BitcoreLib.Transaction()
.from(utxo2)
.to('bc1qm0jxvjvj6pzcc64lu4k7vccsg2x22pj60zke6c', 15000)
.sign(privateKey);
// import transaction in block 2
await transaction_1.TransactionStorage.batchImport({
txs: [tx2],
height: 2,
initialSyncComplete: true,
chain: 'BTC',
network: 'integration'
});
const tx1Outputs = await coin_1.CoinStorage.collection.find({ chain, network, mintTxid: tx1.hash }).toArray();
const spentCoin = tx1Outputs.find(c => c.spentTxid === tx2.hash && c.spentHeight === 2);
(0, chai_1.expect)(spentCoin).to.exist;
const unspentCoins = tx1Outputs.filter(c => c.spentHeight < 0 /* SpentHeightIndicators.minimum */);
(0, chai_1.expect)(unspentCoins.length).to.equal(2);
(0, chai_1.expect)(unspentCoins.filter(c => c.spentHeight === -2 /* SpentHeightIndicators.unspent */ && !c.spentTxid).length).to.equal(2);
const mempoolCoins = await coin_1.CoinStorage.collection.find({ chain, network, mintTxid: mempoolTxid }).toArray();
(0, chai_1.expect)(mempoolCoins.length).to.equal(3);
(0, chai_1.expect)(mempoolCoins.filter(c => c.mintHeight === -3 /* SpentHeightIndicators.conflicting */).length).to.equal(3);
});
});
//# sourceMappingURL=coin.spec.js.map