This commit is contained in:
Fedor Indutny 2014-05-05 13:46:29 +04:00
parent c5d7d7f5f2
commit 4328d7c3ec
9 changed files with 155 additions and 80 deletions

View File

@ -31,10 +31,7 @@ function Chain(options) {
hashes: preload.hashes.slice(), hashes: preload.hashes.slice(),
ts: preload.ts.slice() ts: preload.ts.slice()
}; };
this.request = { this.request = new utils.RequestCache();
map: {},
count: 0
};
if (this.index.hashes.length === 0) if (this.index.hashes.length === 0)
this.add(new bcoin.block(constants.genesis)); this.add(new bcoin.block(constants.genesis));
@ -122,14 +119,7 @@ Chain.prototype.add = function add(block) {
this._bloomBlock(block); this._bloomBlock(block);
// Fullfill request // Fullfill request
if (this.request.map[hash]) { this.request.fullfill(hash, block);
var req = this.request.map[hash];
delete this.request.map[hash];
this.request.count--;
req.forEach(function(cb) {
cb(block);
});
}
if (!this.orphan.map[hash]) if (!this.orphan.map[hash])
break; break;
@ -199,13 +189,8 @@ Chain.prototype.get = function get(hash, cb) {
assert(false); assert(false);
} }
if (this.request.map[hash]) { if (this.request.add(hash, cb))
this.request.map[hash].push(cb);
} else {
this.request.map[hash] = [ cb ];
this.request.count++;
this.emit('missing', hash); this.emit('missing', hash);
}
}; };
Chain.prototype.isFull = function isFull() { Chain.prototype.isFull = function isFull() {

View File

@ -93,7 +93,7 @@ Peer.prototype.broadcast = function broadcast(items) {
items = [ items ]; items = [ items ];
var self = this; var self = this;
items.forEach(function(item) { var result = items.map(function(item) {
var key = item.hash('hex'); var key = item.hash('hex');
var old = this._broadcast.map[key]; var old = this._broadcast.map[key];
if (old) if (old)
@ -101,21 +101,32 @@ Peer.prototype.broadcast = function broadcast(items) {
// Auto-cleanup broadcast map after timeout // Auto-cleanup broadcast map after timeout
var entry = { var entry = {
e: new EventEmitter(),
timeout: setTimeout(function() { timeout: setTimeout(function() {
entry.e.emit('timeout');
delete self._broadcast.map[key]; delete self._broadcast.map[key];
}, this._broadcast.timout), }, this._broadcast.timout),
value: item value: item
}; };
this._broadcast.map[key] = entry; this._broadcast.map[key] = entry;
return entry.e;
}, this); }, this);
this._write(this.framer.inv(items)); this._write(this.framer.inv(items.map(function(item) {
return {
type: item.type,
hash: item.hash()
};
})));
return result;
}; };
Peer.prototype.updateWatch = function updateWatch() { Peer.prototype.updateWatch = function updateWatch() {
if (this.ack) if (this.ack)
this._write(this.framer.filterLoad(this.bloom, 'pubkeyOnly')); this._write(this.framer.filterLoad(this.bloom, 'none'));
}; };
Peer.prototype.destroy = function destroy() { Peer.prototype.destroy = function destroy() {
@ -224,6 +235,8 @@ Peer.prototype._onPacket = function onPacket(packet) {
if (this._res(cmd, payload)) { if (this._res(cmd, payload)) {
return; return;
} else { } else {
if (cmd !== 'merkleblock')
console.log(cmd);
this.emit(cmd, payload); this.emit(cmd, payload);
} }
}; };
@ -244,8 +257,9 @@ Peer.prototype._handleGetData = function handleGetData(items) {
if (!this._broadcast.map[hash]) if (!this._broadcast.map[hash])
return; return;
var entry = this._broadcast.map[hash].value; var entry = this._broadcast.map[hash];
this._write(entry.render(this.framer)); this._write(entry.value.render());
entry.e.emit('request');
}, this); }, this);
}; };

View File

