Browse Source

Merge pull request #444 from maraoz/add/NetworkMonitor

Add NetworkMonitor class
patch-2
Ryan X. Charles 11 years ago
parent
commit
0bcdb7c80e
  1. 1
      bitcore.js
  2. 4
      browser/build.js
  3. 39769
      browser/bundle.js
  4. 29
      examples/NetworkMonitor.js
  5. 29
      lib/Address.js
  6. 2
      lib/Connection.js
  7. 82
      lib/NetworkMonitor.js
  8. 27
      lib/Transaction.js
  9. 5
      lib/TransactionBuilder.js
  10. 6
      package.json
  11. 2
      test/index.html
  12. 2
      test/test.Address.js
  13. 161
      test/test.NetworkMonitor.js
  14. 8
      test/test.PeerManager.js
  15. 67
      test/test.Transaction.js

1
bitcore.js

@ -81,4 +81,5 @@ requireWhenAccessed('PeerManager', './lib/PeerManager');
requireWhenAccessed('Message', './lib/Message');
requireWhenAccessed('Electrum', './lib/Electrum');
requireWhenAccessed('Armory', './lib/Armory');
requireWhenAccessed('NetworkMonitor', './lib/NetworkMonitor');
module.exports.Buffer = Buffer;

4
browser/build.js

