Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

btc-p2p

Package Overview
Dependencies
Maintainers
2
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

btc-p2p - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

CHANGELOG.md

7

examples/app.js

@@ -36,2 +36,9 @@ var BTCNetwork = require('../lib/BTCNetwork').BTCNetwork;

console.log('Peer '+d.peer.getUUID()+' knows of Transaction '+d.hash.toString('hex'));
n.getData({type:1, hash:d.hash}, d.peer, function(err, rs) {
if (err !== false) {
console.log('Data returned error: '+err);
return;
}
console.log('Data returned:', rs);
});
});

@@ -38,0 +45,0 @@ n.on('transactionBlock', function transactionInv(d) {

389

lib/BTCNetwork.js
var PeerManager = require('p2p-manager').PeerManager;
var Message = require('./Message').Message;
var Struct = require('./Struct').Struct
var sha256 = require('sha256');

@@ -43,2 +45,5 @@ var crypto = require('crypto');

this.pendingGetData = {};
this.getDataDebounce = false;
var self = this;

@@ -51,5 +56,12 @@ this.manager

var cmd = d.command;
var handlerName = 'parse' + cmd.charAt(0).toUpperCase() + cmd.slice(1) + 'Message';
if (typeof self[handlerName] == 'function') { // does 'parseCommandMessage' exist as a method?
var rs = self[handlerName].call(self, d.data, d.peer);
var parserName = 'parse' + cmd.charAt(0).toUpperCase() + cmd.slice(1) + 'Message';
if (typeof self[parserName] == 'function') { // does 'parseCommandMessage' exist as a method?
var rs = self[parserName].call(self, d.data);
if (rs === false) return; // Invalid data was sent; ignore it
var handlerName = 'handle' + cmd.charAt(0).toUpperCase() + cmd.slice(1) + 'Message';
if (typeof self[handlerName] == 'function') {
if (self[handlerName].call(self, rs, d.peer) === false) return;
}
self.emit('message', {

@@ -133,15 +145,107 @@ peer: d.peer,

BTCNetwork.prototype.parseAddrMessage = function parseAddrMessage(data, peer) {
BTCNetwork.prototype.parseAddrMessage = function parseAddrMessage(data) {
var addrs = [];
var addrNum = Message.prototype.getVarInt(data, 0);
for (var i = addrNum[1]; i < data.length; i += 30) {
addrs.push(this.getAddr(data.slice(i, i+30)));
var raw = new Struct(data);
try {
var addrNum = raw.readVarInt();
for (var i = 0; i < addrNum; i++) {
addrs.push(this.getAddr(raw.raw(30)));
}
} catch (e) {
return false;
}
if (addrs.length != addrNum[0]) {
this._error('Was supposed to get '+addrNum[0]+' addresses, but got '+parsed.length+' instead', 'info');
}
this.manager.addPool(addrs); // Add these peers to the list of possible peers to connect to
return { addrs: addrs };
};
BTCNetwork.prototype.handleAddrMessage = function handleAddrMessage(data, peer) {
this.manager.addPool(data.addrs); // Add these peers to the list of possible peers to connect to
};
BTCNetwork.prototype.parseAlertMessage = function parseAlertMessage(data) {
var parsed = {};
var raw = new Struct(data);
try {
var msgSize = raw.readVarInt();
var message = raw.raw(msgSize);
var sigSize = raw.readVarInt();
parsed.signature = raw.raw(sigSize);
message = new Struct(message);
parsed.version = message.readUInt32LE();
parsed.relay_until = message.raw(8);
if (parsed.relay_until.readUInt32LE(4) == 0) {
parsed.relay_until = new Date(parsed.relay_until.readUInt32LE(0)*1000);
}
parsed.expiration = message.raw(8);
if (parsed.expiration.readUInt32LE(4) == 0) {
parsed.expiration = new Date(parsed.expiration.readUInt32LE(0)*1000);
}
parsed.uuid = message.readUInt32LE();
parsed.cancel = message.readUInt32LE();
var num = message.readVarInt();
parsed.cancel_set = [];
for (var i = 0; i < num; i++) {
parsed.cancel_set.push(message.readUInt32LE());
}
parsed.min_version = message.readUInt32LE();
parsed.max_version = message.readUInt32LE();
num = message.readVarInt();
parsed.subversion_set = [];
for (var i = 0; i < num; i++) {
parsed.subversion_set.push(message.readVarString());
}
parsed.priority = message.readUInt32LE();
parsed.comment = message.readVarString();
parsed.status_bar = message.readVarString();
num = message.readVarInt();
parsed.reserved = message.raw(num);
} catch(e) {
return false;
}
return parsed;
};
BTCNetwork.prototype.parseGetblocksMessage = function parseGetblocksMessage(data) {
var parsed = {};
var raw = new Struct(data);
try {
parsed.version = raw.readUInt32LE();
var hashCount = raw.readVarInt();
parsed.hashes = [];
for (var i = 0; i < hashCount; i++) {
parsed.hashes.push(raw.raw(32));
}
parsed.hash_stop = raw.raw(32);
} catch (e) {
return false;
}
return parsed;
};
BTCNetwork.prototype.parseGetheadersMessage = function parseGetHeadersMessage(data) {
return this.parseGetblocksMessage(data);
};
BTCNetwork.prototype.parseHeadersMessage = function parseHeadersMessage(data) {
var headers = [];
var raw = new Struct(data);
try {
var num = raw.readVarInt();
for (var i = 0; i < num; i++) {
var header = {};
header.version = raw.readUInt32LE();
header.prev_block = raw.raw(32);
header.merkle_root = raw.raw(32);
header.timestamp = new Date(raw.readUInt32LE()*1000);
header.difficulty = raw.readUInt32LE();
header.nonce = raw.raw(4);
header.txn_count = raw.readVarInt();
headers.push(header);
}
} catch (e) {
return false;
}
return { headers: headers };
};
BTCNetwork.prototype.invTypes = {

@@ -152,44 +256,113 @@ '0': 'error',

};
BTCNetwork.prototype.parseInvMessage = function parseInvMessage(data, peer) {
BTCNetwork.prototype.parseInvMessage = function parseInvMessage(data) {
var items = [];
var invNum = Message.prototype.getVarInt(data, 0);
for (var i = invNum[1]; i < data.length; i += 36) {
var inv_vect = {};
var tmp = data.slice(i, i+36);
inv_vect.type = tmp.readUInt32LE(0);
inv_vect.type_name = (typeof this.invTypes[inv_vect.type] != 'undefined')? this.invTypes[inv_vect.type] : 'unknown';
inv_vect.hash = new Buffer(32);
tmp.copy(inv_vect.hash, 0, 4);
items.push(inv_vect);
this.emit(inv_vect.type_name+'Inv', {
var raw = new Struct(data);
try {
var invNum = raw.readVarInt();
for (var i = 0; i < invNum; i++) {
var inv_vect = {};
inv_vect.type = raw.readUInt32LE();
inv_vect.type_name = (typeof this.invTypes[inv_vect.type] != 'undefined')? this.invTypes[inv_vect.type] : 'unknown';
inv_vect.hash = raw.raw(32);
items.push(inv_vect);
}
} catch(e) {
return false;
}
return { items: items }
};
BTCNetwork.prototype.handleInvMessage = function handleInvMessage(data, peer) {
for (var i = 0; i < data.items.length; i++) {
this.emit(data.items[i].type_name+'Inv', {
peer: peer,
hash: inv_vect.hash
hash: data.items[i].hash
});
}
if (items.length != invNum[0]) {
this._error('Was supposted to get '+invNum[0]+' inventory items, but got '+items.length+' instead', 'info');
};
BTCNetwork.prototype.parseNotfoundMessage = function parseNotfoundMessage(data) {
return this.parseInvMessage(data);
};
BTCNetwork.prototype.parsePingMessage = function parsePingMessage(data) {
var parsed = {};
var raw = new Struct(data);
try {
parsed.nonce = raw.raw(8);
} catch(e) {
return false;
}
return { items: items };
return parsed;
};
BTCNetwork.prototype.parsePongMessage = function parsePongMessage(data) {
return this.parsePingMessage(data);
};
BTCNetwork.prototype.parseTxMessage = function parseTxMessage(data) {
var parsed = {};
var raw = new Struct(data);
try {
parsed.version = raw.readUInt32LE();
var inCount = raw.readVarInt();
parsed.txIn = [];
for (var i = 0; i < inCount; i++) {
var txIn = { outPoint: {} };
txIn.outPoint.hash = raw.raw(32);
txIn.outPoint.index = raw.readUInt32LE();
var scriptSize = raw.readVarInt();
txIn.signature = raw.raw(scriptSize);
txIn.sequence = raw.readUInt32LE();
parsed.txIn.push(txIn);
}
var outCount = raw.readVarInt();
parsed.txOut = [];
for (i = 0; i < outCount; i++) {
var txOut = {};
txOut.value = raw.raw(8);
if (txOut.value.readUInt32LE(4) == 0) {
// Actually a 32-bit number
txOut.value = txOut.value.readUInt32LE(0);
}
var scriptSize = raw.readVarInt();
txOut.script = raw.raw(scriptSize);
parsed.txOut.push(txOut);
}
parsed.lock = raw.readUInt32LE();
parsed.raw = new Buffer(data.length);
data.copy(parsed.raw);
parsed.hash = new Buffer(sha256.x2(data, { asBytes: true }));
} catch(e) {
return false;
}
return parsed;
};
BTCNetwork.prototype.parseVersionMessage = function parseVersionMessage(data, peer) {
var parsed = {};
parsed.version = data.readUInt32LE(0);
parsed.services = new Buffer(8);
data.copy(parsed.services, 0, 4, 12);
parsed.time = new Buffer(8);
data.copy(parsed.time, 0, 12, 20);
if (parsed.time.readUInt32LE(4) == 0) {
// 32-bit date; no need to keep as buffer
parsed.time = new Date(parsed.time.readUInt32LE(0)*1000);
var raw = new Struct(data);
try {
parsed.version = raw.readUInt32LE(0);
parsed.services = raw.raw(8);
parsed.time = raw.raw(8);
if (parsed.time.readUInt32LE(4) == 0) {
// 32-bit date; no need to keep as buffer
parsed.time = new Date(parsed.time.readUInt32LE(0)*1000);
}
parsed.addr_recv = this.getAddr(raw.raw(26));
parsed.addr_from = this.getAddr(raw.raw(26));
parsed.nonce = raw.raw(8);
parsed.client = raw.readVarString();
parsed.height = raw.readUInt32LE();
} catch(e) {
return false;
}
parsed.addr_recv = this.getAddr(data.slice(20, 46));
parsed.addr_from = this.getAddr(data.slice(46, 72));
parsed.nonce = new Buffer(8);
data.copy(parsed.nonce, 0, 72, 80);
parsed.client = Message.prototype.getVarString(data, 80);
parsed.height = data.readUInt32LE(data.length-4);
console.log('VERSION:', parsed);
if (parsed.nonce.toString('hex') === this.nonce.toString('hex')) {
return parsed;
};
BTCNetwork.prototype.handleVersionMessage = function handleVersionMessage(data, peer) {
if (data.nonce.toString('hex') === this.nonce.toString('hex')) {
// We connected to ourselves!

@@ -202,4 +375,4 @@ this.manager.delActive(peer, 'connected to self');

if (this.options.externalIP == false || (this.options.externalIP.toString('hex') != parsed.addr_recv.hostRaw.toString('hex') && parsed.addr_recv.hostRaw.slice(10,16).toHex != '000000000000')) {
this.options.externalIP = parsed.addr_recv.hostRaw;
if (this.options.externalIP == false || (this.options.externalIP.toString('hex') != data.addr_recv.hostRaw.toString('hex') && data.addr_recv.hostRaw.slice(10,16).toHex != '000000000000')) {
this.options.externalIP = data.addr_recv.hostRaw;
this._error('External address discovered to be '+this.options.externalIP.toString('hex'), 'info');

@@ -211,9 +384,7 @@ }

}
peer.info.version = parsed.version;
peer.info.services = parsed.services;
peer.info.nonce = parsed.nonce;
peer.info.client = parsed.client;
peer.info.knownHeight = parsed.height;
return parsed;
peer.info.version = data.version;
peer.info.services = data.services;
peer.info.nonce = data.nonce;
peer.info.client = data.client;
peer.info.knownHeight = data.height;
};

@@ -260,3 +431,4 @@

self.manager.delActive(p, 'Failed to send VERACK after VERSION message');
}, 10000).unref();
}, 10000);
watchdog.unref();
p.once('verackMessage', function() {

@@ -279,2 +451,113 @@ // VERACK received; this peer is active now

// This method call is debounced to better batch the requests.
// It waits until this method has not been called for one second before actually sending the request.
// Send force = true to override this and trigger a request now.
BTCNetwork.prototype.getData = function getData(inventory, peer, callback, force) {
if (!Array.isArray(inventory)) {
inventory = [inventory];
}
if (typeof peer.info.pendingGetData == 'undefined') {
peer.info.pendingGetData = [];
}
peer.info.pendingGetData = peer.info.pendingGetData.concat(inventory);
// Add callback to the list of functions to notify when this batch of data is ready
if (typeof peer.info.getDataListeners == 'undefined') {
peer.info.getDataListeners = [];
}
if (typeof callback == 'function') {
peer.info.getDataListeners.push(callback);
}
if (force !== true && peer.info.pendingGetData.length < 50) {
clearTimeout(peer.info.getDataTimeout);
var self = this;
peer.info.getDataTimeout = setTimeout(function() {
self.getData([], peer, false, true); // Force send now
}, 1000);
return;
}
// Send the request
var toSend = peer.info.pendingGetData.slice(0); // Make local copy
peer.info.pendingGetData = []; // Clear original
var toCallback = peer.info.getDataListeners.slice(0); // Make local copy
peer.info.getDataListeners = []; // Clear original
if (toSend.length == 0) return;
var waitingFor = toSend.length;
console.log('Sending request for:', toSend);
var msg = new Message(this.options.magic, true);
msg.putVarInt(toSend.length);
for (var i = 0; i < toSend.length; i++) {
var inv = toSend[i];
var raw = new Buffer(36);
raw.writeUInt32LE(inv.type, 0);
inv.hash.copy(raw, 4);
msg.put(raw);
}
var doCallbacks = function(err, rs) {
console.log('firing callbacks');
for (var i = 0; i < toCallback.length; i++) {
toCallback[i].call(peer, err, rs);
}
};
if (this.state == 'shutdown') {
doCallbacks('Shutdown in progress'); // Abort
return;
}
console.log(waitingFor+' items pending');
var items = [];
var watchdog = setTimeout(function watchdog() {
if (waitingFor > 0) {
doCallbacks('Peer unresponsive');
}
}, 30000);
watchdog.unref();
var addResult = function addResult(item) {
console.log('add result fired with '+waitingFor+' to go');
if (item !== false) {
items.push(item);
}
if (--waitingFor <= 0) {
// We were the last one
peer.removeListener('notfoundMessage', notFoundHandler);
peer.removeListener('txMessage', txHandler);
peer.removeListener('blockMessage', blockHandler);
if (items.length == 0) {
doCallbacks('Not Found', false);
} else {
doCallbacks(false, items);
}
return;
}
console.log('waiting for '+waitingFor+' more objects');
};
var self = this;
var notFoundHandler = function notFoundHandler(d) {
var numMissing = d.data.readUInt32LE(0);
console.log(numMissing+' reported missing');
waitingFor = waitingFor - numMissing + 1;
addResult(false);
};
var txHandler = function txHandler(d) {
console.log('One transaction came in');
addResult({'type': 'tx', 'data': self.parseTxMessage(d.data, d.peer)});
};
var blockHandler = function blockHandler(d) {
console.log('One block came in');
// TODO: Parse the result before returning
addResult({'type': 'block', 'data': d.data});
};
peer.send('getdata', msg.raw());
peer.on('notfoundMessage', notFoundHandler);
peer.on('txMessage', txHandler);
peer.on('blockMessage', blockHandler);
};
BTCNetwork.prototype.addrPoll = function addrPoll() {

@@ -281,0 +564,0 @@ var peers = this.manager.send(5, 'state', 'active', 'getaddr');

@@ -96,2 +96,13 @@ var sha256 = require('sha256');

};
Message.prototype.varIntSize = function varIntSize(num) {
if (num < 0xfd) {
return 1
} else if (num <= 0xffff) {
return 3
} else if (num <= 0xffffffff) {
return 5
} else {
return 9
}
};
Message.prototype.putVarString = function putVarString(str) {

@@ -98,0 +109,0 @@ return this.putVarInt(str.length).putString(str);

2

package.json
{
"name": "btc-p2p",
"version": "0.1.1",
"version": "0.2.0",
"description": "Manage a network of Bitcoin peers",

@@ -5,0 +5,0 @@ "keywords": [

@@ -12,4 +12,6 @@ # Bitcoin P2P Manager

This is accomplished by several message handlers on the BTCNetwork object, named in the format `parseCommandMessage` (e.g. `parseVersionMessage`, `parseInvMessage`, `parseGetaddrMessage`, etc.). If a handler is not found for a message type, the raw form is bubbled up. To handle additional types of messages, add a handler method named appropriately, and it will be passed `data` and `peer` as arguments. It must return an object; if the result is an array (i.e `inv` messages), return an object with one property set to the array (i.e. `{ items: ARRAY }`).
This is accomplished by several message handlers on the BTCNetwork object, named in the format `parseCommandMessage` (e.g. `parseVersionMessage`, `parseInvMessage`, `parseGetaddrMessage`, etc.). If a handler is not found for a message type, the raw form is bubbled up. To handle additional types of messages, add a parser method named appropriately, and it will be passed `data` as an argument, which is the message payload in Buffer form. It must return an object; if the result is an array (i.e `inv` messages), return an object with one property set to the array (i.e. `{ items: ARRAY }`).
In addition to a parser method, a `handleCommandMessage` method can be defined, which will be passed the parsed result, and `peer` as arguments, where `peer` is the Peer who sent the message.
These parsing methods mean, that if you're subscribed to an event stream that presents raw data (i.e. the `versionMessage` events from an individual Peer), you can parse them like:

@@ -22,4 +24,4 @@

p.on('versionMessage', function(d) {
var parsed = btc.parseVersionMessage(d.data, d.peer);
var parsed = btc.parseVersionMessage(d.data);
});
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc