verify error refactor.
This commit is contained in:
parent
945c2f9564
commit
4d2d9b328c
@ -13,6 +13,7 @@ var network = bcoin.protocol.network;
|
|||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
var assert = utils.assert;
|
var assert = utils.assert;
|
||||||
var BufferReader = require('./reader');
|
var BufferReader = require('./reader');
|
||||||
|
var VerifyError = utils.VerifyError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chain
|
* Chain
|
||||||
@ -61,15 +62,27 @@ utils.inherits(Chain, EventEmitter);
|
|||||||
Chain.prototype._init = function _init() {
|
Chain.prototype._init = function _init() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
function getPeer() {
|
||||||
|
if (!self.node || !self.node.pool)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return self.node.pool.peers.load;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHost() {
|
||||||
|
var peer = getPeer();
|
||||||
|
if (peer)
|
||||||
|
return peer.host;
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
// Hook into events for debugging
|
// Hook into events for debugging
|
||||||
// this.on('block', function(block, entry, peer) {
|
// this.on('block', function(block, entry) {
|
||||||
// var host = peer ? peer.host : 'unknown';
|
|
||||||
// utils.debug('Block %s (%d) added to chain (%s)',
|
// utils.debug('Block %s (%d) added to chain (%s)',
|
||||||
// utils.revHex(entry.hash), entry.height, host);
|
// utils.revHex(entry.hash), entry.height, getHost());
|
||||||
// });
|
// });
|
||||||
|
|
||||||
this.on('competitor', function(block, entry, peer) {
|
this.on('competitor', function(block, entry) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug('Heads up: Competing chain at height %d:'
|
utils.debug('Heads up: Competing chain at height %d:'
|
||||||
+ ' tip-height=%d competitor-height=%d'
|
+ ' tip-height=%d competitor-height=%d'
|
||||||
+ ' tip-hash=%s competitor-hash=%s'
|
+ ' tip-hash=%s competitor-hash=%s'
|
||||||
@ -83,64 +96,58 @@ Chain.prototype._init = function _init() {
|
|||||||
self.tip.chainwork.toString(),
|
self.tip.chainwork.toString(),
|
||||||
entry.chainwork.toString(),
|
entry.chainwork.toString(),
|
||||||
self.tip.chainwork.sub(entry.chainwork).toString(),
|
self.tip.chainwork.sub(entry.chainwork).toString(),
|
||||||
host);
|
getHost());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('resolved', function(block, entry, peer) {
|
this.on('resolved', function(block, entry) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug('Orphan %s (%d) was resolved (%s)',
|
utils.debug('Orphan %s (%d) was resolved (%s)',
|
||||||
utils.revHex(entry.hash), entry.height, host);
|
utils.revHex(entry.hash), entry.height, getHost());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('checkpoint', function(block, data, peer) {
|
this.on('checkpoint', function(block, data) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug('Hit checkpoint block %s (%d) (%s)',
|
utils.debug('Hit checkpoint block %s (%d) (%s)',
|
||||||
utils.revHex(data.checkpoint), data.height, host);
|
utils.revHex(data.checkpoint), data.height, getHost());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('fork', function(block, data, peer) {
|
this.on('fork', function(block, data) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug(
|
utils.debug(
|
||||||
'Fork at height %d: expected=%s received=%s checkpoint=%s peer=%s',
|
'Fork at height %d: expected=%s received=%s checkpoint=%s',
|
||||||
data.height,
|
data.height,
|
||||||
utils.revHex(data.expected),
|
utils.revHex(data.expected),
|
||||||
utils.revHex(data.received),
|
utils.revHex(data.received),
|
||||||
data.checkpoint,
|
data.checkpoint,
|
||||||
host
|
getHost()
|
||||||
);
|
);
|
||||||
if (data.checkpoint)
|
if (data.checkpoint)
|
||||||
utils.debug('WARNING: Block failed a checkpoint.');
|
utils.debug('WARNING: Block failed a checkpoint.');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('invalid', function(block, data, peer) {
|
this.on('invalid', function(block, data) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug(
|
utils.debug(
|
||||||
'Invalid block at height %d: hash=%s peer=%s',
|
'Invalid block at height %d: hash=%s',
|
||||||
data.height,
|
data.height,
|
||||||
utils.revHex(data.hash),
|
utils.revHex(data.hash),
|
||||||
host
|
getHost()
|
||||||
);
|
);
|
||||||
if (data.chain) {
|
if (data.chain) {
|
||||||
utils.debug(
|
utils.debug(
|
||||||
'Peer is sending an invalid continuation chain (%s)',
|
'Peer is sending an invalid continuation chain (%s)',
|
||||||
host);
|
getHost());
|
||||||
} else if (data.seen) {
|
} else if (data.seen) {
|
||||||
utils.debug('Peer is sending an invalid chain (%s)', host);
|
utils.debug('Peer is sending an invalid chain (%s)', getHost());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('exists', function(block, data, peer) {
|
this.on('exists', function(block, data) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
|
||||||
utils.debug('Already have block %s (%s)',
|
utils.debug('Already have block %s (%s)',
|
||||||
data.height, host);
|
data.height, getHost());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('orphan', function(block, data, peer) {
|
this.on('orphan', function(block, data) {
|
||||||
var host = peer ? peer.host : 'unknown';
|
utils.debug('Handled orphan %s (%s)', utils.revHex(data.hash), getHost());
|
||||||
utils.debug('Handled orphan %s (%s)', utils.revHex(data.hash), host);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('purge', function(count, size, peer) {
|
this.on('purge', function(count, size) {
|
||||||
utils.debug('Warning: %d (%dmb) orphans cleared!', count, utils.mb(size));
|
utils.debug('Warning: %d (%dmb) orphans cleared!', count, utils.mb(size));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -366,37 +373,28 @@ Chain.prototype._preload = function _preload(callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._verifyContext = function _verifyContext(block, prev, peer, callback) {
|
Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this._verify(block, prev, peer, function(err, flags) {
|
this._verify(block, prev, function(err, flags) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (flags === false)
|
self._checkDuplicates(block, prev, function(err, result) {
|
||||||
return callback(null, false);
|
|
||||||
|
|
||||||
self._checkDuplicates(block, prev, peer, function(err, result) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!result)
|
self._checkInputs(block, prev, flags, function(err) {
|
||||||
return callback(null, false);
|
|
||||||
|
|
||||||
self._checkInputs(block, prev, flags, peer, function(err, result) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!result)
|
return callback();
|
||||||
return callback(null, false);
|
|
||||||
|
|
||||||
return callback(null, true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
Chain.prototype._verify = function _verify(block, prev, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
|
var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
|
||||||
var height, ts, i, tx, coinbaseHeight;
|
var height, ts, i, tx, coinbaseHeight;
|
||||||
@ -408,11 +406,8 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
callback(err, result);
|
callback(err, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!block.verify(ret)) {
|
if (!block.verify(ret))
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', ret.reason, ret.score));
|
||||||
block, 'invalid', ret.reason, ret.score, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the genesis block
|
// Skip the genesis block
|
||||||
if (block.isGenesis())
|
if (block.isGenesis())
|
||||||
@ -421,12 +416,12 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
// Ensure it's not an orphan
|
// Ensure it's not an orphan
|
||||||
if (!prev) {
|
if (!prev) {
|
||||||
utils.debug('Block has no previous entry: %s', block.rhash);
|
utils.debug('Block has no previous entry: %s', block.rhash);
|
||||||
return done(null, false);
|
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.ensureAncestors(function(err) {
|
prev.ensureAncestors(function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return done(err);
|
||||||
|
|
||||||
height = prev.height + 1;
|
height = prev.height + 1;
|
||||||
medianTime = prev.getMedianTime();
|
medianTime = prev.getMedianTime();
|
||||||
@ -434,16 +429,12 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
// Ensure the timestamp is correct
|
// Ensure the timestamp is correct
|
||||||
if (block.ts <= medianTime) {
|
if (block.ts <= medianTime) {
|
||||||
utils.debug('Block time is lower than median: %s', block.rhash);
|
utils.debug('Block time is lower than median: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'time-too-old', 0));
|
||||||
block, 'invalid', 'time-too-old', 0, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.bits !== self.getTarget(prev, block)) {
|
if (block.bits !== self.getTarget(prev, block)) {
|
||||||
utils.debug('Block is using wrong target: %s', block.rhash);
|
utils.debug('Block is using wrong target: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-diffbits', 100));
|
||||||
block, 'invalid', 'bad-diffbits', 100, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For some reason bitcoind has p2sh in the
|
// For some reason bitcoind has p2sh in the
|
||||||
@ -461,27 +452,21 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
// once the majority of blocks are using it.
|
// once the majority of blocks are using it.
|
||||||
if (block.version < 2 && prev.isOutdated(2)) {
|
if (block.version < 2 && prev.isOutdated(2)) {
|
||||||
utils.debug('Block is outdated (v2): %s', block.rhash);
|
utils.debug('Block is outdated (v2): %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||||
block, 'obsolete', 'bad-version', 0, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow version 3 blocks (sig validation)
|
// Only allow version 3 blocks (sig validation)
|
||||||
// once the majority of blocks are using it.
|
// once the majority of blocks are using it.
|
||||||
if (block.version < 3 && prev.isOutdated(3)) {
|
if (block.version < 3 && prev.isOutdated(3)) {
|
||||||
utils.debug('Block is outdated (v3): %s', block.rhash);
|
utils.debug('Block is outdated (v3): %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||||
block, 'obsolete', 'bad-version', 0, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow version 4 blocks (checklocktimeverify)
|
// Only allow version 4 blocks (checklocktimeverify)
|
||||||
// once the majority of blocks are using it.
|
// once the majority of blocks are using it.
|
||||||
if (block.version < 4 && prev.isOutdated(4)) {
|
if (block.version < 4 && prev.isOutdated(4)) {
|
||||||
utils.debug('Block is outdated (v4): %s', block.rhash);
|
utils.debug('Block is outdated (v4): %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||||
block, 'obsolete', 'bad-version', 0, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow version 5 blocks (segwit)
|
// Only allow version 5 blocks (segwit)
|
||||||
@ -489,9 +474,7 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
|
if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
|
||||||
if (block.version < 5 && prev.isOutdated(5)) {
|
if (block.version < 5 && prev.isOutdated(5)) {
|
||||||
utils.debug('Block is outdated (v5): %s', block.rhash);
|
utils.debug('Block is outdated (v5): %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||||
block, 'obsolete', 'bad-version', 0, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,25 +518,19 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
if (coinbaseHeight) {
|
if (coinbaseHeight) {
|
||||||
if (block.getCoinbaseHeight() !== height) {
|
if (block.getCoinbaseHeight() !== height) {
|
||||||
utils.debug('Block has bad coinbase height: %s', block.rhash);
|
utils.debug('Block has bad coinbase height: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-cb-height', 100));
|
||||||
block, 'invalid', 'bad-cb-height', 100, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.version >= 5 && segwit) {
|
if (block.version >= 5 && segwit) {
|
||||||
if (block.commitmentHash !== block.getCommitmentHash()) {
|
if (block.commitmentHash !== block.getCommitmentHash()) {
|
||||||
utils.debug('Block failed witnessroot test: %s', block.rhash);
|
utils.debug('Block failed witnessroot test: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-blk-wit-length', 100));
|
||||||
block, 'invalid', 'bad-blk-wit-length', 100, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (block.hasWitness()) {
|
if (block.hasWitness()) {
|
||||||
utils.debug('Unexpected witness data found: %s', block.rhash);
|
utils.debug('Unexpected witness data found: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'unexpected-witness', 100));
|
||||||
block, 'invalid', 'unexpected-witness', 100, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,9 +545,7 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
// regards to nSequence and nLockTime.
|
// regards to nSequence and nLockTime.
|
||||||
if (!tx.isFinal(height, ts)) {
|
if (!tx.isFinal(height, ts)) {
|
||||||
utils.debug('TX is not final: %s (%s)', block.rhash, i);
|
utils.debug('TX is not final: %s (%s)', block.rhash, i);
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-txns-nonfinal', 10));
|
||||||
block, 'invalid', 'bad-txns-nonfinal', 10, peer);
|
|
||||||
return done(null, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,18 +553,18 @@ Chain.prototype._verify = function _verify(block, prev, peer, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, peer, callback) {
|
Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var height = prev.height + 1;
|
var height = prev.height + 1;
|
||||||
|
|
||||||
if (this.options.spv || block.type !== 'block')
|
if (this.options.spv || block.type !== 'block')
|
||||||
return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
if (block.isGenesis())
|
if (block.isGenesis())
|
||||||
return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
// Check all transactions
|
// Check all transactions
|
||||||
utils.everySerial(block.txs, function(tx, next) {
|
utils.forEachSerial(block.txs, function(tx, next) {
|
||||||
var hash = tx.hash('hex');
|
var hash = tx.hash('hex');
|
||||||
|
|
||||||
// BIP30 - Ensure there are no duplicate txids
|
// BIP30 - Ensure there are no duplicate txids
|
||||||
@ -603,41 +578,45 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, peer,
|
|||||||
if (result) {
|
if (result) {
|
||||||
utils.debug('Block is overwriting txids: %s', block.rhash);
|
utils.debug('Block is overwriting txids: %s', block.rhash);
|
||||||
if (network.type === 'main' && (height === 91842 || height === 91880))
|
if (network.type === 'main' && (height === 91842 || height === 91880))
|
||||||
return next(null, true);
|
return next();
|
||||||
self.emit('verify-error',
|
return next(new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100));
|
||||||
block, 'invalid', 'bad-txns-BIP30', 100, peer);
|
|
||||||
return next(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next(null, true);
|
next();
|
||||||
});
|
});
|
||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, callback) {
|
Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) {
|
||||||
var height = prev.height + 1;
|
var height = prev.height + 1;
|
||||||
var scriptCheck = true;
|
var scriptCheck = true;
|
||||||
|
var historical = false;
|
||||||
|
|
||||||
if (this.options.spv || block.type !== 'block')
|
if (this.options.spv || block.type !== 'block')
|
||||||
return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
if (block.isGenesis())
|
if (block.isGenesis())
|
||||||
return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
// If we are an ancestor of a checkpoint, we can
|
// If we are an ancestor of a checkpoint, we can
|
||||||
// skip the input verification.
|
// skip the input verification.
|
||||||
if (this.options.useCheckpoints) {
|
if (height <= network.checkpoints.lastHeight) {
|
||||||
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
|
if (this.options.useCheckpoints)
|
||||||
scriptCheck = false;
|
scriptCheck = false;
|
||||||
|
historical = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db.fillBlock(block, function(err) {
|
this.db.fillBlock(block, function(err) {
|
||||||
var i, j, input, tx, hash;
|
var ret = {};
|
||||||
var sigops = 0;
|
var sigops = 0;
|
||||||
|
var i, j, input, tx, hash;
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
|
if (!self._checkReward(block))
|
||||||
|
return callback(new VerifyError(block, 'invalid', 'bad-cb-amount', 100));
|
||||||
|
|
||||||
// Check all transactions
|
// Check all transactions
|
||||||
for (i = 0; i < block.txs.length; i++) {
|
for (i = 0; i < block.txs.length; i++) {
|
||||||
tx = block.txs[i];
|
tx = block.txs[i];
|
||||||
@ -653,19 +632,15 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, c
|
|||||||
|
|
||||||
if (sigops > constants.block.maxSigops) {
|
if (sigops > constants.block.maxSigops) {
|
||||||
utils.debug('Block has too many sigops: %s', block.rhash);
|
utils.debug('Block has too many sigops: %s', block.rhash);
|
||||||
self.emit('verify-error',
|
return callback(new VerifyError(block, 'invalid', 'bad-blk-sigops', 100));
|
||||||
block, 'invalid', 'bad-blk-sigops', 100, peer);
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coinbases do not have prevouts
|
// Coinbases do not have prevouts
|
||||||
if (tx.isCoinbase())
|
if (tx.isCoinbase())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (tx.getOutputValue().cmp(tx.getInputValue()) > 0) {
|
if (!tx.checkInputs(height, ret))
|
||||||
utils.debug('TX is spending funds it does not have: %s', tx.rhash);
|
return callback(new VerifyError(block, 'invalid', ret.reason, ret.score));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < tx.inputs.length; j++) {
|
for (j = 0; j < tx.inputs.length; j++) {
|
||||||
input = tx.inputs[j];
|
input = tx.inputs[j];
|
||||||
@ -675,19 +650,19 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, c
|
|||||||
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
|
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
|
||||||
block.rhash, tx.rhash,
|
block.rhash, tx.rhash,
|
||||||
utils.revHex(input.prevout.hash) + '/' + input.prevout.index);
|
utils.revHex(input.prevout.hash) + '/' + input.prevout.index);
|
||||||
if (height < network.checkpoints.lastHeight)
|
assert(!historical, 'BUG: Spent inputs in historical data!');
|
||||||
throw new Error('BUG: Spent inputs in historical data!');
|
return callback(new VerifyError(block,
|
||||||
self.emit('verify-error',
|
'invalid',
|
||||||
block, 'invalid', 'bad-txns-inputs-missingorspent', 100, peer);
|
'bad-txns-inputs-missingorspent',
|
||||||
return callback(null, false);
|
100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.options.verifySync !== true)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!scriptCheck)
|
if (!scriptCheck)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Disable. Use workers and verifyAsync for now.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Verify the scripts
|
// Verify the scripts
|
||||||
if (!tx.verify(j, true, flags)) {
|
if (!tx.verify(j, true, flags)) {
|
||||||
utils.debug('Block has invalid inputs: %s (%s/%d)',
|
utils.debug('Block has invalid inputs: %s (%s/%d)',
|
||||||
@ -701,23 +676,39 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, peer, c
|
|||||||
utils.toHex(input.coin.script.encode()));
|
utils.toHex(input.coin.script.encode()));
|
||||||
utils.debug('Reserialized Script: %s',
|
utils.debug('Reserialized Script: %s',
|
||||||
utils.toHex(input.coin.script.clone().encode()));
|
utils.toHex(input.coin.script.clone().encode()));
|
||||||
if (height < network.checkpoints.lastHeight)
|
assert(!historical, 'BUG: Invalid inputs in historical data!');
|
||||||
throw new Error('BUG: Bad inputs in historical data!');
|
return callback(new VerifyError(block,
|
||||||
return callback(null, false);
|
'invalid',
|
||||||
|
'mandatory-script-verify-flag-failed',
|
||||||
|
100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable. Use workers and verifyAsync for now.
|
if (self.options.verifySync === true)
|
||||||
// return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
if (!scriptCheck)
|
if (!scriptCheck)
|
||||||
return callback(null, true);
|
return callback();
|
||||||
|
|
||||||
// Verify all txs in parallel.
|
// Verify all txs in parallel.
|
||||||
utils.every(block.txs, function(tx, next) {
|
utils.every(block.txs, function(tx, next) {
|
||||||
tx.verifyAsync(null, true, flags, next);
|
tx.verifyAsync(null, true, flags, next);
|
||||||
}, callback);
|
}, function(err, verified) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
if (!verified) {
|
||||||
|
utils.debug('Block has invalid inputs: %s', block.rhash);
|
||||||
|
assert(!historical, 'BUG: Invalid inputs in historical data!');
|
||||||
|
return callback(new VerifyError(block,
|
||||||
|
'invalid',
|
||||||
|
'mandatory-script-verify-flag-failed',
|
||||||
|
100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -730,13 +721,7 @@ Chain.prototype._checkReward = function _checkReward(block) {
|
|||||||
for (i = 1; i < block.txs.length; i++)
|
for (i = 1; i < block.txs.length; i++)
|
||||||
actual.iadd(block.txs[i].getFee());
|
actual.iadd(block.txs[i].getFee());
|
||||||
|
|
||||||
if (claimed.cmp(actual) > 0) {
|
return claimed.cmp(actual) <= 0;
|
||||||
self.emit('verify-error',
|
|
||||||
block, 'invalid', 'bad-cb-amount', 100, peer);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.getHeight = function getHeight(hash) {
|
Chain.prototype.getHeight = function getHeight(hash) {
|
||||||
@ -1001,14 +986,14 @@ Chain.prototype.onFlush = function onFlush(callback) {
|
|||||||
return this.locker.onFlush(callback);
|
return this.locker.onFlush(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.add = function add(block, peer, callback, force) {
|
Chain.prototype.add = function add(block, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var ret = {};
|
var ret = {};
|
||||||
|
|
||||||
assert(this.loaded);
|
assert(this.loaded);
|
||||||
|
|
||||||
var unlock = this._lock(add, [block, peer, callback], force);
|
var unlock = this._lock(add, [block, callback], force);
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -1024,11 +1009,9 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
hash: hash,
|
hash: hash,
|
||||||
seen: !!self.invalid[hash],
|
seen: !!self.invalid[hash],
|
||||||
chain: !!self.invalid[prevHash]
|
chain: !!self.invalid[prevHash]
|
||||||
}, peer);
|
});
|
||||||
self.invalid[hash] = true;
|
self.invalid[hash] = true;
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'duplicate', 'duplicate', 0));
|
||||||
block, 'duplicate', 'duplicate', 0, peer);
|
|
||||||
return done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we already have this block?
|
// Do we already have this block?
|
||||||
@ -1040,8 +1023,8 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
self.emit('exists', block, {
|
self.emit('exists', block, {
|
||||||
height: -1,
|
height: -1,
|
||||||
hash: hash
|
hash: hash
|
||||||
}, peer);
|
});
|
||||||
return done();
|
return done(new VerifyError(block, 'duplicate', 'duplicate', 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the previous block height/index.
|
// Find the previous block height/index.
|
||||||
@ -1062,10 +1045,8 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
hash: hash,
|
hash: hash,
|
||||||
seen: false,
|
seen: false,
|
||||||
chain: false
|
chain: false
|
||||||
}, peer);
|
});
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', ret.reason, ret.score));
|
||||||
block, 'invalid', ret.reason, ret.score, peer);
|
|
||||||
return done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for genesis block.
|
// Special case for genesis block.
|
||||||
@ -1087,7 +1068,7 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
expected: orphan.hash('hex'),
|
expected: orphan.hash('hex'),
|
||||||
received: hash,
|
received: hash,
|
||||||
checkpoint: false
|
checkpoint: false
|
||||||
}, peer);
|
});
|
||||||
|
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
@ -1096,12 +1077,9 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
height: block.getCoinbaseHeight(),
|
height: block.getCoinbaseHeight(),
|
||||||
hash: hash,
|
hash: hash,
|
||||||
seen: true
|
seen: true
|
||||||
}, peer);
|
});
|
||||||
|
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
|
||||||
block, 'invalid', 'bad-prevblk', 0, peer);
|
|
||||||
|
|
||||||
return done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the best height based on the coinbase.
|
// Update the best height based on the coinbase.
|
||||||
@ -1124,10 +1102,8 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
height: block.getCoinbaseHeight(),
|
height: block.getCoinbaseHeight(),
|
||||||
hash: hash,
|
hash: hash,
|
||||||
seen: false
|
seen: false
|
||||||
}, peer);
|
});
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
|
||||||
block, 'invalid', 'bad-prevblk', 0, peer);
|
|
||||||
return done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the checkpoint.
|
// Verify the checkpoint.
|
||||||
@ -1137,7 +1113,7 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
height: height,
|
height: height,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
checkpoint: checkpoint
|
checkpoint: checkpoint
|
||||||
}, peer);
|
});
|
||||||
|
|
||||||
// Block did not match the checkpoint. The
|
// Block did not match the checkpoint. The
|
||||||
// chain could be reset to the last sane
|
// chain could be reset to the last sane
|
||||||
@ -1154,12 +1130,12 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
expected: checkpoint,
|
expected: checkpoint,
|
||||||
received: hash,
|
received: hash,
|
||||||
checkpoint: true
|
checkpoint: true
|
||||||
}, peer);
|
});
|
||||||
|
|
||||||
self.emit('verify-error',
|
return done(new VerifyError(block,
|
||||||
block, 'checkpoint', 'checkpoint mismatch', 100, peer);
|
'checkpoint',
|
||||||
|
'checkpoint mismatch',
|
||||||
return done();
|
100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1178,10 +1154,10 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
try {
|
try {
|
||||||
block = block.toBlock();
|
block = block.toBlock();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ugly hack to handle
|
return done(new VerifyError(block,
|
||||||
// the error properly.
|
'malformed',
|
||||||
peer.parser.emit('error', e);
|
'error parsing message',
|
||||||
return done(e);
|
100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1196,30 +1172,28 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
// Do "contextual" verification on our block
|
// Do "contextual" verification on our block
|
||||||
// now that we're certain its previous
|
// now that we're certain its previous
|
||||||
// block is in the chain.
|
// block is in the chain.
|
||||||
self._verifyContext(block, prev, peer, function(err, verified) {
|
self._verifyContext(block, prev, function(err) {
|
||||||
var entry;
|
var entry;
|
||||||
|
|
||||||
// Couldn't verify block.
|
if (err) {
|
||||||
// Revert the height.
|
// Couldn't verify block.
|
||||||
if (err || !verified) {
|
// Revert the height.
|
||||||
block.height = -1;
|
block.height = -1;
|
||||||
block.txs.forEach(function(tx) {
|
block.txs.forEach(function(tx) {
|
||||||
tx.height = -1;
|
tx.height = -1;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (err)
|
if (err.type === 'VerifyError') {
|
||||||
|
self.invalid[hash] = true;
|
||||||
|
self.emit('invalid', block, {
|
||||||
|
height: height,
|
||||||
|
hash: hash,
|
||||||
|
seen: false,
|
||||||
|
chain: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return done(err);
|
return done(err);
|
||||||
|
|
||||||
if (!verified) {
|
|
||||||
self.invalid[hash] = true;
|
|
||||||
self.emit('invalid', block, {
|
|
||||||
height: height,
|
|
||||||
hash: hash,
|
|
||||||
seen: false,
|
|
||||||
chain: false
|
|
||||||
}, peer);
|
|
||||||
return done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new chain entry.
|
// Create a new chain entry.
|
||||||
@ -1246,12 +1220,12 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
// Emit our block (and potentially resolved
|
// Emit our block (and potentially resolved
|
||||||
// orphan) only if it is on the main chain.
|
// orphan) only if it is on the main chain.
|
||||||
if (mainChain)
|
if (mainChain)
|
||||||
self.emit('block', block, entry, peer);
|
self.emit('block', block, entry);
|
||||||
else
|
else
|
||||||
self.emit('competitor', block, entry, peer);
|
self.emit('competitor', block, entry);
|
||||||
|
|
||||||
if (!initial)
|
if (!initial)
|
||||||
self.emit('resolved', block, entry, peer);
|
self.emit('resolved', block, entry);
|
||||||
|
|
||||||
// No orphan chain.
|
// No orphan chain.
|
||||||
if (!self.orphan.map[hash])
|
if (!self.orphan.map[hash])
|
||||||
@ -1275,7 +1249,7 @@ Chain.prototype.add = function add(block, peer, callback, force) {
|
|||||||
// Failsafe for large orphan chains. Do not
|
// Failsafe for large orphan chains. Do not
|
||||||
// allow more than 20mb stored in memory.
|
// allow more than 20mb stored in memory.
|
||||||
if (self.orphan.size > self.orphanLimit)
|
if (self.orphan.size > self.orphanLimit)
|
||||||
self.pruneOrphans(peer);
|
self.pruneOrphans();
|
||||||
|
|
||||||
// Keep track of total blocks handled.
|
// Keep track of total blocks handled.
|
||||||
self.total += total;
|
self.total += total;
|
||||||
@ -1308,7 +1282,7 @@ Chain.prototype.purgeOrphans = function purgeOrphans() {
|
|||||||
this.orphan.size = 0;
|
this.orphan.size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.pruneOrphans = function pruneOrphans(peer) {
|
Chain.prototype.pruneOrphans = function pruneOrphans() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var best, last;
|
var best, last;
|
||||||
|
|
||||||
@ -1336,7 +1310,7 @@ Chain.prototype.pruneOrphans = function pruneOrphans(peer) {
|
|||||||
Object.keys(this.orphan.bmap).forEach(function(hash) {
|
Object.keys(this.orphan.bmap).forEach(function(hash) {
|
||||||
var orphan = self.orphan.bmap[hash];
|
var orphan = self.orphan.bmap[hash];
|
||||||
if (orphan !== best)
|
if (orphan !== best)
|
||||||
self.emit('unresolved', orphan, peer);
|
self.emit('unresolved', orphan);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.orphan.map = {};
|
this.orphan.map = {};
|
||||||
|
|||||||
@ -93,6 +93,7 @@ Locker.prototype.lock = function lock(func, args, force) {
|
|||||||
|
|
||||||
Locker.prototype.purgePending = function purgePending() {
|
Locker.prototype.purgePending = function purgePending() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var total = this.pending.length;
|
||||||
|
|
||||||
assert(this.add);
|
assert(this.add);
|
||||||
|
|
||||||
@ -109,6 +110,9 @@ Locker.prototype.purgePending = function purgePending() {
|
|||||||
this.jobs = this.jobs.filter(function(item) {
|
this.jobs = this.jobs.filter(function(item) {
|
||||||
return item[0] !== self.add;
|
return item[0] !== self.add;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (total !== 0)
|
||||||
|
this.emit('flush');
|
||||||
};
|
};
|
||||||
|
|
||||||
Locker.prototype.onFlush = function onFlush(callback) {
|
Locker.prototype.onFlush = function onFlush(callback) {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ var utils = require('./utils');
|
|||||||
var assert = utils.assert;
|
var assert = utils.assert;
|
||||||
var BufferWriter = require('./writer');
|
var BufferWriter = require('./writer');
|
||||||
var BufferReader = require('./reader');
|
var BufferReader = require('./reader');
|
||||||
var DUMMY_PEER = { sendReject: function() {} };
|
var VerifyError = utils.VerifyError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mempool
|
* Mempool
|
||||||
@ -283,24 +283,16 @@ Mempool.prototype.hasTX = function hasTX(hash, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Mempool.prototype.add =
|
Mempool.prototype.add =
|
||||||
Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash, ts, height, now;
|
var hash, ts, height, now;
|
||||||
var flags = Mempool.flags;
|
var flags = Mempool.flags;
|
||||||
var ret = {};
|
var ret = {};
|
||||||
|
|
||||||
var unlock = this._lock(addTX, [tx, peer, callback], force);
|
var unlock = this._lock(addTX, [tx, callback], force);
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (typeof peer === 'function') {
|
|
||||||
callback = peer;
|
|
||||||
peer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!peer)
|
|
||||||
peer = DUMMY_PEER;
|
|
||||||
|
|
||||||
if (this.chain.segwitActive) {
|
if (this.chain.segwitActive) {
|
||||||
flags |= constants.flags.VERIFY_WITNESS;
|
flags |= constants.flags.VERIFY_WITNESS;
|
||||||
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
|
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
|
||||||
@ -312,29 +304,22 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
callback = utils.asyncify(callback);
|
callback = utils.asyncify(callback);
|
||||||
|
|
||||||
if (tx.ts !== 0) {
|
if (tx.ts !== 0) {
|
||||||
peer.sendReject(tx, 'alreadyknown', 'txn-already-in-mempool', 0);
|
return callback(new VerifyError(tx,
|
||||||
return callback(new VerifyError(
|
|
||||||
'alreadyknown',
|
'alreadyknown',
|
||||||
'txn-already-in-mempool',
|
'txn-already-in-mempool',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.chain.segwitActive) {
|
if (!this.chain.segwitActive) {
|
||||||
if (tx.hasWitness()) {
|
if (tx.hasWitness())
|
||||||
peer.sendReject(tx, 'nonstandard', 'no-witness-yet', 0);
|
return callback(new VerifyError(tx, 'nonstandard', 'no-witness-yet', 0));
|
||||||
return callback(new VerifyError('nonstandard', 'no-witness-yet', 0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tx.isSane(ret)) {
|
if (!tx.isSane(ret))
|
||||||
peer.sendReject(tx, 'invalid', ret.reason, ret.score);
|
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
||||||
return callback(new VerifyError('invalid', ret.reason, ret.score));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.isCoinbase()) {
|
if (tx.isCoinbase())
|
||||||
peer.sendReject(tx, 'invalid', 'coinbase', 100);
|
return callback(new VerifyError(tx, 'invalid', 'coinbase', 100));
|
||||||
return callback(new VerifyError('invalid', 'coinbase', 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ts = locktimeFlags & LOCKTIME_MEDIAN_PAST
|
// ts = locktimeFlags & LOCKTIME_MEDIAN_PAST
|
||||||
// ? self.chain.tip.getMedianTime()
|
// ? self.chain.tip.getMedianTime()
|
||||||
@ -343,34 +328,27 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
ts = utils.now();
|
ts = utils.now();
|
||||||
height = this.chain.height + 1;
|
height = this.chain.height + 1;
|
||||||
|
|
||||||
if (!tx.isFinal(ts, height)) {
|
if (!tx.isFinal(ts, height))
|
||||||
peer.sendReject(tx, 'nonstandard', 'non-final', 0);
|
return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0));
|
||||||
return callback(new VerifyError('nonstandard', 'non-final', 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.requireStandard) {
|
if (this.requireStandard) {
|
||||||
if (!tx.isStandard(flags, ret)) {
|
if (!tx.isStandard(flags, ret))
|
||||||
peer.sendReject(tx, 'nonstandard', ret.reason, 0);
|
return callback(new VerifyError(tx, ret.reason, 0));
|
||||||
return callback(new VerifyError(ret.reason, 0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._hasTX(tx, function(err, exists) {
|
this._hasTX(tx, function(err, exists) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (exists) {
|
if (exists)
|
||||||
peer.sendReject(tx, 'alreadyknown', 'txn-already-in-mempool', 0);
|
return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0));
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
|
self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (doubleSpend) {
|
if (doubleSpend) {
|
||||||
peer.sendReject(tx, 'duplicate', 'bad-txns-inputs-spent', 0);
|
return callback(new VerifyError(tx,
|
||||||
return callback(new VerifyError(
|
|
||||||
'duplicate',
|
'duplicate',
|
||||||
'bad-txns-inputs-spent',
|
'bad-txns-inputs-spent',
|
||||||
0));
|
0));
|
||||||
@ -382,7 +360,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
|
|
||||||
if (!tx.hasCoins()) {
|
if (!tx.hasCoins()) {
|
||||||
if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
|
if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'mempool full',
|
'mempool full',
|
||||||
0));
|
0));
|
||||||
@ -392,24 +370,21 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.verify(tx, function(err) {
|
self.verify(tx, function(err) {
|
||||||
if (err) {
|
if (err)
|
||||||
if (err.type === 'VerifyError' && err.score >= 0)
|
|
||||||
peer.sendReject(tx, err.code, err.reason, err.score);
|
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
|
||||||
|
|
||||||
self.limitMempoolSize(function(err, result) {
|
self.limitMempoolSize(function(err, result) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'mempool full',
|
'mempool full',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addUnchecked(tx, peer, callback);
|
self.addUnchecked(tx, callback);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -417,7 +392,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
|
Mempool.prototype.addUnchecked = function addUnchecked(tx, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.tx.addUnchecked(tx, function(err) {
|
this.tx.addUnchecked(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -437,7 +412,7 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
utils.forEachSerial(resolved, function(tx, next) {
|
utils.forEachSerial(resolved, function(tx, next) {
|
||||||
self.addUnchecked(tx, peer, function(err) {
|
self.addUnchecked(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
self.emit('error', err);
|
self.emit('error', err);
|
||||||
utils.debug('Resolved orphan %s in mempool.', tx.rhash);
|
utils.debug('Resolved orphan %s in mempool.', tx.rhash);
|
||||||
@ -462,9 +437,10 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) {
|
|||||||
Mempool.prototype.verify = function verify(tx, callback) {
|
Mempool.prototype.verify = function verify(tx, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var height = this.chain.height + 1;
|
var height = this.chain.height + 1;
|
||||||
var total, input, coin, i, fee, now, free, minFee;
|
|
||||||
var flags = Mempool.flags;
|
var flags = Mempool.flags;
|
||||||
var mandatory = Mempool.mandatory;
|
var mandatory = Mempool.mandatory;
|
||||||
|
var ret = {};
|
||||||
|
var fee, now, free, minFee;
|
||||||
|
|
||||||
if (this.chain.segwitActive) {
|
if (this.chain.segwitActive) {
|
||||||
flags |= constants.flags.VERIFY_WITNESS;
|
flags |= constants.flags.VERIFY_WITNESS;
|
||||||
@ -477,84 +453,42 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'non-BIP68-final',
|
'non-BIP68-final',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.requireStandard && !tx.hasStandardInputs(flags)) {
|
if (self.requireStandard && !tx.hasStandardInputs(flags)) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'bad-txns-nonstandard-inputs',
|
'bad-txns-nonstandard-inputs',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx.getSigops(true) > constants.tx.maxSigops) {
|
if (tx.getSigops(true) > constants.tx.maxSigops) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'bad-txns-too-many-sigops',
|
'bad-txns-too-many-sigops',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
total = new bn(0);
|
if (!tx.checkInputs(height, ret))
|
||||||
for (i = 0; i < tx.inputs.length; i++) {
|
return callback(new VerifyError(tx, 'invalid', ret.reason, ret.score));
|
||||||
input = tx.inputs[i];
|
|
||||||
coin = input.coin;
|
|
||||||
|
|
||||||
if (coin.coinbase) {
|
|
||||||
if (self.chain.height - coin.height < constants.tx.coinbaseMaturity) {
|
|
||||||
return callback(new VerifyError(
|
|
||||||
'invalid',
|
|
||||||
'bad-txns-premature-spend-of-coinbase',
|
|
||||||
0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) {
|
|
||||||
return callback(new VerifyError(
|
|
||||||
'invalid',
|
|
||||||
'bad-txns-inputvalues-outofrange',
|
|
||||||
100));
|
|
||||||
}
|
|
||||||
|
|
||||||
total.iadd(coin.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
|
|
||||||
return callback(new VerifyError(
|
|
||||||
'invalid',
|
|
||||||
'bad-txns-inputvalues-outofrange',
|
|
||||||
100));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.getOutputValue().cmp(total) > 0)
|
|
||||||
return callback(new VerifyError('invalid', 'bad-txns-in-belowout', 100));
|
|
||||||
|
|
||||||
fee = total.sub(tx.getOutputValue());
|
|
||||||
|
|
||||||
if (fee.cmpn(0) < 0)
|
|
||||||
return callback(new VerifyError('invalid', 'bad-txns-fee-negative', 100));
|
|
||||||
|
|
||||||
if (fee.cmp(constants.maxMoney) > 0) {
|
|
||||||
return callback(new VerifyError(
|
|
||||||
'invalid',
|
|
||||||
'bad-txns-fee-outofrange',
|
|
||||||
100));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fee = tx.getFee();
|
||||||
minFee = tx.getMinFee();
|
minFee = tx.getMinFee();
|
||||||
if (fee.cmp(minFee) < 0) {
|
if (fee.cmp(minFee) < 0) {
|
||||||
if (self.relayPriority && fee.cmpn(0) === 0) {
|
if (self.relayPriority && fee.cmpn(0) === 0) {
|
||||||
free = tx.isFree(height);
|
free = tx.isFree(height);
|
||||||
if (!free) {
|
if (!free) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'insufficient priority',
|
'insufficient priority',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'insufficient fee',
|
'insufficient fee',
|
||||||
0));
|
0));
|
||||||
@ -571,7 +505,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
self.lastTime = now;
|
self.lastTime = now;
|
||||||
|
|
||||||
if (self.freeCount > self.limitFreeRelay * 10 * 1000) {
|
if (self.freeCount > self.limitFreeRelay * 10 * 1000) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'insufficientfee',
|
'insufficientfee',
|
||||||
'rate limited free transaction',
|
'rate limited free transaction',
|
||||||
0));
|
0));
|
||||||
@ -581,14 +515,14 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
|
if (self.rejectInsaneFees && fee.cmp(minFee.muln(10000)) > 0)
|
||||||
return callback(new VerifyError('highfee', 'absurdly-high-fee', 0));
|
return callback(new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0));
|
||||||
|
|
||||||
self.countAncestors(tx, function(err, count) {
|
self.countAncestors(tx, function(err, count) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (count > Mempool.ANCESTOR_LIMIT) {
|
if (count > Mempool.ANCESTOR_LIMIT) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'too-long-mempool-chain',
|
'too-long-mempool-chain',
|
||||||
0));
|
0));
|
||||||
@ -605,13 +539,13 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'non-mandatory-script-verify-flag',
|
'non-mandatory-script-verify-flag',
|
||||||
0));
|
0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(new VerifyError(
|
return callback(new VerifyError(tx,
|
||||||
'nonstandard',
|
'nonstandard',
|
||||||
'mandatory-script-verify-flag',
|
'mandatory-script-verify-flag',
|
||||||
0));
|
0));
|
||||||
@ -920,23 +854,6 @@ Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, call
|
|||||||
this.checkLocks(tx, flags, index, callback);
|
this.checkLocks(tx, flags, index, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* VerifyError
|
|
||||||
*/
|
|
||||||
|
|
||||||
function VerifyError(code, reason, score) {
|
|
||||||
Error.call(this);
|
|
||||||
if (Error.captureStackTrace)
|
|
||||||
Error.captureStackTrace(this, VerifyError);
|
|
||||||
this.type = 'VerifyError';
|
|
||||||
this.code = code;
|
|
||||||
this.message = reason;
|
|
||||||
this.reason = score === -1 ? null : reason;
|
|
||||||
this.score = score;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.inherits(VerifyError, Error);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -193,10 +193,6 @@ Pool.prototype._init = function _init() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.chain.on('verify-error', function(block, code, reason, score, peer) {
|
|
||||||
peer.sendReject(block, code, reason, score);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chain.on('fork', function(block, data, peer) {
|
this.chain.on('fork', function(block, data, peer) {
|
||||||
self.emit('fork', data, peer);
|
self.emit('fork', data, peer);
|
||||||
});
|
});
|
||||||
@ -448,14 +444,12 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
|
|
||||||
// If the peer sent us a block that was added
|
// If the peer sent us a block that was added
|
||||||
// to the chain (not orphans), reset the timeout.
|
// to the chain (not orphans), reset the timeout.
|
||||||
self._handleBlock(block, peer, function(err, added) {
|
self._handleBlock(block, peer, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
self.emit('error', err);
|
return self.emit('error', err);
|
||||||
|
|
||||||
if (added) {
|
self._startInterval();
|
||||||
self._startInterval();
|
self._startTimer();
|
||||||
self._startTimer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -468,14 +462,12 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
|
|
||||||
// If the peer sent us a block that was added
|
// If the peer sent us a block that was added
|
||||||
// to the chain (not orphans), reset the timeout.
|
// to the chain (not orphans), reset the timeout.
|
||||||
self._handleBlock(block, peer, function(err, added) {
|
self._handleBlock(block, peer, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
self.emit('error', err);
|
return self.emit('error', err);
|
||||||
|
|
||||||
if (added) {
|
self._startInterval();
|
||||||
self._startInterval();
|
self._startTimer();
|
||||||
self._startTimer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -713,22 +705,33 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
|
|||||||
utils.debug(
|
utils.debug(
|
||||||
'Recieved unrequested block: %s (%s)',
|
'Recieved unrequested block: %s (%s)',
|
||||||
block.rhash, peer.host);
|
block.rhash, peer.host);
|
||||||
return callback(null, false);
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._prehandleBlock(block, peer, function(err) {
|
this._prehandleBlock(block, peer, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.chain.add(block, peer, function(err, added) {
|
self.chain.add(block, function(err) {
|
||||||
if (err)
|
if (err) {
|
||||||
|
if (err.type === 'VerifyError') {
|
||||||
|
if (err.score >= 0)
|
||||||
|
peer.sendReject(block, err.code, err.reason, err.score);
|
||||||
|
|
||||||
|
if (err.reason === 'bad-prevblk') {
|
||||||
|
// self.chain.purgePending();
|
||||||
|
// self.resolveOrphan(peer, null, block.hash('hex'));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scheduleRequests(peer);
|
||||||
|
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
self.scheduleRequests(peer);
|
self.scheduleRequests(peer);
|
||||||
|
|
||||||
if (added === 0)
|
|
||||||
return callback(null, false);
|
|
||||||
|
|
||||||
self.emit('chain-progress', self.chain.getProgress(), peer);
|
self.emit('chain-progress', self.chain.getProgress(), peer);
|
||||||
|
|
||||||
if (self.chain.total % 20 === 0) {
|
if (self.chain.total % 20 === 0) {
|
||||||
@ -749,7 +752,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
|
|||||||
self.chain.locker.jobs.length);
|
self.chain.locker.jobs.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, true);
|
return callback();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -828,7 +831,10 @@ Pool.prototype._createPeer = function _createPeer(options) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
peer.on('tx', function(tx) {
|
peer.on('tx', function(tx) {
|
||||||
self._handleTX(tx, peer);
|
self._handleTX(tx, peer, function(err) {
|
||||||
|
if (err)
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
peer.on('addr', function(data) {
|
peer.on('addr', function(data) {
|
||||||
@ -905,8 +911,13 @@ Pool.prototype._handleTX = function _handleTX(tx, peer, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
addMempool(tx, peer, function(err) {
|
addMempool(tx, peer, function(err) {
|
||||||
if (err)
|
if (err) {
|
||||||
utils.debug('Mempool error: %s', err.message);
|
if (err.type === 'VerifyError') {
|
||||||
|
if (err.score >= 0)
|
||||||
|
peer.sendReject(block, err.code, err.reason, err.score);
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (updated || tx.block)
|
if (updated || tx.block)
|
||||||
self.emit('tx', tx, peer);
|
self.emit('tx', tx, peer);
|
||||||
|
|||||||
@ -929,6 +929,65 @@ TX.prototype.hasStandardInputs = function hasStandardInputs(flags) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TX.prototype.checkInputs = function checkInputs(spendHeight, ret) {
|
||||||
|
var total = new bn(0);
|
||||||
|
var i, input, coin, fee, value;
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
ret = {};
|
||||||
|
|
||||||
|
for (i = 0; i < this.inputs.length; i++) {
|
||||||
|
input = this.inputs[i];
|
||||||
|
coin = input.coin;
|
||||||
|
|
||||||
|
if (coin.coinbase) {
|
||||||
|
if (spendHeight - coin.height < constants.tx.coinbaseMaturity) {
|
||||||
|
ret.reason = 'bad-txns-premature-spend-of-coinbase';
|
||||||
|
ret.score = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coin.value.cmpn(0) < 0 || coin.value.cmp(constants.maxMoney) > 0) {
|
||||||
|
ret.reason = 'bad-txns-inputvalues-outofrange';
|
||||||
|
ret.score = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
total.iadd(coin.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total.cmpn(0) < 0 || total.cmp(constants.maxMoney) > 0) {
|
||||||
|
ret.reason = 'bad-txns-inputvalues-outofrange';
|
||||||
|
ret.score = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = this.getOutputValue();
|
||||||
|
|
||||||
|
if (value.cmp(total) > 0) {
|
||||||
|
ret.reason = 'bad-txns-in-belowout'
|
||||||
|
ret.score = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fee = total.sub(value);
|
||||||
|
|
||||||
|
if (fee.cmpn(0) < 0) {
|
||||||
|
ret.reason = 'bad-txns-fee-negative';
|
||||||
|
ret.score = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fee.cmp(constants.maxMoney) > 0) {
|
||||||
|
ret.reason = 'bad-txns-fee-outofrange';
|
||||||
|
ret.score = 100;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
TX.prototype.maxSize = function maxSize() {
|
TX.prototype.maxSize = function maxSize() {
|
||||||
return this.getVirtualSize();
|
return this.getVirtualSize();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1990,3 +1990,28 @@ if (utils.isBrowser) {
|
|||||||
return this.toArrayLike(Buffer, order, size);
|
return this.toArrayLike(Buffer, order, size);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VerifyError
|
||||||
|
*/
|
||||||
|
|
||||||
|
function VerifyError(object, code, reason, score) {
|
||||||
|
Error.call(this);
|
||||||
|
if (Error.captureStackTrace)
|
||||||
|
Error.captureStackTrace(this, VerifyError);
|
||||||
|
this.type = 'VerifyError';
|
||||||
|
this.hash = object.hash();
|
||||||
|
this.height = object.height;
|
||||||
|
this.code = code;
|
||||||
|
this.reason = score === -1 ? null : reason;
|
||||||
|
this.score = score;
|
||||||
|
this.message = reason
|
||||||
|
+ ' (code=' + code
|
||||||
|
+ ', score=' + score
|
||||||
|
+ ', height=' + this.height
|
||||||
|
+ ', hash=' + utils.revHex(utils.toHex(this.hash)) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.inherits(VerifyError, Error);
|
||||||
|
|
||||||
|
utils.VerifyError = VerifyError;
|
||||||
|
|||||||
@ -95,31 +95,29 @@ describe('Wallet', function() {
|
|||||||
f1.hint = 'f1';
|
f1.hint = 'f1';
|
||||||
fake.hint = 'fake';
|
fake.hint = 'fake';
|
||||||
|
|
||||||
var peer = { sendReject: function() {} };
|
node.mempool.addTX(fake, function(err) {
|
||||||
|
|
||||||
node.mempool.addTX(fake, peer, function(err) {
|
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.addTX(t4, peer, function(err) {
|
node.mempool.addTX(t4, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.getBalance(function(err, balance) {
|
node.mempool.getBalance(function(err, balance) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
assert.equal(balance.toString(10), '0');
|
assert.equal(balance.toString(10), '0');
|
||||||
node.mempool.addTX(t1, peer, function(err) {
|
node.mempool.addTX(t1, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.getBalance(function(err, balance) {
|
node.mempool.getBalance(function(err, balance) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
assert.equal(balance.toString(10), '60000');
|
assert.equal(balance.toString(10), '60000');
|
||||||
node.mempool.addTX(t2, peer, function(err) {
|
node.mempool.addTX(t2, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.getBalance(function(err, balance) {
|
node.mempool.getBalance(function(err, balance) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
assert.equal(balance.toString(10), '50000');
|
assert.equal(balance.toString(10), '50000');
|
||||||
node.mempool.addTX(t3, peer, function(err) {
|
node.mempool.addTX(t3, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.getBalance(function(err, balance) {
|
node.mempool.getBalance(function(err, balance) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
assert.equal(balance.toString(10), '22000');
|
assert.equal(balance.toString(10), '22000');
|
||||||
node.mempool.addTX(f1, peer, function(err) {
|
node.mempool.addTX(f1, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
node.mempool.getBalance(function(err, balance) {
|
node.mempool.getBalance(function(err, balance) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user