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

View File

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

View File

@ -13,8 +13,9 @@ function Pool(options) {
EventEmitter.call(this);
this.options = options || {};
this.size = options.size || 8;
this.size = options.size || 16;
this.parallel = options.parallel || 2000;
this.redundancy = 2;
this.load = {
timeout: options.loadTimeout || 10000,
window: options.loadWindow || 250,
@ -27,7 +28,7 @@ function Pool(options) {
this.requestTimeout = options.requestTimeout || 10000;
this.chain = new bcoin.chain();
this.watchMap = {};
this.bloom = new bcoin.bloom(8 * 10 * 1024,
this.bloom = new bcoin.bloom(8 * 1024,
10,
(Math.random() * 0xffffffff) | 0),
this.peers = {
@ -54,12 +55,13 @@ function Pool(options) {
minDepth: options.minValidateDepth || 0,
// getTx map
map: {},
map: {}
};
// Validation cache
reqs: new bcoin.utils.RequestCache(),
cache: [],
cacheSize: 1000
// Currently broadcasted TXs
this.tx = {
list: [],
timeout: options.txTimeout || 60000
};
this.createConnection = options.createConnection;
@ -211,6 +213,12 @@ Pool.prototype._addPeer = function _addPeer() {
}
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) {
@ -287,6 +295,12 @@ Pool.prototype.unwatch = function unwatch(id) {
};
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')
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 waiting = hashes.length;
this.watch(id);
if (id)
this.watch(id);
hashes.slice().reverse().forEach(function(hash) {
// Get the block that is in index
@ -318,7 +333,8 @@ Pool.prototype.search = function search(id, range) {
waiting--;
e.emit('progress', hashes.length - waiting, hashes.length);
if (waiting === 0) {
self.unwatch(id);
if (id)
self.unwatch(id);
e.emit('end');
}
});
@ -407,14 +423,18 @@ Pool.prototype._doRequests = function _doRequests() {
if (above && below && this.load.hiReached)
this._load();
// Split list between nodes
// Split list between peers
var red = this.redundancy;
var count = this.peers.block.length;
var split = Math.ceil(items.length / count);
for (var i = 0, off = 0; i < count; i++, off += split) {
var peer = this.peers.block[i];
peer.getData(items.slice(off, off + split).map(function(item) {
return item.start(peer);
}));
var split = Math.ceil(items.length * red / count);
for (var i = 0, off = 0; i < count; i += red, off += split) {
var req = items.slice(off, off + split).map(function(item) {
return item.start(this.peers.block[i]);
}, 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) {
this.validate.cache.push({
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.sendTx = function sendTx(tx) {
var e = new EventEmitter();
Pool.prototype._probeValidateCache = function probeValidateCache(tx) {
for (var i = 0; i < this.validate.cache.length; i++) {
var entry = this.validate.cache[i];
if (entry.hash === tx.hash('hex'))
return entry.result;
}
var self = this;
var entry = {
tx: tx,
e: e,
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) {

View File

@ -22,12 +22,12 @@ function TX(data) {
if (data.inputs) {
data.inputs.forEach(function(input) {
this.input(input);
this.input(input, null, this === data);
}, this);
}
if (data.outputs) {
data.outputs.forEach(function(out) {
this.out(out);
this.out(out, null, this === data);
}, this);
}
}
@ -38,21 +38,15 @@ TX.prototype.clone = function clone() {
};
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;
var h = utils.dsha256(this.render());
return enc === 'hex' ? utils.toHex(h) : h;
};
TX.prototype.render = function render() {
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)
i = { tx: i, index: index };
@ -70,14 +64,22 @@ TX.prototype.input = function input(i, index) {
hash: hash,
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
});
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') {
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
if (output.address) {

View File

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

View File

@ -1,15 +1,45 @@
var assert = require('assert');
var bcoin = require('../bcoin');
var hash = require('hash.js');
var utils = bcoin.utils;
function Wallet() {
function Wallet(options, passphrase) {
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;
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() {
var pub = this.key.getPublic('array');
return utils.ripesha(pub);
@ -52,14 +82,21 @@ Wallet.prototype.validateAddress = function validateAddress(addr) {
Wallet.validateAddress = Wallet.prototype.validateAddress;
Wallet.prototype.own = function own(tx) {
return tx.outputs.some(function(output) {
return output.script.length === 5 &&
output.script[0] === 'dup' &&
output.script[1] === 'hash160' &&
utils.isEqual(output.script[2], this.getHash()) &&
output.script[3] === 'eqverify' &&
output.script[4] === 'checksig';
var outputs = tx.outputs.filter(function(output) {
if (output.script.length < 5)
return false;
var s = output.script.slice(-5);
return s[0] === 'dup' &&
s[1] === 'hash160' &&
utils.isEqual(s[2], this.getHash()) &&
s[3] === 'eqverify' &&
s[4] === 'checksig';
}, this);
if (outputs.length === 0)
return false;
return outputs;
};
Wallet.prototype.sign = function sign(tx, type) {

View File

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

View File

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

View File

@ -1,4 +1,5 @@
var assert = require('assert');
var bn = require('bn.js');
var bcoin = require('../');
describe('Wallet', function() {
@ -25,9 +26,15 @@ describe('Wallet', function() {
outputs: [{
value: 5460 * 2,
address: w.getAddress()
}, {
value: 5460 * 2,
address: w.getAddress() + 'x'
}]
});
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()
.input(src, 0)