diff --git a/lib/blockchain.js b/lib/blockchain.js index 49cbafc8..6498491b 100644 --- a/lib/blockchain.js +++ b/lib/blockchain.js @@ -6,7 +6,11 @@ var _ = bitcore.deps._; function BlockChain() { this.tip = null; - this.headers = {}; + this.work = { + '0000000000000000000000000000000000000000000000000000000000000000': 0 + }; + this.height = {}; + this.hashByHeight = {}; this.next = {}; this.prev = {}; } @@ -14,26 +18,137 @@ function BlockChain() { BlockChain.fromObject = function(obj) { var blockchain = new BlockChain(); blockchain.tip = obj.tip; - blockchain.headers = obj.headers; + blockchain.work = obj.work; + blockchain.hashByHeight = obj.hashByHeight; + blockchain.height = obj.height; blockchain.next = obj.next; blockchain.prev = obj.prev; return blockchain; }; -BlockChain.prototype.setTip = function(block) { +var getWork = function(bits) { + var bytes = ((bits >>> 24) & 0xff) >>> 0; + return ((bits & 0xffffff) << (8 * (bytes - 3))) >>> 0; +}; + +BlockChain.prototype.addData = function(block) { $.checkArgument(block instanceof bitcore.Block, 'Argument is not a Block instance'); - this.tip = block.hash; - this.headers[block.hash] = block.header; + var prevHash = bitcore.util.buffer.reverse(block.header.prevHash).toString('hex'); - this.next[prevHash] = block.hash; + + this.work[block.hash] = this.work[prevHash].work + getWork(block.header.bits); this.prev[block.hash] = prevHash; }; +BlockChain.prototype.proposeNewBlock = function(block) { + $.checkArgument(block instanceof bitcore.Block, 'Argument is not a Block instance'); + var prevHash = bitcore.util.buffer.reverse(block.header.prevHash).toString('hex'); + + if (!this.work[prevHash]) { + throw new Error('No previous data to estimate work'); + } + this.addData(block); + + if (this.work[block.hash] > this.work[this.tip]) { + + var toUnconfirm = []; + var toConfirm = []; + var commonAncestor; + + var pointer = block.hash; + while (!this.height[pointer]) { + toConfirm.push(pointer); + pointer = this.prev[pointer]; + } + commonAncestor = pointer; + + pointer = this.tip; + while (pointer !== commonAncestor) { + toUnconfirm.push(pointer); + pointer = this.prev[pointer]; + } + + toConfirm.reverse(); + + var self = this; + toUnconfirm.map(function(hash) { + self.unconfirm(hash); + }); + toConfirm.map(function(hash) { + self.confirm(hash); + }); + return { + unconfirmed: toUnconfirm, + confirmed: toConfirm + }; + } + return { + unconfirmed: [], + confirmed: [] + }; +}; + +BlockChain.prototype.confirm = function(hash) { + var prevHash = this.prev[hash]; + $.checkState(prevHash === this.tip); + + 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); + + this.tip = prevHash; + var height = this.height[hash]; + delete this.next[prevHash]; + delete this.hashByHeight[height]; + delete this.height[hash]; +}; + +BlockChain.prototype.getBlockLocator = function() { + $.checkState(this.tip); + $.checkState(this.height[this.tip]); + + var result = []; + var currentHeight = this.height[this.tip]; + var exponentialBackOff = 1; + for (var i = 0; i < 10; i++) { + result.push(this.hashByHeight[currentHeight--]); + } + while (currentHeight > 0) { + result.push(this.hashByHeight[currentHeight]); + currentHeight -= exponentialBackOff; + exponentialBackOff *= 2; + } + return result; +}; + +BlockChain.prototype.hasData = function(hash) { + return !!this.prev[hash]; +}; + +BlockChain.prototype.prune = function() { + var self = this; + _.each(this.prev, function(key, value) { + if (!self.height[key]) { + delete this.prev[key]; + delete this.work[key]; + } + }); +}; + BlockChain.prototype.toObject = function() { return { tip: this.tip, - headers: _.map(this.headers, function(header) { return header.toObject(); }), + work: this.work, next: this.next, + hashByHeight: this.hashByHeight, + height: this.height, prev: this.prev }; };