@ -24,6 +24,7 @@ var modules = [
'lib/ECIES',
'lib/Electrum',
'lib/Message',
'lib/NetworkMonitor',
'lib/Opcode',
'lib/PayPro',
'lib/Peer',
@ -101,6 +102,9 @@ var createBitcore = function(opts) {
b.require(opts.dir + 'bufferput', {
expose: 'bufferput'
});
b.require(opts.dir + 'events', {
expose: 'events'
});
b.require(opts.dir + 'buffers', {
expose: 'buffers'
});

39769
browser/bundle.js

File diff suppressed because one or more lines are too long

29
examples/NetworkMonitor.js

@ -0,0 +1,29 @@
'use strict';
var run = function() {
// Replace '../bitcore' with 'bitcore' if you use this code elsewhere.
var bitcore = require('../bitcore');
var NetworkMonitor = bitcore.NetworkMonitor;
var config = {
networkName: 'testnet',
host: 'localhost',
port: 18333
};
var nm = new NetworkMonitor.create(config);
// monitor incoming transactions to http://tpfaucet.appspot.com/ donation address
nm.incoming('msj42CCGruhRsFrGATiUuh25dtxYtnpbTx', function(tx) {
console.log('Donation to tpfaucet! '+JSON.stringify(tx.getStandardizedObject()));
});
// connect to bitcoin network and start listening
nm.start();
};
module.exports.run = run;
if (require.main === module) {
run();
}

29
lib/Address.js

@ -96,7 +96,7 @@ Address.fromScript = function(script, network) {
return new Address(version, hash);
};
//extract and address from scriptPubKey
//extract an address from scriptPubKey
Address.fromScriptPubKey = function(scriptPubKey, network) {
if (typeof scriptPubKey === 'string') {
@ -182,5 +182,32 @@ Address.prototype.getScriptPubKey = function() {
return script;
};
Address.fromPubkeyHashScriptSig = function(scriptSig, network) {
return Address.fromPubKey(scriptSig.chunks[1], network);
};
//extract an address from scriptSig
Address.fromScriptSig = function(scriptSig, network) {
if (typeof scriptSig === 'string') {
scriptSig = new Script(new Buffer(scriptSig, 'hex'));
}
if (!network)
network = 'livenet';
var payload = scriptSig.chunks;
if (scriptSig.chunks.length === 2)
return Address.fromPubkeyHashScriptSig(scriptSig, network);
// TODO: support other scriptSig types
return null;
};
Address.getScriptPubKeyFor = function(s) {
return new Address(s).getScriptPubKey();
};
Address.validate = function(s) {
return new Address(s).isValid();
};
module.exports = Address;

2
lib/Connection.js

@ -18,6 +18,7 @@ var doubleSha256 = util.twoSha256;
var SecureRandom = require('./SecureRandom');
var nonce = SecureRandom.getPseudoRandomBuffer(8);
var nodeUtil = require('util');
var EventEmitter = require('events').EventEmitter;
var BIP0031_VERSION = 60000;
@ -59,7 +60,6 @@ function Connection(socket, peer, opts) {
this.setupHandlers();
}
var EventEmitter = require('events').EventEmitter;
nodeUtil.inherits(Connection, EventEmitter);
Connection.prototype.open = function(callback) {
if (typeof callback === 'function') this.once('connect', callback);

82
lib/NetworkMonitor.js

@ -0,0 +1,82 @@
var log = require('../util/log');
var networks = require('../networks');
var Address = require('./Address');
var Peer = require('./Peer');
var PeerManager = require('./PeerManager');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var preconditions = require('preconditions').singleton();
var NetworkMonitor = function(peerman) {
preconditions.checkArgument(peerman);
this.peerman = peerman;
this.networkName = peerman.config.network;
this.init();
}
util.inherits(NetworkMonitor, EventEmitter);
NetworkMonitor.create = function(config) {
var peerman = new PeerManager({
network: config.networkName
});
peerman.addPeer(new Peer(config.host, config.port));
return new NetworkMonitor(peerman);
};
NetworkMonitor.prototype.init = function() {
var self = this;
var handleInv = function(info) {
var invs = info.message.invs;
info.conn.sendGetData(invs);
};
var handleBlock = function(info) {
self.emit('block', info.message);
};
var handleTx = function(info) {
var tx = info.message.tx;
self.emit('tx', tx);
var from = tx.getSendingAddresses(self.networkName);
for (var i = 0; i < from.length; i++) {
var addr = from[i];
self.emit('out:'+addr, tx);
}
var to = tx.getReceivingAddresses(self.networkName);
for (var i = 0; i < to.length; i++) {
var addr = to[i];
self.emit('in:'+addr, tx);
}
};
this.peerman.on('connection', function(conn) {
if (self.connection) throw new Error('Cant handle more than one connection');
self.connection = conn;
conn.on('inv', handleInv);
conn.on('block', handleBlock);
conn.on('tx', handleTx);
});
};
NetworkMonitor.prototype.incoming = function(addrStr, callback) {
preconditions.checkArgument(Address.validate(addrStr));
this.on('in:'+addrStr, callback);
};
NetworkMonitor.prototype.outgoing = function(addrStr, callback) {
preconditions.checkArgument(Address.validate(addrStr));
this.on('out:'+addrStr, callback);
};
NetworkMonitor.prototype.start = function() {
this.peerman.start();
};
NetworkMonitor.prototype.stop = function() {
this.peerman.stop();
};
module.exports = NetworkMonitor;

27
lib/Transaction.js

@ -10,7 +10,6 @@ var Parser = require('../util/BinaryParser');
var Step = require('step');
var buffertools = require('buffertools');
var error = require('../util/error');
var networks = require('../networks');
var WalletKey = require('./WalletKey');
var PrivateKey = require('./PrivateKey');
@ -621,5 +620,31 @@ Transaction.prototype.isComplete = function() {
return ret;
};
Transaction.prototype.getReceivingAddresses = function(networkName) {
if (!networkName) networkName = 'livenet';
ret = [];
for (var i = 0; i<this.outs.length; i++) {
var o = this.outs[i];
var addr = Address.fromScriptPubKey(o.getScript(), networkName)[0].toString();
ret.push(addr);
}
return ret;
};
Transaction.prototype.getSendingAddresses = function(networkName) {
var ret = [];
if (!networkName) networkName = 'livenet';
for (var i = 0; i<this.ins.length; i++) {
var input = this.ins[i];
var scriptSig = input.getScript();
if (scriptSig.getBuffer().length === 0) {
ret.push(null);
continue;
}
var addr = Address.fromScriptSig(scriptSig, networkName);
ret.push(addr?addr.toString():null);
}
return ret;
};
module.exports = Transaction;

5
lib/TransactionBuilder.js

@ -433,7 +433,9 @@ TransactionBuilder._mapKeys = function(keys) {
} else {
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
}
walletKeyMap[wk.storeObj().addr] = wk;
var addr = wk.storeObj().addr;
walletKeyMap[addr] = wk;
}
return walletKeyMap;
};
@ -752,6 +754,7 @@ TransactionBuilder.prototype.sign = function(keys) {
l = ins.length,
walletKeyMap = TransactionBuilder._mapKeys(keys);
for (var i = 0; i < l; i++) {
var input = this.inputMap[i];

6
package.json

@ -84,7 +84,8 @@
"async": "~0.2.10",
"event-stream": "~3.1.5",
"gulp-concat": "~2.2.0",
"gulp": "~3.8.2"
"gulp": "~3.8.2",
"preconditions": "^1.0.7"
},
"testling": {
"harness": "mocha-bdd",
@ -111,5 +112,8 @@
"license": "MIT",
"engines": {
"node": ">=0.10"
},
"devDependencies": {
"sinon": "^1.10.3"
}
}

2
test/index.html

@ -10,6 +10,7 @@
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script src="../node_modules/sinon/pkg/sinon.js"></script>
<script>mocha.setup('bdd')</script>
<script src="../browser/bundle.js"></script>
<script src="../browser/testdata.js"></script>
@ -31,6 +32,7 @@
<script src="test.main.js"></script>
<script src="test.Message.js"></script>
<script src="test.misc.js"></script>
<script src="test.NetworkMonitor.js"></script>
<script src="test.Opcode.js"></script>
<script src="test.PayPro.js"></script>
<script src="test.Peer.js"></script>

2
test/test.Address.js

@ -50,6 +50,7 @@ describe('Address', function() {
var s = a.toString();
a.isValid().should.equal(result);
Address.validate(address).should.equal(result);
s.should.equal(a.toString()); // check that validation doesn't change data
});
});
@ -212,6 +213,7 @@ describe('Address', function() {
var d = data[i];
var b = new Address(d[1]).getScriptPubKey().getBuffer();
b.toString('hex').should.equal(d[0]);
Address.getScriptPubKeyFor(d[1]).getBuffer().toString('hex').should.equal(d[0]);
}
});
});

