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",
|
||||
"email": "chrisk@bitpay.com"
|
||||
},
|
||||
{
|
||||
"name": "Patrick Nagurny",
|
||||
"email": "patrick@bitpay.com"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
|
@ -36,10 +40,13 @@
|
|||
"bindings": "^1.2.1",
|
||||
"mkdirp": "0.5.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": {
|
||||
"async": "1.2.1",
|
||||
"benchmark": "1.0.0",
|
||||
"bitcoin": "^2.3.2",
|
||||
"bitcore": "^0.12.12",
|
||||
|
|
Loading…
Reference in New Issue