refactor pool. improve chains.
This commit is contained in:
parent
484b1f2817
commit
a20cd7edbe
@ -67,6 +67,30 @@ function Chain(options) {
|
|||||||
|
|
||||||
inherits(Chain, EventEmitter);
|
inherits(Chain, EventEmitter);
|
||||||
|
|
||||||
|
Chain.codes = {
|
||||||
|
okay: 0,
|
||||||
|
newOrphan: 1,
|
||||||
|
knownOrphan: 2,
|
||||||
|
forked: 3,
|
||||||
|
invalid: 4,
|
||||||
|
badCheckpoint: 4,
|
||||||
|
unchanged: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.messages = {
|
||||||
|
0: 'Block was added successfully',
|
||||||
|
1: 'Block is a new orphan',
|
||||||
|
2: 'Block is a known orphan',
|
||||||
|
3: 'Block is a greater fork',
|
||||||
|
4: 'Block verification failed',
|
||||||
|
5: 'Block does not match checkpoint',
|
||||||
|
6: 'Chain is unchanged'
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.msg = function msg(code) {
|
||||||
|
return new Error(Chain.messages[code] || 'Unknown');
|
||||||
|
};
|
||||||
|
|
||||||
function compareTs(a, b) {
|
function compareTs(a, b) {
|
||||||
return a -b;
|
return a -b;
|
||||||
}
|
}
|
||||||
@ -151,26 +175,28 @@ Chain.prototype._probeIndex = function _probeIndex(hash, ts) {
|
|||||||
Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
|
Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Already added
|
||||||
if (this._probeIndex(hash, ts))
|
if (this._probeIndex(hash, ts))
|
||||||
return new Error('Already added.');
|
return Chain.codes.unchanged;
|
||||||
|
|
||||||
var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
|
var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
|
||||||
var checkpoint;
|
var checkpoint;
|
||||||
|
|
||||||
// Avoid duplicates
|
// Duplicate height
|
||||||
if (this.index.hashes[pos] === hash
|
if (this.index.hashes[pos] === hash
|
||||||
|| this.index.hashes[pos - 1] === hash
|
|| this.index.hashes[pos - 1] === hash
|
||||||
|| this.index.hashes[pos + 1] === hash) {
|
|| this.index.hashes[pos + 1] === hash) {
|
||||||
return new Error('Duplicate height.');
|
return Chain.codes.unchanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fork at checkpoint
|
||||||
checkpoint = network.checkpoints[height];
|
checkpoint = network.checkpoints[height];
|
||||||
if (checkpoint) {
|
if (checkpoint) {
|
||||||
this.emit('checkpoint', height, hash, checkpoint);
|
this.emit('checkpoint', height, hash, checkpoint);
|
||||||
if (hash !== checkpoint) {
|
if (hash !== checkpoint) {
|
||||||
this.resetLastCheckpoint(height);
|
this.resetLastCheckpoint(height);
|
||||||
this.emit('fork', height, hash, checkpoint);
|
this.emit('fork', height, hash, checkpoint);
|
||||||
return new Error('Forked chain at checkpoint.');
|
return Chain.codes.badCheckpoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,13 +211,15 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
|
|||||||
ts: ts,
|
ts: ts,
|
||||||
height: height
|
height: height
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Chain.codes.okay;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
||||||
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
|
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
|
||||||
|
|
||||||
if (lastHeight < 0)
|
if (lastHeight < 0)
|
||||||
i = 0;
|
lastHeight = 0;
|
||||||
|
|
||||||
this.resetHeight(lastHeight);
|
this.resetHeight(lastHeight);
|
||||||
};
|
};
|
||||||
@ -201,8 +229,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
var index = this.index.heights.indexOf(height);
|
var index = this.index.heights.indexOf(height);
|
||||||
var ahead = this.index.hashes.slice(index + 1);
|
var ahead = this.index.hashes.slice(index + 1);
|
||||||
|
|
||||||
if (index < 0)
|
assert(index >= 0);
|
||||||
throw new Error('Cannot reset to height of ' + height);
|
|
||||||
|
|
||||||
this.block.list.length = 0;
|
this.block.list.length = 0;
|
||||||
this.block.bloom.reset();
|
this.block.bloom.reset();
|
||||||
@ -283,15 +310,14 @@ Chain.prototype.add = function add(block) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = false;
|
|
||||||
var err = null;
|
|
||||||
var initial = block;
|
var initial = block;
|
||||||
|
var code = Chain.codes.unchanged;
|
||||||
var hash, prev, prevProbe, range, hashes;
|
var hash, prev, prevProbe, range, hashes;
|
||||||
|
|
||||||
do {
|
for (;;) {
|
||||||
// No need to revalidate orphans
|
// Only validate the initial block (orphans were already validated)
|
||||||
if (!res && !block.verify()) {
|
if (block === initial && !block.verify()) {
|
||||||
err = new Error('Block verification failed.');
|
code = Chain.codes.invalid;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +326,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
|
|
||||||
// If the block is already known to be an orphan
|
// If the block is already known to be an orphan
|
||||||
if (this.orphan.map[prev]) {
|
if (this.orphan.map[prev]) {
|
||||||
err = new Error('Block is a known orphan.');
|
code = Chain.codes.knownOrphan;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +334,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
|
|
||||||
// Remove forked nodes from storage, if shorter chain is detected
|
// Remove forked nodes from storage, if shorter chain is detected
|
||||||
if (this._killFork(prevProbe)) {
|
if (this._killFork(prevProbe)) {
|
||||||
err = new Error('Fork found.');
|
code = Chain.codes.forked;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,17 +348,15 @@ Chain.prototype.add = function add(block) {
|
|||||||
hashes = this.index.hashes.slice(range.start, range.end + 1);
|
hashes = this.index.hashes.slice(range.start, range.end + 1);
|
||||||
|
|
||||||
this.emit('missing', prev, hashes, block);
|
this.emit('missing', prev, hashes, block);
|
||||||
|
code = Chain.codes.newOrphan;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validated known block at this point - add it to index
|
// Validated known block at this point - add it to index
|
||||||
if (prevProbe) {
|
if (prevProbe)
|
||||||
this._addIndex(hash, block.ts, prevProbe.height + 1);
|
code = this._addIndex(hash, block.ts, prevProbe.height + 1);
|
||||||
block.height = prevProbe.height + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At least one block was added
|
// At least one block was added
|
||||||
res = true;
|
|
||||||
this.block.list.push(block);
|
this.block.list.push(block);
|
||||||
this.block.bloom.add(hash, 'hex');
|
this.block.bloom.add(hash, 'hex');
|
||||||
|
|
||||||
@ -347,7 +371,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
delete this.orphan.bmap[block.hash('hex')];
|
delete this.orphan.bmap[block.hash('hex')];
|
||||||
delete this.orphan.map[hash];
|
delete this.orphan.map[hash];
|
||||||
this.orphan.count--;
|
this.orphan.count--;
|
||||||
} while (true);
|
}
|
||||||
|
|
||||||
// Failsafe for large orphan chains
|
// Failsafe for large orphan chains
|
||||||
if (this.orphan.count > 10000) {
|
if (this.orphan.count > 10000) {
|
||||||
@ -363,7 +387,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
// Compress old blocks
|
// Compress old blocks
|
||||||
this._compress();
|
this._compress();
|
||||||
|
|
||||||
return err;
|
return code;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._compress = function compress() {
|
Chain.prototype._compress = function compress() {
|
||||||
|
|||||||
@ -47,6 +47,8 @@ function Chain(options) {
|
|||||||
lastTs: 0
|
lastTs: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.request = new utils.RequestCache();
|
||||||
|
|
||||||
this.fromJSON({
|
this.fromJSON({
|
||||||
v: 1,
|
v: 1,
|
||||||
type: 'chain',
|
type: 'chain',
|
||||||
@ -63,6 +65,8 @@ function Chain(options) {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.tip = this.index.entries[this.index.entries.length - 1];
|
||||||
|
|
||||||
// Last TS after preload, needed for fill percent
|
// Last TS after preload, needed for fill percent
|
||||||
this.index.lastTs = this.index.entries[this.index.entries.length - 1].ts;
|
this.index.lastTs = this.index.entries[this.index.entries.length - 1].ts;
|
||||||
|
|
||||||
@ -74,6 +78,30 @@ function Chain(options) {
|
|||||||
|
|
||||||
inherits(Chain, EventEmitter);
|
inherits(Chain, EventEmitter);
|
||||||
|
|
||||||
|
Chain.codes = {
|
||||||
|
okay: 0,
|
||||||
|
newOrphan: 1,
|
||||||
|
knownOrphan: 2,
|
||||||
|
forked: 3,
|
||||||
|
invalid: 4,
|
||||||
|
badCheckpoint: 4,
|
||||||
|
unchanged: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.messages = {
|
||||||
|
0: 'Block was added successfully',
|
||||||
|
1: 'Block is a new orphan',
|
||||||
|
2: 'Block is a known orphan',
|
||||||
|
3: 'Block is a greater fork',
|
||||||
|
4: 'Block verification failed',
|
||||||
|
5: 'Block does not match checkpoint',
|
||||||
|
6: 'Chain is unchanged'
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.msg = function msg(code) {
|
||||||
|
return new Error(Chain.messages[code] || 'Unknown');
|
||||||
|
};
|
||||||
|
|
||||||
Chain.prototype._init = function _init() {
|
Chain.prototype._init = function _init() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var s;
|
var s;
|
||||||
@ -111,19 +139,24 @@ Chain.prototype._init = function _init() {
|
|||||||
Chain.prototype._addIndex = function _addIndex(entry) {
|
Chain.prototype._addIndex = function _addIndex(entry) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (this.index.entries[entry.hash])
|
// Already added
|
||||||
return new Error('Already added.');
|
if (this.index.heights[entry.hash] != null) {
|
||||||
|
assert(this.index.heights[entry.hash] === entry.height);
|
||||||
|
return Chain.codes.unchanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplcate height
|
||||||
if (this.index.hashes[entry.height] === entry.hash)
|
if (this.index.hashes[entry.height] === entry.hash)
|
||||||
return new Error('Duplicate height.');
|
return Chain.codes.unchanged;
|
||||||
|
|
||||||
|
// Fork at checkpoint
|
||||||
checkpoint = network.checkpoints[entry.height];
|
checkpoint = network.checkpoints[entry.height];
|
||||||
if (checkpoint) {
|
if (checkpoint) {
|
||||||
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
|
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
|
||||||
if (hash !== checkpoint) {
|
if (hash !== checkpoint) {
|
||||||
this.resetLastCheckpoint(entry.height);
|
this.resetLastCheckpoint(entry.height);
|
||||||
this.emit('fork', entry.height, entry.hash, checkpoint);
|
this.emit('fork', entry.height, entry.hash, checkpoint);
|
||||||
return new Error('Forked chain at checkpoint.');
|
return Chain.codes.badCheckpoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,14 +164,18 @@ Chain.prototype._addIndex = function _addIndex(entry) {
|
|||||||
this.index.hashes[entry.height] = entry.hash;
|
this.index.hashes[entry.height] = entry.hash;
|
||||||
this.index.heights[entry.hash] = entry.height;
|
this.index.heights[entry.hash] = entry.height;
|
||||||
|
|
||||||
|
this.tip = this.index.entries[this.index.entries.length - 1];
|
||||||
|
|
||||||
this._save(entry);
|
this._save(entry);
|
||||||
|
|
||||||
|
return Chain.codes.okay;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
||||||
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
|
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
|
||||||
|
|
||||||
if (lastHeight < 0)
|
if (lastHeight < 0)
|
||||||
i = 0;
|
lastHeight = 0;
|
||||||
|
|
||||||
this.resetHeight(lastHeight);
|
this.resetHeight(lastHeight);
|
||||||
};
|
};
|
||||||
@ -147,8 +184,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var ahead = this.index.entries.slice(height + 1);
|
var ahead = this.index.entries.slice(height + 1);
|
||||||
|
|
||||||
if (height >= this.index.entries - 1)
|
assert(height < this.index.entries - 1);
|
||||||
throw new Error('Cannot reset to height of ' + height);
|
|
||||||
|
|
||||||
this.orphan.map = {};
|
this.orphan.map = {};
|
||||||
this.orphan.bmap = {};
|
this.orphan.bmap = {};
|
||||||
@ -160,6 +196,8 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
}, {});
|
}, {});
|
||||||
this.index.hashes.length = height + 1;
|
this.index.hashes.length = height + 1;
|
||||||
|
|
||||||
|
this.tip = this.index.entries[this.index.entries.length - 1];
|
||||||
|
|
||||||
this.index.lastTs = Math.min(
|
this.index.lastTs = Math.min(
|
||||||
this.index.lastTs,
|
this.index.lastTs,
|
||||||
this.index.entries[this.index.entries.length - 1].ts
|
this.index.entries[this.index.entries.length - 1].ts
|
||||||
@ -185,15 +223,15 @@ Chain.prototype.add = function add(block) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = false;
|
|
||||||
var err = null;
|
|
||||||
var initial = block;
|
var initial = block;
|
||||||
|
var code = Chain.codes.unchanged;
|
||||||
|
var length = this.index.entries.length;
|
||||||
var hash, prev, prevProbe, range, i, entry;
|
var hash, prev, prevProbe, range, i, entry;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// No need to revalidate orphans
|
// Only validate the initial block (orphans were already validated)
|
||||||
if (!res && !block.verify()) {
|
if (block === initial && !block.verify()) {
|
||||||
err = new Error('Block verification failed.');
|
code = Chain.codes.invalid;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +240,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
|
|
||||||
// If the block is already known to be an orphan
|
// If the block is already known to be an orphan
|
||||||
if (this.orphan.map[prev]) {
|
if (this.orphan.map[prev]) {
|
||||||
err = new Error('Block is a known orphan.');
|
code = Chain.codes.knownOrphan;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +251,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
this.orphan.count++;
|
this.orphan.count++;
|
||||||
this.orphan.map[prev] = block;
|
this.orphan.map[prev] = block;
|
||||||
this.orphan.bmap[hash] = block;
|
this.orphan.bmap[hash] = block;
|
||||||
|
code = Chain.codes.newOrphan;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,28 +264,28 @@ Chain.prototype.add = function add(block) {
|
|||||||
height: i + 1
|
height: i + 1
|
||||||
});
|
});
|
||||||
|
|
||||||
// Already have block
|
// Add entry if we do not have it (or there is another hash at its height)
|
||||||
if (this.index.hashes[entry.height] === hash)
|
if (this.index.hashes[entry.height] !== hash) {
|
||||||
break;
|
assert(this.index.heights[entry.hash] == null);
|
||||||
|
|
||||||
assert(this.index.heights[entry.hash] == null);
|
// If we have a block at the same height, use chain with higher work
|
||||||
|
if (this.index.hashes[entry.height]) {
|
||||||
// Remove forked nodes from storage, if shorter chain is detected
|
if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
|
||||||
if (this.index.hashes[entry.height]) {
|
this.resetHeight(entry.height - 1);
|
||||||
if (!this.tip) {
|
this._addIndex(entry);
|
||||||
this.tip = entry;
|
code = Chain.codes.forked;
|
||||||
} else if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
|
// Breaking here only works because
|
||||||
// Found fork
|
// we deleted the orphan map in resetHeight.
|
||||||
this.resetHeight(entry.height - 1);
|
break;
|
||||||
this.tip = entry;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validated known block at this point - add it to index
|
||||||
|
code = this._addIndex(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validated known block at this point - add it to index
|
// Fullfill request
|
||||||
this._addIndex(entry);
|
this.request.fullfill(hash, block);
|
||||||
|
|
||||||
// At least one block was added
|
|
||||||
res = true;
|
|
||||||
|
|
||||||
if (!this.orphan.map[hash])
|
if (!this.orphan.map[hash])
|
||||||
break;
|
break;
|
||||||
@ -265,7 +304,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
this.orphan.count = 0;
|
this.orphan.count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return err;
|
return code;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.has = function has(hash, noIndex, cb) {
|
Chain.prototype.has = function has(hash, noIndex, cb) {
|
||||||
|
|||||||
@ -530,13 +530,25 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
|
Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
|
||||||
|
this.emit('debug',
|
||||||
|
'Requesting headers packet from %s with getheaders',
|
||||||
|
this.address);
|
||||||
this._write(this.framer.getHeaders(hashes, stop));
|
this._write(this.framer.getHeaders(hashes, stop));
|
||||||
};
|
};
|
||||||
|
|
||||||
Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) {
|
Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) {
|
||||||
|
this.emit('debug',
|
||||||
|
'Requesting inv packet from %s with getblocks',
|
||||||
|
this.address);
|
||||||
this._write(this.framer.getBlocks(hashes, stop));
|
this._write(this.framer.getBlocks(hashes, stop));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Peer.prototype.loadItems = function loadItems(hashes, stop) {
|
||||||
|
if (this.pool.options.headers)
|
||||||
|
return this.loadHeaders(hashes, stop);
|
||||||
|
return this.loadBlocks(hashes, stop);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -32,6 +32,7 @@ function Pool(options) {
|
|||||||
network.set(this.options.network);
|
network.set(this.options.network);
|
||||||
|
|
||||||
this.options.fullNode = !!this.options.fullNode;
|
this.options.fullNode = !!this.options.fullNode;
|
||||||
|
this.options.headers = !!this.options.headers;
|
||||||
this.options.relay = this.options.relay == null
|
this.options.relay = this.options.relay == null
|
||||||
? (this.options.fullNode ? true : false)
|
? (this.options.fullNode ? true : false)
|
||||||
: this.options.relay;
|
: this.options.relay;
|
||||||
@ -47,7 +48,9 @@ function Pool(options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.load = {
|
this.load = {
|
||||||
timeout: options.loadTimeout || 3000,
|
// timeout: options.loadTimeout || 3000,
|
||||||
|
// interval: options.loadInterval || 5000,
|
||||||
|
timeout: options.loadTimeout || 30000,
|
||||||
interval: options.loadInterval || 5000,
|
interval: options.loadInterval || 5000,
|
||||||
window: options.loadWindow || 250,
|
window: options.loadWindow || 250,
|
||||||
lastRange: null,
|
lastRange: null,
|
||||||
@ -58,13 +61,6 @@ function Pool(options) {
|
|||||||
hiReached: false
|
hiReached: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.options.fullNode) {
|
|
||||||
if (!options.loadTimeout)
|
|
||||||
this.load.timeout = 30000;
|
|
||||||
if (!options.loadInterval)
|
|
||||||
this.load.interval = 5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.maxRetries = options.maxRetries || 42;
|
this.maxRetries = options.maxRetries || 42;
|
||||||
this.requestTimeout = options.requestTimeout || 10000;
|
this.requestTimeout = options.requestTimeout || 10000;
|
||||||
|
|
||||||
@ -179,9 +175,58 @@ Pool.prototype._init = function _init() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Pool.prototype._startTimer = function _startTimer() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._stopTimer();
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
// Chain is full and up-to-date
|
||||||
|
if (self.chain.isFull()) {
|
||||||
|
self._stopTimer();
|
||||||
|
self.emit('full');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.peers.load)
|
||||||
|
self.peers.load.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._timer = setTimeout(destroy, this.load.timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
Pool.prototype._stopTimer = function _stopTimer() {
|
||||||
|
if (!this._timer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clearTimeout(this._timer);
|
||||||
|
delete this._timer;
|
||||||
|
};
|
||||||
|
|
||||||
|
Pool.prototype._startInterval = function _startInterval() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._stopInterval();
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
// self._load();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._interval = setInterval(load, this.load.interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
Pool.prototype._stopInterval = function _stopInterval() {
|
||||||
|
if (!this._interval)
|
||||||
|
return;
|
||||||
|
|
||||||
|
clearInterval(this._interval);
|
||||||
|
delete this._interval;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
Pool.prototype._addLoader = function _addLoader() {
|
Pool.prototype._addLoader = function _addLoader() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var peer, interval, timer;
|
var peer;
|
||||||
|
|
||||||
if (this.destroyed)
|
if (this.destroyed)
|
||||||
return;
|
return;
|
||||||
@ -201,20 +246,24 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
self.emit('error', err, peer);
|
self.emit('error', err, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.once('close', onclose);
|
peer.on('debug', function() {
|
||||||
function onclose() {
|
var args = Array.prototype.slice.call(arguments);
|
||||||
clearTimeout(timer);
|
self.emit.apply(self, ['debug'].concat(args));
|
||||||
clearInterval(interval);
|
});
|
||||||
|
|
||||||
|
peer.once('close', function() {
|
||||||
|
self._stopInterval();
|
||||||
|
self._stopTimer();
|
||||||
self._removePeer(peer);
|
self._removePeer(peer);
|
||||||
if (self.destroyed)
|
if (self.destroyed)
|
||||||
return;
|
return;
|
||||||
self._addLoader();
|
self._addLoader();
|
||||||
}
|
});
|
||||||
|
|
||||||
peer.once('ack', function() {
|
peer.once('ack', function() {
|
||||||
peer.updateWatch();
|
peer.updateWatch();
|
||||||
if (!self._load())
|
if (!self._load())
|
||||||
clearTimeout(timer);
|
self._stopTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on('version', function(version) {
|
peer.on('version', function(version) {
|
||||||
@ -223,25 +272,6 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
self.emit('version', version, peer);
|
self.emit('version', version, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
function load() {
|
|
||||||
// self._load();
|
|
||||||
}
|
|
||||||
|
|
||||||
interval = setInterval(load, this.load.interval);
|
|
||||||
|
|
||||||
function destroy() {
|
|
||||||
// Chain is full and up-to-date
|
|
||||||
if (self.chain.isFull()) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
self.emit('full');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
timer = setTimeout(destroy, this.load.timeout);
|
|
||||||
|
|
||||||
peer.on('merkleblock', function(block) {
|
peer.on('merkleblock', function(block) {
|
||||||
self._handleBlock(block, peer);
|
self._handleBlock(block, peer);
|
||||||
});
|
});
|
||||||
@ -250,67 +280,126 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
self._handleBlock(block, peer);
|
self._handleBlock(block, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on('blocks', function(hashes) {
|
if (self.options.headers) {
|
||||||
self._handleInv(hashes, peer);
|
peer.on('blocks', function(hashes) {
|
||||||
});
|
self._handleInv(hashes, peer);
|
||||||
|
});
|
||||||
|
|
||||||
// Split blocks and request them using multiple peers
|
peer.on('headers', function(headers) {
|
||||||
peer.on('headers', function(headers) {
|
self._handleHeaders(headers, peer);
|
||||||
var i, header, last, block;
|
});
|
||||||
|
} else {
|
||||||
|
peer.on('blocks', function(hashes) {
|
||||||
|
self._handleBlocks(hashes, peer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (headers.length === 0)
|
this._startInterval();
|
||||||
return;
|
this._startTimer();
|
||||||
|
};
|
||||||
|
|
||||||
self.emit('debug',
|
Pool.prototype._handleHeaders = function _handleHeaders(hashes, peer) {
|
||||||
'Recieved %s headers from %s',
|
var i, header, last, block;
|
||||||
headers.length,
|
|
||||||
peer.address);
|
|
||||||
|
|
||||||
self.emit('headers', headers);
|
assert(this.options.headers);
|
||||||
|
|
||||||
for (i = 0; i < headers.length; i++) {
|
if (headers.length === 0)
|
||||||
block = bcoin.block(headers[i], 'header');
|
return;
|
||||||
|
|
||||||
if (last && block.prevBlock !== last.hash('hex'))
|
this.emit('debug',
|
||||||
break;
|
'Recieved %s headers from %s',
|
||||||
|
headers.length,
|
||||||
|
peer.address);
|
||||||
|
|
||||||
if (!block.verify())
|
this.emit('headers', headers);
|
||||||
break;
|
|
||||||
|
|
||||||
if (!self.chain.hasBlock(block) && !self.chain.hasOrphan(block))
|
for (i = 0; i < headers.length; i++) {
|
||||||
self._request(self.block.type, block.hash('hex'));
|
block = bcoin.block(headers[i], 'header');
|
||||||
|
|
||||||
// We could do headers-first:
|
if (last && block.prevBlock !== last.hash('hex'))
|
||||||
// self._addIndex(block, peer);
|
break;
|
||||||
|
|
||||||
// Resolve orphan chain
|
if (!block.verify())
|
||||||
// if (self.chain.hasOrphan(block)) {
|
break;
|
||||||
// peer.loadHeaders(
|
|
||||||
// self.chain.locatorHashes(),
|
|
||||||
// self.chain.getOrphanRoot(block)
|
|
||||||
// );
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
last = block;
|
if (!this.chain.has(block))
|
||||||
|
this._request(this.block.type, block.hash('hex'));
|
||||||
|
|
||||||
|
// For headers-first:
|
||||||
|
// this._addIndex(block, peer);
|
||||||
|
|
||||||
|
last = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the getheaders process
|
||||||
|
if (last && headers.length >= 1999)
|
||||||
|
peer.loadHeaders(this.chain.locatorHashes(last), null);
|
||||||
|
|
||||||
|
// Push our getdata packet
|
||||||
|
this._scheduleRequests();
|
||||||
|
|
||||||
|
// Reset interval to avoid calling getheaders unnecessarily
|
||||||
|
this._startInterval();
|
||||||
|
|
||||||
|
// Reset timeout to avoid killing the loader
|
||||||
|
this._startTimer();
|
||||||
|
|
||||||
|
this.emit('debug',
|
||||||
|
'Requesting %s block packets from %s with getdata',
|
||||||
|
this.request.active,
|
||||||
|
peer.address
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
|
||||||
|
var i, hash;
|
||||||
|
|
||||||
|
assert(!this.options.headers);
|
||||||
|
|
||||||
|
if (hashes.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit('blocks', hashes);
|
||||||
|
|
||||||
|
this.emit('debug',
|
||||||
|
'Recieved %s block hashes from %s',
|
||||||
|
hashes.length,
|
||||||
|
peer.address);
|
||||||
|
|
||||||
|
for (i = 0; i < hashes.length; i++) {
|
||||||
|
hash = hashes[i];
|
||||||
|
|
||||||
|
// Resolve orphan chain
|
||||||
|
if (this.chain.hasOrphan(hash)) {
|
||||||
|
peer.loadBlocks(
|
||||||
|
this.chain.locatorHashes(),
|
||||||
|
this.chain.getOrphanRoot(hash)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart the getheaders process
|
// Request block if we don't have it
|
||||||
if (last && headers.length >= 1999)
|
if (!this.chain.has(hash))
|
||||||
peer.loadHeaders(self.chain.locatorHashes(last), 0);
|
this._request(this.block.type, hash);
|
||||||
// peer.loadHeaders([last.hash('hex')], 0);
|
}
|
||||||
|
|
||||||
// Schedule our requests
|
// Restart the entire getblocks process
|
||||||
self._scheduleRequests();
|
peer.loadBlocks(this.chain.locatorHashes(), null);
|
||||||
|
|
||||||
// Reset interval to avoid calling getheaders unnecessarily
|
// Push our getdata packet
|
||||||
clearInterval(interval);
|
this._scheduleRequests();
|
||||||
interval = setInterval(load, self.load.interval);
|
|
||||||
|
|
||||||
// Reset timeout to avoid killing the loader
|
// Reset interval to avoid calling getblocks unnecessarily
|
||||||
clearTimeout(timer);
|
this._startInterval();
|
||||||
timer = setTimeout(destroy, self.load.timeout);
|
|
||||||
});
|
// Reset timeout to avoid killing the loader
|
||||||
|
this._startTimer();
|
||||||
|
|
||||||
|
this.emit('debug',
|
||||||
|
'Requesting %s block packets from %s with getdata',
|
||||||
|
this.request.active,
|
||||||
|
peer.address
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._handleInv = function _handleInv(hashes, peer) {
|
Pool.prototype._handleInv = function _handleInv(hashes, peer) {
|
||||||
@ -318,9 +407,16 @@ Pool.prototype._handleInv = function _handleInv(hashes, peer) {
|
|||||||
|
|
||||||
for (i = 0; i < hashes.length; i++) {
|
for (i = 0; i < hashes.length; i++) {
|
||||||
hash = utils.toHex(hashes[i]);
|
hash = utils.toHex(hashes[i]);
|
||||||
if (!this.chain.hasBlock(hash) && !this.chain.hasOrphan(hash))
|
if (!this.chain.has(hash)) {
|
||||||
this.peers.load.loadHeaders(this.chain.locatorHashes(), hash);
|
if (this.options.headers)
|
||||||
|
this.peers.load.loadHeaders(this.chain.locatorHashes(), hash);
|
||||||
|
else
|
||||||
|
this._request(this.block.type, hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.options.headers)
|
||||||
|
this._scheduleRequests();
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._handleBlock = function _handleBlock(block, peer) {
|
Pool.prototype._handleBlock = function _handleBlock(block, peer) {
|
||||||
@ -342,6 +438,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not use with headers-first:
|
||||||
if (!this._addIndex(block, peer))
|
if (!this._addIndex(block, peer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -350,25 +447,25 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) {
|
|||||||
|
|
||||||
Pool.prototype._addIndex = function _addIndex(block, peer) {
|
Pool.prototype._addIndex = function _addIndex(block, peer) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash, size, orphan, err;
|
var hash, size, orphan, res;
|
||||||
|
|
||||||
hash = block.hash('hex');
|
hash = block.hash('hex');
|
||||||
size = this.chain.size();
|
size = this.chain.size();
|
||||||
orphan = this.chain.hasOrphan(block);
|
orphan = this.chain.hasOrphan(block);
|
||||||
|
|
||||||
err = this.chain.add(block);
|
res = this.chain.add(block);
|
||||||
if (err)
|
if (res)
|
||||||
this.emit('chain-error', err, peer);
|
this.emit('chain-error', bcoin.chain.msg(res), peer);
|
||||||
|
|
||||||
if (this.chain.hasOrphan(block)) {
|
if (this.chain.hasOrphan(block)) {
|
||||||
// Resolve orphan chain
|
// Resolve orphan chain
|
||||||
// peer.loadHeaders(
|
peer.loadBlocks(
|
||||||
// this.chain.locatorHashes(),
|
this.chain.locatorHashes(),
|
||||||
// this.chain.getOrphanRoot(block)
|
this.chain.getOrphanRoot(block)
|
||||||
// );
|
);
|
||||||
// Emit our orphan if it is new
|
// Emit our orphan if it is new
|
||||||
if (!orphan)
|
if (!orphan)
|
||||||
this.emit('orphan', block, peer);
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,18 +522,15 @@ Pool.prototype._load = function _load() {
|
|||||||
|
|
||||||
this.load.hiReached = false;
|
this.load.hiReached = false;
|
||||||
|
|
||||||
if (this.peers.load) {
|
|
||||||
this.emit('debug',
|
|
||||||
'Requesting headers packet from %s with getheaders',
|
|
||||||
this.peers.load.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.peers.load) {
|
if (!this.peers.load) {
|
||||||
this._addLoader();
|
this._addLoader();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.peers.load.loadHeaders(this.chain.locatorHashes(), 0);
|
if (this.options.headers)
|
||||||
|
this.peers.load.loadHeaders(this.chain.locatorHashes(), null);
|
||||||
|
else
|
||||||
|
this.peers.load.loadBlocks(this.chain.locatorHashes(), null);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -466,6 +560,11 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
|||||||
self.emit('error', err, peer);
|
self.emit('error', err, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
peer.on('debug', function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
self.emit.apply(self, ['debug'].concat(args));
|
||||||
|
});
|
||||||
|
|
||||||
peer.once('close', function() {
|
peer.once('close', function() {
|
||||||
self._removePeer(peer);
|
self._removePeer(peer);
|
||||||
if (self.destroyed)
|
if (self.destroyed)
|
||||||
@ -925,8 +1024,9 @@ Pool.prototype._request = function _request(type, hash, options, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block should be not in chain, or be requested
|
// Block should be not in chain, or be requested
|
||||||
// if (!options.force && (type === 'block' || type === 'filtered'))
|
// Do not use with headers-first
|
||||||
// return this.chain.has(hash, true, next);
|
if (!options.force && (type === 'block' || type === 'filtered'))
|
||||||
|
return this.chain.has(hash, true, next);
|
||||||
|
|
||||||
return next(false);
|
return next(false);
|
||||||
};
|
};
|
||||||
@ -988,29 +1088,29 @@ Pool.prototype._doRequests = function _doRequests() {
|
|||||||
if (above && below && this.load.hiReached)
|
if (above && below && this.load.hiReached)
|
||||||
this._load();
|
this._load();
|
||||||
|
|
||||||
if (this.options.fullNode) {
|
if (this.options.multiplePeers) {
|
||||||
req = items.map(function(item) {
|
mapReq = function(item) {
|
||||||
return item.start(this.peers.load);
|
return item.start(this.peers.block[i]);
|
||||||
}, this);
|
};
|
||||||
|
|
||||||
this.peers.load.getData(req);
|
// Split list between peers
|
||||||
|
red = this.redundancy;
|
||||||
|
count = this.peers.block.length;
|
||||||
|
split = Math.ceil(items.length * red / count);
|
||||||
|
for (i = 0, off = 0; i < count; i += red, off += split) {
|
||||||
|
req = items.slice(off, off + split).map(mapReq, this);
|
||||||
|
for (j = 0; j < red && i + j < count; j++)
|
||||||
|
this.peers.block[i + j].getData(req);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapReq = function(item) {
|
req = items.map(function(item) {
|
||||||
return item.start(this.peers.block[i]);
|
return item.start(this.peers.load);
|
||||||
};
|
}, this);
|
||||||
|
|
||||||
// Split list between peers
|
this.peers.load.getData(req);
|
||||||
red = this.redundancy;
|
|
||||||
count = this.peers.block.length;
|
|
||||||
split = Math.ceil(items.length * red / count);
|
|
||||||
for (i = 0, off = 0; i < count; i += red, off += split) {
|
|
||||||
req = items.slice(off, off + split).map(mapReq, this);
|
|
||||||
for (j = 0; j < red && i + j < count; j++)
|
|
||||||
this.peers.block[i + j].getData(req);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.getBlock = function getBlock(hash, cb) {
|
Pool.prototype.getBlock = function getBlock(hash, cb) {
|
||||||
|
|||||||
@ -201,6 +201,10 @@ Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) {
|
|||||||
var p = [];
|
var p = [];
|
||||||
var off, i, hash, len;
|
var off, i, hash, len;
|
||||||
|
|
||||||
|
// getheaders can have a null hash
|
||||||
|
if (cmd === 'getheaders' && !hashes)
|
||||||
|
hashes = [];
|
||||||
|
|
||||||
writeU32(p, constants.version, 0);
|
writeU32(p, constants.version, 0);
|
||||||
off = 4 + utils.writeIntv(p, hashes.length, 4);
|
off = 4 + utils.writeIntv(p, hashes.length, 4);
|
||||||
p.length = off + 32 * (hashes.length + 1);
|
p.length = off + 32 * (hashes.length + 1);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user