161
test/test.NetworkMonitor.js

@ -0,0 +1,161 @@
'use strict';
var chai = chai || require('chai');
var sinon = sinon || require('sinon');
var bitcore = bitcore || require('../bitcore');
var Transaction = bitcore.Transaction;
var NetworkMonitor = bitcore.NetworkMonitor;
var EventEmitter = require('events').EventEmitter;
var should = chai.should();
var nop = function() {};
describe('NetworkMonitor', function() {
var config = {
networkName: 'testnet',
host: 'localhost',
port: 18333
};
var fakePM = {};
fakePM.on = nop;
fakePM.config = {
network: config.networkName
};
it('should initialze the main object', function() {
should.exist(NetworkMonitor);
});
it('should be able to instanciate', function() {
var nm = new NetworkMonitor(fakePM);
should.exist(nm);
});
it('should be able to create instance', function() {
var nm = new NetworkMonitor.create(config);
should.exist(nm);
});
it('should be able to start instance', function() {
var nm = new NetworkMonitor.create(config);
nm.start.bind(nm).should.not.throw();
nm.stop();
});
it('should be able to stop instance', function() {
var nm = new NetworkMonitor.create(config);
nm.start();
nm.stop.bind(nm).should.not.throw();
});
it('should be able to register listeners', function() {
var nm = new NetworkMonitor.create(config);
(function() {
nm.on('block', nop);
}).should.not.throw();
(function() {
nm.incoming('n2tTCgsJPJBZZEKLiJx9KoU4idJQB37j9E', nop);
}).should.not.throw();
(function() {
nm.outgoing('n2tTCgsJPJBZZEKLiJx9KoU4idJQB37j9E', nop);
}).should.not.throw();
});
var createConnectedNM = function() {
var nm = new NetworkMonitor.create(config);
var fakeConnection = new EventEmitter();
nm.peerman.emit('connection', fakeConnection);
return nm;
};
it('should store connection', function() {
var nm = createConnectedNM();
should.exist(nm.connection);
});
describe('block event', function() {
it('should be called on blocks', function(done) {
var nm = createConnectedNM();
nm.on('block', function(m) {
should.exist(m);
done();
});
nm.connection.emit('block', {
message: 'test'
});
});
});
var observedAddress = 'mwABUqsGjjeTgExrBmyyWEErS8yA4QNAUJ';
var simulateNetworkTx = function(raw, nm) {
var tx = new Transaction();
tx.parse(new Buffer(raw, 'hex'));
nm.connection.emit('tx', {
message: {
tx: tx
}
});
return tx;
};
var incomingRaw = '01000000017ee4912333a1add2b03041b7abf4f64c365634a2d31ebfef4f47684c5adcfc49010000006a473044022064e5a4bd31615d184f7c660fcb7e072bfaaf8d87ad8f208ec85276b66420aeb102201c4fe9921495b07492a26648d4b124b346028dbdd91fea1ce3a32a21f1accb31012102749393ba256c17f67ff1c3e7d3ad72e610f07146b2d1d996287b1c500a75062effffffff0200ca9a3b000000001976a914ab9448d3b5adab710665e82506ae5cbd4ba7ba1288acf0bfe71c000000001976a9146c45fa9d90420668f7ff16e33d3d21b0d7e73bc188ac00000000';
var unrelatedRaw = '010000000114bae675546f758e0dbab95aa88d4db0c63e26f8fd6cbbce3a4827446d4937cf00000000700048304502201da760691f18a0ab140de1437e4bd29767b74add8cca8e38d46a2f37d9a8188f022100cbf4e121d97b4db846d236957da7fc17fd706ad47b41ae63adf953982e34f70901255121022f58491a833933a9bea80d8e820e66bee91bd8c71bfa972fe70482360b48129951aeffffffff01706f9800000000001976a91400a26ff8123593e10d0a9eba2a74db33cd69299288ac00000000';
var outgoingRaw = '0100000001613b50ef601ac068b7805afb8615bb06371881321a478b62d1f52d21f2a8529c000000006b483045022100e3c38e6da99bc8e4b6150404d3afc9ee74b5b48a245311e8fb0e019a3f69570102201eda167b14d675f7b9cf60cf1b0c65b1d66efa4a339743aaec047ce90b92e52e0121031915a253ead0da95c46ff64d07fe4d562a29b7fc211c6a8f49764ac85c039de4ffffffff01f0a29a3b000000001976a914c69536a7d60748bb1953e5e186edf920efa823e388ac00000000';
describe('tx event', function() {
it('should be called on network transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.on('tx', spy);
var tx1 = simulateNetworkTx(incomingRaw, nm);
var tx2 = simulateNetworkTx(unrelatedRaw, nm);
var tx3 = simulateNetworkTx(outgoingRaw, nm);
spy.calledWith(tx1).should.equal(true);
spy.calledWith(tx2).should.equal(true);
spy.calledWith(tx3).should.equal(true);
spy.callCount.should.equal(3);
});
});
describe('incoming tx event', function() {
it('should be called on incoming transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.incoming(observedAddress, spy);
var tx = simulateNetworkTx(incomingRaw, nm);
spy.calledWith(tx).should.equal(true);
spy.callCount.should.equal(1);
});
it('should not be called on unrelated transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.incoming(observedAddress, spy);
var tx = simulateNetworkTx(unrelatedRaw, nm);
spy.calledWith(tx).should.equal(false);
spy.callCount.should.equal(0);
});
it('should not be called on outgoing transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.incoming(observedAddress, spy);
var tx = simulateNetworkTx(outgoingRaw, nm);
spy.calledWith(tx).should.equal(false);
spy.callCount.should.equal(0);
});
});
describe('outgoing tx event', function() {
it('should be called on outgoing transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.outgoing(observedAddress, spy);
var tx = simulateNetworkTx(outgoingRaw, nm);
spy.calledWith(tx).should.equal(true);
spy.callCount.should.equal(1);
});
it('should not be called on unrelated transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.outgoing(observedAddress, spy);
var tx = simulateNetworkTx(unrelatedRaw, nm);
spy.calledWith(tx).should.equal(false);
spy.callCount.should.equal(0);
});
it('should not be called on incoming transactions', function() {
var nm = createConnectedNM();
var spy = sinon.spy();
nm.outgoing(observedAddress, spy);
var tx = simulateNetworkTx(incomingRaw, nm);
spy.calledWith(tx).should.equal(false);
spy.callCount.should.equal(0);
});
});
});

