incorporate chainlib bitcoin into bitcoind.js
This commit is contained in:
parent
d0327b2f85
commit
b8b4ac02bf
|
@ -0,0 +1,54 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BitcoinNode = require('../lib/node');
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var log = chainlib.log;
|
||||||
|
log.debug = function() {};
|
||||||
|
|
||||||
|
var privkey = 'tprv8ZgxMBicQKsPdj1QowoT9z1tY5Et38qaMjCHZVoPdPFb6narfmYkqTygEVHfUmY78k3HcaEpkyNCAQDANaXtwNe1HLFvcA7nqYj1B7wTSTo';
|
||||||
|
|
||||||
|
var configuration = {
|
||||||
|
db: {
|
||||||
|
xprivkey: privkey,
|
||||||
|
path: './bitcoind.db'
|
||||||
|
},
|
||||||
|
p2p: {
|
||||||
|
addrs: [
|
||||||
|
{
|
||||||
|
ip: {
|
||||||
|
v4: '127.0.0.1'
|
||||||
|
},
|
||||||
|
port: 8333
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dnsSeed: false
|
||||||
|
},
|
||||||
|
testnet: false
|
||||||
|
};
|
||||||
|
|
||||||
|
var node = new BitcoinNode(configuration);
|
||||||
|
|
||||||
|
var startHeight;
|
||||||
|
var count = 100;
|
||||||
|
var times = Array(count);
|
||||||
|
|
||||||
|
node.on('ready', function() {
|
||||||
|
times[node.chain.tip.__height % count] = Date.now();
|
||||||
|
startHeight = node.chain.tip.__height;
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on('error', function(err) {
|
||||||
|
log.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
node.chain.on('addblock', function(block) {
|
||||||
|
console.log('New Best Tip:', block.hash);
|
||||||
|
var startTime = times[node.chain.tip.__height % count];
|
||||||
|
|
||||||
|
if(startTime) {
|
||||||
|
var timeElapsed = (Date.now() - startTime) / 1000;
|
||||||
|
console.log(Math.round(count / timeElapsed) + ' blocks per second');
|
||||||
|
}
|
||||||
|
|
||||||
|
times[node.chain.tip.__height % count] = Date.now();
|
||||||
|
});
|
|
@ -0,0 +1,149 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util');
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var BaseBlock = chainlib.Block;
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var BufferReader = bitcore.encoding.BufferReader;
|
||||||
|
var BN = bitcore.crypto.BN;
|
||||||
|
|
||||||
|
function Block(obj) {
|
||||||
|
if (!obj) {
|
||||||
|
obj = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseBlock.call(this, obj);
|
||||||
|
|
||||||
|
this.bits = obj.bits;
|
||||||
|
this.nonce = obj.nonce || 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Block, BaseBlock);
|
||||||
|
|
||||||
|
Block.prototype.validate = function(chain, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Get previous block
|
||||||
|
chain.db.getBlock(self.prevHash, function(err, prevBlock) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate POW
|
||||||
|
|
||||||
|
// First make sure the current block hash is less than the target derived from this block's bits
|
||||||
|
if (!self.validProofOfWork(chain)) {
|
||||||
|
return callback(new Error('Invalid proof of work (hash is greater than target)'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second make sure that this block's bits is correct based off of the last block
|
||||||
|
chain.getNextWorkRequired(prevBlock, function(err, bits) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bits !== self.bits) {
|
||||||
|
return callback(new Error(
|
||||||
|
'Invalid proof of work, expected block bits "' + self.bits + '" to equal "' + bits + '"'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate block data
|
||||||
|
chain.db.validateBlockData(self, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Boolean} - If the proof-of-work hash satisfies the target difficulty
|
||||||
|
*/
|
||||||
|
Block.prototype.validProofOfWork = function validProofOfWork(chain) {
|
||||||
|
var pow = new BN(this.hash, 'hex');
|
||||||
|
var target = chain.getTargetFromBits(this.bits);
|
||||||
|
|
||||||
|
if (pow.cmp(target) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.fromBuffer = function(buffer) {
|
||||||
|
var br = new BufferReader(buffer);
|
||||||
|
return Block.fromBufferReader(br);
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.fromBufferReader = function(br) {
|
||||||
|
var obj = {};
|
||||||
|
obj.version = br.readUInt32LE();
|
||||||
|
obj.prevHash = BufferReader(br.read(32)).readReverse().toString('hex');
|
||||||
|
var nullHash = new Buffer(Array(32)).toString('hex');
|
||||||
|
if (obj.prevHash === nullHash) {
|
||||||
|
obj.prevHash = null;
|
||||||
|
}
|
||||||
|
obj.merkleRoot = BufferReader(br.read(32)).readReverse().toString('hex');
|
||||||
|
var timestamp = br.readUInt32LE();
|
||||||
|
obj.timestamp = new Date(timestamp * 1000);
|
||||||
|
obj.bits = br.readUInt32LE();
|
||||||
|
obj.nonce = br.readUInt32LE();
|
||||||
|
obj.data = br.readAll();
|
||||||
|
return new Block(obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.prototype.toObject = function() {
|
||||||
|
return {
|
||||||
|
version: this.version,
|
||||||
|
prevHash: this.prevHash,
|
||||||
|
merkleRoot: this.merkleRoot,
|
||||||
|
timestamp: this.timestamp.toISOString(),
|
||||||
|
bits: this.bits,
|
||||||
|
nonce: this.nonce,
|
||||||
|
data: this.data.toString('hex')
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Block.prototype.headerToBufferWriter = function(bw) {
|
||||||
|
/* jshint maxstatements: 20 */
|
||||||
|
|
||||||
|
// version
|
||||||
|
bw.writeUInt32LE(this.version);
|
||||||
|
|
||||||
|
// prevhash
|
||||||
|
if (!this.prevHash) {
|
||||||
|
bw.write(new Buffer(Array(32)));
|
||||||
|
} else {
|
||||||
|
var prevHashBuffer = new Buffer(this.prevHash, 'hex');
|
||||||
|
prevHashBuffer = BufferReader(prevHashBuffer).readReverse();
|
||||||
|
if (prevHashBuffer.length !== 32) {
|
||||||
|
throw new Error('"prevHash" is expected to be 32 bytes');
|
||||||
|
}
|
||||||
|
bw.write(prevHashBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkleroot
|
||||||
|
if (!this.merkleRoot) {
|
||||||
|
bw.write(new Buffer(Array(32)));
|
||||||
|
} else {
|
||||||
|
var merkleRoot = new Buffer(this.merkleRoot, 'hex');
|
||||||
|
merkleRoot = BufferReader(merkleRoot).readReverse();
|
||||||
|
if (merkleRoot.length !== 32) {
|
||||||
|
throw new Error('"merkleRoot" is expected to be 32 bytes');
|
||||||
|
}
|
||||||
|
bw.write(merkleRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// timestamp
|
||||||
|
bw.writeUInt32LE(Math.floor(this.timestamp.getTime() / 1000));
|
||||||
|
|
||||||
|
// bits
|
||||||
|
bw.writeUInt32LE(this.bits);
|
||||||
|
|
||||||
|
// nonce
|
||||||
|
bw.writeUInt32LE(this.nonce);
|
||||||
|
|
||||||
|
return bw;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Block;
|
|
@ -0,0 +1,238 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util');
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var BaseChain = chainlib.Chain;
|
||||||
|
var BN = bitcore.crypto.BN;
|
||||||
|
var Block = require('./block');
|
||||||
|
|
||||||
|
Chain.DEFAULTS = {
|
||||||
|
MAX_HASHES: new BN('10000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
||||||
|
TARGET_TIMESPAN: 14 * 24 * 60 * 60 * 1000, // two weeks
|
||||||
|
TARGET_SPACING: 10 * 60 * 1000, // ten minutes
|
||||||
|
MAX_BITS: 0x1d00ffff,
|
||||||
|
MIN_BITS: 0x03000000
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will instantiate a new Chain instance
|
||||||
|
* @param {Object} options - The options for the chain
|
||||||
|
* @param {Number} options.minBits - The minimum number of bits
|
||||||
|
* @param {Number} options.maxBits - The maximum number of bits
|
||||||
|
* @param {BN|Number} options.targetTimespan - The number of milliseconds for difficulty retargeting
|
||||||
|
* @param {BN|Number} options.targetSpacing - The number of milliseconds between blocks
|
||||||
|
* @returns {Chain}
|
||||||
|
* @extends BaseChain
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Chain(options) {
|
||||||
|
/* jshint maxstatements: 20 */
|
||||||
|
/* jshint maxcomplexity: 12 */
|
||||||
|
if (!(this instanceof Chain)) {
|
||||||
|
return new Chain(options);
|
||||||
|
}
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
BaseChain.call(this, options);
|
||||||
|
|
||||||
|
this.minBits = options.minBits || Chain.DEFAULTS.MIN_BITS;
|
||||||
|
this.maxBits = options.maxBits || Chain.DEFAULTS.MAX_BITS;
|
||||||
|
|
||||||
|
this.maxHashes = options.maxHashes || Chain.DEFAULTS.MAX_HASHES;
|
||||||
|
|
||||||
|
this.targetTimespan = options.targetTimespan || Chain.DEFAULTS.TARGET_TIMESPAN;
|
||||||
|
this.targetSpacing = options.targetSpacing || Chain.DEFAULTS.TARGET_SPACING;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Chain, BaseChain);
|
||||||
|
|
||||||
|
Chain.prototype._writeBlock = function(block, callback) {
|
||||||
|
// Update hashes
|
||||||
|
this.cache.hashes[block.hash] = block.prevHash;
|
||||||
|
// call db.putBlock to update prevHash index, but it won't write the block to disk
|
||||||
|
this.db.putBlock(block, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype._validateBlock = function(block, callback) {
|
||||||
|
// All validation is done by bitcoind
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.startBuilder = function() {
|
||||||
|
// Unused in bitcoind.js
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.buildGenesisBlock = function buildGenesisBlock(options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
var genesis = new Block({
|
||||||
|
prevHash: null,
|
||||||
|
height: 0,
|
||||||
|
timestamp: options.timestamp || new Date(),
|
||||||
|
nonce: options.nonce || 0,
|
||||||
|
bits: options.bits || this.maxBits
|
||||||
|
});
|
||||||
|
var data = this.db.buildGenesisData();
|
||||||
|
genesis.merkleRoot = data.merkleRoot;
|
||||||
|
genesis.data = data.buffer;
|
||||||
|
return genesis;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of blocks for a retargeting interval
|
||||||
|
* @returns {BN}
|
||||||
|
*/
|
||||||
|
Chain.prototype.getDifficultyInterval = function getDifficultyInterval() {
|
||||||
|
return this.targetTimespan / this.targetSpacing;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will recalculate the bits based on the hash rate of the previous interval
|
||||||
|
* @see https://en.bitcoin.it/wiki/Difficulty#How_is_difficulty_stored_in_blocks.3F
|
||||||
|
* @param {Number} bits - The bits of the previous interval block
|
||||||
|
* @param {Number} timespan - The number of milliseconds that elapsed in the last interval
|
||||||
|
* @returns {Number} The compacted target in bits
|
||||||
|
*/
|
||||||
|
Chain.prototype.getRetargetedBits = function getRetargetedBits(bits, timespan) {
|
||||||
|
// Based off Bitcoin's code:
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp#L53
|
||||||
|
|
||||||
|
if(timespan < this.targetTimespan / 4) {
|
||||||
|
timespan = Math.floor(this.targetTimespan / 4);
|
||||||
|
} else if(timespan > this.targetTimespan * 4) {
|
||||||
|
timespan = this.targetTimespan * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldTarget = this.getTargetFromBits(bits);
|
||||||
|
var newTarget = oldTarget.mul(new BN(timespan, 10)).div(new BN(this.targetTimespan, 10));
|
||||||
|
var newBits = this.getBitsFromTarget(newTarget);
|
||||||
|
|
||||||
|
if(newBits > this.maxBits) {
|
||||||
|
newBits = this.maxBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBits;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of blocks for a retargeting interval
|
||||||
|
* @param {Block} - block - An instance of a block
|
||||||
|
* @param {Function} - callback - A callback function that accepts arguments: Error and Number
|
||||||
|
*/
|
||||||
|
Chain.prototype.getNextWorkRequired = function getNextWorkRequired(block, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var interval = this.getDifficultyInterval();
|
||||||
|
|
||||||
|
self.getHeightForBlock(block.hash, function(err, height) {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height === 0) {
|
||||||
|
return callback(null, self.maxBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not on interval, return the same amount of difficulty
|
||||||
|
if ((height + 1) % interval !== 0) {
|
||||||
|
return callback(null, block.bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise compute the new difficulty
|
||||||
|
self.getBlockAtHeight(block, height + 1 - interval, function(err, lastIntervalBlock){
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var timespan = (Math.floor(block.timestamp.getTime() / 1000) * 1000) - (Math.floor(lastIntervalBlock.timestamp.getTime() / 1000) * 1000);
|
||||||
|
var bits = self.getRetargetedBits(lastIntervalBlock.bits, timespan);
|
||||||
|
|
||||||
|
return callback(null, bits);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the actual target from the compact form
|
||||||
|
* @param {Number} - bits
|
||||||
|
* @returns {BN}
|
||||||
|
*/
|
||||||
|
Chain.prototype.getTargetFromBits = function getTargetFromBits(bits) {
|
||||||
|
if(bits <= this.minBits) {
|
||||||
|
throw new Error('bits is too small (' + bits + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bits > this.maxBits) {
|
||||||
|
throw new Error('bits is too big (' + bits + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = bits & 0xffffff;
|
||||||
|
var b = bits >>> 24;
|
||||||
|
|
||||||
|
var exp = (8 * (b - 3));
|
||||||
|
|
||||||
|
// Exponents via bit shift (works for powers of 2)
|
||||||
|
var z = (new BN(2, 10)).shln(exp - 1);
|
||||||
|
var target = (new BN(a, 10)).mul(z);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the compact target "bits" from the target
|
||||||
|
* @param {BN|Number} - target
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
Chain.prototype.getBitsFromTarget = function getBitsFromTarget(target) {
|
||||||
|
target = new BN(target, 'hex');
|
||||||
|
|
||||||
|
var tmp = target;
|
||||||
|
|
||||||
|
var b = 0;
|
||||||
|
while(tmp.cmp(new BN(0, 10)) > 0) {
|
||||||
|
b++;
|
||||||
|
tmp = tmp.shrn(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = target.shrn((b - 3) * 8);
|
||||||
|
var bits = Number('0x' + b.toString(16) + a.toString(16, 6));
|
||||||
|
return bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.getDifficultyFromBits = function getDifficultyFromBits(bits) {
|
||||||
|
var currentTarget = this.getTargetFromBits(bits);
|
||||||
|
var genesisTarget = this.getTargetFromBits(this.genesis.bits);
|
||||||
|
return genesisTarget.div(currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.getBlockWeight = function getBlockWeight(blockHash, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.db.getBlock(blockHash, function(err, block) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
} else if(!block) {
|
||||||
|
return callback(new Error('Block not found (' + blockHash + ')'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = self.getTargetFromBits(block.bits);
|
||||||
|
var a = self.maxHashes.sub(target).sub(new BN(1, 10));
|
||||||
|
var b = target.add(new BN(1, 10));
|
||||||
|
var c = a.div(b);
|
||||||
|
var d = c.add(new BN(1, 10));
|
||||||
|
return callback(null, d);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Chain;
|
|
@ -0,0 +1,480 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util');
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var BaseDB = chainlib.DB;
|
||||||
|
var Transaction = require('./transaction');
|
||||||
|
var async = require('async');
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||||
|
var errors = require('./errors');
|
||||||
|
var levelup = chainlib.deps.levelup;
|
||||||
|
var log = chainlib.log;
|
||||||
|
var PublicKey = bitcore.PublicKey;
|
||||||
|
var Address = bitcore.Address;
|
||||||
|
|
||||||
|
function DB(options) {
|
||||||
|
if(!options) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseDB.call(this, options);
|
||||||
|
|
||||||
|
this.coinbaseAmount = options.coinbaseAmount || 50 * 1e8;
|
||||||
|
this.Transaction = Transaction;
|
||||||
|
|
||||||
|
this.network = bitcore.Networks.get(options.network) || bitcore.Networks.testnet;
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(DB, BaseDB);
|
||||||
|
|
||||||
|
DB.PREFIXES = {
|
||||||
|
TX: 'tx',
|
||||||
|
SPENTS: 'sp',
|
||||||
|
OUTPUTS: 'outs'
|
||||||
|
};
|
||||||
|
DB.CONCURRENCY = 10;
|
||||||
|
|
||||||
|
DB.prototype.getBlock = function(hash, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// get block from bitcoind
|
||||||
|
this.bitcoind.getBlock(hash, function(err, blockData) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, self.Block.fromBuffer(blockData));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.putBlock = function(block, callback) {
|
||||||
|
// block is already stored in bitcoind, but we need to update
|
||||||
|
// our prevhash index still
|
||||||
|
this._updatePrevHashIndex(block, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*DB.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||||
|
|
||||||
|
};*/
|
||||||
|
|
||||||
|
DB.prototype.validateBlockData = function(block, callback) {
|
||||||
|
// bitcoind does the validation
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.buildGenesisData = function() {
|
||||||
|
var coinbaseTx = this.buildCoinbaseTransaction();
|
||||||
|
var bw = new BufferWriter();
|
||||||
|
bw.writeVarintNum(1);
|
||||||
|
bw.write(coinbaseTx.toBuffer());
|
||||||
|
var merkleRoot = this.getMerkleRoot([coinbaseTx]);
|
||||||
|
var buffer = bw.concat();
|
||||||
|
return {
|
||||||
|
merkleRoot: merkleRoot,
|
||||||
|
buffer: buffer
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.buildCoinbaseTransaction = function(transactions, data) {
|
||||||
|
if(!this.wallet) {
|
||||||
|
throw new Error('Wallet required to build coinbase');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!data) {
|
||||||
|
data = bitcore.crypto.Random.getRandomBuffer(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fees = 0;
|
||||||
|
|
||||||
|
if(transactions && transactions.length) {
|
||||||
|
fees = this.getInputTotal(transactions) - this.getOutputTotal(transactions, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var coinbaseTx = new this.Transaction();
|
||||||
|
coinbaseTx.to('1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T', this.coinbaseAmount + fees);
|
||||||
|
|
||||||
|
var script = bitcore.Script.buildDataOut(data);
|
||||||
|
|
||||||
|
var input = new bitcore.Transaction.Input({
|
||||||
|
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
outputIndex: 0xffffffff,
|
||||||
|
sequenceNumber: 4294967295,
|
||||||
|
script: script
|
||||||
|
});
|
||||||
|
|
||||||
|
coinbaseTx.inputs = [input];
|
||||||
|
return coinbaseTx;
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getOutputTotal = function(transactions, excludeCoinbase) {
|
||||||
|
var totals = transactions.map(function(tx) {
|
||||||
|
if(tx.isCoinbase() && excludeCoinbase) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return tx._getOutputAmount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var grandTotal = totals.reduce(function(previousValue, currentValue) {
|
||||||
|
return previousValue + currentValue;
|
||||||
|
});
|
||||||
|
return grandTotal;
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getInputTotal = function(transactions) {
|
||||||
|
var totals = transactions.map(function(tx) {
|
||||||
|
if(tx.isCoinbase()) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return tx._getInputAmount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var grandTotal = totals.reduce(function(previousValue, currentValue) {
|
||||||
|
return previousValue + currentValue;
|
||||||
|
});
|
||||||
|
return grandTotal;
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._updateOutputs = function(block, addOutput, callback) {
|
||||||
|
var txs = this.getTransactionsFromBlock(block);
|
||||||
|
|
||||||
|
log.debug('Processing transactions', txs);
|
||||||
|
log.debug('Updating outputs');
|
||||||
|
|
||||||
|
var action = 'put';
|
||||||
|
if (!addOutput) {
|
||||||
|
action = 'del';
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < txs.length; i++) {
|
||||||
|
|
||||||
|
var tx = txs[i];
|
||||||
|
var txid = tx.id;
|
||||||
|
var inputs = tx.inputs;
|
||||||
|
var outputs = tx.outputs;
|
||||||
|
|
||||||
|
for (var j = 0; j < outputs.length; j++) {
|
||||||
|
var output = outputs[j];
|
||||||
|
|
||||||
|
var script = output.script;
|
||||||
|
if(!script) {
|
||||||
|
log.debug('Invalid script');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!script.isPublicKeyHashOut() && !script.isScriptHashOut() && !script.isPublicKeyOut()) {
|
||||||
|
// ignore for now
|
||||||
|
log.debug('script was not pubkeyhashout, scripthashout, or pubkeyout');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var address;
|
||||||
|
|
||||||
|
if(script.isPublicKeyOut()) {
|
||||||
|
var pubkey = script.chunks[0].buf;
|
||||||
|
address = Address.fromPublicKey(new PublicKey(pubkey), this.network);
|
||||||
|
} else {
|
||||||
|
address = output.script.toAddress(this.network);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputIndex = j;
|
||||||
|
|
||||||
|
var timestamp = block.timestamp.getTime();
|
||||||
|
var height = block.height;
|
||||||
|
|
||||||
|
operations.push({
|
||||||
|
type: action,
|
||||||
|
key: [DB.PREFIXES.OUTPUTS, address, timestamp, txid, outputIndex].join('-'),
|
||||||
|
value: [output.satoshis, script, height].join(':')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tx.isCoinbase()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < inputs.length; j++) {
|
||||||
|
var input = inputs[j];
|
||||||
|
|
||||||
|
var prevTxId = input.prevTxId.toString('hex');
|
||||||
|
var prevOutputIndex = input.outputIndex;
|
||||||
|
var timestamp = block.timestamp.getTime();
|
||||||
|
var inputIndex = j;
|
||||||
|
|
||||||
|
operations.push({
|
||||||
|
type: action,
|
||||||
|
key: [DB.PREFIXES.SPENTS, prevTxId, prevOutputIndex].join('-'),
|
||||||
|
value: [txid, inputIndex, timestamp].join(':')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setImmediate(function() {
|
||||||
|
callback(null, operations);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._updateTransactions = function(block, addTransaction, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
DB.super_.prototype._updateTransactions.call(self, block, addTransaction, function(err, operations) {
|
||||||
|
if(err || !addTransaction) {
|
||||||
|
return callback(err, operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove transactions from mempool with inputs that were spent
|
||||||
|
var mempoolTransactions = self.mempool.getTransactions();
|
||||||
|
var blockTransactions = self.getTransactionsFromBlock(block);
|
||||||
|
var newMempoolTransactions = [];
|
||||||
|
|
||||||
|
for(var i = 0; i < mempoolTransactions.length; i++) {
|
||||||
|
var txHasInputsInBlock = false;
|
||||||
|
for(var j = 0; j < mempoolTransactions[i].inputs.length; j++) {
|
||||||
|
for(var k = 0; k < blockTransactions.length; k++) {
|
||||||
|
for(var l = 0; l < blockTransactions[k].inputs.length; l++) {
|
||||||
|
var mempoolInput = mempoolTransactions[i].inputs[j];
|
||||||
|
var blockInput = blockTransactions[k].inputs[l];
|
||||||
|
if(mempoolInput.prevTxId === blockInput.prevTxId &&
|
||||||
|
mempoolInput.outputIndex === blockInput.outputIndex) {
|
||||||
|
txHasInputsInBlock = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(txHasInputsInBlock) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(txHasInputsInBlock) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!txHasInputsInBlock) {
|
||||||
|
newMempoolTransactions.push(mempoolTransactions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mempool.transactions = newMempoolTransactions;
|
||||||
|
|
||||||
|
callback(null, operations);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._onChainAddBlock = function(block, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
log.debug('DB handling new chain block');
|
||||||
|
|
||||||
|
// Remove block from mempool
|
||||||
|
self.mempool.removeBlock(block.hash);
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
this._updateOutputs.bind(this, block, true), // add outputs
|
||||||
|
this._updateTransactions.bind(this, block, true) // add transactions
|
||||||
|
], function(err, results) {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations = [];
|
||||||
|
for (var i = 0; i < results.length; i++) {
|
||||||
|
operations = operations.concat(results[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug('Updating the database with operations', operations);
|
||||||
|
|
||||||
|
self.store.batch(operations, callback);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DB.prototype._onChainRemoveBlock = function(block, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
this._updateOutputs.bind(this, block, false), // remove outputs
|
||||||
|
this._updateTransactions.bind(this, block, false) // remove transactions
|
||||||
|
], function(err, results) {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations = [];
|
||||||
|
for (var i = 0; i < results.length; i++) {
|
||||||
|
operations = operations.concat(results[i]);
|
||||||
|
}
|
||||||
|
self.store.batch(operations, callback);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getAPIMethods = function() {
|
||||||
|
return [
|
||||||
|
['getTransaction', this, this.getTransaction, 2],
|
||||||
|
['getBalance', this, this.getBalance, 2],
|
||||||
|
['sendFunds', this, this.sendFunds, 2],
|
||||||
|
['getOutputs', this, this.getOutputs, 2],
|
||||||
|
['getUnspentOutputs', this, this.getUnspentOutputs, 2],
|
||||||
|
['isSpent', this, this.isSpent, 2]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getBalance = function(address, queryMempool, callback) {
|
||||||
|
this.getUnspentOutputs(address, queryMempool, function(err, outputs) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var satoshis = outputs.map(function(output) {
|
||||||
|
return output.satoshis;
|
||||||
|
});
|
||||||
|
|
||||||
|
var sum = satoshis.reduce(function(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return callback(null, sum);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getOutputs = function(address, queryMempool, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var outputs = [];
|
||||||
|
var key = [DB.PREFIXES.OUTPUTS, address].join('-');
|
||||||
|
|
||||||
|
var stream = this.store.createReadStream({
|
||||||
|
start: key,
|
||||||
|
end: key + '~'
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('data', function(data) {
|
||||||
|
|
||||||
|
var key = data.key.split('-');
|
||||||
|
var value = data.value.split(':');
|
||||||
|
|
||||||
|
var output = {
|
||||||
|
address: key[1],
|
||||||
|
txid: key[3],
|
||||||
|
outputIndex: Number(key[4]),
|
||||||
|
satoshis: Number(value[0]),
|
||||||
|
script: value[1],
|
||||||
|
blockHeight: Number(value[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs.push(output);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var error;
|
||||||
|
|
||||||
|
stream.on('error', function(streamError) {
|
||||||
|
if (streamError) {
|
||||||
|
error = streamError;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('close', function() {
|
||||||
|
if (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(queryMempool) {
|
||||||
|
var mempoolOutputs = self._getMempoolOutputs(address);
|
||||||
|
|
||||||
|
outputs = outputs.concat(self._getMempoolOutputs(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, outputs);
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._getMempoolOutputs = function(address) {
|
||||||
|
var outputs = [];
|
||||||
|
|
||||||
|
var transactions = this.mempool.getTransactions();
|
||||||
|
transactions.forEach(function(tx) {
|
||||||
|
// add additional info to outputs
|
||||||
|
var outputObjects = [];
|
||||||
|
for(var i = 0; i < tx.outputs.length; i++) {
|
||||||
|
var output = {};
|
||||||
|
output.script = tx.outputs[i].script.toString();
|
||||||
|
output.satoshis = tx.outputs[i].satoshis;
|
||||||
|
output.txid = tx.hash;
|
||||||
|
output.outputIndex = i;
|
||||||
|
output.address = tx.outputs[i].script.toAddress().toString();
|
||||||
|
outputObjects.push(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered = outputObjects.filter(function(output) {
|
||||||
|
return address.toString() === output.address;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(filtered.length) {
|
||||||
|
outputs = outputs.concat(filtered);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.getUnspentOutputs = function(address, queryMempool, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.getOutputs(address, queryMempool, function(err, outputs) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
} else if(!outputs.length) {
|
||||||
|
return callback(new errors.NoOutputs('Address ' + address + ' has no outputs'), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isUnspent = function(output, callback) {
|
||||||
|
self.isUnspent(output, queryMempool, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
async.filter(outputs, isUnspent, function(results) {
|
||||||
|
callback(null, results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.isUnspent = function(output, queryMempool, callback) {
|
||||||
|
this.isSpent(output, queryMempool, function(spent) {
|
||||||
|
callback(!spent);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.isSpent = function(output, queryMempool, callback) {
|
||||||
|
if(queryMempool && this._isSpentMempool(output)) {
|
||||||
|
return callback(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSpentDB(output, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype.isSpentDB = function(output, callback) {
|
||||||
|
// Query bitcoind
|
||||||
|
return callback(null, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
DB.prototype._isSpentMempool = function(output) {
|
||||||
|
// Query bitcoind
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = DB;
|
|
@ -0,0 +1,8 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var createError = require('errno').create;
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
|
||||||
|
var errors = chainlib.errors;
|
||||||
|
|
||||||
|
module.exports = errors;
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"livenet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
|
||||||
|
"testnet": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var Chain = require('./chain');
|
||||||
|
var Block = require('./block');
|
||||||
|
var DB = require('./db');
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var P2P = chainlib.P2P;
|
||||||
|
var BaseNode = chainlib.Node;
|
||||||
|
var util = require('util');
|
||||||
|
var log = chainlib.log;
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var Networks = bitcore.Networks;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
|
var genesis = require('./genesis.json');
|
||||||
|
var bitcoind = require('./bitcoind');
|
||||||
|
|
||||||
|
function Node(config) {
|
||||||
|
BaseNode.call(this, config);
|
||||||
|
this.testnet = config.testnet;
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Node, BaseNode);
|
||||||
|
|
||||||
|
Node.prototype._loadConfiguration = function(config) {
|
||||||
|
var self = this;
|
||||||
|
this._loadBitcoind(config);
|
||||||
|
Node.super_.prototype._loadConfiguration.call(self, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.SYNC_STRATEGIES = {
|
||||||
|
P2P: 'p2p',
|
||||||
|
BITCOIND: 'bitcoind'
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype.setSyncStrategy = function(strategy) {
|
||||||
|
this.syncStrategy = strategy;
|
||||||
|
|
||||||
|
if (this.syncStrategy === Node.SYNC_STRATEGIES.P2P) {
|
||||||
|
this.p2p.startSync();
|
||||||
|
} else if (this.syncStrategy === Node.SYNC_STRATEGIES.BITCOIND) {
|
||||||
|
this.p2p.disableSync = true;
|
||||||
|
this._syncBitcoind();
|
||||||
|
} else {
|
||||||
|
throw new Error('Strategy "' + strategy + '" is unknown.');
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._loadBitcoind = function(config) {
|
||||||
|
var bitcoindConfig = {};
|
||||||
|
if (config.testnet) {
|
||||||
|
bitcoindConfig.directory = '~/.bitcoin/testnet3';
|
||||||
|
} else {
|
||||||
|
bitcoindConfig.directory = '~/.bitcoin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the bitcoind daemon
|
||||||
|
this.bitcoind = bitcoind(bitcoindConfig);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._syncBitcoind = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
log.info('Starting Bitcoind Sync');
|
||||||
|
|
||||||
|
var info = self.bitcoind.getInfo();
|
||||||
|
var height;
|
||||||
|
|
||||||
|
async.whilst(function() {
|
||||||
|
if (self.syncStrategy !== Node.SYNC_STRATEGIES.BITCOIND) {
|
||||||
|
log.info('Stopping Bitcoind Sync');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
height = self.chain.tip.__height;
|
||||||
|
return height < info.blocks;
|
||||||
|
}, function(next) {
|
||||||
|
self.bitcoind.getBlock(height + 1, function(err, blockBuffer) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
self.chain.addBlock(self.Block.fromBuffer(blockBuffer), next);
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
return self.emit('error', err);
|
||||||
|
}
|
||||||
|
// we're done resume syncing via p2p to handle forks
|
||||||
|
self.p2p.synced = true;
|
||||||
|
self.setSyncStrategy(Node.SYNC_STRATEGIES.P2P);
|
||||||
|
self.emit('synced');
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._loadNetwork = function(config) {
|
||||||
|
if (config.network) {
|
||||||
|
Networks.add(config.network);
|
||||||
|
this.network = Networks.get(config.network.name);
|
||||||
|
} else if (config.testnet) {
|
||||||
|
this.network = Networks.get('testnet');
|
||||||
|
} else {
|
||||||
|
this.network = Networks.get('livenet');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._loadDB = function(config) {
|
||||||
|
if (config.DB) {
|
||||||
|
// Other modules can inherit from our DB and replace it with their own
|
||||||
|
DB = config.DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.db = new DB(config.db);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._loadP2P = function(config) {
|
||||||
|
if (!config.p2p) {
|
||||||
|
config.p2p = {};
|
||||||
|
}
|
||||||
|
config.p2p.noListen = true;
|
||||||
|
config.p2p.network = this.network;
|
||||||
|
config.p2p.Transaction = this.db.Transaction;
|
||||||
|
config.p2p.Block = this.Block;
|
||||||
|
config.p2p.disableSync = true; // Disable p2p syncing and instead use bitcoind sync
|
||||||
|
this.p2p = new P2P(config.p2p);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._loadConsensus = function(config) {
|
||||||
|
if (!config.consensus) {
|
||||||
|
config.consensus = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Block = Block;
|
||||||
|
|
||||||
|
var genesisBlock;
|
||||||
|
if (config.genesis) {
|
||||||
|
genesisBlock = config.genesis;
|
||||||
|
} else if (config.testnet) {
|
||||||
|
genesisBlock = genesis.testnet;
|
||||||
|
} else {
|
||||||
|
genesisBlock = genesis.livenet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isString(genesisBlock)) {
|
||||||
|
genesisBlock = this.Block.fromBuffer(new Buffer(genesisBlock, 'hex'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass genesis to chain
|
||||||
|
config.consensus.genesis = genesisBlock;
|
||||||
|
this.chain = new Chain(config.consensus);
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._initializeBitcoind = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Bitcoind
|
||||||
|
this.bitcoind.on('ready', function(status) {
|
||||||
|
log.info('Bitcoin Daemon Ready');
|
||||||
|
self.db.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bitcoind.on('open', function(status) {
|
||||||
|
log.info('Bitcoin Core Daemon Status:', status);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bitcoind.on('error', function(err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._initializeDatabase = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Database
|
||||||
|
this.db.on('ready', function() {
|
||||||
|
log.info('Bitcoin Database Ready');
|
||||||
|
self.chain.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.db.on('error', function(err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._initializeChain = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
this.chain.on('ready', function() {
|
||||||
|
log.info('Bitcoin Chain Ready');
|
||||||
|
self.p2p.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.chain.on('error', function(err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._initializeP2P = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Peer-to-Peer
|
||||||
|
this.p2p.on('ready', function() {
|
||||||
|
log.info('Bitcoin P2P Ready');
|
||||||
|
self.emit('ready');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.p2p.on('synced', function() {
|
||||||
|
log.info('Bitcoin P2P Synced');
|
||||||
|
self.emit('synced');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.p2p.on('error', function(err) {
|
||||||
|
Error.captureStackTrace(err);
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Node.prototype._initialize = function() {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// DB References
|
||||||
|
this.db.chain = this.chain;
|
||||||
|
this.db.Block = this.Block;
|
||||||
|
this.db.bitcoind = this.bitcoind;
|
||||||
|
|
||||||
|
// Chain References
|
||||||
|
this.chain.db = this.db;
|
||||||
|
this.chain.p2p = this.p2p;
|
||||||
|
|
||||||
|
// P2P References
|
||||||
|
this.p2p.db = this.db;
|
||||||
|
this.p2p.chain = this.chain;
|
||||||
|
|
||||||
|
// Setup Chain of Events
|
||||||
|
this._initializeBitcoind();
|
||||||
|
this._initializeDatabase();
|
||||||
|
this._initializeChain();
|
||||||
|
this._initializeP2P();
|
||||||
|
|
||||||
|
this.on('ready', function() {
|
||||||
|
self.setSyncStrategy(Node.SYNC_STRATEGIES.BITCOIND);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Node;
|
|
@ -0,0 +1,133 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var bitcoinconsensus = require('libbitcoinconsensus');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var chainlib = require('chainlib');
|
||||||
|
var BaseTransaction = chainlib.Transaction;
|
||||||
|
var BaseDatabase = chainlib.DB;
|
||||||
|
var levelup = chainlib.deps.levelup;
|
||||||
|
|
||||||
|
Transaction.prototype.validate = function(db, poolTransactions, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(db instanceof BaseDatabase)) {
|
||||||
|
throw new Error('First argument is expected to be an instance of Database');
|
||||||
|
}
|
||||||
|
|
||||||
|
// coinbase is valid
|
||||||
|
if (this.isCoinbase()) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
var verified = this.verify();
|
||||||
|
if(verified !== true) {
|
||||||
|
return callback(new Error(verified));
|
||||||
|
}
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
self._validateInputs.bind(self, db, poolTransactions),
|
||||||
|
self._validateOutputs.bind(self),
|
||||||
|
self._checkSufficientInputs.bind(self)
|
||||||
|
],
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._validateInputs = function(db, poolTransactions, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Verify inputs are unspent
|
||||||
|
async.each(self.inputs, function(input, next) {
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
self._populateInput.bind(self, db, input, poolTransactions),
|
||||||
|
self._checkSpent.bind(self, db, input, poolTransactions),
|
||||||
|
self._checkScript.bind(self, input, self.inputs.indexOf(input))
|
||||||
|
],
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
async.each(
|
||||||
|
this.inputs,
|
||||||
|
function(input, next) {
|
||||||
|
self._populateInput(db, input, poolTransactions, next);
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._populateInput = function(db, input, poolTransactions, callback) {
|
||||||
|
if (!input.prevTxId || !Buffer.isBuffer(input.prevTxId)) {
|
||||||
|
return callback(new Error('Input is expected to have prevTxId as a buffer'));
|
||||||
|
}
|
||||||
|
var txid = input.prevTxId.toString('hex');
|
||||||
|
db.getTransactionFromDB(txid, function(err, prevTx) {
|
||||||
|
if(err instanceof levelup.errors.NotFoundError) {
|
||||||
|
// Check the pool for transaction
|
||||||
|
for(var i = 0; i < poolTransactions.length; i++) {
|
||||||
|
if(txid === poolTransactions[i].hash) {
|
||||||
|
input.output = poolTransactions[i].outputs[input.outputIndex];
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(new Error('Previous tx ' + input.prevTxId.toString('hex') + ' not found'));
|
||||||
|
} else if(err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
input.output = prevTx.outputs[input.outputIndex];
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._checkSpent = function(db, input, poolTransactions, callback) {
|
||||||
|
// TODO check and see if another transaction in the pool spent the output
|
||||||
|
db.isSpentDB(input, function(spent) {
|
||||||
|
if(spent) {
|
||||||
|
return callback(new Error('Input already spent'));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._checkScript = function(input, index, callback) {
|
||||||
|
if (input.output.script) {
|
||||||
|
var scriptPubkey = input.output._scriptBuffer;
|
||||||
|
var txTo = this.toBuffer();
|
||||||
|
var valid = bitcoinconsensus.verifyScript(scriptPubkey, txTo, index);
|
||||||
|
if(valid) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callback(new Error('Script does not validate'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._validateOutputs = function(callback) {
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._checkSufficientInputs = function(callback) {
|
||||||
|
var inputTotal = this._getInputAmount();
|
||||||
|
var outputTotal = this._getOutputAmount();
|
||||||
|
if(inputTotal < outputTotal) {
|
||||||
|
return callback(new Error('Insufficient inputs'));
|
||||||
|
} else {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.manyToBuffer = function(transactions) {
|
||||||
|
return BaseTransaction.manyToBuffer(transactions);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Transaction;
|
11
package.json
11
package.json
|
@ -20,6 +20,10 @@
|
||||||
{
|
{
|
||||||
"name": "Chris Kleeschulte",
|
"name": "Chris Kleeschulte",
|
||||||
"email": "chrisk@bitpay.com"
|
"email": "chrisk@bitpay.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Patrick Nagurny",
|
||||||
|
"email": "patrick@bitpay.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -36,10 +40,13 @@
|
||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.1",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
"nan": "1.3.0",
|
"nan": "1.3.0",
|
||||||
"tiny": "0.0.10"
|
"tiny": "0.0.10",
|
||||||
|
"chainlib": "^0.1.1",
|
||||||
|
"libbitcoinconsensus": "^0.0.3",
|
||||||
|
"errno": "^0.1.2",
|
||||||
|
"async": "1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "1.2.1",
|
|
||||||
"benchmark": "1.0.0",
|
"benchmark": "1.0.0",
|
||||||
"bitcoin": "^2.3.2",
|
"bitcoin": "^2.3.2",
|
||||||
"bitcore": "^0.12.12",
|
"bitcore": "^0.12.12",
|
||||||
|
|
Loading…
Reference in New Issue