flocore-node/lib/blockchain.js
2015-04-27 15:22:29 -03:00

182 lines
4.6 KiB
JavaScript

'use strict';
var bitcore = require('bitcore');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferUtil = bitcore.util.buffer;
var NULL = '0000000000000000000000000000000000000000000000000000000000000000';
function BlockChain() {
this.tip = NULL;
this.work = {};
this.work[NULL] = 0;
this.height = {};
this.height[NULL] = -1;
this.hashByHeight = {
'-1': NULL
};
this.next = {};
this.prev = {};
}
BlockChain.NULL = NULL;
BlockChain.fromObject = function(obj) {
var blockchain = new BlockChain();
blockchain.tip = obj.tip;
blockchain.work = obj.work;
blockchain.hashByHeight = obj.hashByHeight;
blockchain.height = obj.height;
blockchain.next = obj.next;
blockchain.prev = obj.prev;
return blockchain;
};
var getWork = function(bits) {
var bytes = ((bits >>> 24) & 0xff) >>> 0;
return ((bits & 0xffffff) << (8 * (bytes - 3))) >>> 0;
};
BlockChain.prototype.addData = function(header) {
$.checkArgument(header instanceof bitcore.Block.BlockHeader, 'Argument is not a BlockHeader instance');
var prevHash = BufferUtil.reverse(header.prevHash).toString('hex');
var hash = header.hash;
this.work[hash] = this.work[prevHash] + getWork(header.bits);
this.prev[hash] = prevHash;
};
BlockChain.prototype._appendNewBlock = function(hash) {
var toUnconfirm = [];
var toConfirm = [];
var self = this;
var pointer = hash;
while (_.isUndefined(this.height[pointer])) {
toConfirm.push(pointer);
pointer = this.prev[pointer];
}
var commonAncestor = pointer;
pointer = this.tip;
while (pointer !== commonAncestor) {
toUnconfirm.push(pointer);
pointer = this.prev[pointer];
}
toConfirm.reverse();
toUnconfirm.map(function(hash) {
self.unconfirm(hash);
});
toConfirm.map(function(hash) {
self.confirm(hash);
});
return {
unconfirmed: toUnconfirm,
confirmed: toConfirm
};
};
BlockChain.prototype.proposeNewHeader = function(header) {
$.checkArgument(header instanceof bitcore.Block.BlockHeader, 'Argument is not a BlockHeader instance');
var prevHash = BufferUtil.reverse(header.prevHash).toString('hex');
var hash = header.hash;
$.checkState(this.hasData(prevHash), 'No previous data to estimate work');
this.addData(header);
var work = this.work[hash];
var tipWork = this.work[this.tip];
$.checkState(!_.isUndefined(work), 'No work found for ' + hash);
$.checkState(!_.isUndefined(tipWork), 'No work found for tip ' + this.tip);
if (work > tipWork) {
return this._appendNewBlock(hash);
}
return {
unconfirmed: [],
confirmed: []
};
};
BlockChain.prototype.proposeNewBlock = function(block) {
$.checkArgument(block instanceof bitcore.Block, 'Argument is not a Block instance');
return this.proposeNewHeader(block.header);
};
BlockChain.prototype.confirm = function(hash) {
var prevHash = this.prev[hash];
$.checkState(prevHash === this.tip, 'Attempting to confirm a non-contiguous block.');
this.tip = hash;
var height = this.height[prevHash] + 1;
this.next[prevHash] = hash;
this.hashByHeight[height] = hash;
this.height[hash] = height;
};
BlockChain.prototype.unconfirm = function(hash) {
var prevHash = this.prev[hash];
$.checkState(hash === this.tip, 'Attempting to unconfirm a non-tip block');
this.tip = prevHash;
var height = this.height[hash];
delete this.next[prevHash];
delete this.hashByHeight[height];
delete this.height[hash];
};
BlockChain.prototype.hasData = function(hash) {
return !_.isUndefined(this.work[hash]);
};
BlockChain.prototype.prune = function() {
var self = this;
_.each(this.prev, function(key) {
if (!self.height[key]) {
delete self.prev[key];
delete self.work[key];
}
});
};
BlockChain.prototype.toObject = function() {
return {
tip: this.tip,
work: this.work,
next: this.next,
hashByHeight: this.hashByHeight,
height: this.height,
prev: this.prev
};
};
BlockChain.prototype.toJSON = function() {
return JSON.stringify(this.toObject());
};
BlockChain.prototype.getBlockLocator = function() {
$.checkState(this.tip);
$.checkState(!_.isUndefined(this.height[this.tip]));
var result = [];
var currentHeight = this.getCurrentHeight();
var exponentialBackOff = 1;
for (var i = 0; i < 10; i++) {
if (currentHeight >= 0) {
result.push(this.hashByHeight[currentHeight--]);
}
}
while (currentHeight > 0) {
result.push(this.hashByHeight[currentHeight]);
currentHeight -= exponentialBackOff;
exponentialBackOff *= 2;
}
return result;
};
BlockChain.prototype.getCurrentHeight = function() {
return this.height[this.tip];
};
module.exports = BlockChain;