8
test/test.PeerManager.js

@ -5,16 +5,10 @@ var bitcore = bitcore || require('../bitcore');
var should = chai.should();
var PeerManagerModule = bitcore.PeerManager;
var PeerManager;
var PeerManager = bitcore.PeerManager;
describe('PeerManager', function() {
it('should initialze the main object', function() {
should.exist(PeerManagerModule);
});
it('should be able to create class', function() {
PeerManager = PeerManagerModule;
should.exist(PeerManager);
});
it('should be able to create instance', function() {

67
test/test.Transaction.js

@ -7,17 +7,17 @@ var bitcore = bitcore || require('../bitcore');
var should = chai.should();
var Transaction = bitcore.Transaction;
var In;
var Out;
var TransactionBuilder = bitcore.TransactionBuilder;
var Script = bitcore.Script;
var Address = bitcore.Address;
var util = bitcore.util;
var buffertools = require('buffertools');
var testdata = testdata || require('./testdata');
// Read tests from test/data/tx_valid.json and tx_invalid.json
// Format is an array of arrays
// Inner arrays are either [ "comment" ]
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
// Inner arrays are either [ 'comment' ]
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],'], serializedTransaction, enforceP2SH
// ... where all scripts are stringified scripts.
// Returns an object with the Transaction object, and an array of input objects
function parse_test_transaction(entry) {
@ -48,12 +48,10 @@ function parse_test_transaction(entry) {
}
describe('Transaction', function() {
it('should initialze the main object', function() {
it('should initialize the main objects', function() {
should.exist(Transaction);
In = Transaction.In;
Out = Transaction.Out;
should.exist(In);
should.exist(Out);
should.exist(Transaction.In);
should.exist(Transaction.Out);
});
@ -122,19 +120,60 @@ describe('Transaction', function() {
coreTest(testdata.dataTxInvalid, false);
it('#normalized hash', function() {
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
// string output generated from: bitcoind createrawtransaction '[{'txid': '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1','vout':1},{'txid':'2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2','vout':0} ]' '{'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE':0.08}'
//
var Parser = bitcore.BinaryParser;
var tx = new Transaction();
tx.parse(new Buffer('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000','hex'));
tx.parse(new Buffer('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000', 'hex'));
tx.getNormalizedHash().toString('hex').should.equal('1f7d2666e2d0d663e098abb76db6ba392da972d21c14b6ea6f4336171d29966b');
var tx2 = new Transaction();
tx2.parse(new Buffer('0100000001c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a010000004b00473044022059085ff1b8ad03033e60969b1c770aa29ba5d74c28a9992c514b100d860792f1022057a307f77f91f4563651eefc0a959aa916d275c58525320309b6aeeff43d0d8a010000ffffffff0215cd5b07000000001976a91434f8e0c5be216025a52addf18a987543cad23f7a88acdbd53e340000000017a9147a769913c0721b1e0aa6bf8a93f4ef810c60587a8700000000','hex'));
tx2.parse(new Buffer('0100000001c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a010000004b00473044022059085ff1b8ad03033e60969b1c770aa29ba5d74c28a9992c514b100d860792f1022057a307f77f91f4563651eefc0a959aa916d275c58525320309b6aeeff43d0d8a010000ffffffff0215cd5b07000000001976a91434f8e0c5be216025a52addf18a987543cad23f7a88acdbd53e340000000017a9147a769913c0721b1e0aa6bf8a93f4ef810c60587a8700000000', 'hex'));
tx2.getNormalizedHash().toString('hex').should.equal('e298bbf3734898581b8e342f2064236abf0acca6ac7e9a3009a16ef7b64d4983');
});
describe('#send and receiving addresses', function() {
var a1 = 'n1pKARYYUnZwxBuGj3y7WqVDu6VLN7n971';
var a2 = 'mtxYYJXZJmQc2iJRHQ4RZkfxU5K7TE2qMJ';
var utxos = [{
address: a1,
txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1',
vout: 1,
scriptPubKey: Address.getScriptPubKeyFor(a1).serialize().toString('hex'),
amount: 0.5,
confirmations: 200
}, {
address: a2,
txid: '88c4520ffd97ea565578afe0b40919120be704b36561c71ba4e450e83cb3c9fd',
vout: 1,
scriptPubKey: Address.getScriptPubKeyFor(a2).serialize().toString('hex'),
amount: 0.5001,
confirmations: 200
}];
var destAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1';
var outs = [{
address: destAddress,
amount: 1.0
}];
var txb = new TransactionBuilder()
.setUnspent(utxos)
.setOutputs(outs)
.sign(['cVBtNonMyTydnS3NnZyipbduXo9KZfF1aUZ3uQHcvJB6UARZbiWG',
'cRVF68hhZp1PUQCdjr2k6aVYb2cn6uabbySDPBizAJ3PXF7vDXTL']);
var tx = txb.build();
it('should find receiving addresses', function() {
var to = tx.getReceivingAddresses('testnet');
to.length.should.equal(1);
to[0].should.equal(destAddress);
});
it('should find sending addresses', function() {
var from = tx.getSendingAddresses('testnet');
from.length.should.equal(2);
from[0].should.equal(a1);
from[1].should.equal(a2);
});
});
});

Loading…
Cancel
Save