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.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||||
bcoin.utils = require('./bcoin/utils');
|
bcoin.utils = require('./bcoin/utils');
|
||||||
|
bcoin.bloom = require('./bcoin/bloom');
|
||||||
bcoin.protocol = require('./bcoin/protocol');
|
bcoin.protocol = require('./bcoin/protocol');
|
||||||
|
bcoin.tx = require('./bcoin/tx');
|
||||||
|
bcoin.block = require('./bcoin/block');
|
||||||
bcoin.wallet = require('./bcoin/wallet');
|
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.minVersion = 70001;
|
||||||
exports.version = 70002;
|
exports.version = 70002;
|
||||||
exports.magic = 0xd9b4bef9;
|
exports.magic = 0xd9b4bef9;
|
||||||
|
exports.genesis = utils.toArray(
|
||||||
|
'000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 'hex');
|
||||||
|
|
||||||
// version - services field
|
// version - services field
|
||||||
exports.services = {
|
exports.services = {
|
||||||
network: 1
|
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) {
|
Framer.prototype.packet = function packet(cmd, payload) {
|
||||||
var h = this.header('version', payload);
|
var h = this.header(cmd, payload);
|
||||||
return h.concat(payload);
|
return h.concat(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,3 +89,114 @@ Framer.prototype.version = function version(packet) {
|
|||||||
|
|
||||||
return this.packet('version', p);
|
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);
|
util.inherits(Parser, EventEmitter);
|
||||||
module.exports = Parser;
|
module.exports = Parser;
|
||||||
|
|
||||||
Parser.prototype.execute = function(data) {
|
Parser.prototype.feed = function feed(data) {
|
||||||
if (data) {
|
this.pendingTotal += data.length;
|
||||||
this.pendingTotal += data.length;
|
this.pending.push(data);
|
||||||
this.pending.push(data);
|
|
||||||
}
|
|
||||||
while (this.pendingTotal >= this.waiting) {
|
while (this.pendingTotal >= this.waiting) {
|
||||||
// Concat chunks
|
// Concat chunks
|
||||||
var chunk = new Array(this.waiting);
|
var chunk = new Array(this.waiting);
|
||||||
@ -88,6 +86,10 @@ Parser.prototype.parseHeader = function parseHeader(h) {
|
|||||||
Parser.prototype.parsePayload = function parsePayload(cmd, p) {
|
Parser.prototype.parsePayload = function parsePayload(cmd, p) {
|
||||||
if (cmd === 'version')
|
if (cmd === 'version')
|
||||||
return this.parseVersion(p);
|
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
|
else
|
||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
@ -97,9 +99,6 @@ Parser.prototype.parseVersion = function parseVersion(p) {
|
|||||||
return this.emit('error', new Error('version packet is too small'));
|
return this.emit('error', new Error('version packet is too small'));
|
||||||
|
|
||||||
var v = readU32(p, 0);
|
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);
|
var services = readU64(p, 4);
|
||||||
|
|
||||||
// Timestamp
|
// Timestamp
|
||||||
@ -123,3 +122,60 @@ Parser.prototype.parseVersion = function parseVersion(p) {
|
|||||||
relay: relay
|
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 bn = require('bn.js');
|
||||||
var hash = require('hash.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' +
|
var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' +
|
||||||
'abcdefghijkmnopqrstuvwxyz';
|
'abcdefghijkmnopqrstuvwxyz';
|
||||||
@ -82,13 +113,17 @@ utils.ripesha = function ripesha(data, enc) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
utils.checksum = function checksum(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) {
|
utils.sha256 = function sha256(data, enc) {
|
||||||
return hash.sha256().update(data, enc).digest();
|
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) {
|
utils.readU32 = function readU32(arr, off) {
|
||||||
if (!off)
|
if (!off)
|
||||||
off = 0;
|
off = 0;
|
||||||
@ -114,6 +149,7 @@ utils.writeU32 = function writeU32(dst, num, off) {
|
|||||||
dst[off + 1] = (num >>> 8) & 0xff;
|
dst[off + 1] = (num >>> 8) & 0xff;
|
||||||
dst[off + 2] = (num >>> 16) & 0xff;
|
dst[off + 2] = (num >>> 16) & 0xff;
|
||||||
dst[off + 3] = (num >>> 24) & 0xff;
|
dst[off + 3] = (num >>> 24) & 0xff;
|
||||||
|
return 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.writeAscii = function writeAscii(dst, str, off) {
|
utils.writeAscii = function writeAscii(dst, str, off) {
|
||||||
@ -137,3 +173,18 @@ utils.stringify = function stringify(arr) {
|
|||||||
res += String.fromCharCode(arr[i]);
|
res += String.fromCharCode(arr[i]);
|
||||||
return res;
|
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",
|
"homepage": "https://github.com/indutny/bcoin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": "^0.1.7",
|
"bn.js": "^0.2.0",
|
||||||
"elliptic": "^0.6.0",
|
"elliptic": "^0.7.0",
|
||||||
"hash.js": "^0.2.0"
|
"hash.js": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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();
|
framer = bcoin.protocol.framer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should encode/decode version packet', function(cb) {
|
function packetTest(command, payload, test) {
|
||||||
var ver = framer.version();
|
it('should encode/decode ' + command, function(cb) {
|
||||||
parser.once('packet', function(packet) {
|
var ver = framer[command]();
|
||||||
assert.equal(packet.cmd, 'version');
|
parser.once('packet', function(packet) {
|
||||||
assert.equal(packet.payload.v, 70002);
|
assert.equal(packet.cmd, command);
|
||||||
assert.equal(packet.payload.relay, false);
|
test(packet.payload);
|
||||||
|
cb();
|
||||||
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