bitcore-wallet-service
Version:
A service for Mutisig HD Bitcoin Wallets
425 lines (385 loc) • 13.8 kB
JavaScript
;
var _ = require('lodash');
var async = require('async');
const CWC = require('crypto-wallet-core');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var { WalletService } = require('../../ts_build/lib/server');
var { BlockchainMonitor } = require('../../ts_build/lib/blockchainmonitor');
var { Constants } = require('../../ts_build/lib/common/constants');
var helpers = require('./helpers');
var storage, blockchainExplorer, blockchainExplorerEVM, bcmonitor;
var socket = {
handlers: {}
};
socket.on = function(eventName, handler) {
this.handlers[eventName] = handler;
};
describe('Blockchain monitor', function() {
this.timeout(5000);
var server, wallet;
before(function(done) {
helpers.before(function(res) {
storage = res.storage;
blockchainExplorer = res.blockchainExplorer;
blockchainExplorerEVM = _.cloneDeep(blockchainExplorer);
blockchainExplorer.initSocket = function(callbacks) {
socket.handlers['coin'] = function(data) {
callbacks.onIncomingPayments(data);
};
socket.handlers['block'] = callbacks.onBlock;
};
blockchainExplorerEVM.initSocket = function(callbacks) {
socket.handlers['tx'] = function(data) {
// copied from v8.tx
const tx = data.tx;
// script output, or similar.
if (!tx || !Constants.EVM_CHAINS[tx.chain]) return;
let tokenAddress;
let address;
let amount;
if (tx.abiType && tx.abiType.type === 'ERC20') {
tokenAddress = tx.to;
address = CWC.Web3.utils.toChecksumAddress(tx.abiType.params[0].value);
amount = tx.abiType.params[1].value;
} else {
address = tx.to;
amount = tx.value;
}
const out = {
address,
amount,
tokenAddress
};
return callbacks.onIncomingPayments({ out, txid: tx.txid });
};
// no uses in eth, interferes with btc
//socket.handlers['block'] = callbacks.onBlock;
};
done();
});
});
after(function(done) {
helpers.after(done);
});
beforeEach(function(done) {
blockchainExplorer.last = [];
blockchainExplorer.lastTx = [];
// TODO
const collections = {
WALLETS: 'wallets',
TXS: 'txs',
ADDRESSES: 'addresses',
NOTIFICATIONS: 'notifications',
COPAYERS_LOOKUP: 'copayers_lookup',
PREFERENCES: 'preferences',
EMAIL_QUEUE: 'email_queue',
CACHE: 'cache',
FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
PUSH_NOTIFICATION_SUBS: 'push_notification_subs',
TX_CONFIRMATION_SUBS: 'tx_confirmation_subs',
LOCKS: 'locks'
};
async.each(
_.values(collections),
(x, icb) => {
storage.db.collection(x).deleteMany({}, icb);
},
err => {
should.not.exist(err);
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
bcmonitor = new BlockchainMonitor();
bcmonitor.start(
{
lockOpts: {},
messageBroker: server.messageBroker,
storage: storage,
blockchainExplorers: {
btc: {
livenet: blockchainExplorer
},
eth: {
livenet: blockchainExplorerEVM
},
matic: {
livenet: blockchainExplorerEVM
}
}
},
function(err) {
should.not.exist(err);
done();
}
);
});
}
);
});
it('should notify copayers of incoming txs', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
out: { address: address.address, amount: 1500 }
};
socket.handlers['coin'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.find(notifications, {
type: 'NewIncomingTx'
});
should.exist(notification);
notification.walletId.should.equal(wallet.id);
notification.data.txid.should.equal('123');
notification.data.address.should.equal(address.address);
notification.data.amount.should.equal(1500);
done();
});
}, 100);
});
});
it('should not notify copayers of incoming txs btc, amount =0', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
out: { address: address.address, amount: 0 }
};
socket.handlers['coin'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.find(notifications, {
type: 'NewIncomingTx'
});
should.not.exist(notification);
done();
});
}, 100);
});
});
it('should notify copayers of incoming txs ETH, amount =0', function(done) {
helpers.createAndJoinWallet(1, 1, { coin: 'eth' }, function(s, w) {
s.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
tx: {
chain: 'ETH',
network: 'mainnet',
to: address.address,
value: 0,
txid: '123'
}
};
socket.handlers['tx'](incoming);
setTimeout(function() {
s.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.find(notifications, {
type: 'NewIncomingTx'
});
should.exist(notification);
notification.data.txid.should.equal('123');
notification.data.address.should.equal(address.address);
notification.data.amount.should.equal(0);
done();
});
}, 100);
});
});
});
it('should not notify copayers of incoming txs more than once', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123'
};
incoming.out = { address: address.address, amount: 15000 };
socket.handlers['coin'](incoming);
setTimeout(function() {
socket.handlers['coin'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.filter(notifications, {
type: 'NewIncomingTx'
});
notification[0].data.amount.should.equal(15000);
notification.length.should.equal(1);
done();
});
}, 100);
}, 200);
});
});
it('should not notify copayers of incoming txs more than once', function(done) {
helpers.createAndJoinWallet(1, 1, { coin: 'eth' }, function(s, w) {
s.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
tx: {
chain: 'ETH',
network: 'mainnet',
blockHeight: -1,
blockHash: null,
data:
'MHhhOTA1OWNiYjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDk4MmZhMTBhZDliOTc1NzQ5YzhmY2UxM2YyMmQ3ZWNlNGVhMjM5MjEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTU1Y2Mw',
txid: '0x2fb6db15ac76eea2118e04b5a93036eb75c3ea56652bf99bcb9798ae77019378',
blockTime: '2019-12-04T19:19:25.504Z',
blockTimeNormalized: '2019-12-04T19:19:25.504Z',
fee: 198995000000000,
transactionIndex: 0,
value: 0,
wallets: ['5d8b6c452522995f80c27bf5', '5d924a407eca6e5f89d2be5c'],
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
from: '0xC877cBCF020A8AA259A1Efab1559B2A3A7259086',
gasLimit: 39799,
gasPrice: 5000000000,
nonce: 52,
internal: [],
abiType: { type: 'ERC20', name: 'transfer', params: [{ value: address.address }, { value: 1e10 }] }
}
};
socket.handlers['tx'](incoming);
socket.handlers['tx'](incoming);
setTimeout(function() {
s.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.filter(notifications, {
type: 'NewIncomingTx'
});
notification[0].data.amount.should.equal(1e10);
notification.length.should.equal(1);
done();
});
}, 100);
});
});
});
it('should parse v8 amount ', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123'
};
incoming.out = { address: address.address, amount: 1500 };
socket.handlers['coin'](incoming);
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notification = _.filter(notifications, {
type: 'NewIncomingTx'
});
notification[0].data.amount.should.equal(1500);
notification.length.should.equal(1);
done();
});
}, 100);
});
});
it('should notify copayers of tx confirmation', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
var incoming = {
txid: '123',
vout: [{}]
};
incoming.vout[0][address.address] = 1500;
server.txConfirmationSubscribe(
{
txid: '123'
},
function(err) {
should.not.exist(err);
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block2');
setTimeout(function() {
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notifications = _.filter(notifications, {
type: 'TxConfirmation'
});
notifications.length.should.equal(1);
var n = notifications[0];
n.walletId.should.equal(wallet.id);
n.creatorId.should.equal(server.copayerId);
n.data.txid.should.equal('123');
done();
});
}, 50);
}, 50);
}
);
});
});
describe('Block Notify Throttling', function() {
it('should throttle _notifyNewBlock if setting requires', function(done) {
const notifyNewBlockSpy = sinon.spy(bcmonitor, '_notifyNewBlock');
bcmonitor.blockThrottleSettings = { btc: { livenet: 2 } };
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
// it always calls _notifyNewBlock the first time
notifyNewBlockSpy.calledOnce.should.be.true;
socket.handlers['block']('block2');
socket.handlers['block']('block3');
socket.handlers['block']('block4');
setTimeout(function() {
notifyNewBlockSpy.calledTwice.should.be.false;
socket.handlers['block']('block5');
setTimeout(function() {
notifyNewBlockSpy.calledTwice.should.be.true;
done();
}, 100);
}, 2000);
}, 100);
});
it('should not throttle _notifyNewBlock if setting doesn"t exist', function(done) {
const notifyNewBlockSpy = sinon.spy(bcmonitor, '_notifyNewBlock');
bcmonitor.blockThrottleSettings = { btc: {} };
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
// it always calls _notifyNewBlock the first time
notifyNewBlockSpy.calledOnce.should.be.true;
socket.handlers['block']('block2');
setTimeout(function() {
notifyNewBlockSpy.calledTwice.should.be.true;
socket.handlers['block']('block3');
setTimeout(function() {
notifyNewBlockSpy.calledThrice.should.be.true;
done();
}, 100);
}, 100);
}, 100);
});
it('should not throttle _notifyNewBlock if setting is zero', function(done) {
const notifyNewBlockSpy = sinon.spy(bcmonitor, '_notifyNewBlock');
bcmonitor.blockThrottleSettings = { btc: { livenet: 0 } };
blockchainExplorer.getTxidsInBlock = sinon.stub().callsArgWith(1, null, ['123', '456']);
socket.handlers['block']('block1');
setTimeout(function() {
// it always calls _notifyNewBlock the first time
notifyNewBlockSpy.calledOnce.should.be.true;
socket.handlers['block']('block2');
setTimeout(function() {
notifyNewBlockSpy.calledTwice.should.be.true;
socket.handlers['block']('block3');
setTimeout(function() {
notifyNewBlockSpy.calledThrice.should.be.true;
done();
}, 100);
}, 100);
}, 100);
});
});
});