wip
This commit is contained in:
parent
106d243873
commit
a20cb1688f
@ -3,5 +3,9 @@ var elliptic = require('elliptic');
|
||||
|
||||
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||
bcoin.utils = require('./bcoin/utils');
|
||||
bcoin.bloom = require('./bcoin/bloom');
|
||||
bcoin.protocol = require('./bcoin/protocol');
|
||||
bcoin.tx = require('./bcoin/tx');
|
||||
bcoin.block = require('./bcoin/block');
|
||||
bcoin.wallet = require('./bcoin/wallet');
|
||||
bcoin.peer = require('./bcoin/peer');
|
||||
|
||||
42
lib/bcoin/block.js
Normal file
42
lib/bcoin/block.js
Normal file
@ -0,0 +1,42 @@
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
function Block(data) {
|
||||
if (!(this instanceof Block))
|
||||
return new Block(data);
|
||||
|
||||
this.type = 'block';
|
||||
this.version = data.version;
|
||||
this.prevBlock = data.prevBlock;
|
||||
this.merkleRoot = data.merkleRoot;
|
||||
this.ts = data.ts;
|
||||
this.bits = data.bits;
|
||||
this.nonce = data.nonce;
|
||||
|
||||
this._hash = null;
|
||||
}
|
||||
module.exports = Block;
|
||||
|
||||
Block.prototype.hash = function hash(enc) {
|
||||
// Hash it
|
||||
if (!this._hash)
|
||||
this._hash = utils.dsha256(this.abbr());
|
||||
return enc === 'hex' ? utils.toHex(this._hash) : this._hash;
|
||||
};
|
||||
|
||||
Block.prototype.abbr = function abbr() {
|
||||
var res = new Array(80);
|
||||
utils.writeU32(res, this.version, 0);
|
||||
utils.copy(this.prevBlock, res, 4);
|
||||
utils.copy(this.merkleRoot, res, 36);
|
||||
utils.writeU32(res, this.ts, 68);
|
||||
utils.writeU32(res, this.bits, 72);
|
||||
utils.writeU32(res, this.nonce, 76);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
Block.prototype.render = function render(framer) {
|
||||
console.log('here');
|
||||
return [];
|
||||
};
|
||||
139
lib/bcoin/bloom.js
Normal file
139
lib/bcoin/bloom.js
Normal file
@ -0,0 +1,139 @@
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
function Bloom(size, n, tweak) {
|
||||
if (!(this instanceof Bloom))
|
||||
return new Bloom(size, n, tweak);
|
||||
|
||||
this.filter = new Array(Math.ceil(size / 32));
|
||||
for (var i = 0; i < this.filter.length; i++)
|
||||
this.filter[i] = 0;
|
||||
this.size = size;
|
||||
this.n = n;
|
||||
this.tweak = tweak;
|
||||
}
|
||||
module.exports = Bloom;
|
||||
|
||||
Bloom.prototype.hash = function hash(val, n) {
|
||||
return Bloom.hash(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size;
|
||||
};
|
||||
|
||||
Bloom.prototype.add = function add(val) {
|
||||
for (var i = 0; i < this.n; i++) {
|
||||
var bit = this.hash(val, i);
|
||||
var pos = 1 << (bit & 0x1f);
|
||||
var shift = bit >> 5;
|
||||
|
||||
this.filter[shift] |= pos;
|
||||
}
|
||||
};
|
||||
|
||||
Bloom.prototype.test = function test(val) {
|
||||
for (var i = 0; i < this.n; i++) {
|
||||
var bit = this.hash(val, i);
|
||||
var pos = 1 << (bit & 0x1f);
|
||||
var shift = bit >> 5;
|
||||
|
||||
if ((this.filter[shift] & pos) === 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Bloom.prototype.toArray = function toArray() {
|
||||
var bytes = Math.ceil(this.size / 8);
|
||||
var res = new Array(this.filter.length * 4);
|
||||
for (var i = 0; i < this.filter.length; i++) {
|
||||
var w = this.filter[i];
|
||||
res[i * 4] = w & 0xff;
|
||||
res[i * 4 + 1] = (w >> 8) & 0xff;
|
||||
res[i * 4 + 2] = (w >> 16) & 0xff;
|
||||
res[i * 4 + 3] = (w >> 24) & 0xff;
|
||||
}
|
||||
|
||||
return res.slice(0, bytes);
|
||||
};
|
||||
|
||||
function mul32(a, b) {
|
||||
var alo = a & 0xffff;
|
||||
var blo = b & 0xffff;
|
||||
var ahi = a >>> 16;
|
||||
var bhi = b >>> 16;
|
||||
|
||||
var lo = alo * blo;
|
||||
var hi = (ahi * blo + bhi * alo) & 0xffff;
|
||||
hi += lo >>> 16;
|
||||
lo &= 0xffff;
|
||||
var r = (hi << 16) | lo;
|
||||
|
||||
if (r < 0)
|
||||
r += 0x100000000;
|
||||
return r;
|
||||
}
|
||||
|
||||
function sum32(a, b) {
|
||||
var r = (a + b) & 0xffffffff;
|
||||
if (r < 0)
|
||||
r += 0x100000000;
|
||||
return r;
|
||||
}
|
||||
|
||||
function rotl32(w, b) {
|
||||
return (w << b) | (w >>> (32 - b));
|
||||
}
|
||||
|
||||
function hash(data, seed) {
|
||||
data = utils.toArray(data);
|
||||
|
||||
var c1 = 0xcc9e2d51;
|
||||
var c2 = 0x1b873593;
|
||||
var r1 = 15;
|
||||
var r2 = 13;
|
||||
var m = 5;
|
||||
var n = 0xe6546b64;
|
||||
|
||||
var hash = seed;
|
||||
for (var i = 0; i + 4 <= data.length; i += 4) {
|
||||
var w = data[i] |
|
||||
(data[i + 1] << 8) |
|
||||
(data[i + 2] << 16) |
|
||||
(data[i + 3] << 24);
|
||||
|
||||
w = mul32(w, c1);
|
||||
w = rotl32(w, r1);
|
||||
w = mul32(w, c2);
|
||||
|
||||
hash ^= w;
|
||||
hash = rotl32(hash, r2);
|
||||
hash = mul32(hash, m);
|
||||
hash = sum32(hash, n);
|
||||
}
|
||||
|
||||
if (i !== data.length) {
|
||||
var r = 0;
|
||||
for (var j = data.length - 1; j >= i; j--)
|
||||
r = (r << 8) | data[j];
|
||||
|
||||
r = mul32(r, c1);
|
||||
r = rotl32(r, r1);
|
||||
if (r < 0)
|
||||
r += 0x100000000;
|
||||
r = mul32(r, c2);
|
||||
|
||||
hash ^= r;
|
||||
}
|
||||
|
||||
hash ^= data.length;
|
||||
hash ^= hash >>> 16;
|
||||
hash = mul32(hash, 0x85ebca6b);
|
||||
hash ^= hash >>> 13;
|
||||
hash = mul32(hash, 0xc2b2ae35);
|
||||
hash ^= hash >>> 16;
|
||||
|
||||
if (hash < 0)
|
||||
hash += 0x100000000;
|
||||
|
||||
return hash;
|
||||
}
|
||||
Bloom.hash = hash;
|
||||
312
lib/bcoin/peer.js
Normal file
312
lib/bcoin/peer.js
Normal file
@ -0,0 +1,312 @@
|
||||
var assert = require('assert');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
var constants = bcoin.protocol.constants;
|
||||
|
||||
// Browserify, I'm looking at you
|
||||
try {
|
||||
var NodeBuffer = require('buf' + 'fer').Buffer;
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
function Peer(socket, options) {
|
||||
if (!(this instanceof Peer))
|
||||
return new Peer(socket, options);
|
||||
|
||||
EventEmitter.call(this);
|
||||
this.socket = socket;
|
||||
this.parser = new bcoin.protocol.parser();
|
||||
this.framer = new bcoin.protocol.framer();
|
||||
this.bloom = new bcoin.bloom(8 * 10 * 1024,
|
||||
10,
|
||||
(Math.random() * 0xffffffff) | 0),
|
||||
this.version = null;
|
||||
this.destroyed = false;
|
||||
|
||||
this.options = options || {};
|
||||
this._broadcast = {
|
||||
timout: this.options.broadcastTimeout || 30000,
|
||||
map: {}
|
||||
};
|
||||
|
||||
this._request = {
|
||||
timeout: this.options.requestTimeout || 30000,
|
||||
queue: []
|
||||
};
|
||||
|
||||
this._ping = {
|
||||
timer: null,
|
||||
interval: this.options.pingInterval || 5000
|
||||
};
|
||||
|
||||
this._init();
|
||||
}
|
||||
util.inherits(Peer, EventEmitter);
|
||||
module.exports = Peer;
|
||||
|
||||
Peer.prototype._init = function init() {
|
||||
var self = this;
|
||||
this.socket.once('error', function(err) {
|
||||
self._error(err);
|
||||
});
|
||||
this.socket.once('close', function() {
|
||||
self._error('socket hangup');
|
||||
});
|
||||
this.socket.on('data', function(chunk) {
|
||||
self.parser.feed(chunk);
|
||||
});
|
||||
this.parser.on('packet', function(packet) {
|
||||
self._onPacket(packet);
|
||||
});
|
||||
this.parser.on('error', function(err) {
|
||||
self._error(err);
|
||||
});
|
||||
|
||||
this._ping.timer = setInterval(function() {
|
||||
self._write(self.framer.ping([
|
||||
0xde, 0xad, 0xbe, 0xef,
|
||||
0xde, 0xad, 0xbe, 0xef
|
||||
]));
|
||||
}, this._ping.interval);
|
||||
|
||||
// Send hello
|
||||
this._write(this.framer.version());
|
||||
this._req('verack', function(err, payload) {
|
||||
if (err)
|
||||
return self._error(err);
|
||||
self.emit('ack');
|
||||
});
|
||||
};
|
||||
|
||||
Peer.prototype.broadcast = function broadcast(items) {
|
||||
if (!Array.isArray(items))
|
||||
items = [ items ];
|
||||
|
||||
var self = this;
|
||||
items.forEach(function(item) {
|
||||
var key = item.hash('hex');
|
||||
var old = this._broadcast.map[key];
|
||||
if (old)
|
||||
clearTimeout(old.timer);
|
||||
|
||||
// Auto-cleanup broadcast map after timeout
|
||||
var entry = {
|
||||
timeout: setTimeout(function() {
|
||||
delete self._broadcast.map[key];
|
||||
}, this._broadcast.timout),
|
||||
value: item
|
||||
};
|
||||
|
||||
this._broadcast.map[key] = entry;
|
||||
}, this);
|
||||
|
||||
this._write(this.framer.inv(items));
|
||||
};
|
||||
|
||||
Peer.prototype.watch = function watch(id) {
|
||||
this.bloom.add(id);
|
||||
this._write(this.framer.filterLoad(this.bloom, 'pubkeyOnly'));
|
||||
};
|
||||
|
||||
Peer.prototype.loadBlocks = function loadBlocks() {
|
||||
if (this.loadingBlocks)
|
||||
return;
|
||||
this.loadingBlocks = true;
|
||||
this._write(this.framer.getBlocks([ constants.genesis ]));
|
||||
};
|
||||
|
||||
Peer.prototype.destroy = function destroy() {
|
||||
if (this.destroyed)
|
||||
return;
|
||||
this.destroyed = true;
|
||||
this.socket.destroy();
|
||||
this.socket = null;
|
||||
|
||||
// Clean-up timeouts
|
||||
Object.keys(this._broadcast.map).forEach(function(key) {
|
||||
clearTimeout(this._broadcast.map[key].timer);
|
||||
}, this);
|
||||
|
||||
clearInterval(this._ping.timer);
|
||||
this._ping.timer = null;
|
||||
};
|
||||
|
||||
// Private APIs
|
||||
|
||||
Peer.prototype._write = function write(chunk) {
|
||||
if (NodeBuffer)
|
||||
this.socket.write(new NodeBuffer(chunk));
|
||||
else
|
||||
this.socket.write(chunk);
|
||||
};
|
||||
|
||||
Peer.prototype._error = function error(err) {
|
||||
if (this.destroyed)
|
||||
return;
|
||||
this.destroy();
|
||||
this.emit('error', typeof err === 'string' ? new Error(err) : err);
|
||||
};
|
||||
|
||||
Peer.prototype._req = function _req(cmd, cb) {
|
||||
var self = this;
|
||||
var entry = {
|
||||
cmd: cmd,
|
||||
cb: cb,
|
||||
ontimeout: function() {
|
||||
var i = self._request.queue.indexOf(entry);
|
||||
if (i !== -1)
|
||||
self.request.queue.splice(i, 1);
|
||||
cb(new Error('Timed out'), null);
|
||||
},
|
||||
timer: null
|
||||
};
|
||||
entry.timer = setTimeout(entry.ontimeout, this._request.timeout)
|
||||
this._request.queue.push(entry);
|
||||
};
|
||||
|
||||
Peer.prototype._res = function _res(cmd, payload) {
|
||||
var entry = this._request.queue[0];
|
||||
if (!entry || entry.cmd && entry.cmd !== cmd)
|
||||
return;
|
||||
|
||||
var res = entry.cb(null, payload, cmd);
|
||||
|
||||
// If callback returns false - it hasn't finished processing responses
|
||||
if (res === false) {
|
||||
assert(!entry.cmd);
|
||||
|
||||
// Restart timer
|
||||
entry.timer = setTimeout(entry.ontimeout, this._request.timeout)
|
||||
} else {
|
||||
this._request.queue.shift();
|
||||
clearTimeout(entry.timer);
|
||||
entry.timer = null;
|
||||
}
|
||||
};
|
||||
|
||||
Peer.prototype._getData = function _getData(items, cb) {
|
||||
var map = {};
|
||||
var waiting = items.length;
|
||||
items.forEach(function(item) {
|
||||
map[utils.toHex(item.hash)] = {
|
||||
item: item,
|
||||
once: false,
|
||||
result: null
|
||||
};
|
||||
});
|
||||
|
||||
var self = this;
|
||||
|
||||
function markEntry(hash, result) {
|
||||
var entry = map[utils.toHex(hash)];
|
||||
if (!entry || entry.once) {
|
||||
done(new Error('Invalid notfound entry hash'));
|
||||
return false;
|
||||
}
|
||||
|
||||
entry.once = true;
|
||||
entry.result = result;
|
||||
waiting--;
|
||||
return true;
|
||||
}
|
||||
|
||||
this._write(this.framer.getData(items));
|
||||
// Process all incoming data, until all data is returned
|
||||
this._req(null, function(err, payload, cmd) {
|
||||
var ok = true;
|
||||
if (cmd === 'notfound') {
|
||||
ok = payload.every(function(item) {
|
||||
return markEntry(item.hash, null);
|
||||
});
|
||||
} else if (cmd === 'tx') {
|
||||
var tx = bcoin.tx(payload);
|
||||
ok = markEntry(tx.hash(), b);
|
||||
} else if (cmd === 'merkleblock') {
|
||||
var b = bcoin.block(payload);
|
||||
ok = markEntry(b.hash(), b);
|
||||
} else if (cmd === 'block') {
|
||||
var b = bcoin.block(payload);
|
||||
ok = markEntry(b.hash(), b);
|
||||
} else {
|
||||
done(new Error('Unknown packet in reply to getdata: ' + cmd));
|
||||
return;
|
||||
}
|
||||
if (!ok)
|
||||
return;
|
||||
|
||||
if (waiting === 0)
|
||||
done();
|
||||
else
|
||||
return false;
|
||||
});
|
||||
|
||||
function done(err) {
|
||||
if (err)
|
||||
return cb(err);
|
||||
|
||||
cb(null, items.map(function(item) {
|
||||
return map[utils.toHex(item.hash)].result;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
Peer.prototype._onPacket = function onPacket(packet) {
|
||||
var cmd = packet.cmd;
|
||||
var payload = packet.payload;
|
||||
|
||||
if (cmd === 'version')
|
||||
return this._handleVersion(payload);
|
||||
else if (cmd === 'inv')
|
||||
return this._handleInv(payload);
|
||||
else if (cmd === 'getdata')
|
||||
return this._handleGetData(payload);
|
||||
else
|
||||
return this._res(cmd, payload);
|
||||
};
|
||||
|
||||
Peer.prototype._handleVersion = function handleVersion(payload) {
|
||||
if (payload.v < constants.minVersion)
|
||||
return this._error('peer doesn\'t support required protocol version');
|
||||
|
||||
// ACK
|
||||
this._write(this.framer.verack());
|
||||
this.version = payload;
|
||||
};
|
||||
|
||||
Peer.prototype._handleGetData = function handleGetData(items) {
|
||||
items.forEach(function(item) {
|
||||
// Filter out not broadcasted things
|
||||
var hash = utils.toHex(item.hash);
|
||||
if (!this._broadcast.map[hash])
|
||||
return;
|
||||
|
||||
var entry = this._broadcast.map[hash].value;
|
||||
this._write(entry.render(this.framer));
|
||||
}, this);
|
||||
};
|
||||
|
||||
Peer.prototype._handleInv = function handleInv(items) {
|
||||
// Always request what advertised
|
||||
var req = items.filter(function(item) {
|
||||
return item.type === 'tx' || item.type === 'block';
|
||||
}).map(function(item) {
|
||||
if (item.type === 'tx')
|
||||
return item;
|
||||
if (item.type === 'block')
|
||||
return { type: 'filtered', hash: item.hash };
|
||||
});
|
||||
|
||||
var self = this;
|
||||
this._getData(req, function(err, data) {
|
||||
if (err)
|
||||
return self._error(err);
|
||||
console.log(data.join(', '));
|
||||
});
|
||||
};
|
||||
|
||||
Peer.prototype._handleMerkleBlock = function handleMerkleBlock(block) {
|
||||
console.log(utils.toHex(block.prevBlock));
|
||||
};
|
||||
@ -1,8 +1,33 @@
|
||||
var bcoin = require('../../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
exports.minVersion = 70001;
|
||||
exports.version = 70002;
|
||||
exports.magic = 0xd9b4bef9;
|
||||
exports.genesis = utils.toArray(
|
||||
'000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 'hex');
|
||||
|
||||
// version - services field
|
||||
exports.services = {
|
||||
network: 1
|
||||
};
|
||||
|
||||
exports.inv = {
|
||||
error: 0,
|
||||
tx: 1,
|
||||
block: 2,
|
||||
filtered: 3
|
||||
};
|
||||
|
||||
exports.invByVal = {
|
||||
0: 'error',
|
||||
1: 'tx',
|
||||
2: 'block',
|
||||
3: 'filtered'
|
||||
};
|
||||
|
||||
exports.filterFlags = {
|
||||
none: 0,
|
||||
all: 1,
|
||||
pubkeyOnly: 2
|
||||
};
|
||||
|
||||
@ -37,7 +37,7 @@ Framer.prototype.header = function header(cmd, payload) {
|
||||
};
|
||||
|
||||
Framer.prototype.packet = function packet(cmd, payload) {
|
||||
var h = this.header('version', payload);
|
||||
var h = this.header(cmd, payload);
|
||||
return h.concat(payload);
|
||||
};
|
||||
|
||||
@ -89,3 +89,114 @@ Framer.prototype.version = function version(packet) {
|
||||
|
||||
return this.packet('version', p);
|
||||
};
|
||||
|
||||
Framer.prototype.verack = function verack() {
|
||||
return this.packet('verack', []);
|
||||
};
|
||||
|
||||
function varint(arr, value, off) {
|
||||
if (!off)
|
||||
off = 0;
|
||||
if (value < 0xfd) {
|
||||
arr[off] = value;
|
||||
return 1;
|
||||
} else if (value <= 0xffff) {
|
||||
arr[off] = 0xfd;
|
||||
arr[off + 1] = value & 0xff;
|
||||
arr[off + 2] = value >>> 8;
|
||||
return 3;
|
||||
} else if (value <= 0xffffffff) {
|
||||
arr[off] = 0xfd;
|
||||
arr[off + 3] = value & 0xff;
|
||||
arr[off + 4] = (value >>> 8) & 0xff;
|
||||
arr[off + 5] = (value >>> 16) & 0xff;
|
||||
arr[off + 6] = value >>> 24;
|
||||
return 5;
|
||||
} else {
|
||||
throw new Error('64bit varint not supported yet');
|
||||
}
|
||||
}
|
||||
|
||||
Framer.prototype._inv = function _inv(command, items) {
|
||||
var res = [];
|
||||
var off = varint(res, items.length, 0);
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
// Type
|
||||
off += writeU32(res, constants.inv[items[i].type], off);
|
||||
|
||||
// Hash
|
||||
var hash = items[i].hash;
|
||||
assert.equal(hash.length, 32);
|
||||
res = res.concat(hash);
|
||||
|
||||
off += hash.length;
|
||||
}
|
||||
|
||||
return this.packet(command, res);
|
||||
};
|
||||
|
||||
Framer.prototype.inv = function inv(items) {
|
||||
return this._inv('inv', items);
|
||||
};
|
||||
|
||||
Framer.prototype.getData = function getData(items) {
|
||||
return this._inv('getdata', items);
|
||||
};
|
||||
|
||||
Framer.prototype.notFound = function notFound(items) {
|
||||
return this._inv('notfound', items);
|
||||
};
|
||||
|
||||
Framer.prototype.ping = function ping(nonce) {
|
||||
return this.packet('ping', nonce);
|
||||
};
|
||||
|
||||
Framer.prototype.pong = function pong(nonce) {
|
||||
return this.packet('pong', nonce.slice(0, 8));
|
||||
};
|
||||
|
||||
Framer.prototype.filterLoad = function filterLoad(bloom, update) {
|
||||
var filter = bloom.toArray();
|
||||
var before = [];
|
||||
varint(before, filter.length, 0)
|
||||
|
||||
var after = new Array(9);
|
||||
|
||||
// Number of hash functions
|
||||
writeU32(after, bloom.n, 0);
|
||||
|
||||
// nTweak
|
||||
writeU32(after, bloom.tweak, 4);
|
||||
|
||||
// nFlags
|
||||
after[8] = constants.filterFlags[update];
|
||||
|
||||
var r = this.packet('filterload', before.concat(filter, after));
|
||||
return r;
|
||||
};
|
||||
|
||||
Framer.prototype.filterClear = function filterClear() {
|
||||
return this.packet('filterclear', []);
|
||||
};
|
||||
|
||||
Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
|
||||
var p = [];
|
||||
writeU32(p, constants.version, 0);
|
||||
var off = 4 + varint(p, hashes.length, 4);
|
||||
p.length = off + 32 * (hashes.length + 1);
|
||||
|
||||
for (var i = 0; i < hashes.length; i++) {
|
||||
var len = utils.copy(hashes[i], p, off);
|
||||
for (; len < 32; len++)
|
||||
p[off + len] = 0;
|
||||
off += len;
|
||||
}
|
||||
|
||||
var len = stop ? utils.copy(stop, p, off) : 0;
|
||||
for (; len < 32; len++)
|
||||
p[off + len] = 0;
|
||||
assert.equal(off + len, p.length);
|
||||
|
||||
return this.packet('getblocks', p);
|
||||
};
|
||||
|
||||
@ -23,11 +23,9 @@ function Parser() {
|
||||
util.inherits(Parser, EventEmitter);
|
||||
module.exports = Parser;
|
||||
|
||||
Parser.prototype.execute = function(data) {
|
||||
if (data) {
|
||||
this.pendingTotal += data.length;
|
||||
this.pending.push(data);
|
||||
}
|
||||
Parser.prototype.feed = function feed(data) {
|
||||
this.pendingTotal += data.length;
|
||||
this.pending.push(data);
|
||||
while (this.pendingTotal >= this.waiting) {
|
||||
// Concat chunks
|
||||
var chunk = new Array(this.waiting);
|
||||
@ -88,6 +86,10 @@ Parser.prototype.parseHeader = function parseHeader(h) {
|
||||
Parser.prototype.parsePayload = function parsePayload(cmd, p) {
|
||||
if (cmd === 'version')
|
||||
return this.parseVersion(p);
|
||||
else if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound')
|
||||
return this.parseInvList(p);
|
||||
else if (cmd === 'merkleblock')
|
||||
return this.parseMerkleBlock(p);
|
||||
else
|
||||
return p;
|
||||
};
|
||||
@ -97,9 +99,6 @@ Parser.prototype.parseVersion = function parseVersion(p) {
|
||||
return this.emit('error', new Error('version packet is too small'));
|
||||
|
||||
var v = readU32(p, 0);
|
||||
if (v < constants.minVersion)
|
||||
return this.emit('error', new Error('version number is too small'));
|
||||
|
||||
var services = readU64(p, 4);
|
||||
|
||||
// Timestamp
|
||||
@ -123,3 +122,60 @@ Parser.prototype.parseVersion = function parseVersion(p) {
|
||||
relay: relay
|
||||
};
|
||||
};
|
||||
|
||||
function readIntv(p, off) {
|
||||
if (!off)
|
||||
off = 0;
|
||||
|
||||
var r, bytes;
|
||||
if (p[off] < 0xfd) {
|
||||
r = p[off];
|
||||
bytes = 1;
|
||||
} else if (p[off] === 0xfd) {
|
||||
r = p[off + 1] | (p[off + 2] << 8);
|
||||
bytes = 3;
|
||||
} else if (p[off] === 0xfe) {
|
||||
r = readU32(p, off + 1);
|
||||
bytes = 5;
|
||||
} else {
|
||||
r = 0;
|
||||
bytes = 9;
|
||||
}
|
||||
|
||||
return { off: bytes, r: r };
|
||||
}
|
||||
|
||||
Parser.prototype.parseInvList = function parseInvList(p) {
|
||||
var count = readIntv(p, 0);
|
||||
p = p.slice(count.off);
|
||||
count = count.r;
|
||||
if (p.length < count * 36)
|
||||
return this.emit('error', new Error('Invalid getdata size'));
|
||||
|
||||
var items = [];
|
||||
for (var i = 0, off = 0; i < count; i++, off += 36) {
|
||||
items.push({
|
||||
type: constants.invByVal[readU32(p, off)],
|
||||
hash: p.slice(off + 4, off + 36)
|
||||
});
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
Parser.prototype.parseMerkleBlock = function parsMerkleBlock(p) {
|
||||
if (p.length < 84)
|
||||
return this.emit('error', new Error('Invalid merkleblock size'));
|
||||
|
||||
return {
|
||||
version: readU32(p, 0),
|
||||
prevBlock: p.slice(4, 36),
|
||||
merkleRoot: p.slice(36, 68),
|
||||
ts: readU32(p, 68),
|
||||
bits: readU32(p, 72),
|
||||
nonce: readU32(p, 76),
|
||||
totalTx: readU32(p, 80)
|
||||
|
||||
// hashes:
|
||||
// flags:
|
||||
};
|
||||
};
|
||||
|
||||
28
lib/bcoin/tx.js
Normal file
28
lib/bcoin/tx.js
Normal file
@ -0,0 +1,28 @@
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
function TX(data) {
|
||||
if (!(this instanceof TX))
|
||||
return new TX(data);
|
||||
this.type = 'tx';
|
||||
|
||||
this._hash = null;
|
||||
this._raw = data || null;
|
||||
}
|
||||
module.exports = TX;
|
||||
|
||||
TX.prototype.hash = function hash(enc) {
|
||||
if (!this._hash) {
|
||||
// First, obtain the raw TX data
|
||||
this.render();
|
||||
|
||||
// Hash it
|
||||
this._hash = utils.dsha256(this._raw);
|
||||
}
|
||||
return enc === 'hex' ? utils.toHex(this._hash) : this._hash;
|
||||
};
|
||||
|
||||
TX.prototype.render = function render(framer) {
|
||||
console.log('here');
|
||||
return [];
|
||||
};
|
||||
@ -4,6 +4,37 @@ var assert = require('assert');
|
||||
var bn = require('bn.js');
|
||||
var hash = require('hash.js');
|
||||
|
||||
function toArray(msg, enc) {
|
||||
if (Array.isArray(msg))
|
||||
return msg.slice();
|
||||
if (!msg)
|
||||
return [];
|
||||
var res = [];
|
||||
if (typeof msg === 'string') {
|
||||
if (!enc) {
|
||||
for (var i = 0; i < msg.length; i++) {
|
||||
var c = msg.charCodeAt(i);
|
||||
var hi = c >> 8;
|
||||
var lo = c & 0xff;
|
||||
if (hi)
|
||||
res.push(hi, lo);
|
||||
else
|
||||
res.push(lo);
|
||||
}
|
||||
} else if (enc === 'hex') {
|
||||
msg = msg.replace(/[^a-z0-9]+/ig, '');
|
||||
if (msg.length % 2 != 0)
|
||||
msg = '0' + msg;
|
||||
for (var i = 0; i < msg.length; i += 2)
|
||||
res.push(parseInt(msg[i] + msg[i + 1], 16));
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < msg.length; i++)
|
||||
res[i] = msg[i] | 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
utils.toArray = toArray;
|
||||
|
||||
var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' +
|
||||
'abcdefghijkmnopqrstuvwxyz';
|
||||
@ -82,13 +113,17 @@ utils.ripesha = function ripesha(data, enc) {
|
||||
};
|
||||
|
||||
utils.checksum = function checksum(data, enc) {
|
||||
return utils.sha256(utils.sha256(data, enc)).slice(0, 4);
|
||||
return utils.dsha256(data, enc).slice(0, 4);
|
||||
};
|
||||
|
||||
utils.sha256 = function sha256(data, enc) {
|
||||
return hash.sha256().update(data, enc).digest();
|
||||
};
|
||||
|
||||
utils.dsha256 = function dsha256(data, enc) {
|
||||
return utils.sha256(utils.sha256(data, enc));
|
||||
};
|
||||
|
||||
utils.readU32 = function readU32(arr, off) {
|
||||
if (!off)
|
||||
off = 0;
|
||||
@ -114,6 +149,7 @@ utils.writeU32 = function writeU32(dst, num, off) {
|
||||
dst[off + 1] = (num >>> 8) & 0xff;
|
||||
dst[off + 2] = (num >>> 16) & 0xff;
|
||||
dst[off + 3] = (num >>> 24) & 0xff;
|
||||
return 4;
|
||||
};
|
||||
|
||||
utils.writeAscii = function writeAscii(dst, str, off) {
|
||||
@ -137,3 +173,18 @@ utils.stringify = function stringify(arr) {
|
||||
res += String.fromCharCode(arr[i]);
|
||||
return res;
|
||||
};
|
||||
|
||||
function zero2(word) {
|
||||
if (word.length === 1)
|
||||
return '0' + word;
|
||||
else
|
||||
return word;
|
||||
}
|
||||
|
||||
function toHex(msg) {
|
||||
var res = '';
|
||||
for (var i = 0; i < msg.length; i++)
|
||||
res += zero2(msg[i].toString(16));
|
||||
return res;
|
||||
}
|
||||
utils.toHex = toHex;
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/indutny/bcoin",
|
||||
"dependencies": {
|
||||
"bn.js": "^0.1.7",
|
||||
"elliptic": "^0.6.0",
|
||||
"bn.js": "^0.2.0",
|
||||
"elliptic": "^0.7.0",
|
||||
"hash.js": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
29
test/bloom-test.js
Normal file
29
test/bloom-test.js
Normal file
@ -0,0 +1,29 @@
|
||||
var assert = require('assert');
|
||||
var bcoin = require('../');
|
||||
|
||||
describe('Bloom', function() {
|
||||
it('should do proper murmur3', function() {
|
||||
var h = bcoin.bloom.hash;
|
||||
|
||||
assert.equal(h('', 0), 0);
|
||||
assert.equal(h('', 0xfba4c795), 0x6a396f08);
|
||||
assert.equal(h('00', 0xfba4c795), 0x2a101837);
|
||||
assert.equal(h('hello world', 0), 0x5e928f0f);
|
||||
});
|
||||
|
||||
it('should test and add stuff', function() {
|
||||
var b = bcoin.bloom(512, 10, 156);
|
||||
|
||||
b.add('hello');
|
||||
assert(b.test('hello'));
|
||||
assert(!b.test('hello!'));
|
||||
assert(!b.test('ping'));
|
||||
|
||||
b.add('hello!');
|
||||
assert(b.test('hello!'));
|
||||
assert(!b.test('ping'));
|
||||
|
||||
b.add('ping');
|
||||
assert(b.test('ping'));
|
||||
});
|
||||
});
|
||||
@ -9,15 +9,23 @@ describe('Protocol', function() {
|
||||
framer = bcoin.protocol.framer();
|
||||
});
|
||||
|
||||
it('should encode/decode version packet', function(cb) {
|
||||
var ver = framer.version();
|
||||
parser.once('packet', function(packet) {
|
||||
assert.equal(packet.cmd, 'version');
|
||||
assert.equal(packet.payload.v, 70002);
|
||||
assert.equal(packet.payload.relay, false);
|
||||
|
||||
cb();
|
||||
function packetTest(command, payload, test) {
|
||||
it('should encode/decode ' + command, function(cb) {
|
||||
var ver = framer[command]();
|
||||
parser.once('packet', function(packet) {
|
||||
assert.equal(packet.cmd, command);
|
||||
test(packet.payload);
|
||||
cb();
|
||||
});
|
||||
parser.feed(ver);
|
||||
});
|
||||
parser.execute(ver);
|
||||
}
|
||||
|
||||
packetTest('version', {}, function(payload) {
|
||||
assert.equal(payload.v, 70002);
|
||||
assert.equal(payload.relay, false);
|
||||
});
|
||||
|
||||
packetTest('verack', {}, function(payload) {
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user