refactor: remove extra properties from tx and block.

This commit is contained in:
Christopher Jeffrey 2016-12-11 05:34:12 -08:00
parent 931a2a9398
commit 59645ac3ec
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
29 changed files with 1725 additions and 1266 deletions

View File

@ -41,6 +41,8 @@
"laxbreak": true, "laxbreak": true,
"laxcomma": true, "laxcomma": true,
"noyield": true,
"predef": [ "predef": [
"it", "it",
"describe", "describe",

View File

@ -127,7 +127,8 @@ function escape(html, encode) {
.replace(/'/g, '''); .replace(/'/g, ''');
} }
function addItem(tx) { function addItem(item, entry) {
var height = entry ? entry.height : -1;
var el; var el;
if (items.length === 20) { if (items.length === 20) {
@ -137,11 +138,11 @@ function addItem(tx) {
} }
el = create('<a style="display:block;" href="#' el = create('<a style="display:block;" href="#'
+ tx.rhash + '">' + tx.rhash + ' (' + tx.height + item.rhash() + '">' + item.rhash() + ' (' + height
+ ' - ' + kb(tx.getSize()) + ')</a>'); + ' - ' + kb(item.getSize()) + ')</a>');
tdiv.appendChild(el); tdiv.appendChild(el);
setMouseup(el, tx); setMouseup(el, item);
items.push(el); items.push(el);

View File

@ -217,10 +217,7 @@ Chain.prototype._open = co(function* open() {
this.emit('tip', tip); this.emit('tip', tip);
if (!this.synced && this.isFull()) { this.maybeSync();
this.synced = true;
this.emit('full');
}
}); });
/** /**
@ -961,16 +958,11 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
try { try {
result = yield this.verifyContext(block, prev); result = yield this.verifyContext(block, prev);
} catch (e) { } catch (e) {
// Couldn't verify block.
// Revert the height.
block.setHeight(-1);
if (e.type === 'VerifyError') { if (e.type === 'VerifyError') {
if (!e.malleated) if (!e.malleated)
this.setInvalid(entry.hash); this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height); this.emit('invalid', block, entry.height);
} }
throw e; throw e;
} }
@ -1002,16 +994,11 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
// as we can before saving. // as we can before saving.
yield this.verify(block, prev); yield this.verify(block, prev);
} catch (e) { } catch (e) {
// Couldn't verify block.
// Revert the height.
block.setHeight(-1);
if (e.type === 'VerifyError') { if (e.type === 'VerifyError') {
if (!e.malleated) if (!e.malleated)
this.setInvalid(entry.hash); this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height); this.emit('invalid', block, entry.height);
} }
throw e; throw e;
} }
@ -1215,7 +1202,7 @@ Chain.prototype.add = co(function* add(block) {
Chain.prototype._add = co(function* add(block) { Chain.prototype._add = co(function* add(block) {
var ret = new VerifyResult(); var ret = new VerifyResult();
var initial = true; var initial = true;
var hash, height, entry, prev; var hash, height, entry, prev, result;
while (block) { while (block) {
hash = block.hash('hex'); hash = block.hash('hex');
@ -1306,11 +1293,6 @@ Chain.prototype._add = co(function* add(block) {
} }
} }
// Update the block height early
// Some things in verifyContext may
// need access to height on txs.
block.setHeight(height);
// Create a new chain entry. // Create a new chain entry.
entry = ChainEntry.fromBlock(this, block, prev); entry = ChainEntry.fromBlock(this, block, prev);
@ -1338,16 +1320,19 @@ Chain.prototype._add = co(function* add(block) {
// Try to resolve orphan chain. // Try to resolve orphan chain.
block = this.resolveOrphan(hash); block = this.resolveOrphan(hash);
initial = false; initial = false;
if (!result)
result = entry;
} }
// 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.
this.pruneOrphans(); this.pruneOrphans();
if (!this.synced && this.isFull()) { // Check sync state.
this.synced = true; this.maybeSync();
this.emit('full');
} return result;
}); });
/** /**
@ -1763,6 +1748,18 @@ Chain.prototype.isFull = function isFull(force) {
return !this.isInitial(force); return !this.isInitial(force);
}; };
/**
* Potentially emit a `sync` event.
* @private
*/
Chain.prototype.maybeSync = function maybeSync() {
if (!this.synced && this.isFull()) {
this.synced = true;
this.emit('full');
}
};
/** /**
* Test the chain to see if it is still in the initial * Test the chain to see if it is still in the initial
* syncing phase. Mimic's bitcoind's `IsInitialBlockDownload()` * syncing phase. Mimic's bitcoind's `IsInitialBlockDownload()`

View File

@ -25,9 +25,9 @@ var layout = require('./layout');
var LRU = require('../utils/lru'); var LRU = require('../utils/lru');
var Block = require('../primitives/block'); var Block = require('../primitives/block');
var Outpoint = require('../primitives/outpoint'); var Outpoint = require('../primitives/outpoint');
var TX = require('../primitives/tx');
var Address = require('../primitives/address'); var Address = require('../primitives/address');
var ChainEntry = require('./chainentry'); var ChainEntry = require('./chainentry');
var TXMeta = require('../primitives/txmeta');
var U8 = encoding.U8; var U8 = encoding.U8;
var U32 = encoding.U32; var U32 = encoding.U32;
var DUMMY = new Buffer([0]); var DUMMY = new Buffer([0]);
@ -520,11 +520,7 @@ ChainDB.prototype.getState = co(function* getState() {
ChainDB.prototype.saveGenesis = co(function* saveGenesis() { ChainDB.prototype.saveGenesis = co(function* saveGenesis() {
var genesis = this.network.genesisBlock; var genesis = this.network.genesisBlock;
var block = Block.fromRaw(genesis, 'hex'); var block = Block.fromRaw(genesis, 'hex');
var entry; var entry = ChainEntry.fromBlock(this.chain, block);
block.setHeight(0);
entry = ChainEntry.fromBlock(this.chain, block);
this.logger.info('Writing genesis block to ChainDB.'); this.logger.info('Writing genesis block to ChainDB.');
@ -875,6 +871,34 @@ ChainDB.prototype.getCoinView = co(function* getCoinView(tx) {
return view; return view;
}); });
/**
* Get coin viewpoint (historical).
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}.
*/
ChainDB.prototype.getSpentView = co(function* getSpentView(tx) {
var view = yield this.getCoinView(tx);
var entries = view.toArray();
var i, coins, meta;
for (i = 0; i < entries.length; i++) {
coins = entries[i];
if (!coins.isEmpty())
continue;
meta = yield this.getMeta(coins.hash);
if (!meta)
continue;
view.addTX(meta.tx, meta.height);
}
return view;
});
/** /**
* Get coins necessary to be resurrected during a reorg. * Get coins necessary to be resurrected during a reorg.
* @param {Hash} hash * @param {Hash} hash
@ -895,25 +919,12 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) {
*/ */
ChainDB.prototype.getBlock = co(function* getBlock(hash) { ChainDB.prototype.getBlock = co(function* getBlock(hash) {
var item, data, block; var data = yield this.getRawBlock(hash);
if (this.options.spv)
return;
item = yield this.getBoth(hash);
if (!item.hash)
return;
data = yield this.db.get(layout.b(item.hash));
if (!data) if (!data)
return; return;
block = Block.fromRaw(data); return Block.fromRaw(data);
block.setHeight(item.height);
return block;
}); });
/** /**
@ -979,12 +990,12 @@ ChainDB.prototype.getBlockView = co(function* getBlockView(block) {
}); });
/** /**
* Retrieve a transaction (not filled with coins). * Get a transaction with metadata.
* @param {Hash} hash * @param {Hash} hash
* @returns {Promise} - Returns {@link TX}. * @returns {Promise} - Returns {@link TXMeta}.
*/ */
ChainDB.prototype.getTX = co(function* getTX(hash) { ChainDB.prototype.getMeta = co(function* getMeta(hash) {
var data; var data;
if (!this.options.indexTX) if (!this.options.indexTX)
@ -995,7 +1006,20 @@ ChainDB.prototype.getTX = co(function* getTX(hash) {
if (!data) if (!data)
return; return;
return TX.fromExtended(data); return TXMeta.fromRaw(data);
});
/**
* Retrieve a transaction.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TX}.
*/
ChainDB.prototype.getTX = co(function* getTX(hash) {
var meta = yield this.getMeta(hash);
if (!meta)
return;
return meta.tx;
}); });
/** /**
@ -1010,28 +1034,6 @@ ChainDB.prototype.hasTX = function hasTX(hash) {
return this.db.has(layout.t(hash)); return this.db.has(layout.t(hash));
}; };
/**
* Get a transaction and fill it with coins (historical).
* @param {Hash} hash
* @returns {Promise} - Returns {@link TX}.
*/
ChainDB.prototype.getFullTX = co(function* getFullTX(hash) {
var tx;
if (!this.options.indexTX)
return;
tx = yield this.getTX(hash);
if (!tx)
return;
yield this.fillHistory(tx);
return tx;
});
/** /**
* Get all coins pertinent to an address. * Get all coins pertinent to an address.
* @param {Address[]} addresses * @param {Address[]} addresses
@ -1113,6 +1115,25 @@ ChainDB.prototype.getHashesByAddress = co(function* getHashesByAddress(addresses
*/ */
ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
var mtxs = yield this.getMetaByAddress(addresses);
var out = [];
var i, mtx;
for (i = 0; i < mtxs.length; i++) {
mtx = mtxs[i];
out.push(mtx.tx);
}
return out;
});
/**
* Get all transactions pertinent to an address.
* @param {Address[]} addresses
* @returns {Promise} - Returns {@link TXMeta}[].
*/
ChainDB.prototype.getMetaByAddress = co(function* getTXByAddress(addresses) {
var txs = []; var txs = [];
var i, hashes, hash, tx; var i, hashes, hash, tx;
@ -1126,7 +1147,7 @@ ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
hash = hashes[i]; hash = hashes[i];
tx = yield this.getTX(hash); tx = yield this.getMeta(hash);
if (tx) if (tx)
txs.push(tx); txs.push(tx);
} }
@ -1670,7 +1691,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(entry, block, view) {
} }
// Index the transaction if enabled. // Index the transaction if enabled.
this.indexTX(tx, view); this.indexTX(tx, view, entry, i);
} }
// Commit new coin state. // Commit new coin state.
@ -1790,15 +1811,20 @@ ChainDB.prototype.saveOptions = function saveOptions() {
* @private * @private
* @param {TX} tx * @param {TX} tx
* @param {CoinView} view * @param {CoinView} view
* @param {ChainEntry} entry
* @param {Number} index
*/ */
ChainDB.prototype.indexTX = function indexTX(tx, view) { ChainDB.prototype.indexTX = function indexTX(tx, view, entry, index) {
var hash = tx.hash(); var hash = tx.hash();
var i, input, output, prevout; var i, meta, input, output;
var hashes, addr; var prevout, hashes, addr;
if (this.options.indexTX) { if (this.options.indexTX) {
this.put(layout.t(hash), tx.toExtended()); meta = TXMeta.fromTX(tx, entry, index);
this.put(layout.t(hash), meta.toRaw());
if (this.options.indexAddress) { if (this.options.indexAddress) {
hashes = tx.getHashes(view); hashes = tx.getHashes(view);
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
@ -1844,8 +1870,7 @@ ChainDB.prototype.indexTX = function indexTX(tx, view) {
ChainDB.prototype.unindexTX = function unindexTX(tx, view) { ChainDB.prototype.unindexTX = function unindexTX(tx, view) {
var hash = tx.hash(); var hash = tx.hash();
var i, input, output, prevout; var i, input, output, prevout, hashes, addr;
var hashes, addr;
if (this.options.indexTX) { if (this.options.indexTX) {
this.del(layout.t(hash)); this.del(layout.t(hash));

File diff suppressed because it is too large Load Diff

View File

@ -655,7 +655,7 @@ HTTPServer.prototype._init = function _init() {
enforce(req.options.hash, 'Hash is required.'); enforce(req.options.hash, 'Hash is required.');
tx = yield this.node.getTX(req.options.hash); tx = yield this.node.getMeta(req.options.hash);
if (!tx) if (!tx)
return send(404); return send(404);
@ -669,7 +669,7 @@ HTTPServer.prototype._init = function _init() {
enforce(req.options.address, 'Address is required.'); enforce(req.options.address, 'Address is required.');
txs = yield this.node.getTXByAddress(req.options.address); txs = yield this.node.getMetaByAddress(req.options.address);
send(200, txs.map(function(tx) { send(200, txs.map(function(tx) {
return tx.getJSON(this.network); return tx.getJSON(this.network);
@ -682,7 +682,7 @@ HTTPServer.prototype._init = function _init() {
enforce(req.options.address, 'Address is required.'); enforce(req.options.address, 'Address is required.');
txs = yield this.node.getTXByAddress(req.options.address); txs = yield this.node.getMetaByAddress(req.options.address);
send(200, txs.map(function(tx) { send(200, txs.map(function(tx) {
return tx.getJSON(this.network); return tx.getJSON(this.network);
@ -692,7 +692,7 @@ HTTPServer.prototype._init = function _init() {
// Block by hash/height // Block by hash/height
this.get('/block/:block', con(function* (req, res, send, next) { this.get('/block/:block', con(function* (req, res, send, next) {
var hash = req.options.hash || req.options.height; var hash = req.options.hash || req.options.height;
var block, view; var block, view, height;
enforce(hash != null, 'Hash or height required.'); enforce(hash != null, 'Hash or height required.');
@ -706,7 +706,9 @@ HTTPServer.prototype._init = function _init() {
if (!view) if (!view)
return send(404); return send(404);
send(200, block.getJSON(this.network, view)); height = yield this.chain.db.getHeight(hash);
send(200, block.getJSON(this.network, view, height));
})); }));
// Mempool snapshot // Mempool snapshot
@ -900,7 +902,7 @@ HTTPServer.prototype._init = function _init() {
var options = req.options; var options = req.options;
var passphrase = options.passphrase; var passphrase = options.passphrase;
var tx = yield req.wallet.send(options, passphrase); var tx = yield req.wallet.send(options, passphrase);
var details = yield req.wallet.toDetails(tx); var details = yield req.wallet.getDetails(tx.hash('hex'));
send(200, details.toJSON()); send(200, details.toJSON());
})); }));
@ -1741,12 +1743,12 @@ ClientSocket.prototype.watchChain = function watchChain() {
this.watching = true; this.watching = true;
this.bind(this.chain, 'connect', function(entry, block) { this.bind(this.chain, 'connect', function(entry, block, view) {
self.connectBlock(entry, block); self.connectBlock(entry, block, view);
}); });
this.bind(this.chain, 'disconnect', function(entry, block) { this.bind(this.chain, 'disconnect', function(entry, block, view) {
self.disconnectBlock(entry, block); self.disconnectBlock(entry, block, view);
}); });
this.bind(this.chain, 'reset', function(tip) { this.bind(this.chain, 'reset', function(tip) {
@ -1777,7 +1779,7 @@ ClientSocket.prototype.unwatchChain = function unwatchChain() {
this.unbind(pool, 'tx'); this.unbind(pool, 'tx');
}; };
ClientSocket.prototype.connectBlock = function connectBlock(entry, block) { ClientSocket.prototype.connectBlock = function connectBlock(entry, block, view) {
var raw = this.frameEntry(entry); var raw = this.frameEntry(entry);
var txs; var txs;
@ -1786,12 +1788,12 @@ ClientSocket.prototype.connectBlock = function connectBlock(entry, block) {
if (!this.filter) if (!this.filter)
return; return;
txs = this.filterBlock(block); txs = this.filterBlock(entry, block, view);
this.emit('block connect', raw, txs); this.emit('block connect', raw, txs);
}; };
ClientSocket.prototype.disconnectBlock = function disconnectBlock(entry, block) { ClientSocket.prototype.disconnectBlock = function disconnectBlock(entry, block, view) {
var raw = this.frameEntry(entry); var raw = this.frameEntry(entry);
this.emit('entry disconnect', raw); this.emit('entry disconnect', raw);
@ -1821,7 +1823,7 @@ ClientSocket.prototype.rescanBlock = function rescanBlock(entry, txs) {
}); });
}; };
ClientSocket.prototype.filterBlock = function filterBlock(block) { ClientSocket.prototype.filterBlock = function filterBlock(entry, block, view) {
var txs = []; var txs = [];
var i, tx; var i, tx;
@ -1831,7 +1833,7 @@ ClientSocket.prototype.filterBlock = function filterBlock(block) {
for (i = 0; i < block.txs.length; i++) { for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i]; tx = block.txs[i];
if (this.filterTX(tx)) if (this.filterTX(tx))
txs.push(this.frameTX(tx)); txs.push(this.frameTX(tx, view, entry, i));
} }
return txs; return txs;
@ -1884,7 +1886,7 @@ ClientSocket.prototype.scanner = function scanner(entry, txs) {
var i; var i;
for (i = 0; i < txs.length; i++) for (i = 0; i < txs.length; i++)
raw[i] = this.frameTX(txs[i]); raw[i] = this.frameTX(txs[i], null, entry, i);
return this.rescanBlock(block, raw); return this.rescanBlock(block, raw);
}; };
@ -1895,10 +1897,10 @@ ClientSocket.prototype.frameEntry = function frameEntry(entry) {
return entry.toJSON(); return entry.toJSON();
}; };
ClientSocket.prototype.frameTX = function frameTX(tx) { ClientSocket.prototype.frameTX = function frameTX(tx, view, entry, index) {
if (this.raw) if (this.raw)
return tx.toRaw(); return tx.toRaw();
return tx.getJSON(this.network); return tx.getJSON(this.network, view, entry, index);
}; };
ClientSocket.prototype.join = function join(id) { ClientSocket.prototype.join = function join(id) {

View File

@ -23,6 +23,7 @@ var Locker = require('../utils/locker');
var Outpoint = require('../primitives/outpoint'); var Outpoint = require('../primitives/outpoint');
var TX = require('../primitives/tx'); var TX = require('../primitives/tx');
var Coin = require('../primitives/coin'); var Coin = require('../primitives/coin');
var TXMeta = require('../primitives/txmeta');
var MempoolEntry = require('./mempoolentry'); var MempoolEntry = require('./mempoolentry');
var CoinView = require('../coins/coinview'); var CoinView = require('../coins/coinview');
var Coins = require('../coins/coins'); var Coins = require('../coins/coins');
@ -229,9 +230,6 @@ Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
if (this.hasTX(hash)) if (this.hasTX(hash))
continue; continue;
tx = tx.clone();
tx.unsetBlock();
try { try {
yield this._addTX(tx); yield this._addTX(tx);
} catch (e) { } catch (e) {
@ -365,7 +363,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() {
/** /**
* Retrieve a transaction from the mempool. * Retrieve a transaction from the mempool.
* Note that this will not be filled with coins. * Note that this will not be filled with coins.
* @param {TX|Hash} hash * @param {Hash} hash
* @returns {TX} * @returns {TX}
*/ */
@ -378,8 +376,7 @@ Mempool.prototype.getTX = function getTX(hash) {
/** /**
* Retrieve a transaction from the mempool. * Retrieve a transaction from the mempool.
* Note that this will not be filled with coins. * @param {Hash} hash
* @param {TX|Hash} hash
* @returns {MempoolEntry} * @returns {MempoolEntry}
*/ */
@ -509,6 +506,54 @@ Mempool.prototype.getTXByAddress = function getTXByAddress(addresses) {
return txs; return txs;
}; };
/**
* Find all transactions pertaining to a certain address.
* @param {Address[]} addresses
* @returns {TXMeta[]}
*/
Mempool.prototype.getMetaByAddress = function getMetaByAddress(addresses) {
var txs = [];
var i, j, tx, hash;
if (!Array.isArray(addresses))
addresses = [addresses];
for (i = 0; i < addresses.length; i++) {
hash = Address.getHash(addresses[i], 'hex');
if (!hash)
continue;
tx = this.txIndex.getMeta(hash);
for (j = 0; j < tx.length; j++)
txs.push(tx[j]);
}
return txs;
};
/**
* Retrieve a transaction from the mempool.
* Note that this will not be filled with coins.
* @param {Hash} hash
* @returns {TXMeta}
*/
Mempool.prototype.getMeta = function getMeta(hash) {
var entry = this.getEntry(hash);
var meta;
if (!entry)
return;
meta = TXMeta.fromTX(entry.tx);
meta.ps = entry.ts;
return meta;
};
/** /**
* Test the mempool to see if it contains a transaction. * Test the mempool to see if it contains a transaction.
* @param {Hash} hash * @param {Hash} hash
@ -1760,6 +1805,24 @@ AddressIndex.prototype.getTX = function getTX(address) {
return out; return out;
}; };
AddressIndex.prototype.getMeta = function getMeta(address) {
var items = this.index[address];
var out = [];
var i, hash, tx;
if (!items)
return out;
for (i = 0; i < items.length; i++) {
hash = items[i].toString('hex');
tx = this.mempool.getMeta(hash);
assert(tx);
out.push(tx);
}
return out;
};
AddressIndex.prototype.addTX = function addTX(tx, view) { AddressIndex.prototype.addTX = function addTX(tx, view) {
var key = tx.hash('hex'); var key = tx.hash('hex');
var hashes = tx.getHashes(view, 'hex'); var hashes = tx.getHashes(view, 'hex');

View File

@ -137,9 +137,9 @@ Miner.prototype._init = function _init() {
self.attempt.destroy(); self.attempt.destroy();
}); });
this.on('block', function(block) { this.on('block', function(block, entry) {
// Emit the block hex as a failsafe (in case we can't send it) // Emit the block hex as a failsafe (in case we can't send it)
self.logger.info('Found block: %d (%s).', block.height, block.rhash()); self.logger.info('Found block: %d (%s).', entry.height, entry.rhash());
self.logger.debug('Raw: %s', block.toRaw().toString('hex')); self.logger.debug('Raw: %s', block.toRaw().toString('hex'));
}); });
@ -195,7 +195,7 @@ Miner.prototype._close = co(function* close() {
Miner.prototype.start = co(function* start() { Miner.prototype.start = co(function* start() {
var self = this; var self = this;
var block; var block, entry;
assert(!this.running, 'Miner is already running.'); assert(!this.running, 'Miner is already running.');
@ -237,7 +237,7 @@ Miner.prototype.start = co(function* start() {
continue; continue;
try { try {
yield this.chain.add(block); entry = yield this.chain.add(block);
} catch (e) { } catch (e) {
if (this.stopping) if (this.stopping)
break; break;
@ -248,7 +248,7 @@ Miner.prototype.start = co(function* start() {
if (this.stopping) if (this.stopping)
break; break;
this.emit('block', block); this.emit('block', block, entry);
} }
this.emit('done'); this.emit('done');
@ -486,7 +486,7 @@ Miner.prototype.build = function build(attempt) {
attempt.fees += item.fee; attempt.fees += item.fee;
attempt.items.push(item); attempt.items.push(item);
block.txs.push(tx.clone()); block.txs.push(tx);
deps = depMap[hash]; deps = depMap[hash];

View File

@ -125,7 +125,6 @@ MinerBlock.prototype._init = function _init() {
block.ts = Math.max(this.network.now(), this.tip.ts + 1); block.ts = Math.max(this.network.now(), this.tip.ts + 1);
block.bits = this.bits; block.bits = this.bits;
block.nonce = 0; block.nonce = 0;
block.height = this.height;
// Coinbase input. // Coinbase input.
input = new Input(); input = new Input();
@ -300,7 +299,7 @@ MinerBlock.prototype.addTX = function addTX(tx, view) {
this.fees += item.fee; this.fees += item.fee;
// Add the tx to our block // Add the tx to our block
this.block.txs.push(tx.clone()); this.block.txs.push(tx);
this.items.push(item); this.items.push(item);
// Update coinbase value // Update coinbase value

View File

@ -338,7 +338,6 @@ CompactBlock.prototype.toBlock = function toBlock() {
for (i = 0; i < this.available.length; i++) { for (i = 0; i < this.available.length; i++) {
tx = this.available[i]; tx = this.available[i];
assert(tx, 'Compact block is not full.'); assert(tx, 'Compact block is not full.');
tx.setBlock(block, i);
block.txs[i] = tx; block.txs[i] = tx;
} }

View File

@ -1718,7 +1718,7 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) {
var blocks = 0; var blocks = 0;
var unknown = -1; var unknown = -1;
var items = packet.items; var items = packet.items;
var i, j, item, tx, block, result; var i, j, item, tx, block, result, height;
if (items.length > 50000) if (items.length > 50000)
throw new Error('getdata size too large (' + items.length + ').'); throw new Error('getdata size too large (' + items.length + ').');
@ -1790,7 +1790,8 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) {
break; break;
case constants.inv.CMPCT_BLOCK: case constants.inv.CMPCT_BLOCK:
// Fallback to full block. // Fallback to full block.
if (block.height < this.chain.tip.height - 10) { height = yield this.chain.db.getHeight(item.hash);
if (height < this.chain.tip.height - 10) {
result = yield this._sendBlock(item, this.compactWitness); result = yield this._sendBlock(item, this.compactWitness);
if (!result) { if (!result) {
notFound.push(item); notFound.push(item);
@ -2335,7 +2336,7 @@ Peer.prototype._handleCmpctBlock = co(function* _handleCmpctBlock(packet) {
Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) { Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) {
var req = packet.request; var req = packet.request;
var res, item, block; var res, item, block, height;
if (this.chain.db.options.spv) if (this.chain.db.options.spv)
return; return;
@ -2358,7 +2359,9 @@ Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) {
return; return;
} }
if (block.height < this.chain.tip.height - 15) { height = yield this.chain.db.getHeight(req.hash);
if (height < this.chain.tip.height - 15) {
this.logger.debug( this.logger.debug(
'Peer sent a getblocktxn for a block > 15 deep (%s)', 'Peer sent a getblocktxn for a block > 15 deep (%s)',
this.hostname); this.hostname);

View File

@ -403,22 +403,55 @@ FullNode.prototype.getCoin = function getCoin(hash, index) {
*/ */
FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) { FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) {
var coins = this.mempool.getCoinsByAddress(addresses); var mempool = this.mempool.getCoinsByAddress(addresses);
var i, blockCoins, coin, spent; var chain = yield this.chain.db.getCoinsByAddress(addresses);
var out = [];
var i, coin, spent;
blockCoins = yield this.chain.db.getCoinsByAddress(addresses); for (i = 0; i < chain.length; i++) {
coin = chain[i];
for (i = 0; i < blockCoins.length; i++) {
coin = blockCoins[i];
spent = this.mempool.isSpent(coin.hash, coin.index); spent = this.mempool.isSpent(coin.hash, coin.index);
if (spent) if (spent)
continue; continue;
coins.push(coin); out.push(coin);
} }
return coins; for (i = 0; i < mempool.length; i++) {
coin = mempool[i];
out.push(coin);
}
return out;
});
/**
* Retrieve transactions pertaining to an
* address from the mempool or chain database.
* @param {Address} addresses
* @returns {Promise} - Returns {@link TXMeta}[].
*/
FullNode.prototype.getMetaByAddress = co(function* getTXByAddress(addresses) {
var mempool = this.mempool.getMetaByAddress(addresses);
var chain = yield this.chain.db.getMetaByAddress(addresses);
return chain.concat(mempool);
});
/**
* Retrieve a transaction from the mempool or chain database.
* @param {Hash} hash
* @returns {Promise} - Returns {@link TXMeta}.
*/
FullNode.prototype.getMeta = co(function* getMeta(hash) {
var meta = this.mempool.getMeta(hash);
if (meta)
return meta;
return yield this.chain.db.getMeta(hash);
}); });
/** /**
@ -429,9 +462,16 @@ FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses)
*/ */
FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
var mempool = this.mempool.getTXByAddress(addresses); var mtxs = yield this.getMetaByAddress(addresses);
var txs = yield this.chain.db.getTXByAddress(addresses); var out = [];
return mempool.concat(txs); var i, mtx;
for (i = 0; i < mtxs.length; i++) {
mtx = mtxs[i];
out.push(mtx.tx);
}
return out;
}); });
/** /**
@ -440,14 +480,12 @@ FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
* @returns {Promise} - Returns {@link TX}. * @returns {Promise} - Returns {@link TX}.
*/ */
FullNode.prototype.getTX = function getTX(hash) { FullNode.prototype.getTX = co(function* getTX(hash) {
var tx = this.mempool.getTX(hash); var mtx = yield this.getMeta(hash);
if (!mtx)
if (tx) return;
return Promise.resolve(tx); return mtx.tx;
});
return this.chain.db.getTX(hash);
};
/** /**
* Test whether the mempool or chain contains a transaction. * Test whether the mempool or chain contains a transaction.
@ -462,20 +500,6 @@ FullNode.prototype.hasTX = function hasTX(hash) {
return this.chain.db.hasTX(hash); return this.chain.db.hasTX(hash);
}; };
/**
* Check whether a coin has been spent.
* @param {Hash} hash
* @param {Number} index
* @returns {Promise} - Returns Boolean.
*/
FullNode.prototype.isSpent = function isSpent(hash, index) {
if (this.mempool.isSpent(hash, index))
return Promise.resolve(true);
return this.chain.db.isSpent(hash, index);
};
/* /*
* Expose * Expose
*/ */

View File

@ -32,7 +32,6 @@ var InvItem = require('./invitem');
* @property {Number} bits * @property {Number} bits
* @property {Number} nonce * @property {Number} nonce
* @property {Number} totalTX - Transaction count. * @property {Number} totalTX - Transaction count.
* @property {Number} height - Block height (-1 if not present).
* @property {TX[]} txs - Transaction vector. * @property {TX[]} txs - Transaction vector.
* @property {ReversedHash} rhash - Reversed block hash (uint256le). * @property {ReversedHash} rhash - Reversed block hash (uint256le).
*/ */
@ -48,14 +47,13 @@ function AbstractBlock(options) {
this.bits = 0; this.bits = 0;
this.nonce = 0; this.nonce = 0;
this.totalTX = 0; this.totalTX = 0;
this.height = -1;
this.txs = null; this.txs = null;
this.mutable = false; this.mutable = false;
this.memory = false;
this._valid = null; this._valid = null;
this._validHeaders = null; this._validHeaders = null;
this._hash = null; this._hash = null;
this._hhash = null; this._hhash = null;
this._size = null; this._size = null;
@ -65,6 +63,8 @@ function AbstractBlock(options) {
this.parseOptions(options); this.parseOptions(options);
} }
AbstractBlock.prototype.memory = false;
/** /**
* Inject properties from options object. * Inject properties from options object.
* @private * @private
@ -92,11 +92,6 @@ AbstractBlock.prototype.parseOptions = function parseOptions(options) {
this.totalTX = options.totalTX; this.totalTX = options.totalTX;
} }
if (options.height != null) {
assert(util.isNumber(options.height));
this.height = options.height;
}
if (options.mutable != null) if (options.mutable != null)
this.mutable = !!options.mutable; this.mutable = !!options.mutable;
@ -118,7 +113,6 @@ AbstractBlock.prototype.parseJSON = function parseJSON(json) {
assert(util.isNumber(json.bits)); assert(util.isNumber(json.bits));
assert(util.isNumber(json.nonce)); assert(util.isNumber(json.nonce));
assert(util.isNumber(json.totalTX)); assert(util.isNumber(json.totalTX));
assert(util.isNumber(json.height));
this.version = json.version; this.version = json.version;
this.prevBlock = util.revHex(json.prevBlock); this.prevBlock = util.revHex(json.prevBlock);
@ -127,7 +121,6 @@ AbstractBlock.prototype.parseJSON = function parseJSON(json) {
this.bits = json.bits; this.bits = json.bits;
this.nonce = json.nonce; this.nonce = json.nonce;
this.totalTX = json.totalTX; this.totalTX = json.totalTX;
this.height = json.height;
return this; return this;
}; };
@ -260,24 +253,6 @@ AbstractBlock.prototype.verifyPOW = function verifyPOW() {
return btcutils.verifyPOW(this.hash(), this.bits); return btcutils.verifyPOW(this.hash(), this.bits);
}; };
/**
* Set the `height` property and the `height`
* property of all transactions within the block.
* @param {Number} height
*/
AbstractBlock.prototype.setHeight = function setHeight(height) {
var i;
this.height = height;
if (!this.txs)
return;
for (i = 0; i < this.txs.length; i++)
this.txs[i].height = height;
};
/** /**
* Get little-endian block hash. * Get little-endian block hash.
* @returns {Hash} * @returns {Hash}

View File

@ -244,13 +244,11 @@ Block.prototype.hasWitness = function hasWitness() {
/** /**
* Add a transaction to the block's tx vector. * Add a transaction to the block's tx vector.
* @param {TX} tx * @param {TX} tx
* @returns {TX} * @returns {Number}
*/ */
Block.prototype.addTX = function addTX(tx) { Block.prototype.addTX = function addTX(tx) {
var index = this.txs.push(tx) - 1; return this.txs.push(tx) - 1;
tx.setBlock(this, index);
return index;
}; };
/** /**
@ -617,15 +615,16 @@ Block.prototype.inspect = function inspect() {
/** /**
* Inspect the block and return a more * Inspect the block and return a more
* user-friendly representation of the data. * user-friendly representation of the data.
* @param {CoinView?} view * @param {CoinView} view
* @param {Number} height
* @returns {Object} * @returns {Object}
*/ */
Block.prototype.format = function format(view) { Block.prototype.format = function format(view, height) {
var commitmentHash = this.getCommitmentHash('hex'); var commitmentHash = this.getCommitmentHash('hex');
return { return {
hash: this.rhash(), hash: this.rhash(),
height: this.height, height: height != null ? height : -1,
size: this.getSize(), size: this.getSize(),
virtualSize: this.getVirtualSize(), virtualSize: this.getVirtualSize(),
date: util.date(this.ts), date: util.date(this.ts),
@ -638,9 +637,9 @@ Block.prototype.format = function format(view) {
ts: this.ts, ts: this.ts,
bits: this.bits, bits: this.bits,
nonce: this.nonce, nonce: this.nonce,
txs: this.txs.map(function(tx) { txs: this.txs.map(function(tx, i) {
return tx.inspect(view); return tx.format(view, null, i);
}) }, this)
}; };
}; };
@ -661,14 +660,15 @@ Block.prototype.toJSON = function toJSON() {
* of little-endian uint256s. * of little-endian uint256s.
* @param {Network} network * @param {Network} network
* @param {CoinView} view * @param {CoinView} view
* @param {Number} height
* @returns {Object} * @returns {Object}
*/ */
Block.prototype.getJSON = function getJSON(network, view) { Block.prototype.getJSON = function getJSON(network, view, height) {
network = Network.get(network); network = Network.get(network);
return { return {
hash: this.rhash(), hash: this.rhash(),
height: this.height, height: height,
version: this.version, version: this.version,
prevBlock: util.revHex(this.prevBlock), prevBlock: util.revHex(this.prevBlock),
merkleRoot: util.revHex(this.merkleRoot), merkleRoot: util.revHex(this.merkleRoot),
@ -676,9 +676,9 @@ Block.prototype.getJSON = function getJSON(network, view) {
bits: this.bits, bits: this.bits,
nonce: this.nonce, nonce: this.nonce,
totalTX: this.totalTX, totalTX: this.totalTX,
txs: this.txs.map(function(tx) { txs: this.txs.map(function(tx, i) {
return tx.getJSON(network, view); return tx.getJSON(network, view, this, i);
}) }, this)
}; };
}; };

View File

@ -205,16 +205,6 @@ Coin.prototype.getJSON = function getJSON(network, minimal) {
}; };
}; };
/**
* Instantiate an Coin from a jsonified coin object.
* @param {Object} json - The jsonified coin object.
* @returns {Coin}
*/
Coin.fromJSON = function fromJSON(json) {
return new Coin().fromJSON(json);
};
/** /**
* Inject JSON properties into coin. * Inject JSON properties into coin.
* @private * @private
@ -236,11 +226,21 @@ Coin.prototype.fromJSON = function fromJSON(json) {
this.script.fromJSON(json.script); this.script.fromJSON(json.script);
this.coinbase = json.coinbase; this.coinbase = json.coinbase;
this.hash = json.hash ? util.revHex(json.hash) : null; this.hash = json.hash ? util.revHex(json.hash) : null;
this.index = json.index; this.index = json.index != null ? json.index : -1;
return this; return this;
}; };
/**
* Instantiate an Coin from a jsonified coin object.
* @param {Object} json - The jsonified coin object.
* @returns {Coin}
*/
Coin.fromJSON = function fromJSON(json) {
return new Coin().fromJSON(json);
};
/** /**
* Write the coin to a buffer writer. * Write the coin to a buffer writer.
* @param {BufferWriter} bw * @param {BufferWriter} bw

View File

@ -209,6 +209,62 @@ Headers.fromBlock = function fromBlock(block) {
return headers; return headers;
}; };
/**
* Convert the block to an object suitable
* for JSON serialization.
* @returns {Object}
*/
Headers.prototype.toJSON = function toJSON() {
return this.getJSON();
};
/**
* Convert the block to an object suitable
* for JSON serialization. Note that the hashes
* will be reversed to abide by bitcoind's legacy
* of little-endian uint256s.
* @param {Network} network
* @param {CoinView} view
* @param {Number} height
* @returns {Object}
*/
Headers.prototype.getJSON = function getJSON(network, view, height) {
return {
hash: this.rhash(),
height: height,
version: this.version,
prevBlock: util.revHex(this.prevBlock),
merkleRoot: util.revHex(this.merkleRoot),
ts: this.ts,
bits: this.bits,
nonce: this.nonce,
totalTX: this.totalTX
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
Headers.prototype.fromJSON = function fromJSON(json) {
this.parseJSON(json);
return this;
};
/**
* Instantiate a merkle block from a jsonified block object.
* @param {Object} json - The jsonified block object.
* @returns {Headers}
*/
Headers.fromJSON = function fromJSON(json) {
return new Headers().fromJSON(json);
};
/** /**
* Inspect the headers and return a more * Inspect the headers and return a more
* user-friendly representation of the data. * user-friendly representation of the data.
@ -216,9 +272,21 @@ Headers.fromBlock = function fromBlock(block) {
*/ */
Headers.prototype.inspect = function inspect() { Headers.prototype.inspect = function inspect() {
return this.format();
};
/**
* Inspect the headers and return a more
* user-friendly representation of the data.
* @param {CoinView} view
* @param {Number} height
* @returns {Object}
*/
Headers.prototype.format = function format(view, height) {
return { return {
hash: this.rhash(), hash: this.rhash(),
height: this.height, height: height != null ? height : -1,
date: util.date(this.ts), date: util.date(this.ts),
version: util.hex32(this.version), version: util.hex32(this.version),
prevBlock: util.revHex(this.prevBlock), prevBlock: util.revHex(this.prevBlock),

View File

@ -15,3 +15,4 @@ exports.NetworkAddress = require('./netaddress');
exports.Outpoint = require('./outpoint'); exports.Outpoint = require('./outpoint');
exports.Output = require('./output'); exports.Output = require('./output');
exports.TX = require('./tx'); exports.TX = require('./tx');
exports.TXMeta = require('./txmeta');

View File

@ -248,7 +248,7 @@ Input.prototype.format = function format(coin) {
redeem: this.getRedeem(coin), redeem: this.getRedeem(coin),
sequence: this.sequence, sequence: this.sequence,
prevout: this.prevout, prevout: this.prevout,
coin: coin coin: coin || null
}; };
}; };
@ -289,7 +289,7 @@ Input.prototype.getJSON = function getJSON(network, coin) {
witness: this.witness.toJSON(), witness: this.witness.toJSON(),
sequence: this.sequence, sequence: this.sequence,
address: address, address: address,
coin: coin ? coin.getJSON(network, true) : null coin: coin ? coin.getJSON(network, true) : undefined
}; };
}; };

View File

@ -33,50 +33,19 @@ var BufferReader = require('../utils/reader');
* @exports MemBlock * @exports MemBlock
* @constructor * @constructor
* @param {NakedBlock} options * @param {NakedBlock} options
* @property {Boolean} memory - Always true.
* @property {Number} coinbaseHeight - The coinbase height which
* was extracted by the parser (the coinbase is the only
* transaction we parse ahead of time).
* @property {Buffer} raw - The raw block data.
*/ */
function MemBlock(options) { function MemBlock() {
if (!(this instanceof MemBlock)) if (!(this instanceof MemBlock))
return new MemBlock(options); return new MemBlock();
AbstractBlock.call(this, options); this._cbHeight = -1;
this._raw = null;
this.memory = true;
this.coinbaseHeight = -1;
this.raw = null;
if (options)
this.fromOptions(options);
} }
util.inherits(MemBlock, AbstractBlock); util.inherits(MemBlock, AbstractBlock);
/** MemBlock.prototype.memory = true;
* Inject properties from options object.
* @private
* @param {NakedBlock} options
*/
MemBlock.prototype.fromOptions = function fromOptions(options) {
this.coinbaseHeight = options.coinbaseHeight;
this.raw = options.raw;
return this;
};
/**
* Instantiate memblock from options object.
* @param {NakedBlock} options
* @returns {MemBlock}
*/
MemBlock.fromOptions = function fromOptions(options) {
return new MemBlock().fromOptions(options);
};
/** /**
* Serialize the block headers. * Serialize the block headers.
@ -84,7 +53,7 @@ MemBlock.fromOptions = function fromOptions(options) {
*/ */
MemBlock.prototype.abbr = function abbr() { MemBlock.prototype.abbr = function abbr() {
return this.raw.slice(0, 80); return this._raw.slice(0, 80);
}; };
/** /**
@ -93,7 +62,7 @@ MemBlock.prototype.abbr = function abbr() {
*/ */
MemBlock.prototype.getSize = function getSize() { MemBlock.prototype.getSize = function getSize() {
return this.raw.length; return this._raw.length;
}; };
/** /**
@ -115,7 +84,7 @@ MemBlock.prototype._verify = function _verify(ret) {
*/ */
MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
return this.coinbaseHeight; return this._cbHeight;
}; };
/** /**
@ -153,8 +122,8 @@ MemBlock.prototype.fromRaw = function fromRaw(data) {
} }
} }
this.coinbaseHeight = height; this._cbHeight = height;
this.raw = br.data; this._raw = br.data;
return this; return this;
}; };
@ -175,7 +144,7 @@ MemBlock.fromRaw = function fromRaw(data) {
*/ */
MemBlock.prototype.toRaw = function toRaw() { MemBlock.prototype.toRaw = function toRaw() {
return this.raw; return this._raw;
}; };
/** /**
@ -184,7 +153,7 @@ MemBlock.prototype.toRaw = function toRaw() {
*/ */
MemBlock.prototype.toNormal = function toNormal() { MemBlock.prototype.toNormal = function toNormal() {
return this.raw; return this._raw;
}; };
/** /**
@ -195,11 +164,11 @@ MemBlock.prototype.toNormal = function toNormal() {
*/ */
MemBlock.prototype.toBlock = function toBlock() { MemBlock.prototype.toBlock = function toBlock() {
var block = Block.fromRaw(this.raw); var block = Block.fromRaw(this._raw);
block._hash = this._hash; block._hash = this._hash;
block._cbHeight = this.coinbaseHeight; block._cbHeight = this._cbHeight;
block._validHeaders = this._validHeaders; block._validHeaders = this._validHeaders;
this.raw = null; this._raw = null;
return block; return block;
}; };

View File

@ -100,7 +100,7 @@ MerkleBlock.prototype.getSize = function getSize() {
/** /**
* Add a transaction to the block's tx vector. * Add a transaction to the block's tx vector.
* @param {TX} tx * @param {TX} tx
* @returns {TX} * @returns {Number}
*/ */
MerkleBlock.prototype.addTX = function addTX(tx) { MerkleBlock.prototype.addTX = function addTX(tx) {
@ -108,9 +108,8 @@ MerkleBlock.prototype.addTX = function addTX(tx) {
var index = this.map[hash]; var index = this.map[hash];
this.txs.push(tx); this.txs.push(tx);
tx.setBlock(this, index);
return index; return index != null ? index : -1;
}; };
/** /**
@ -314,10 +313,21 @@ MerkleBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
*/ */
MerkleBlock.prototype.inspect = function inspect() { MerkleBlock.prototype.inspect = function inspect() {
return this.format();
};
/**
* Inspect the block and return a more
* user-friendly representation of the data.
* @param {CoinView} view
* @param {Number} height
* @returns {Object}
*/
MerkleBlock.prototype.format = function format(view, height) {
return { return {
type: 'merkleblock',
hash: this.rhash(), hash: this.rhash(),
height: this.height, height: height != null ? height : -1,
date: util.date(this.ts), date: util.date(this.ts),
version: util.hex32(this.version), version: util.hex32(this.version),
prevBlock: util.revHex(this.prevBlock), prevBlock: util.revHex(this.prevBlock),
@ -433,17 +443,29 @@ MerkleBlock.fromRaw = function fromRaw(data, enc) {
/** /**
* Convert the block to an object suitable * Convert the block to an object suitable
* for JSON serialization. Note that the hashes * for JSON serialization.
* will be reversed to abide by bitcoind's legacy
* of little-endian uint256s.
* @returns {Object} * @returns {Object}
*/ */
MerkleBlock.prototype.toJSON = function toJSON() { MerkleBlock.prototype.toJSON = function toJSON() {
return this.getJSON();
};
/**
* Convert the block to an object suitable
* for JSON serialization. Note that the hashes
* will be reversed to abide by bitcoind's legacy
* of little-endian uint256s.
* @param {Network} network
* @param {CoinView} view
* @param {Number} height
* @returns {Object}
*/
MerkleBlock.prototype.getJSON = function getJSON(network, view, height) {
return { return {
type: 'merkleblock',
hash: this.rhash(), hash: this.rhash(),
height: this.height, height: height,
version: this.version, version: this.version,
prevBlock: util.revHex(this.prevBlock), prevBlock: util.revHex(this.prevBlock),
merkleRoot: util.revHex(this.merkleRoot), merkleRoot: util.revHex(this.merkleRoot),
@ -468,7 +490,6 @@ MerkleBlock.prototype.fromJSON = function fromJSON(json) {
var i, hash; var i, hash;
assert(json, 'MerkleBlock data is required.'); assert(json, 'MerkleBlock data is required.');
assert.equal(json.type, 'merkleblock');
assert(Array.isArray(json.hashes)); assert(Array.isArray(json.hashes));
assert(typeof json.flags === 'string'); assert(typeof json.flags === 'string');
@ -640,7 +661,6 @@ MerkleBlock.fromMatches = function fromMatches(block, matches) {
merkle.bits = block.bits; merkle.bits = block.bits;
merkle.nonce = block.nonce; merkle.nonce = block.nonce;
merkle.totalTX = totalTX; merkle.totalTX = totalTX;
merkle.height = block.height;
merkle.hashes = hashes; merkle.hashes = hashes;
merkle.flags = flags; merkle.flags = flags;
merkle.txs = txs; merkle.txs = txs;

View File

@ -33,7 +33,6 @@ var encoding = require('../utils/encoding');
* @constructor * @constructor
* @param {Object} options * @param {Object} options
* @param {Number?} options.version * @param {Number?} options.version
* @param {Number?} options.ps
* @param {Number?} options.changeIndex * @param {Number?} options.changeIndex
* @param {Input[]?} options.inputs * @param {Input[]?} options.inputs
* @param {Output[]?} options.outputs * @param {Output[]?} options.outputs
@ -45,16 +44,6 @@ var encoding = require('../utils/encoding');
* @property {Input[]} inputs * @property {Input[]} inputs
* @property {Output[]} outputs * @property {Output[]} outputs
* @property {Number} locktime - nLockTime * @property {Number} locktime - nLockTime
* @property {Number} ts - Timestamp of the block the transaction
* was included in (unix time).
* @property {Hash|null} block - Hash of the block the transaction
* was included in.
* @property {Number} index - Transaction's index in the block tx vector.
* @property {Number} ps - "Pending Since": The time at which the transaction
* was first seen. Only non-zero on unconfirmed transactions.
* @property {Number} changeIndex - Index of the change output (-1 if unknown).
* @property {Number} height - Height of the block the
* transaction was included in (-1 if unconfirmed).
* @property {CoinView} view * @property {CoinView} view
*/ */
@ -110,11 +99,6 @@ MTX.prototype.fromOptions = function fromOptions(options) {
this.locktime = options.locktime; this.locktime = options.locktime;
} }
if (options.ps != null) {
assert(util.isNumber(options.ps));
this.ps = options.ps;
}
if (options.changeIndex != null) { if (options.changeIndex != null) {
assert(util.isNumber(options.changeIndex)); assert(util.isNumber(options.changeIndex));
this.changeIndex = options.changeIndex; this.changeIndex = options.changeIndex;

View File

@ -49,15 +49,6 @@ var BAD_NONSTD_P2WSH = 3;
* @property {Input[]} inputs * @property {Input[]} inputs
* @property {Output[]} outputs * @property {Output[]} outputs
* @property {Number} locktime - nLockTime * @property {Number} locktime - nLockTime
* @property {Number} ts - Timestamp of the block the transaction
* was included in (unix time).
* @property {Hash|null} block - Hash of the block the transaction
* was included in.
* @property {Number} index - Transaction's index in the block tx vector.
* @property {Number} ps - "Pending Since": The time at which the transaction
* was first seen. Only non-zero on unconfirmed transactions.
* @property {Number} height - Height of the block the
* transaction was included in (-1 if unconfirmed).
*/ */
function TX(options) { function TX(options) {
@ -69,12 +60,6 @@ function TX(options) {
this.inputs = []; this.inputs = [];
this.outputs = []; this.outputs = [];
this.locktime = 0; this.locktime = 0;
this.ts = 0;
this.block = null;
this.index = -1;
this.ps = util.now();
this.height = -1;
this.mutable = false; this.mutable = false;
this._hash = null; this._hash = null;
@ -133,30 +118,6 @@ TX.prototype.fromOptions = function fromOptions(options) {
this.locktime = options.locktime; this.locktime = options.locktime;
} }
if (options.ts != null)
assert(util.isNumber(options.locktime));
this.ts = options.ts;
if (options.block !== undefined) {
assert(options.block === null || typeof options.block === 'string');
this.block = options.block;
}
if (options.index != null) {
assert(util.isNumber(options.index));
this.index = options.index;
}
if (options.ps != null) {
assert(util.isNumber(options.ps));
this.ps = options.ps;
}
if (options.height != null) {
assert(util.isNumber(options.height));
this.height = options.height;
}
return this; return this;
}; };
@ -179,30 +140,6 @@ TX.prototype.clone = function clone() {
return new TX(this); return new TX(this);
}; };
/**
* Set the block the transaction was included in.
* @param {Block|MerkleBlock} block
* @param {Number} index
*/
TX.prototype.setBlock = function setBlock(block, index) {
this.ts = block.ts;
this.block = block.hash('hex');
this.height = block.height;
this.index = index == null ? -1 : index;
};
/**
* Remove all relevant block data from the transaction.
*/
TX.prototype.unsetBlock = function unsetBlock() {
this.ts = 0;
this.block = null;
this.height = -1;
this.index = -1;
};
/** /**
* Hash the transaction with the non-witness serialization. * Hash the transaction with the non-witness serialization.
* @param {String?} enc - Can be `'hex'` or `null`. * @param {String?} enc - Can be `'hex'` or `null`.
@ -1852,25 +1789,6 @@ TX.prototype.getRate = function getRate(view, size) {
return btcutils.getRate(size, this.getFee(view)); return btcutils.getRate(size, this.getFee(view));
}; };
/**
* Calculate current number of transaction confirmations.
* @param {Number?} height - Current chain height. If not
* present, network chain height will be used.
* @returns {Number} confirmations
*/
TX.prototype.getConfirmations = function getConfirmations(height) {
assert(typeof height === 'number', 'Must pass in height.');
if (this.height === -1)
return 0;
if (height < this.height)
return 0;
return height - this.height + 1;
};
/** /**
* Get all unique outpoint hashes. * Get all unique outpoint hashes.
* @returns {Hash[]} Outpoint hashes. * @returns {Hash[]} Outpoint hashes.
@ -1955,17 +1873,6 @@ TX.prototype.isWatched = function isWatched(filter) {
return false; return false;
}; };
/**
* Get little-endian block hash.
* @returns {Hash|null}
*/
TX.prototype.rblock = function() {
return this.block
? util.revHex(this.block)
: null;
};
/** /**
* Get little-endian tx hash. * Get little-endian tx hash.
* @returns {Hash} * @returns {Hash}
@ -2025,12 +1932,18 @@ TX.prototype.inspect = function inspect() {
* Inspect the transaction and return a more * Inspect the transaction and return a more
* user-friendly representation of the data. * user-friendly representation of the data.
* @param {CoinView} view * @param {CoinView} view
* @param {ChainEntry} entry
* @param {Number} index
* @returns {Object} * @returns {Object}
*/ */
TX.prototype.format = function format(view) { TX.prototype.format = function format(view, entry, index) {
var rate = 0; var rate = 0;
var fee = 0; var fee = 0;
var height = -1;
var block = null;
var ts = 0;
var date = null;
if (view) { if (view) {
fee = this.getFee(view); fee = this.getFee(view);
@ -2041,21 +1954,30 @@ TX.prototype.format = function format(view) {
rate = 0; rate = 0;
} }
if (entry) {
height = entry.height;
block = util.revHex(entry.hash);
ts = entry.ts;
date = util.date(ts);
}
if (index == null)
index = -1;
return { return {
hash: this.txid(), hash: this.txid(),
witnessHash: this.wtxid(), witnessHash: this.wtxid(),
size: this.getSize(), size: this.getSize(),
virtualSize: this.getVirtualSize(), virtualSize: this.getVirtualSize(),
height: this.height,
value: Amount.btc(this.getOutputValue()), value: Amount.btc(this.getOutputValue()),
fee: Amount.btc(fee), fee: Amount.btc(fee),
rate: Amount.btc(rate), rate: Amount.btc(rate),
minFee: Amount.btc(this.getMinFee()), minFee: Amount.btc(this.getMinFee()),
date: util.date(this.ts || this.ps), height: height,
block: this.block ? util.revHex(this.block) : null, block: block,
ts: this.ts, ts: ts,
ps: this.ps, date: date,
index: this.index, index: index,
version: this.version, version: this.version,
flag: this.flag, flag: this.flag,
inputs: this.inputs.map(function(input) { inputs: this.inputs.map(function(input) {
@ -2084,12 +2006,13 @@ TX.prototype.toJSON = function toJSON() {
* of little-endian uint256s. * of little-endian uint256s.
* @param {Network} network * @param {Network} network
* @param {CoinView} view * @param {CoinView} view
* @param {ChainEntry} entry
* @param {Number} index
* @returns {Object} * @returns {Object}
*/ */
TX.prototype.getJSON = function getJSON(network, view) { TX.prototype.getJSON = function getJSON(network, view, entry, index) {
var rate = 0; var rate, fee, height, block, ts, date;
var fee = 0;
if (view) { if (view) {
fee = this.getFee(view); fee = this.getFee(view);
@ -2098,6 +2021,16 @@ TX.prototype.getJSON = function getJSON(network, view) {
// Rate can exceed 53 bits in testing. // Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate)) if (!util.isSafeInteger(rate))
rate = 0; rate = 0;
fee = Amount.btc(fee);
rate = Amount.btc(rate);
}
if (entry) {
height = entry.height;
block = util.revHex(entry.hash);
ts = entry.ts;
date = util.date(ts);
} }
network = Network.get(network); network = Network.get(network);
@ -2105,14 +2038,14 @@ TX.prototype.getJSON = function getJSON(network, view) {
return { return {
hash: this.txid(), hash: this.txid(),
witnessHash: this.wtxid(), witnessHash: this.wtxid(),
height: this.height, fee: fee,
block: this.rblock(), rate: rate,
ts: this.ts, ps: util.now(),
ps: this.ps, height: height,
date: util.date(this.ts || this.ps), block: block,
index: this.index, ts: ts,
fee: Amount.btc(fee), date: date,
rate: Amount.btc(rate), index: index,
version: this.version, version: this.version,
flag: this.flag, flag: this.flag,
inputs: this.inputs.map(function(input) { inputs: this.inputs.map(function(input) {
@ -2136,23 +2069,11 @@ TX.prototype.fromJSON = function fromJSON(json) {
var i, input, output; var i, input, output;
assert(json, 'TX data is required.'); assert(json, 'TX data is required.');
assert.equal(json.type, 'tx');
assert(util.isNumber(json.version)); assert(util.isNumber(json.version));
assert(util.isNumber(json.flag)); assert(util.isNumber(json.flag));
assert(Array.isArray(json.inputs)); assert(Array.isArray(json.inputs));
assert(Array.isArray(json.outputs)); assert(Array.isArray(json.outputs));
assert(util.isNumber(json.locktime)); assert(util.isNumber(json.locktime));
assert(!json.block || typeof json.block === 'string');
assert(util.isNumber(json.height));
assert(util.isNumber(json.ts));
assert(util.isNumber(json.ps));
assert(util.isNumber(json.index));
this.block = json.block ? util.revHex(json.block) : null;
this.height = json.height;
this.ts = json.ts;
this.ps = json.ps;
this.index = json.index;
this.version = json.version; this.version = json.version;
@ -2455,87 +2376,6 @@ TX.isWitness = function isWitness(br) {
&& br.data[br.offset + 5] !== 0; && br.data[br.offset + 5] !== 0;
}; };
/**
* Serialize a transaction to BCoin "extended format".
* This is the serialization format BCoin uses internally
* to store transactions in the database. The extended
* serialization includes the height, block hash, index,
* timestamp, and pending-since time.
* @returns {Buffer}
*/
TX.prototype.toExtended = function toExtended() {
var bw = new BufferWriter();
var height = this.height;
var index = this.index;
if (height === -1)
height = 0x7fffffff;
if (index === -1)
index = 0x7fffffff;
this.toWriter(bw);
bw.writeU32(this.ps);
if (this.block) {
bw.writeU8(1);
bw.writeHash(this.block);
} else {
bw.writeU8(0);
}
bw.writeU32(height);
bw.writeU32(this.ts);
bw.writeU32(index);
return bw.render();
};
/**
* Inject properties from "extended" serialization format.
* @private
* @param {Buffer} data
*/
TX.prototype.fromExtended = function fromExtended(data) {
var br = new BufferReader(data);
this.fromReader(br);
this.ps = br.readU32();
if (br.readU8() === 1)
this.block = br.readHash('hex');
this.height = br.readU32();
this.ts = br.readU32();
this.index = br.readU32();
if (this.height === 0x7fffffff)
this.height = -1;
if (this.index === 0x7fffffff)
this.index = -1;
return this;
};
/**
* Instantiate a transaction from a Buffer
* in "extended" serialization format.
* @param {Buffer} data
* @param {String?} enc - One of `"hex"` or `null`.
* @returns {TX}
*/
TX.fromExtended = function fromExtended(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new TX().fromExtended(data);
};
/** /**
* Test whether an object is a TX. * Test whether an object is a TX.
* @param {Object} obj * @param {Object} obj

280
lib/primitives/txmeta.js Normal file
View File

@ -0,0 +1,280 @@
/*!
* txmeta.js - extended transaction object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var TX = require('./tx');
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
/**
* An extended transaction object.
* @constructor
*/
function TXMeta(options) {
if (!(this instanceof TXMeta))
return new TXMeta(options);
this.tx = new TX();
this.ps = util.now();
this.height = -1;
this.block = null;
this.ts = 0;
this.index = 0;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
TXMeta.prototype.fromOptions = function fromOptions(options) {
if (options.tx) {
assert(options.tx instanceof TX);
this.tx = options.tx;
}
if (options.ps != null) {
assert(util.isNumber(options.ps));
this.ps = options.ps;
}
if (options.height != null) {
assert(util.isNumber(options.height));
this.height = options.height;
}
if (options.block !== undefined) {
assert(options.block === null || typeof options.block === 'string');
this.block = options.block;
}
if (options.ts != null) {
assert(util.isNumber(options.ts));
this.ts = options.ts;
}
if (options.index != null) {
assert(util.isNumber(options.index));
this.index = options.index;
}
return this;
};
/**
* Instantiate TXMeta from options.
* @param {Object} options
* @returns {TXMeta}
*/
TXMeta.fromOptions = function fromOptions(options) {
return new TXMeta().fromOptions(options);
};
/**
* Inject properties from options object.
* @private
* @param {Object} options
*/
TXMeta.prototype.fromTX = function fromTX(tx, entry, index) {
this.tx = tx;
if (entry) {
this.height = entry.height;
this.block = entry.hash;
this.ts = entry.ts;
this.index = index;
}
return this;
};
/**
* Instantiate TXMeta from options.
* @param {Object} options
* @returns {TXMeta}
*/
TXMeta.fromTX = function fromTX(tx, entry, index) {
return new TXMeta().fromTX(tx, entry, index);
};
/**
* Inspect the transaction.
* @returns {Object}
*/
TXMeta.prototype.inspect = function inspect() {
return this.format();
};
/**
* Inspect the transaction.
* @returns {Object}
*/
TXMeta.prototype.format = function format(view) {
var data = this.tx.format(view, null, this.index);
data.ps = this.ps;
data.height = this.height;
data.block = this.block ? util.revHex(this.block) : null;
data.ts = this.ts;
return data;
};
/**
* Convert transaction to JSON.
* @returns {Object}
*/
TXMeta.prototype.toJSON = function toJSON() {
return this.getJSON();
};
/**
* Convert the transaction to an object suitable
* for JSON serialization.
* @param {Network} network
* @param {CoinView} view
* @returns {Object}
*/
TXMeta.prototype.getJSON = function getJSON(network, view) {
var json = this.tx.getJSON(network, view, null, this.index);
json.ps = this.ps;
json.height = this.height;
json.block = this.block ? util.revHex(this.block) : null;
json.ts = this.ts;
return json;
};
/**
* Inject properties from a json object.
* @private
* @param {Object} json
*/
TXMeta.prototype.fromJSON = function fromJSON(json) {
this.tx.fromJSON(json);
assert(util.isNumber(json.ps));
assert(util.isNumber(json.height));
assert(!json.block || typeof json.block === 'string');
assert(util.isNumber(json.ts));
assert(util.isNumber(json.index));
this.ps = json.ps;
this.height = json.height;
this.block = util.revHex(json.block);
this.index = json.index;
return this;
};
/**
* Instantiate a transaction from a
* jsonified transaction object.
* @param {Object} json - The jsonified transaction object.
* @returns {TX}
*/
TXMeta.fromJSON = function fromJSON(json) {
return new TXMeta().fromJSON(JSON);
};
/**
* Serialize a transaction to BCoin "extended format".
* This is the serialization format BCoin uses internally
* to store transactions in the database. The extended
* serialization includes the height, block hash, index,
* timestamp, and pending-since time.
* @returns {Buffer}
*/
TXMeta.prototype.toRaw = function toRaw() {
var bw = new BufferWriter();
this.tx.toWriter(bw);
bw.writeU32(this.ps);
if (this.block) {
bw.writeU8(1);
bw.writeHash(this.block);
bw.writeU32(this.height);
bw.writeU32(this.ts);
bw.writeU32(this.index);
} else {
bw.writeU8(0);
}
return bw.render();
};
/**
* Inject properties from "extended" serialization format.
* @private
* @param {Buffer} data
*/
TXMeta.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
this.tx.fromReader(br);
this.ps = br.readU32();
if (br.readU8() === 1) {
this.block = br.readHash('hex');
this.height = br.readU32();
this.ts = br.readU32();
this.index = br.readU32();
if (this.index === 0x7fffffff)
this.index = -1;
}
return this;
};
/**
* Instantiate a transaction from a Buffer
* in "extended" serialization format.
* @param {Buffer} data
* @param {String?} enc - One of `"hex"` or `null`.
* @returns {TX}
*/
TXMeta.fromRaw = function fromRaw(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new TXMeta().fromRaw(data);
};
/**
* Test whether an object is an TXMeta.
* @param {Object} obj
* @returns {Boolean}
*/
TXMeta.isTXMeta = function isTXMeta(obj) {
return obj
&& Array.isArray(obj.inputs)
&& typeof obj.locktime === 'number'
&& typeof obj.ps === 'number';
};
/*
* Expose
*/
module.exports = TXMeta;

View File

@ -335,7 +335,7 @@ WalletClient.prototype.rescan = function rescan(start) {
*/ */
function parseEntry(data, enc) { function parseEntry(data, enc) {
var br, block, hash; var br, block, hash, height;
if (typeof data === 'string') if (typeof data === 'string')
data = new Buffer(data, 'hex'); data = new Buffer(data, 'hex');
@ -343,11 +343,10 @@ function parseEntry(data, enc) {
br = new BufferReader(data); br = new BufferReader(data);
block = Headers.fromAbbr(br); block = Headers.fromAbbr(br);
block.height = br.readU32(); height = br.readU32();
hash = block.hash('hex'); hash = block.hash('hex');
return new BlockMeta(hash, block.height, block.ts); return new BlockMeta(hash, height, block.ts);
} }
function parseBlock(entry, txs) { function parseBlock(entry, txs) {
@ -358,10 +357,6 @@ function parseBlock(entry, txs) {
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; tx = txs[i];
tx = parseTX(tx); tx = parseTX(tx);
tx.block = block.hash;
tx.height = block.height;
tx.ts = block.ts;
tx.index = -1;
out.push(tx); out.push(tx);
} }

View File

@ -6,11 +6,12 @@
'use strict'; 'use strict';
var util = require('../utils/util');
var assert = require('assert'); var assert = require('assert');
var util = require('../utils/util');
var constants = require('../protocol/constants'); var constants = require('../protocol/constants');
var BufferReader = require('../utils/reader'); var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer'); var BufferWriter = require('../utils/writer');
var TX = require('../primitives/tx');
/** /**
* Chain State * Chain State
@ -470,6 +471,150 @@ PathMapRecord.fromRaw = function fromRaw(hash, data) {
return new PathMapRecord(hash).fromRaw(data); return new PathMapRecord(hash).fromRaw(data);
}; };
/**
* TXRecord
* @constructor
*/
function TXRecord(tx, block) {
if (!(this instanceof TXRecord))
return new TXRecord(tx, block);
this.tx = null;
this.hash = null;
this.ps = util.now();
this.height = -1;
this.block = null;
this.index = -1;
this.ts = 0;
if (tx)
this.fromTX(tx, block);
}
TXRecord.prototype.fromTX = function fromTX(tx, block) {
this.tx = tx;
this.hash = tx.hash('hex');
if (block)
this.setBlock(block);
return this;
};
TXRecord.fromTX = function fromTX(tx, block) {
return new TXRecord().fromTX(tx, block);
};
TXRecord.prototype.setBlock = function setBlock(block) {
this.height = block.height;
this.block = block.hash;
this.ts = block.ts;
};
TXRecord.prototype.unsetBlock = function unsetBlock() {
this.height = -1;
this.block = null;
this.ts = 0;
};
TXRecord.prototype.getBlock = function getBlock() {
if (this.height === -1)
return;
return new BlockMeta(this.hash, this.height, this.ts);
};
/**
* Calculate current number of transaction confirmations.
* @param {Number?} height - Current chain height. If not
* present, network chain height will be used.
* @returns {Number} confirmations
*/
TXRecord.prototype.getConfirmations = function getConfirmations(height) {
assert(typeof height === 'number', 'Must pass in height.');
if (this.height === -1)
return 0;
if (height < this.height)
return 0;
return height - this.height + 1;
};
/**
* Serialize a transaction to BCoin "extended format".
* This is the serialization format BCoin uses internally
* to store transactions in the database. The extended
* serialization includes the height, block hash, index,
* timestamp, and pending-since time.
* @returns {Buffer}
*/
TXRecord.prototype.toRaw = function toRaw() {
var bw = new BufferWriter();
var index = this.index;
this.tx.toWriter(bw);
bw.writeU32(this.ps);
if (this.block) {
if (index === -1)
index = 0x7fffffff;
bw.writeU8(1);
bw.writeHash(this.block);
bw.writeU32(this.height);
bw.writeU32(this.ts);
bw.writeU32(index);
} else {
bw.writeU8(0);
}
return bw.render();
};
/**
* Inject properties from "extended" serialization format.
* @private
* @param {Buffer} data
*/
TXRecord.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data);
this.tx = new TX();
this.tx.fromReader(br);
this.hash = this.tx.hash('hex');
this.ps = br.readU32();
if (br.readU8() === 1) {
this.block = br.readHash('hex');
this.height = br.readU32();
this.ts = br.readU32();
this.index = br.readU32();
if (this.index === 0x7fffffff)
this.index = -1;
}
return this;
};
/**
* Instantiate a transaction from a Buffer
* in "extended" serialization format.
* @param {Buffer} data
* @param {String?} enc - One of `"hex"` or `null`.
* @returns {TX}
*/
TXRecord.fromRaw = function fromRaw(data) {
return new TXRecord().fromRaw(data);
};
/* /*
* Helpers * Helpers
*/ */
@ -516,5 +661,6 @@ exports.BlockMapRecord = BlockMapRecord;
exports.TXMapRecord = TXMapRecord; exports.TXMapRecord = TXMapRecord;
exports.OutpointMapRecord = OutpointMapRecord; exports.OutpointMapRecord = OutpointMapRecord;
exports.PathMapRecord = PathMapRecord; exports.PathMapRecord = PathMapRecord;
exports.TXRecord = TXRecord;
module.exports = exports; module.exports = exports;

View File

@ -16,13 +16,14 @@ var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer'); var BufferWriter = require('../utils/writer');
var btcutils = require('../btc/utils'); var btcutils = require('../btc/utils');
var Amount = require('../btc/amount'); var Amount = require('../btc/amount');
var TX = require('../primitives/tx'); var CoinView = require('../coins/coinview');
var Coin = require('../primitives/coin'); var Coin = require('../primitives/coin');
var Outpoint = require('../primitives/outpoint'); var Outpoint = require('../primitives/outpoint');
var records = require('./records'); var records = require('./records');
var layout = require('./layout').txdb; var layout = require('./layout').txdb;
var BlockMapRecord = records.BlockMapRecord; var BlockMapRecord = records.BlockMapRecord;
var OutpointMapRecord = records.OutpointMapRecord; var OutpointMapRecord = records.OutpointMapRecord;
var TXRecord = records.TXRecord;
var DUMMY = new Buffer([0]); var DUMMY = new Buffer([0]);
/** /**
@ -345,7 +346,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx, block) {
coin = yield this.getCoin(prevout.hash, prevout.index); coin = yield this.getCoin(prevout.hash, prevout.index);
if (coin) { if (coin) {
if (this.options.verify && tx.height === -1) { if (this.options.verify && !block) {
if (!(yield tx.verifyInputAsync(i, coin, flags))) if (!(yield tx.verifyInputAsync(i, coin, flags)))
return false; return false;
} }
@ -364,7 +365,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx, block) {
// decided to index for the hell // decided to index for the hell
// of it. // of it.
if (coin) { if (coin) {
if (this.options.verify && tx.height === -1) { if (this.options.verify && !block) {
if (!(yield tx.verifyInputAsync(i, coin, flags))) if (!(yield tx.verifyInputAsync(i, coin, flags)))
return false; return false;
} }
@ -476,7 +477,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, block, resolved)
assert(input.prevout.index === i); assert(input.prevout.index === i);
// We can finally verify this input. // We can finally verify this input.
if (this.options.verify && orphan.tx.height === -1) if (this.options.verify && !orphan.block)
valid = yield orphan.tx.verifyInputAsync(orphan.index, output, flags); valid = yield orphan.tx.verifyInputAsync(orphan.index, output, flags);
// If it's valid and fully resolved, // If it's valid and fully resolved,
@ -588,11 +589,12 @@ TXDB.prototype.removeInput = function removeInput(tx, index) {
* Resolve orphan input. * Resolve orphan input.
* @param {TX} tx * @param {TX} tx
* @param {Number} index * @param {Number} index
* @param {Number} height
* @param {Path} path * @param {Path} path
* @returns {Boolean} * @returns {Boolean}
*/ */
TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, path) { TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var spent = yield this.getSpent(hash, index); var spent = yield this.getSpent(hash, index);
var stx, credit; var stx, credit;
@ -611,9 +613,9 @@ TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, path) {
assert(stx); assert(stx);
// Crete the credit and add the undo coin. // Crete the credit and add the undo coin.
credit = Credit.fromTX(tx, index, tx.height); credit = Credit.fromTX(tx, index, height);
this.spendCredit(credit, stx, spent.index); this.spendCredit(credit, stx.tx, spent.index);
// If the spender is unconfirmed, save // If the spender is unconfirmed, save
// the credit as well, and mark it as // the credit as well, and mark it as
@ -624,7 +626,7 @@ TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, path) {
if (stx.height === -1) { if (stx.height === -1) {
credit.spent = true; credit.spent = true;
yield this.saveCredit(credit, path); yield this.saveCredit(credit, path);
if (tx.height !== -1) if (height !== -1)
this.pending.confirmed += credit.coin.value; this.pending.confirmed += credit.coin.value;
} }
@ -747,13 +749,12 @@ TXDB.prototype.removeOutpointMap = co(function* removeOutpointMap(hash, i) {
/** /**
* Append to the global block record. * Append to the global block record.
* @param {TX} tx * @param {Hash} hash
* @param {Number} height * @param {Number} height
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.addBlockMap = co(function* addBlockMap(tx, height) { TXDB.prototype.addBlockMap = co(function* addBlockMap(hash, height) {
var hash = tx.hash('hex');
var block = yield this.walletdb.getBlockMap(height); var block = yield this.walletdb.getBlockMap(height);
if (!block) if (!block)
@ -767,13 +768,12 @@ TXDB.prototype.addBlockMap = co(function* addBlockMap(tx, height) {
/** /**
* Remove from the global block record. * Remove from the global block record.
* @param {TX} * @param {Hash} hash
* @param {Number} height * @param {Number} height
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.removeBlockMap = co(function* removeBlockMap(tx, height) { TXDB.prototype.removeBlockMap = co(function* removeBlockMap(hash, height) {
var hash = tx.hash('hex');
var block = yield this.walletdb.getBlockMap(height); var block = yield this.walletdb.getBlockMap(height);
if (!block) if (!block)
@ -820,20 +820,18 @@ TXDB.prototype.getBlock = co(function* getBlock(height) {
/** /**
* Append to the global block record. * Append to the global block record.
* @param {TX} tx * @param {Hash} hash
* @param {BlockMeta} entry * @param {BlockMeta} meta
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.addBlock = co(function* addBlock(tx, entry) { TXDB.prototype.addBlock = co(function* addBlock(hash, meta) {
var hash = tx.hash(); var key = layout.b(meta.height);
var height = tx.height;
var key = layout.b(height);
var data = yield this.get(key); var data = yield this.get(key);
var block, size; var block, size;
if (!data) { if (!data) {
block = new BlockRecord(tx.block, tx.height, tx.ts); block = BlockRecord.fromMeta(meta);
data = block.toRaw(); data = block.toRaw();
} }
@ -849,13 +847,12 @@ TXDB.prototype.addBlock = co(function* addBlock(tx, entry) {
/** /**
* Remove from the global block record. * Remove from the global block record.
* @param {TX} tx * @param {Hash} hash
* @param {Number} height * @param {Number} height
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.removeBlock = co(function* removeBlock(tx, height) { TXDB.prototype.removeBlock = co(function* removeBlock(hash, height) {
var hash = tx.hash();
var key = layout.b(height); var key = layout.b(height);
var data = yield this.get(key); var data = yield this.get(key);
var block, size; var block, size;
@ -881,34 +878,31 @@ TXDB.prototype.removeBlock = co(function* removeBlock(tx, height) {
/** /**
* Append to the global block record. * Append to the global block record.
* @param {TX} tx * @param {Hash} hash
* @param {BlockMeta} entry * @param {BlockMeta} meta
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.addBlockSlow = co(function* addBlock(tx, entry) { TXDB.prototype.addBlockSlow = co(function* addBlockSlow(hash, meta) {
var hash = tx.hash('hex'); var block = yield this.getBlock(meta.height);
var height = tx.height;
var block = yield this.getBlock(height);
if (!block) if (!block)
block = new BlockRecord(tx.block, tx.height, tx.ts); block = BlockRecord.fromMeta(meta);
if (!block.add(hash)) if (!block.add(hash))
return; return;
this.put(layout.b(height), block.toRaw()); this.put(layout.b(meta.height), block.toRaw());
}); });
/** /**
* Remove from the global block record. * Remove from the global block record.
* @param {TX} tx * @param {Hash} hash
* @param {Number} height * @param {Number} height
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.removeBlockSlow = co(function* removeBlock(tx, height) { TXDB.prototype.removeBlockSlow = co(function* removeBlockSlow(hash, height) {
var hash = tx.hash('hex');
var block = yield this.getBlock(height); var block = yield this.getBlock(height);
if (!block) if (!block)
@ -960,6 +954,7 @@ TXDB.prototype.add = co(function* add(tx, block) {
TXDB.prototype._add = co(function* add(tx, block) { TXDB.prototype._add = co(function* add(tx, block) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var existing = yield this.getTX(hash); var existing = yield this.getTX(hash);
var wtx;
assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); assert(!tx.mutable, 'Cannot add mutable TX to wallet.');
@ -970,20 +965,16 @@ TXDB.prototype._add = co(function* add(tx, block) {
// The incoming tx won't confirm the // The incoming tx won't confirm the
// existing one anyway. Ignore. // existing one anyway. Ignore.
if (tx.height === -1) if (!block)
return; return;
// Save the index (can't get this elsewhere).
existing.height = tx.height;
existing.block = tx.block;
existing.ts = tx.ts;
existing.index = tx.index;
// Confirm transaction. // Confirm transaction.
return yield this._confirm(existing, block); return yield this._confirm(existing, block);
} }
if (tx.height === -1) { wtx = TXRecord.fromTX(tx, block);
if (!block) {
// We ignore any unconfirmed txs // We ignore any unconfirmed txs
// that are replace-by-fee. // that are replace-by-fee.
if (yield this.isRBF(tx)) { if (yield this.isRBF(tx)) {
@ -1007,20 +998,22 @@ TXDB.prototype._add = co(function* add(tx, block) {
} }
// Finally we can do a regular insertion. // Finally we can do a regular insertion.
return yield this.insert(tx, block); return yield this.insert(wtx, block);
}); });
/** /**
* Insert transaction. * Insert transaction.
* @private * @private
* @param {TX} tx * @param {TXRecord} wtx
* @param {BlockMeta} block * @param {BlockMeta} block
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.insert = co(function* insert(tx, block) { TXDB.prototype.insert = co(function* insert(wtx, block) {
var hash = tx.hash('hex'); var tx = wtx.tx;
var details = new Details(this, tx); var hash = wtx.hash;
var height = block ? block.height : -1;
var details = new Details(this, wtx, block);
var updated = false; var updated = false;
var i, input, output, coin; var i, input, output, coin;
var prevout, credit, path, account; var prevout, credit, path, account;
@ -1067,7 +1060,7 @@ TXDB.prototype.insert = co(function* insert(tx, block) {
this.pending.coin--; this.pending.coin--;
this.pending.unconfirmed -= coin.value; this.pending.unconfirmed -= coin.value;
if (tx.height === -1) { if (!block) {
// If the tx is not mined, we do not // If the tx is not mined, we do not
// disconnect the coin, we simply mark // disconnect the coin, we simply mark
// a `spent` flag on the credit. This // a `spent` flag on the credit. This
@ -1104,17 +1097,17 @@ TXDB.prototype.insert = co(function* insert(tx, block) {
// Attempt to resolve an input we // Attempt to resolve an input we
// did not know was ours at the time. // did not know was ours at the time.
if (yield this.resolveInput(tx, i, path)) { if (yield this.resolveInput(tx, i, height, path)) {
updated = true; updated = true;
continue; continue;
} }
credit = Credit.fromTX(tx, i, tx.height); credit = Credit.fromTX(tx, i, height);
this.pending.coin++; this.pending.coin++;
this.pending.unconfirmed += output.value; this.pending.unconfirmed += output.value;
if (tx.height !== -1) if (block)
this.pending.confirmed += output.value; this.pending.confirmed += output.value;
yield this.saveCredit(credit, path); yield this.saveCredit(credit, path);
@ -1130,15 +1123,14 @@ TXDB.prototype.insert = co(function* insert(tx, block) {
return; return;
} }
// Save and index the transaction in bcoin's // Save and index the transaction record.
// own "extended" transaction serialization. this.put(layout.t(hash), wtx.toRaw());
this.put(layout.t(hash), tx.toExtended()); this.put(layout.m(wtx.ps, hash), DUMMY);
this.put(layout.m(tx.ps, hash), DUMMY);
if (tx.height === -1) if (!block)
this.put(layout.p(hash), DUMMY); this.put(layout.p(hash), DUMMY);
else else
this.put(layout.h(tx.height, hash), DUMMY); this.put(layout.h(height, hash), DUMMY);
// Do some secondary indexing for account-based // Do some secondary indexing for account-based
// queries. This saves us a lot of time for // queries. This saves us a lot of time for
@ -1147,17 +1139,17 @@ TXDB.prototype.insert = co(function* insert(tx, block) {
account = details.accounts[i]; account = details.accounts[i];
this.put(layout.T(account, hash), DUMMY); this.put(layout.T(account, hash), DUMMY);
this.put(layout.M(account, tx.ps, hash), DUMMY); this.put(layout.M(account, wtx.ps, hash), DUMMY);
if (tx.height === -1) if (!block)
this.put(layout.P(account, hash), DUMMY); this.put(layout.P(account, hash), DUMMY);
else else
this.put(layout.H(account, tx.height, hash), DUMMY); this.put(layout.H(account, height, hash), DUMMY);
} }
if (tx.height !== -1) { if (block) {
yield this.addBlockMap(tx, tx.height); yield this.addBlockMap(hash, height);
yield this.addBlock(tx, block); yield this.addBlock(tx.hash(), block);
} }
// Update the transaction counter and // Update the transaction counter and
@ -1191,25 +1183,21 @@ TXDB.prototype.insert = co(function* insert(tx, block) {
*/ */
TXDB.prototype.confirm = co(function* confirm(hash, block) { TXDB.prototype.confirm = co(function* confirm(hash, block) {
var tx = yield this.getTX(hash); var wtx = yield this.getTX(hash);
var details; var details;
if (!tx) if (!wtx)
return; return;
if (tx.height !== -1) if (wtx.height !== -1)
throw new Error('TX is already confirmed.'); throw new Error('TX is already confirmed.');
assert(block); assert(block);
tx.height = block.height;
tx.block = block.hash;
tx.ts = block.ts;
this.start(); this.start();
try { try {
details = yield this._confirm(tx, block); details = yield this._confirm(wtx, block);
} catch (e) { } catch (e) {
this.drop(); this.drop();
throw e; throw e;
@ -1223,17 +1211,21 @@ TXDB.prototype.confirm = co(function* confirm(hash, block) {
/** /**
* Attempt to confirm a transaction. * Attempt to confirm a transaction.
* @private * @private
* @param {TX} tx * @param {TXRecord} wtx
* @param {BlockMeta} block * @param {BlockMeta} block
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype._confirm = co(function* confirm(tx, block) { TXDB.prototype._confirm = co(function* confirm(wtx, block) {
var hash = tx.hash('hex'); var tx = wtx.tx;
var details = new Details(this, tx); var hash = wtx.hash;
var height = block.height;
var details = new Details(this, wtx, block);
var i, account, output, coin, input, prevout; var i, account, output, coin, input, prevout;
var path, credit, credits; var path, credit, credits;
wtx.setBlock(block);
if (!tx.isCoinbase()) { if (!tx.isCoinbase()) {
credits = yield this.getSpentCredits(tx); credits = yield this.getSpentCredits(tx);
@ -1300,12 +1292,12 @@ TXDB.prototype._confirm = co(function* confirm(tx, block) {
// spent in the mempool, we need to // spent in the mempool, we need to
// update the undo coin's height. // update the undo coin's height.
if (credit.spent) if (credit.spent)
yield this.updateSpentCoin(tx, i); yield this.updateSpentCoin(tx, i, height);
// Update coin height and confirmed // Update coin height and confirmed
// balance. Save once again. // balance. Save once again.
coin = credit.coin; coin = credit.coin;
coin.height = tx.height; coin.height = height;
this.pending.confirmed += output.value; this.pending.confirmed += output.value;
@ -1318,20 +1310,20 @@ TXDB.prototype._confirm = co(function* confirm(tx, block) {
// Save the new serialized transaction as // Save the new serialized transaction as
// the block-related properties have been // the block-related properties have been
// updated. Also reindex for height. // updated. Also reindex for height.
this.put(layout.t(hash), tx.toExtended()); this.put(layout.t(hash), wtx.toRaw());
this.del(layout.p(hash)); this.del(layout.p(hash));
this.put(layout.h(tx.height, hash), DUMMY); this.put(layout.h(height, hash), DUMMY);
// Secondary indexing also needs to change. // Secondary indexing also needs to change.
for (i = 0; i < details.accounts.length; i++) { for (i = 0; i < details.accounts.length; i++) {
account = details.accounts[i]; account = details.accounts[i];
this.del(layout.P(account, hash)); this.del(layout.P(account, hash));
this.put(layout.H(account, tx.height, hash), DUMMY); this.put(layout.H(account, height, hash), DUMMY);
} }
if (tx.height !== -1) { if (block) {
yield this.addBlockMap(tx, tx.height); yield this.addBlockMap(hash, height);
yield this.addBlock(tx, block); yield this.addBlock(tx.hash(), block);
} }
// Commit the new state. The balance has updated. // Commit the new state. The balance has updated.
@ -1353,25 +1345,27 @@ TXDB.prototype._confirm = co(function* confirm(tx, block) {
*/ */
TXDB.prototype.remove = co(function* remove(hash) { TXDB.prototype.remove = co(function* remove(hash) {
var tx = yield this.getTX(hash); var wtx = yield this.getTX(hash);
if (!tx) if (!wtx)
return; return;
return yield this.removeRecursive(tx); return yield this.removeRecursive(wtx);
}); });
/** /**
* Remove a transaction from the * Remove a transaction from the
* database. Disconnect inputs. * database. Disconnect inputs.
* @private * @private
* @param {TX} tx * @param {TXRecord} wtx
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.erase = co(function* erase(tx) { TXDB.prototype.erase = co(function* erase(wtx, block) {
var hash = tx.hash('hex'); var tx = wtx.tx;
var details = new Details(this, tx); var hash = wtx.hash;
var height = block ? block.height : -1;
var details = new Details(this, wtx, block);
var i, path, account, credits; var i, path, account, credits;
var input, output, coin, credit; var input, output, coin, credit;
@ -1405,7 +1399,7 @@ TXDB.prototype.erase = co(function* erase(tx) {
this.pending.coin++; this.pending.coin++;
this.pending.unconfirmed += coin.value; this.pending.unconfirmed += coin.value;
if (tx.height !== -1) if (block)
this.pending.confirmed += coin.value; this.pending.confirmed += coin.value;
this.unspendCredit(tx, i); this.unspendCredit(tx, i);
@ -1424,12 +1418,12 @@ TXDB.prototype.erase = co(function* erase(tx) {
details.setOutput(i, path); details.setOutput(i, path);
credit = Credit.fromTX(tx, i, tx.height); credit = Credit.fromTX(tx, i, height);
this.pending.coin--; this.pending.coin--;
this.pending.unconfirmed -= output.value; this.pending.unconfirmed -= output.value;
if (tx.height !== -1) if (block)
this.pending.confirmed -= output.value; this.pending.confirmed -= output.value;
yield this.removeCredit(credit, path); yield this.removeCredit(credit, path);
@ -1441,29 +1435,29 @@ TXDB.prototype.erase = co(function* erase(tx) {
// Remove the transaction data // Remove the transaction data
// itself as well as unindex. // itself as well as unindex.
this.del(layout.t(hash)); this.del(layout.t(hash));
this.del(layout.m(tx.ps, hash)); this.del(layout.m(wtx.ps, hash));
if (tx.height === -1) if (!block)
this.del(layout.p(hash)); this.del(layout.p(hash));
else else
this.del(layout.h(tx.height, hash)); this.del(layout.h(height, hash));
// Remove all secondary indexing. // Remove all secondary indexing.
for (i = 0; i < details.accounts.length; i++) { for (i = 0; i < details.accounts.length; i++) {
account = details.accounts[i]; account = details.accounts[i];
this.del(layout.T(account, hash)); this.del(layout.T(account, hash));
this.del(layout.M(account, tx.ps, hash)); this.del(layout.M(account, wtx.ps, hash));
if (tx.height === -1) if (!block)
this.del(layout.P(account, hash)); this.del(layout.P(account, hash));
else else
this.del(layout.H(account, tx.height, hash)); this.del(layout.H(account, height, hash));
} }
if (tx.height !== -1) { if (block) {
yield this.removeBlockMap(tx, tx.height); yield this.removeBlockMap(hash, height);
yield this.removeBlockSlow(tx, tx.height); yield this.removeBlockSlow(hash, height);
} }
// Update the transaction counter // Update the transaction counter
@ -1482,12 +1476,13 @@ TXDB.prototype.erase = co(function* erase(tx) {
* Remove a transaction and recursively * Remove a transaction and recursively
* remove all of its spenders. * remove all of its spenders.
* @private * @private
* @param {TX} tx - Transaction to be removed. * @param {TXRecord} wtx
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) { TXDB.prototype.removeRecursive = co(function* removeRecursive(wtx) {
var hash = tx.hash('hex'); var tx = wtx.tx;
var hash = wtx.hash;
var i, spent, stx, details; var i, spent, stx, details;
for (i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
@ -1507,7 +1502,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
this.start(); this.start();
// Remove the spender. // Remove the spender.
details = yield this.erase(tx); details = yield this.erase(wtx, wtx.getBlock());
assert(details); assert(details);
@ -1547,30 +1542,31 @@ TXDB.prototype.unconfirm = co(function* unconfirm(hash) {
*/ */
TXDB.prototype._unconfirm = co(function* unconfirm(hash) { TXDB.prototype._unconfirm = co(function* unconfirm(hash) {
var tx = yield this.getTX(hash); var wtx = yield this.getTX(hash);
if (!tx) if (!wtx)
return; return;
return yield this.disconnect(tx); return yield this.disconnect(wtx, wtx.getBlock());
}); });
/** /**
* Unconfirm a transaction. Necessary after a reorg. * Unconfirm a transaction. Necessary after a reorg.
* @param {Hash} hash * @param {TXRecord} wtx
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.disconnect = co(function* disconnect(tx) { TXDB.prototype.disconnect = co(function* disconnect(wtx, block) {
var hash = tx.hash('hex'); var tx = wtx.tx;
var details = new Details(this, tx); var hash = wtx.hash;
var height = tx.height; var height = block.height;
var details = new Details(this, wtx, block);
var i, account, output, coin, credits; var i, account, output, coin, credits;
var input, path, credit; var input, path, credit;
assert(height !== -1); assert(block);
tx.unsetBlock(); wtx.unsetBlock();
if (!tx.isCoinbase()) { if (!tx.isCoinbase()) {
// We need to reconnect the coins. Start // We need to reconnect the coins. Start
@ -1616,12 +1612,12 @@ TXDB.prototype.disconnect = co(function* disconnect(tx) {
// Potentially update undo coin height. // Potentially update undo coin height.
if (!credit) { if (!credit) {
yield this.updateSpentCoin(tx, i); yield this.updateSpentCoin(tx, i, height);
continue; continue;
} }
if (credit.spent) if (credit.spent)
yield this.updateSpentCoin(tx, i); yield this.updateSpentCoin(tx, i, height);
details.setOutput(i, path); details.setOutput(i, path);
@ -1635,13 +1631,13 @@ TXDB.prototype.disconnect = co(function* disconnect(tx) {
yield this.saveCredit(credit, path); yield this.saveCredit(credit, path);
} }
yield this.removeBlockMap(tx, height); yield this.removeBlockMap(hash, height);
yield this.removeBlock(tx, height); yield this.removeBlock(tx.hash(), height);
// We need to update the now-removed // We need to update the now-removed
// block properties and reindex due // block properties and reindex due
// to the height change. // to the height change.
this.put(layout.t(hash), tx.toExtended()); this.put(layout.t(hash), wtx.toRaw());
this.put(layout.p(hash), DUMMY); this.put(layout.p(hash), DUMMY);
this.del(layout.h(height, hash)); this.del(layout.h(height, hash));
@ -1674,14 +1670,15 @@ TXDB.prototype.disconnect = co(function* disconnect(tx) {
* @returns {Promise} - Returns Boolean. * @returns {Promise} - Returns Boolean.
*/ */
TXDB.prototype.removeConflict = co(function* removeConflict(tx) { TXDB.prototype.removeConflict = co(function* removeConflict(wtx) {
var tx = wtx.tx;
var details; var details;
this.logger.warning('Handling conflicting tx: %s.', tx.txid()); this.logger.warning('Handling conflicting tx: %s.', tx.txid());
this.drop(); this.drop();
details = yield this.removeRecursive(tx); details = yield this.removeRecursive(wtx);
this.start(); this.start();
@ -1704,7 +1701,7 @@ TXDB.prototype.removeConflict = co(function* removeConflict(tx) {
TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, conf) { TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, conf) {
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var spends = []; var spends = [];
var i, input, prevout, spent, spender; var i, input, prevout, spent, spender, block;
if (tx.isCoinbase()) if (tx.isCoinbase())
return true; return true;
@ -1726,8 +1723,9 @@ TXDB.prototype.removeConflicts = co(function* removeConflicts(tx, conf) {
spender = yield this.getTX(spent.hash); spender = yield this.getTX(spent.hash);
assert(spender); assert(spender);
block = spender.getBlock();
if (conf && spender.height !== -1) if (conf && block)
return false; return false;
spends[i] = spender; spends[i] = spender;
@ -1841,14 +1839,11 @@ TXDB.prototype.filterLocked = function filterLocked(coins) {
TXDB.prototype.getLocked = function getLocked() { TXDB.prototype.getLocked = function getLocked() {
var keys = Object.keys(this.locked); var keys = Object.keys(this.locked);
var outpoints = []; var outpoints = [];
var i, key, hash, index, outpoint; var i, key;
for (i = 0; i < keys.length; i++) { for (i = 0; i < keys.length; i++) {
key = keys[i]; key = keys[i];
hash = key.slice(0, 64); outpoints.push(Outpoint.fromKey(key));
index = +key.slice(64);
outpoint = new Outpoint(hash, index);
outpoints.push(outpoint);
} }
return outpoints; return outpoints;
@ -2163,7 +2158,7 @@ TXDB.prototype.getHistory = function getHistory(account) {
return this.values({ return this.values({
gte: layout.t(constants.NULL_HASH), gte: layout.t(constants.NULL_HASH),
lte: layout.t(constants.HIGH_HASH), lte: layout.t(constants.HIGH_HASH),
parse: TX.fromExtended parse: TXRecord.fromRaw
}); });
}; };
@ -2352,7 +2347,7 @@ TXDB.prototype.getAccountCoins = co(function* getAccountCoins(account) {
}); });
/** /**
* Fill a transaction with coins (all historical coins). * Get historical coins for a transaction.
* @param {TX} tx * @param {TX} tx
* @returns {Promise} - Returns {@link TX}. * @returns {Promise} - Returns {@link TX}.
*/ */
@ -2381,6 +2376,60 @@ TXDB.prototype.getSpentCoins = co(function* getSpentCoins(tx) {
return coins; return coins;
}); });
/**
* Get a coin viewpoint.
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}.
*/
TXDB.prototype.getCoinView = co(function* getCoinView(tx) {
var view = new CoinView();
var i, input, prevout, coin;
if (tx.isCoinbase())
return view;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
coin = yield this.getCoin(prevout.hash, prevout.index);
if (!coin)
continue;
view.addCoin(coin);
}
return view;
});
/**
* Get historical coin viewpoint.
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}.
*/
TXDB.prototype.getSpentView = co(function* getSpentView(tx) {
var view = new CoinView();
var i, coins, coin;
if (tx.isCoinbase())
return view;
coins = yield this.getSpentCoins(tx);
for (i = 0; i < coins.length; i++) {
coin = coins[i];
if (!coin)
continue;
view.addCoin(coin);
}
return view;
});
/** /**
* Get TXDB state. * Get TXDB state.
* @returns {Promise} * @returns {Promise}
@ -2402,12 +2451,12 @@ TXDB.prototype.getState = co(function* getState() {
*/ */
TXDB.prototype.getTX = co(function* getTX(hash) { TXDB.prototype.getTX = co(function* getTX(hash) {
var tx = yield this.get(layout.t(hash)); var raw = yield this.get(layout.t(hash));
if (!tx) if (!raw)
return; return;
return TX.fromExtended(tx); return TXRecord.fromRaw(raw);
}); });
/** /**
@ -2417,31 +2466,31 @@ TXDB.prototype.getTX = co(function* getTX(hash) {
*/ */
TXDB.prototype.getDetails = co(function* getDetails(hash) { TXDB.prototype.getDetails = co(function* getDetails(hash) {
var tx = yield this.getTX(hash); var wtx = yield this.getTX(hash);
if (!tx) if (!wtx)
return; return;
return yield this.toDetails(tx); return yield this.toDetails(wtx);
}); });
/** /**
* Convert transaction to transaction details. * Convert transaction to transaction details.
* @param {TX|TX[]} txs * @param {TXRecord[]} wtxs
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.toDetails = co(function* toDetails(txs) { TXDB.prototype.toDetails = co(function* toDetails(wtxs) {
var i, out, tx, details; var i, out, wtx, details;
if (!Array.isArray(txs)) if (!Array.isArray(wtxs))
return yield this._toDetails(txs); return yield this._toDetails(wtxs);
out = []; out = [];
for (i = 0; i < txs.length; i++) { for (i = 0; i < wtxs.length; i++) {
tx = txs[i]; wtx = wtxs[i];
details = yield this._toDetails(tx); details = yield this._toDetails(wtx);
if (!details) if (!details)
continue; continue;
@ -2455,12 +2504,14 @@ TXDB.prototype.toDetails = co(function* toDetails(txs) {
/** /**
* Convert transaction to transaction details. * Convert transaction to transaction details.
* @private * @private
* @param {TX} tx * @param {TXRecord} wtx
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype._toDetails = co(function* _toDetails(tx) { TXDB.prototype._toDetails = co(function* _toDetails(wtx) {
var details = new Details(this, tx); var tx = wtx.tx;
var block = wtx.getBlock();
var details = new Details(this, wtx, block);
var coins = yield this.getSpentCoins(tx); var coins = yield this.getSpentCoins(tx);
var i, coin, path, output; var i, coin, path, output;
@ -2575,10 +2626,11 @@ TXDB.prototype.hasSpentCoin = function hasSpentCoin(spent) {
* Update spent coin height in storage. * Update spent coin height in storage.
* @param {TX} tx - Sending transaction. * @param {TX} tx - Sending transaction.
* @param {Number} index * @param {Number} index
* @param {Number} height
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, index) { TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, index, height) {
var prevout = Outpoint.fromTX(tx, index); var prevout = Outpoint.fromTX(tx, index);
var spent = yield this.getSpent(prevout.hash, prevout.index); var spent = yield this.getSpent(prevout.hash, prevout.index);
var coin; var coin;
@ -2591,7 +2643,7 @@ TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, index) {
if (!coin) if (!coin)
return; return;
coin.height = tx.height; coin.height = height;
this.put(layout.d(spent.hash, spent.index), coin.toRaw()); this.put(layout.d(spent.hash, spent.index), coin.toRaw());
}); });
@ -2686,7 +2738,7 @@ TXDB.prototype.getAccountBalance = co(function* getAccountBalance(account) {
TXDB.prototype.zap = co(function* zap(account, age) { TXDB.prototype.zap = co(function* zap(account, age) {
var hashes = []; var hashes = [];
var now = util.now(); var now = util.now();
var i, txs, tx, hash; var i, txs, wtx;
assert(util.isUInt32(age)); assert(util.isUInt32(age));
@ -2696,20 +2748,19 @@ TXDB.prototype.zap = co(function* zap(account, age) {
}); });
for (i = 0; i < txs.length; i++) { for (i = 0; i < txs.length; i++) {
tx = txs[i]; wtx = txs[i];
hash = tx.hash('hex');
if (tx.height !== -1) if (wtx.height !== -1)
continue; continue;
assert(now - tx.ps >= age); assert(now - wtx.ps >= age);
this.logger.debug('Zapping TX: %s (%s)', this.logger.debug('Zapping TX: %s (%s)',
hash, this.wallet.id); wtx.txid(), this.wallet.id);
yield this.remove(hash); yield this.remove(wtx.hash);
hashes.push(hash); hashes.push(wtx.hash);
} }
return hashes; return hashes;
@ -3003,9 +3054,9 @@ Credit.fromTX = function fromTX(tx, index, height) {
* @param {TX} tx * @param {TX} tx
*/ */
function Details(txdb, tx) { function Details(txdb, wtx, block) {
if (!(this instanceof Details)) if (!(this instanceof Details))
return new Details(txdb, tx); return new Details(txdb, wtx, block);
this.wallet = txdb.wallet; this.wallet = txdb.wallet;
this.network = this.wallet.network; this.network = this.wallet.network;
@ -3014,17 +3065,22 @@ function Details(txdb, tx) {
this.chainHeight = txdb.walletdb.state.height; this.chainHeight = txdb.walletdb.state.height;
this.hash = tx.hash('hex'); this.hash = wtx.hash;
this.size = tx.getSize(); this.tx = wtx.tx;
this.vsize = tx.getVirtualSize(); this.ps = wtx.ps;
this.tx = tx; this.size = this.tx.getSize();
this.vsize = this.tx.getVirtualSize();
this.block = tx.block; this.block = null;
this.height = tx.height; this.height = -1;
this.ts = tx.ts; this.ts = 0;
this.index = tx.index; this.index = -1;
this.ps = tx.ps; if (block) {
this.block = block.hash;
this.height = block.height;
this.ts = block.ts;
}
this.inputs = []; this.inputs = [];
this.outputs = []; this.outputs = [];
@ -3181,10 +3237,10 @@ Details.prototype.toJSON = function toJSON() {
rate: Amount.btc(rate), rate: Amount.btc(rate),
confirmations: this.getConfirmations(), confirmations: this.getConfirmations(),
inputs: this.inputs.map(function(input) { inputs: this.inputs.map(function(input) {
return input.toJSON(self.network); return input.getJSON(self.network);
}), }),
outputs: this.outputs.map(function(output) { outputs: this.outputs.map(function(output) {
return output.toJSON(self.network); return output.getJSON(self.network);
}), }),
tx: this.tx.toRaw().toString('hex') tx: this.tx.toRaw().toString('hex')
}; };
@ -3207,13 +3263,22 @@ function DetailsMember() {
this.path = null; this.path = null;
} }
/**
* Convert the member to a more json-friendly object.
* @returns {Object}
*/
DetailsMember.prototype.toJSON = function toJSON() {
return this.getJSON();
};
/** /**
* Convert the member to a more json-friendly object. * Convert the member to a more json-friendly object.
* @param {Network} network * @param {Network} network
* @returns {Object} * @returns {Object}
*/ */
DetailsMember.prototype.toJSON = function toJSON(network) { DetailsMember.prototype.getJSON = function getJSON(network) {
return { return {
value: Amount.btc(this.value), value: Amount.btc(this.value),
address: this.address address: this.address
@ -3316,7 +3381,6 @@ BlockRecord.prototype.fromRaw = function fromRaw(data) {
/** /**
* Instantiate wallet block from serialized data. * Instantiate wallet block from serialized data.
* @param {Hash} hash
* @param {Buffer} data * @param {Buffer} data
* @returns {BlockRecord} * @returns {BlockRecord}
*/ */
@ -3360,6 +3424,29 @@ BlockRecord.prototype.toJSON = function toJSON() {
}; };
}; };
/**
* Instantiate wallet block from block meta.
* @private
* @param {BlockMeta} block
*/
BlockRecord.prototype.fromMeta = function fromMeta(block) {
this.hash = block.hash;
this.height = block.height;
this.ts = block.ts;
return this;
};
/**
* Instantiate wallet block from block meta.
* @param {BlockMeta} block
* @returns {BlockRecord}
*/
BlockRecord.fromMeta = function fromMeta(block) {
return new BlockRecord().fromMeta(block);
};
/* /*
* Helpers * Helpers
*/ */

View File

@ -1652,24 +1652,26 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
*/ */
Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase) { Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase) {
var tx = yield this.getTX(hash); var wtx = yield this.getTX(hash);
var i, oldFee, fee, path, input, output, change; var i, tx, view, oldFee, fee, path, input, output, change;
if (!tx) if (!wtx)
throw new Error('Transaction not found.'); throw new Error('Transaction not found.');
tx = wtx.tx;
if (tx.isCoinbase()) if (tx.isCoinbase())
throw new Error('Transaction is a coinbase.'); throw new Error('Transaction is a coinbase.');
yield this.fillHistory(tx); view = yield this.getSpentView(tx);
if (!tx.hasCoins()) if (!tx.hasCoins(view))
throw new Error('Not all coins available.'); throw new Error('Not all coins available.');
if (!util.isUInt32(rate)) if (!util.isUInt32(rate))
throw new Error('Rate must be a number.'); throw new Error('Rate must be a number.');
oldFee = tx.getFee(); oldFee = tx.getFee(view);
fee = tx.getMinFee(null, rate); fee = tx.getMinFee(null, rate);
if (fee > constants.tx.MAX_FEE) if (fee > constants.tx.MAX_FEE)
@ -1679,6 +1681,7 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
throw new Error('Fee is not increasing.'); throw new Error('Fee is not increasing.');
tx = MTX.fromRaw(tx.toRaw()); tx = MTX.fromRaw(tx.toRaw());
tx.view = view;
for (i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
@ -1715,7 +1718,7 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
if (change.value < 0) if (change.value < 0)
throw new Error('Fee is too high.'); throw new Error('Fee is too high.');
if (change.isDust(constants.tx.MIN_RELAY)) { if (change.isDust()) {
tx.outputs.splice(tx.changeIndex, 1); tx.outputs.splice(tx.changeIndex, 1);
tx.changeIndex = -1; tx.changeIndex = -1;
} }
@ -2080,23 +2083,33 @@ Wallet.prototype.sign = co(function* sign(tx, passphrase) {
}); });
/** /**
* Fill transaction with historical coins. * Get a coin viewpoint.
* @param {TX} tx * @param {TX} tx
* @returns {Promise} - Returns {@link TX}. * @returns {Promise} - Returns {@link CoinView}.
*/ */
Wallet.prototype.fillHistory = function fillHistory(tx) { Wallet.prototype.getCoinView = function getCoinView(tx) {
return this.txdb.fillHistory(tx); return this.txdb.getCoinView(tx);
};
/**
* Get a historical coin viewpoint.
* @param {TX} tx
* @returns {Promise} - Returns {@link CoinView}.
*/
Wallet.prototype.getSpentView = function getSpentView(tx) {
return this.txdb.getSpentView(tx);
}; };
/** /**
* Convert transaction to transaction details. * Convert transaction to transaction details.
* @param {TX} tx * @param {TXRecord} wtx
* @returns {Promise} - Returns {@link Details}. * @returns {Promise} - Returns {@link Details}.
*/ */
Wallet.prototype.toDetails = function toDetails(tx) { Wallet.prototype.toDetails = function toDetails(wtx) {
return this.txdb.toDetails(tx); return this.txdb.toDetails(wtx);
}; };
/** /**

View File

@ -25,7 +25,6 @@ var Account = require('./account');
var LDB = require('../db/ldb'); var LDB = require('../db/ldb');
var Bloom = require('../utils/bloom'); var Bloom = require('../utils/bloom');
var Logger = require('../node/logger'); var Logger = require('../node/logger');
var TX = require('../primitives/tx');
var Outpoint = require('../primitives/outpoint'); var Outpoint = require('../primitives/outpoint');
var layouts = require('./layout'); var layouts = require('./layout');
var records = require('./records'); var records = require('./records');
@ -35,6 +34,7 @@ var BlockMapRecord = records.BlockMapRecord;
var BlockMeta = records.BlockMeta; var BlockMeta = records.BlockMeta;
var PathMapRecord = records.PathMapRecord; var PathMapRecord = records.PathMapRecord;
var OutpointMapRecord = records.OutpointMapRecord; var OutpointMapRecord = records.OutpointMapRecord;
var TXRecord = records.TXRecord;
var U32 = encoding.U32; var U32 = encoding.U32;
var cob = co.cob; var cob = co.cob;
var DUMMY = new Buffer([0]); var DUMMY = new Buffer([0]);
@ -1467,10 +1467,12 @@ WalletDB.prototype.getPendingTX = co(function* getPendingTX() {
WalletDB.prototype.resend = co(function* resend() { WalletDB.prototype.resend = co(function* resend() {
var keys = yield this.getPendingTX(); var keys = yield this.getPendingTX();
var txs = []; var txs = [];
var i, key, data, tx; var i, key, data, wtx;
if (keys.length > 0) if (keys.length === 0)
this.logger.info('Rebroadcasting %d WalletDB transactions.', keys.length); return;
this.logger.info('Rebroadcasting %d WalletDB transactions.', keys.length);
for (i = 0; i < keys.length; i++) { for (i = 0; i < keys.length; i++) {
key = keys[i]; key = keys[i];
@ -1479,12 +1481,12 @@ WalletDB.prototype.resend = co(function* resend() {
if (!data) if (!data)
continue; continue;
tx = TX.fromExtended(data); wtx = TXRecord.fromRaw(data);
if (tx.isCoinbase()) if (wtx.tx.isCoinbase())
continue; continue;
txs.push(tx); txs.push(wtx.tx);
} }
txs = btcutils.sortTX(txs); txs = btcutils.sortTX(txs);
@ -2072,22 +2074,9 @@ WalletDB.prototype.rescanBlock = co(function* rescanBlock(entry, txs) {
WalletDB.prototype.addTX = co(function* addTX(tx) { WalletDB.prototype.addTX = co(function* addTX(tx) {
var unlock = yield this.txLock.lock(); var unlock = yield this.txLock.lock();
var block;
try { try {
if (tx.height !== -1) { return yield this._insert(tx);
block = yield this.getBlock(tx.height);
if (!block)
throw new Error('WDB: Cannot insert tx from unknown chain.');
if (tx.block !== block.hash)
throw new Error('WDB: Cannot insert tx from alternate chain.');
this.logger.warning('WalletDB is inserting confirmed transaction.');
}
return yield this._insert(tx, block);
} finally { } finally {
unlock(); unlock();
} }

View File

@ -299,16 +299,16 @@ describe('Wallet', function() {
assert.equal(balance.unconfirmed, 11000); assert.equal(balance.unconfirmed, 11000);
txs = yield w.getHistory(); txs = yield w.getHistory();
assert(txs.some(function(tx) { assert(txs.some(function(wtx) {
return tx.hash('hex') === f1.hash('hex'); return wtx.hash === f1.hash('hex');
})); }));
balance = yield f.getBalance(); balance = yield f.getBalance();
assert.equal(balance.unconfirmed, 10000); assert.equal(balance.unconfirmed, 10000);
txs = yield f.getHistory(); txs = yield f.getHistory();
assert(txs.some(function(tx) { assert(txs.some(function(wtx) {
return tx.hash('hex') === f1.hash('hex'); return wtx.tx.hash('hex') === f1.hash('hex');
})); }));
})); }));
@ -321,8 +321,8 @@ describe('Wallet', function() {
txs = yield w.getHistory(); txs = yield w.getHistory();
assert.equal(txs.length, 5); assert.equal(txs.length, 5);
total = txs.reduce(function(t, tx) { total = txs.reduce(function(t, wtx) {
return t + tx.getOutputValue(); return t + wtx.tx.getOutputValue();
}, 0); }, 0);
assert.equal(total, 154000); assert.equal(total, 154000);
@ -341,8 +341,8 @@ describe('Wallet', function() {
txs = yield w.getHistory(); txs = yield w.getHistory();
assert.equal(txs.length, 2); assert.equal(txs.length, 2);
total = txs.reduce(function(t, tx) { total = txs.reduce(function(t, wtx) {
return t + tx.getOutputValue(); return t + wtx.tx.getOutputValue();
}, 0); }, 0);
assert.equal(total, 56000); assert.equal(total, 56000);
})); }));
@ -449,16 +449,16 @@ describe('Wallet', function() {
assert.equal(balance.unconfirmed, 11000); assert.equal(balance.unconfirmed, 11000);
txs = yield w.getHistory(); txs = yield w.getHistory();
assert(txs.some(function(tx) { assert(txs.some(function(wtx) {
return tx.hash('hex') === f1.hash('hex'); return wtx.tx.hash('hex') === f1.hash('hex');
})); }));
balance = yield f.getBalance(); balance = yield f.getBalance();
assert.equal(balance.unconfirmed, 10000); assert.equal(balance.unconfirmed, 10000);
txs = yield f.getHistory(); txs = yield f.getHistory();
assert(txs.some(function(tx) { assert(txs.some(function(wtx) {
return tx.hash('hex') === f1.hash('hex'); return wtx.tx.hash('hex') === f1.hash('hex');
})); }));
yield walletdb.addTX(t2); yield walletdb.addTX(t2);
@ -667,10 +667,9 @@ describe('Wallet', function() {
multisig = co(function* multisig(witness, bullshitNesting, cb) { multisig = co(function* multisig(witness, bullshitNesting, cb) {
var flags = constants.flags.STANDARD_VERIFY_FLAGS; var flags = constants.flags.STANDARD_VERIFY_FLAGS;
var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change; var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change;
var view;
var rec = bullshitNesting ? 'nested' : 'receive'; var rec = bullshitNesting ? 'nested' : 'receive';
var depth = bullshitNesting ? 'nestedDepth' : 'receiveDepth'; var depth = bullshitNesting ? 'nestedDepth' : 'receiveDepth';
var view, block;
if (witness) if (witness)
flags |= constants.flags.VERIFY_WITNESS; flags |= constants.flags.VERIFY_WITNESS;
@ -728,11 +727,7 @@ describe('Wallet', function() {
utx = utx.toTX(); utx = utx.toTX();
// Simulate a confirmation // Simulate a confirmation
var block = nextBlock(); block = nextBlock();
utx.height = block.height;
utx.block = block.hash;
utx.ts = block.ts;
utx.index = 0;
assert.equal(w1.account[depth], 1); assert.equal(w1.account[depth], 1);
@ -773,10 +768,6 @@ describe('Wallet', function() {
// Simulate a confirmation // Simulate a confirmation
block = nextBlock(); block = nextBlock();
send.height = block.height;
send.block = block.hash;
send.ts = block.ts;
send.index = 0;
yield walletdb.addBlock(block, [send]); yield walletdb.addBlock(block, [send]);
@ -924,7 +915,6 @@ describe('Wallet', function() {
.addOutput(account.receive.getAddress(), 5460) .addOutput(account.receive.getAddress(), 5460)
.addOutput(account.receive.getAddress(), 5460); .addOutput(account.receive.getAddress(), 5460);
t1.ps = 0xdeadbeef;
t1.addInput(dummy()); t1.addInput(dummy());
t1 = t1.toTX(); t1 = t1.toTX();
@ -1053,14 +1043,14 @@ describe('Wallet', function() {
it('should get range of txs', cob(function* () { it('should get range of txs', cob(function* () {
var w = wallet; var w = wallet;
var txs = yield w.getRange({ start: 0xdeadbeef - 1000 }); var txs = yield w.getRange({ start: util.now() - 1000 });
assert.equal(txs.length, 1); assert.equal(txs.length, 2);
})); }));
it('should get range of txs from account', cob(function* () { it('should get range of txs from account', cob(function* () {
var w = wallet; var w = wallet;
var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 }); var txs = yield w.getRange('foo', { start: util.now() - 1000 });
assert.equal(txs.length, 1); assert.equal(txs.length, 2);
})); }));
it('should not get range of txs from non-existent account', cob(function* () { it('should not get range of txs from non-existent account', cob(function* () {
@ -1086,7 +1076,7 @@ describe('Wallet', function() {
it('should import privkey', cob(function* () { it('should import privkey', cob(function* () {
var key = KeyRing.generate(); var key = KeyRing.generate();
var w = yield walletdb.create({ passphrase: 'test' }); var w = yield walletdb.create({ passphrase: 'test' });
var options, k, t1, t2, tx; var options, k, t1, t2, wtx;
yield w.importKey('default', key, 'test'); yield w.importKey('default', key, 'test');
@ -1106,9 +1096,9 @@ describe('Wallet', function() {
yield walletdb.addTX(t1); yield walletdb.addTX(t1);
tx = yield w.getTX(t1.hash('hex')); wtx = yield w.getTX(t1.hash('hex'));
assert(tx); assert(wtx);
assert.equal(t1.hash('hex'), tx.hash('hex')); assert.equal(t1.hash('hex'), wtx.hash);
options = { options = {
rate: 10000, rate: 10000,
@ -1120,7 +1110,7 @@ describe('Wallet', function() {
t2 = yield w.createTX(options); t2 = yield w.createTX(options);
yield w.sign(t2); yield w.sign(t2);
assert(t2.verify()); assert(t2.verify());
assert(t2.inputs[0].prevout.hash === tx.hash('hex')); assert(t2.inputs[0].prevout.hash === wtx.hash);
ewallet = w; ewallet = w;
ekey = key; ekey = key;
@ -1159,15 +1149,15 @@ describe('Wallet', function() {
it('should get details', cob(function* () { it('should get details', cob(function* () {
var w = wallet; var w = wallet;
var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 }); var txs = yield w.getRange('foo', { start: util.now() - 1000 });
var details = yield w.toDetails(txs); var details = yield w.toDetails(txs);
assert.equal(details[0].toJSON().outputs[0].path.name, 'foo'); assert.equal(details[1].toJSON().outputs[0].path.name, 'foo');
})); }));
it('should rename wallet', cob(function* () { it('should rename wallet', cob(function* () {
var w = wallet; var w = wallet;
yield wallet.rename('test'); yield wallet.rename('test');
var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 }); var txs = yield w.getRange('foo', { start: util.now() - 1000 });
var details = yield w.toDetails(txs); var details = yield w.toDetails(txs);
assert.equal(details[0].toJSON().id, 'test'); assert.equal(details[0].toJSON().id, 'test');
})); }));