@ -13,8 +13,9 @@ function Pool(options) {
EventEmitter.call(this); EventEmitter.call(this);
this.options = options || {}; this.options = options || {};
this.size = options.size || 8; this.size = options.size || 16;
this.parallel = options.parallel || 2000; this.parallel = options.parallel || 2000;
this.redundancy = 2;
this.load = { this.load = {
timeout: options.loadTimeout || 10000, timeout: options.loadTimeout || 10000,
window: options.loadWindow || 250, window: options.loadWindow || 250,
@ -27,7 +28,7 @@ function Pool(options) {
this.requestTimeout = options.requestTimeout || 10000; this.requestTimeout = options.requestTimeout || 10000;
this.chain = new bcoin.chain(); this.chain = new bcoin.chain();
this.watchMap = {}; this.watchMap = {};
this.bloom = new bcoin.bloom(8 * 10 * 1024, this.bloom = new bcoin.bloom(8 * 1024,
10, 10,
(Math.random() * 0xffffffff) | 0), (Math.random() * 0xffffffff) | 0),
this.peers = { this.peers = {
@ -54,12 +55,13 @@ function Pool(options) {
minDepth: options.minValidateDepth || 0, minDepth: options.minValidateDepth || 0,
// getTx map // getTx map
map: {}, map: {}
};
// Validation cache // Currently broadcasted TXs
reqs: new bcoin.utils.RequestCache(), this.tx = {
cache: [], list: [],
cacheSize: 1000 timeout: options.txTimeout || 60000
}; };
this.createConnection = options.createConnection; this.createConnection = options.createConnection;
@ -211,6 +213,12 @@ Pool.prototype._addPeer = function _addPeer() {
} }
peer.updateWatch(); peer.updateWatch();
self.tx.list.forEach(function(entry) {
peer.broadcast(entry.tx)[0].once('request', function() {
entry.e.emit('ack');
});
});
}); });
peer.on('merkleblock', function(block) { peer.on('merkleblock', function(block) {
@ -287,6 +295,12 @@ Pool.prototype.unwatch = function unwatch(id) {
}; };
Pool.prototype.search = function search(id, range) { Pool.prototype.search = function search(id, range) {
// Optional id argument
if (typeof id === 'object' && !Array.isArray(id) ||
typeof id === 'number') {
range = id;
id = null;
}
if (typeof id === 'string') if (typeof id === 'string')
id = utils.toArray(id, 'hex'); id = utils.toArray(id, 'hex');
@ -307,7 +321,8 @@ Pool.prototype.search = function search(id, range) {
var hashes = this.chain.hashesInRange(range.start, range.end); var hashes = this.chain.hashesInRange(range.start, range.end);
var waiting = hashes.length; var waiting = hashes.length;
this.watch(id); if (id)
this.watch(id);
hashes.slice().reverse().forEach(function(hash) { hashes.slice().reverse().forEach(function(hash) {
// Get the block that is in index // Get the block that is in index
@ -318,7 +333,8 @@ Pool.prototype.search = function search(id, range) {
waiting--; waiting--;
e.emit('progress', hashes.length - waiting, hashes.length); e.emit('progress', hashes.length - waiting, hashes.length);
if (waiting === 0) { if (waiting === 0) {
self.unwatch(id); if (id)
self.unwatch(id);
e.emit('end'); e.emit('end');
} }
}); });
@ -407,14 +423,18 @@ Pool.prototype._doRequests = function _doRequests() {
if (above && below && this.load.hiReached) if (above && below && this.load.hiReached)
this._load(); this._load();
// Split list between nodes // Split list between peers
var red = this.redundancy;
var count = this.peers.block.length; var count = this.peers.block.length;
var split = Math.ceil(items.length / count); var split = Math.ceil(items.length * red / count);
for (var i = 0, off = 0; i < count; i++, off += split) { for (var i = 0, off = 0; i < count; i += red, off += split) {
var peer = this.peers.block[i]; var req = items.slice(off, off + split).map(function(item) {
peer.getData(items.slice(off, off + split).map(function(item) { return item.start(this.peers.block[i]);
return item.start(peer); }, this);
}));
for (var j = 0; j < red && i + j < count; j++) {
this.peers.block[i + j].getData(req);
}
} }
}; };
@ -478,21 +498,28 @@ Pool.prototype.getTx = function getTx(hash, range, cb) {
} }
}; };
Pool.prototype._addValidateCache = function addValidateCache(tx, result) { Pool.prototype.sendTx = function sendTx(tx) {
this.validate.cache.push({ var e = new EventEmitter();
hash: tx.hash('hex'),
result: result
});
if (this.validate.cache.length > this.validate.cacheSize)
this.validate.cache = this.validate.cache.slice(-this.validate.cacheSize);
};
Pool.prototype._probeValidateCache = function probeValidateCache(tx) { var self = this;
for (var i = 0; i < this.validate.cache.length; i++) { var entry = {
var entry = this.validate.cache[i]; tx: tx,
if (entry.hash === tx.hash('hex')) e: e,
return entry.result; timer: setTimeout(function() {
} var i = self.tx.list.indexOf(entry);
if (i !== -1)
self.tx.list.splice(i, 1);
}, this.tx.timeout)
};
this.tx.list.push(entry);
this.peers.block.forEach(function(peer) {
peer.broadcast(tx)[0].once('request', function() {
e.emit('ack');
});
});
return e;
}; };
function LoadRequest(pool, type, hash, cb) { function LoadRequest(pool, type, hash, cb) {

View File

@ -22,12 +22,12 @@ function TX(data) {
if (data.inputs) { if (data.inputs) {
data.inputs.forEach(function(input) { data.inputs.forEach(function(input) {
this.input(input); this.input(input, null, this === data);
}, this); }, this);
} }
if (data.outputs) { if (data.outputs) {
data.outputs.forEach(function(out) { data.outputs.forEach(function(out) {
this.out(out); this.out(out, null, this === data);
}, this); }, this);
} }
} }
@ -38,21 +38,15 @@ TX.prototype.clone = function clone() {
}; };
TX.prototype.hash = function hash(enc) { TX.prototype.hash = function hash(enc) {
if (!this._hash) { var h = utils.dsha256(this.render());
// First, obtain the raw TX data return enc === 'hex' ? utils.toHex(h) : h;
this.render();
// Hash it
this._hash = utils.dsha256(this._raw);
}
return enc === 'hex' ? utils.toHex(this._hash) : this._hash;
}; };
TX.prototype.render = function render() { TX.prototype.render = function render() {
return bcoin.protocol.framer.tx(this); return bcoin.protocol.framer.tx(this);
}; };
TX.prototype.input = function input(i, index) { TX.prototype.input = function input(i, index, clone) {
if (i instanceof TX) if (i instanceof TX)
i = { tx: i, index: index }; i = { tx: i, index: index };
@ -70,14 +64,22 @@ TX.prototype.input = function input(i, index) {
hash: hash, hash: hash,
index: i.out ? i.out.index : i.index, index: i.out ? i.out.index : i.index,
}, },
script: bcoin.script.decode(i.script), script: clone ? i.script.slice() : bcoin.script.decode(i.script),
seq: i.seq === undefined ? 0xffffffff : i.seq seq: i.seq === undefined ? 0xffffffff : i.seq
}); });
return this; return this;
}; };
TX.prototype.out = function out(output, value) { TX.prototype.inputTx = function inputTx(i, tx) {
if (!(tx instanceof TX))
tx = new TX(tx);
assert(i <= this.inputs.length);
this.inputs[i].out.tx = tx;
};
TX.prototype.out = function out(output, value, clone) {
if (typeof output === 'string') { if (typeof output === 'string') {
output = { output = {
address: output, address: output,
@ -85,7 +87,8 @@ TX.prototype.out = function out(output, value) {
}; };
} }
var script = bcoin.script.decode(output.script); var script = clone ? output.script.slice() :
bcoin.script.decode(output.script);
// Default script if given address // Default script if given address
if (output.address) { if (output.address) {

View File

@ -290,6 +290,7 @@ utils.nextTick = function nextTick(fn) {
function RequestCache() { function RequestCache() {
this.map = {}; this.map = {};
this.count = 0;
}; };
utils.RequestCache = RequestCache; utils.RequestCache = RequestCache;
@ -299,6 +300,7 @@ RequestCache.prototype.add = function add(id, cb) {
return false; return false;
} else { } else {
this.map[id] = [ cb ]; this.map[id] = [ cb ];
this.count++;
return true; return true;
} }
}; };
@ -309,6 +311,7 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) {
var cbs = this.map[id]; var cbs = this.map[id];
delete this.map[id]; delete this.map[id];
this.count--;
cbs.forEach(function(cb) { cbs.forEach(function(cb) {
cb(err, data); cb(err, data);
}); });

View File

@ -1,15 +1,45 @@
var assert = require('assert'); var assert = require('assert');
var bcoin = require('../bcoin'); var bcoin = require('../bcoin');
var hash = require('hash.js');
var utils = bcoin.utils; var utils = bcoin.utils;
function Wallet() { function Wallet(options, passphrase) {
if (!(this instanceof Wallet)) if (!(this instanceof Wallet))
return new Wallet(); return new Wallet(options, passphrase);
this.key = bcoin.ecdsa.genKeyPair(); // bcoin.wallet('scope', 'password')
if (typeof options === 'string' && typeof passphrase === 'string') {
options = {
scope: options,
passphrase: passphrase
};
}
if (!options)
options = {};
this.key = null;
if (options.passphrase) {
this.key = bcoin.ecdsa.genKeyPair({
pers: options.scope,
entropy: hash.sha256().update(options.passphrase).digest()
});
} else if (options.priv) {
this.key = bcoin.ecdsa.keyPair(options.priv);
} else {
this.key = bcoin.ecdsa.genKeyPair();
}
} }
module.exports = Wallet; module.exports = Wallet;
Wallet.prototype.getPrivateKey = function getPrivateKey() {
return this.key.getPrivate().toArray();
};
Wallet.prototype.getPublicKey = function getPublicKey() {
return this.key.getPublic('array');
};
Wallet.prototype.getHash = function getHash() { Wallet.prototype.getHash = function getHash() {
var pub = this.key.getPublic('array'); var pub = this.key.getPublic('array');
return utils.ripesha(pub); return utils.ripesha(pub);
@ -52,14 +82,21 @@ Wallet.prototype.validateAddress = function validateAddress(addr) {
Wallet.validateAddress = Wallet.prototype.validateAddress; Wallet.validateAddress = Wallet.prototype.validateAddress;
Wallet.prototype.own = function own(tx) { Wallet.prototype.own = function own(tx) {
return tx.outputs.some(function(output) { var outputs = tx.outputs.filter(function(output) {
return output.script.length === 5 && if (output.script.length < 5)
output.script[0] === 'dup' && return false;
output.script[1] === 'hash160' &&
utils.isEqual(output.script[2], this.getHash()) && var s = output.script.slice(-5);
output.script[3] === 'eqverify' && return s[0] === 'dup' &&
output.script[4] === 'checksig'; s[1] === 'hash160' &&
utils.isEqual(s[2], this.getHash()) &&
s[3] === 'eqverify' &&
s[4] === 'checksig';
}, this); }, this);
if (outputs.length === 0)
return false;
return outputs;
}; };
Wallet.prototype.sign = function sign(tx, type) { Wallet.prototype.sign = function sign(tx, type) {

View File

@ -23,7 +23,7 @@
"dependencies": { "dependencies": {
"async": "^0.8.0", "async": "^0.8.0",
"bn.js": "^0.3.0", "bn.js": "^0.3.0",
"elliptic": "^0.8.0", "elliptic": "^0.9.0",
"hash.js": "^0.2.0" "hash.js": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -48,8 +48,7 @@ describe('TX', function() {
it('should be verifiable', function() { it('should be verifiable', function() {
var tx = bcoin.tx(parser.parseTx(bcoin.utils.toArray(raw, 'hex'))); var tx = bcoin.tx(parser.parseTx(bcoin.utils.toArray(raw, 'hex')));
tx.inputs[0].out.tx = tx.inputTx(0, bcoin.tx(parser.parseTx(bcoin.utils.toArray(inp, 'hex'))));
bcoin.tx(parser.parseTx(bcoin.utils.toArray(inp, 'hex')));
assert(tx.verify()); assert(tx.verify());
}); });

View File

@ -1,4 +1,5 @@
var assert = require('assert'); var assert = require('assert');
var bn = require('bn.js');
var bcoin = require('../'); var bcoin = require('../');
describe('Wallet', function() { describe('Wallet', function() {
@ -25,9 +26,15 @@ describe('Wallet', function() {
outputs: [{ outputs: [{
value: 5460 * 2, value: 5460 * 2,
address: w.getAddress() address: w.getAddress()
}, {
value: 5460 * 2,
address: w.getAddress() + 'x'
}] }]
}); });
assert(w.own(src)); assert(w.own(src));
assert.equal(w.own(src).reduce(function(acc, out) {
return acc.iadd(out.value);
}, new bn(0)).toString(10), 5460 * 2);
var tx = bcoin.tx() var tx = bcoin.tx()
.input(src, 0) .input(src, 0)