bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
238 lines • 9.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WalletAddressStorage = exports.WalletAddressModel = void 0;
const stream_1 = require("stream");
const utils_1 = require("../utils");
const base_1 = require("./base");
const coin_1 = require("./coin");
const transaction_1 = require("./transaction");
class WalletAddressModel extends base_1.BaseModel {
constructor(storage) {
super('walletaddresses', storage);
this.allowedPaging = [];
}
onConnect() {
this.collection.createIndex({ chain: 1, network: 1, address: 1, wallet: 1 }, { background: true, unique: true });
this.collection.createIndex({ chain: 1, network: 1, wallet: 1, address: 1 }, { background: true, unique: true });
this.collection.createIndex({ chain: 1, network: 1, address: 1, lastQueryTime: 1 }, { background: true, sparse: true });
}
_apiTransform(walletAddress, options) {
let transform = { address: walletAddress.address };
if (options && options.object) {
return transform;
}
return JSON.stringify(transform);
}
async updateCoins(params) {
const { wallet, addresses, opts } = params;
const { chain, network } = wallet;
const { reprocess } = opts || {};
class AddressInputStream extends stream_1.Readable {
constructor() {
super({ objectMode: true });
this.addressBatches = (0, utils_1.partition)(addresses, 1000);
this.index = 0;
}
_read() {
if (this.index < this.addressBatches.length) {
this.push(this.addressBatches[this.index]);
this.index++;
}
else {
this.push(null);
}
}
}
class FilterExistingAddressesStream extends stream_1.Transform {
constructor() {
super({ objectMode: true });
}
async _transform(addressBatch, _, callback) {
try {
if (reprocess) {
this.push(addressBatch);
}
else {
const exists = (await exports.WalletAddressStorage.collection
.find({ chain, network, wallet: wallet._id, address: { $in: addressBatch } })
.project({ address: 1, processed: 1 })
.toArray())
.filter(walletAddress => walletAddress.processed)
.map(walletAddress => walletAddress.address);
this.push(addressBatch.filter(address => !exists.includes(address)));
}
callback();
}
catch (err) {
callback(err);
}
}
}
class AddNewAddressesStream extends stream_1.Transform {
constructor() {
super({ objectMode: true });
}
async _transform(addressBatch, _, callback) {
if (!addressBatch.length) {
return callback();
}
try {
await exports.WalletAddressStorage.collection.bulkWrite(addressBatch.map(address => ({
insertOne: {
document: { chain, network, wallet: wallet._id, address, processed: false }
}
})), { ordered: false });
}
catch (err) {
// Ignore duplicate keys, they may be half processed
if (err.code !== 11000) {
return callback(err);
}
}
this.push(addressBatch);
callback();
}
}
class UpdateCoinsStream extends stream_1.Transform {
constructor() {
super({ objectMode: true });
}
async _transform(addressBatch, _, callback) {
if (!addressBatch.length) {
return callback();
}
try {
await coin_1.CoinStorage.collection.bulkWrite(addressBatch.map(address => ({
updateMany: {
filter: { chain, network, address },
update: { $addToSet: { wallets: wallet._id } }
}
})), { ordered: false });
this.push(addressBatch);
callback();
}
catch (err) {
callback(err);
}
}
}
class UpdatedTxidsStream extends stream_1.Transform {
constructor() {
super({ objectMode: true });
this.txids = {};
}
async _transform(addressBatch, _, callback) {
if (!addressBatch.length) {
return callback();
}
const coinStream = coin_1.CoinStorage.collection
.find({ chain, network, address: { $in: addressBatch } })
.project({ mintTxid: 1, spentTxid: 1 });
coinStream.on('data', (coin) => {
if (!this.txids[coin.mintTxid]) {
this.txids[coin.mintTxid] = true;
this.push({ txid: coin.mintTxid });
}
if (!this.txids[coin.spentTxid]) {
this.txids[coin.spentTxid] = true;
this.push({ txid: coin.spentTxid });
}
});
let errored = false;
coinStream.on('error', err => {
errored = true;
coinStream.destroy(err);
callback(err);
});
coinStream.on('end', () => {
if (errored) {
return;
}
this.push({ addressBatch });
callback();
});
}
}
class TxUpdaterStream extends stream_1.Transform {
constructor() {
super({ objectMode: true });
}
async _transform(data, _, callback) {
const { txid, addressBatch } = data;
if (addressBatch) {
this.push(addressBatch);
return callback();
}
try {
await transaction_1.TransactionStorage.collection.updateMany({ chain, network, txid }, { $addToSet: { wallets: wallet._id } });
callback();
}
catch (err) {
callback(err);
}
}
}
class MarkProcessedStream extends stream_1.Writable {
constructor() {
super({ objectMode: true });
}
async _write(addressBatch, _, callback) {
if (!addressBatch.length) {
return callback();
}
try {
await exports.WalletAddressStorage.collection.bulkWrite(addressBatch.map(address => {
return {
updateOne: {
filter: { chain, network, address, wallet: wallet._id },
update: { $set: { processed: true } }
}
};
}), { ordered: false });
callback();
}
catch (err) {
callback(err);
}
}
}
const addressInputStream = new AddressInputStream();
const filterExistingAddressesStream = new FilterExistingAddressesStream();
const addNewAddressesStream = new AddNewAddressesStream();
const updateCoinsStream = new UpdateCoinsStream();
const updatedTxidsStream = new UpdatedTxidsStream();
const txUpdaterStream = new TxUpdaterStream();
const markProcessedStream = new MarkProcessedStream();
const handleStreamError = (stream, reject) => {
stream.on('error', err => {
stream.destroy();
return reject(err);
});
};
return new Promise((resolve, reject) => {
markProcessedStream.on('unpipe', () => {
return resolve();
});
handleStreamError(filterExistingAddressesStream, reject);
handleStreamError(addNewAddressesStream, reject);
handleStreamError(updateCoinsStream, reject);
handleStreamError(updatedTxidsStream, reject);
handleStreamError(txUpdaterStream, reject);
handleStreamError(markProcessedStream, reject);
addressInputStream
.pipe(filterExistingAddressesStream)
.pipe(addNewAddressesStream)
.pipe(updateCoinsStream)
.pipe(updatedTxidsStream)
.pipe(txUpdaterStream)
.pipe(markProcessedStream);
});
}
async updateLastQueryTime(params) {
const { chain, network, address } = params;
return this.collection.updateOne({ chain, network, address }, { $set: { lastQueryTime: new Date() } });
}
}
exports.WalletAddressModel = WalletAddressModel;
exports.WalletAddressStorage = new WalletAddressModel();
//# sourceMappingURL=walletAddress.js.map