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,
"laxcomma": true,
"noyield": true,
"predef": [
"it",
"describe",

View File

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

View File

@ -217,10 +217,7 @@ Chain.prototype._open = co(function* open() {
this.emit('tip', tip);
if (!this.synced && this.isFull()) {
this.synced = true;
this.emit('full');
}
this.maybeSync();
});
/**
@ -961,16 +958,11 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
try {
result = yield this.verifyContext(block, prev);
} catch (e) {
// Couldn't verify block.
// Revert the height.
block.setHeight(-1);
if (e.type === 'VerifyError') {
if (!e.malleated)
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
}
@ -1002,16 +994,11 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
// as we can before saving.
yield this.verify(block, prev);
} catch (e) {
// Couldn't verify block.
// Revert the height.
block.setHeight(-1);
if (e.type === 'VerifyError') {
if (!e.malleated)
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
}
@ -1215,7 +1202,7 @@ Chain.prototype.add = co(function* add(block) {
Chain.prototype._add = co(function* add(block) {
var ret = new VerifyResult();
var initial = true;
var hash, height, entry, prev;
var hash, height, entry, prev, result;
while (block) {
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.
entry = ChainEntry.fromBlock(this, block, prev);
@ -1338,16 +1320,19 @@ Chain.prototype._add = co(function* add(block) {
// Try to resolve orphan chain.
block = this.resolveOrphan(hash);
initial = false;
if (!result)
result = entry;
}
// Failsafe for large orphan chains. Do not
// allow more than 20mb stored in memory.
this.pruneOrphans();
if (!this.synced && this.isFull()) {
this.synced = true;
this.emit('full');
}
// Check sync state.
this.maybeSync();
return result;
});
/**
@ -1763,6 +1748,18 @@ Chain.prototype.isFull = function isFull(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
* syncing phase. Mimic's bitcoind's `IsInitialBlockDownload()`

View File

@ -25,9 +25,9 @@ var layout = require('./layout');
var LRU = require('../utils/lru');
var Block = require('../primitives/block');
var Outpoint = require('../primitives/outpoint');
var TX = require('../primitives/tx');
var Address = require('../primitives/address');
var ChainEntry = require('./chainentry');
var TXMeta = require('../primitives/txmeta');
var U8 = encoding.U8;
var U32 = encoding.U32;
var DUMMY = new Buffer([0]);
@ -520,11 +520,7 @@ ChainDB.prototype.getState = co(function* getState() {
ChainDB.prototype.saveGenesis = co(function* saveGenesis() {
var genesis = this.network.genesisBlock;
var block = Block.fromRaw(genesis, 'hex');
var entry;
block.setHeight(0);
entry = ChainEntry.fromBlock(this.chain, block);
var entry = ChainEntry.fromBlock(this.chain, block);
this.logger.info('Writing genesis block to ChainDB.');
@ -875,6 +871,34 @@ ChainDB.prototype.getCoinView = co(function* getCoinView(tx) {
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.
* @param {Hash} hash
@ -895,25 +919,12 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) {
*/
ChainDB.prototype.getBlock = co(function* getBlock(hash) {
var item, data, block;
if (this.options.spv)
return;
item = yield this.getBoth(hash);
if (!item.hash)
return;
data = yield this.db.get(layout.b(item.hash));
var data = yield this.getRawBlock(hash);
if (!data)
return;
block = Block.fromRaw(data);
block.setHeight(item.height);
return block;
return Block.fromRaw(data);
});
/**
@ -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
* @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;
if (!this.options.indexTX)
@ -995,7 +1006,20 @@ ChainDB.prototype.getTX = co(function* getTX(hash) {
if (!data)
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));
};
/**
* 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.
* @param {Address[]} addresses
@ -1113,6 +1115,25 @@ ChainDB.prototype.getHashesByAddress = co(function* getHashesByAddress(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 i, hashes, hash, tx;
@ -1126,7 +1147,7 @@ ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
tx = yield this.getTX(hash);
tx = yield this.getMeta(hash);
if (tx)
txs.push(tx);
}
@ -1670,7 +1691,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(entry, block, view) {
}
// Index the transaction if enabled.
this.indexTX(tx, view);
this.indexTX(tx, view, entry, i);
}
// Commit new coin state.
@ -1790,15 +1811,20 @@ ChainDB.prototype.saveOptions = function saveOptions() {
* @private
* @param {TX} tx
* @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 i, input, output, prevout;
var hashes, addr;
var i, meta, input, output;
var prevout, hashes, addr;
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) {
hashes = tx.getHashes(view);
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) {
var hash = tx.hash();
var i, input, output, prevout;
var hashes, addr;
var i, input, output, prevout, hashes, addr;
if (this.options.indexTX) {
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.');
tx = yield this.node.getTX(req.options.hash);
tx = yield this.node.getMeta(req.options.hash);
if (!tx)
return send(404);
@ -669,7 +669,7 @@ HTTPServer.prototype._init = function _init() {
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) {
return tx.getJSON(this.network);
@ -682,7 +682,7 @@ HTTPServer.prototype._init = function _init() {
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) {
return tx.getJSON(this.network);
@ -692,7 +692,7 @@ HTTPServer.prototype._init = function _init() {
// Block by hash/height
this.get('/block/:block', con(function* (req, res, send, next) {
var hash = req.options.hash || req.options.height;
var block, view;
var block, view, height;
enforce(hash != null, 'Hash or height required.');
@ -706,7 +706,9 @@ HTTPServer.prototype._init = function _init() {
if (!view)
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
@ -900,7 +902,7 @@ HTTPServer.prototype._init = function _init() {
var options = req.options;
var passphrase = 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());
}));
@ -1741,12 +1743,12 @@ ClientSocket.prototype.watchChain = function watchChain() {
this.watching = true;
this.bind(this.chain, 'connect', function(entry, block) {
self.connectBlock(entry, block);
this.bind(this.chain, 'connect', function(entry, block, view) {
self.connectBlock(entry, block, view);
});
this.bind(this.chain, 'disconnect', function(entry, block) {
self.disconnectBlock(entry, block);
this.bind(this.chain, 'disconnect', function(entry, block, view) {
self.disconnectBlock(entry, block, view);
});
this.bind(this.chain, 'reset', function(tip) {
@ -1777,7 +1779,7 @@ ClientSocket.prototype.unwatchChain = function unwatchChain() {
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 txs;
@ -1786,12 +1788,12 @@ ClientSocket.prototype.connectBlock = function connectBlock(entry, block) {
if (!this.filter)
return;
txs = this.filterBlock(block);
txs = this.filterBlock(entry, block, view);
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);
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 i, tx;
@ -1831,7 +1833,7 @@ ClientSocket.prototype.filterBlock = function filterBlock(block) {
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
if (this.filterTX(tx))
txs.push(this.frameTX(tx));
txs.push(this.frameTX(tx, view, entry, i));
}
return txs;
@ -1884,7 +1886,7 @@ ClientSocket.prototype.scanner = function scanner(entry, txs) {
var 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);
};
@ -1895,10 +1897,10 @@ ClientSocket.prototype.frameEntry = function frameEntry(entry) {
return entry.toJSON();
};
ClientSocket.prototype.frameTX = function frameTX(tx) {
ClientSocket.prototype.frameTX = function frameTX(tx, view, entry, index) {
if (this.raw)
return tx.toRaw();
return tx.getJSON(this.network);
return tx.getJSON(this.network, view, entry, index);
};
ClientSocket.prototype.join = function join(id) {

View File

@ -23,6 +23,7 @@ var Locker = require('../utils/locker');
var Outpoint = require('../primitives/outpoint');
var TX = require('../primitives/tx');
var Coin = require('../primitives/coin');
var TXMeta = require('../primitives/txmeta');
var MempoolEntry = require('./mempoolentry');
var CoinView = require('../coins/coinview');
var Coins = require('../coins/coins');
@ -229,9 +230,6 @@ Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
if (this.hasTX(hash))
continue;
tx = tx.clone();
tx.unsetBlock();
try {
yield this._addTX(tx);
} catch (e) {
@ -365,7 +363,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() {
/**
* Retrieve a transaction from the mempool.
* Note that this will not be filled with coins.
* @param {TX|Hash} hash
* @param {Hash} hash
* @returns {TX}
*/
@ -378,8 +376,7 @@ Mempool.prototype.getTX = function getTX(hash) {
/**
* Retrieve a transaction from the mempool.
* Note that this will not be filled with coins.
* @param {TX|Hash} hash
* @param {Hash} hash
* @returns {MempoolEntry}
*/
@ -509,6 +506,54 @@ Mempool.prototype.getTXByAddress = function getTXByAddress(addresses) {
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.
* @param {Hash} hash
@ -1760,6 +1805,24 @@ AddressIndex.prototype.getTX = function getTX(address) {
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) {
var key = tx.hash('hex');
var hashes = tx.getHashes(view, 'hex');

View File

@ -137,9 +137,9 @@ Miner.prototype._init = function _init() {
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)
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'));
});
@ -195,7 +195,7 @@ Miner.prototype._close = co(function* close() {
Miner.prototype.start = co(function* start() {
var self = this;
var block;
var block, entry;
assert(!this.running, 'Miner is already running.');
@ -237,7 +237,7 @@ Miner.prototype.start = co(function* start() {
continue;
try {
yield this.chain.add(block);
entry = yield this.chain.add(block);
} catch (e) {
if (this.stopping)
break;
@ -248,7 +248,7 @@ Miner.prototype.start = co(function* start() {
if (this.stopping)
break;
this.emit('block', block);
this.emit('block', block, entry);
}
this.emit('done');
@ -486,7 +486,7 @@ Miner.prototype.build = function build(attempt) {
attempt.fees += item.fee;
attempt.items.push(item);
block.txs.push(tx.clone());
block.txs.push(tx);
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.bits = this.bits;
block.nonce = 0;
block.height = this.height;
// Coinbase input.
input = new Input();
@ -300,7 +299,7 @@ MinerBlock.prototype.addTX = function addTX(tx, view) {
this.fees += item.fee;
// Add the tx to our block
this.block.txs.push(tx.clone());
this.block.txs.push(tx);
this.items.push(item);
// Update coinbase value

View File

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

View File

@ -1718,7 +1718,7 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) {
var blocks = 0;
var unknown = -1;
var items = packet.items;
var i, j, item, tx, block, result;
var i, j, item, tx, block, result, height;
if (items.length > 50000)
throw new Error('getdata size too large (' + items.length + ').');
@ -1790,7 +1790,8 @@ Peer.prototype._handleGetData = co(function* _handleGetData(packet) {
break;
case constants.inv.CMPCT_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);
if (!result) {
notFound.push(item);
@ -2335,7 +2336,7 @@ Peer.prototype._handleCmpctBlock = co(function* _handleCmpctBlock(packet) {
Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) {
var req = packet.request;
var res, item, block;
var res, item, block, height;
if (this.chain.db.options.spv)
return;
@ -2358,7 +2359,9 @@ Peer.prototype._handleGetBlockTxn = co(function* _handleGetBlockTxn(packet) {
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(
'Peer sent a getblocktxn for a block > 15 deep (%s)',
this.hostname);

View File

@ -403,22 +403,55 @@ FullNode.prototype.getCoin = function getCoin(hash, index) {
*/
FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) {
var coins = this.mempool.getCoinsByAddress(addresses);
var i, blockCoins, coin, spent;
var mempool = this.mempool.getCoinsByAddress(addresses);
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 < blockCoins.length; i++) {
coin = blockCoins[i];
for (i = 0; i < chain.length; i++) {
coin = chain[i];
spent = this.mempool.isSpent(coin.hash, coin.index);
if (spent)
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) {
var mempool = this.mempool.getTXByAddress(addresses);
var txs = yield this.chain.db.getTXByAddress(addresses);
return mempool.concat(txs);
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;
});
/**
@ -440,14 +480,12 @@ FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
* @returns {Promise} - Returns {@link TX}.
*/
FullNode.prototype.getTX = function getTX(hash) {
var tx = this.mempool.getTX(hash);
if (tx)
return Promise.resolve(tx);
return this.chain.db.getTX(hash);
};
FullNode.prototype.getTX = co(function* getTX(hash) {
var mtx = yield this.getMeta(hash);
if (!mtx)
return;
return mtx.tx;
});
/**
* 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);
};
/**
* 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
*/

View File

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

View File

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

View File

@ -209,6 +209,62 @@ Headers.fromBlock = function fromBlock(block) {
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
* user-friendly representation of the data.
@ -216,9 +272,21 @@ Headers.fromBlock = function fromBlock(block) {
*/
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 {
hash: this.rhash(),
height: this.height,
height: height != null ? height : -1,
date: util.date(this.ts),
version: util.hex32(this.version),
prevBlock: util.revHex(this.prevBlock),

View File

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

View File

@ -248,7 +248,7 @@ Input.prototype.format = function format(coin) {
redeem: this.getRedeem(coin),
sequence: this.sequence,
prevout: this.prevout,
coin: coin
coin: coin || null
};
};
@ -289,7 +289,7 @@ Input.prototype.getJSON = function getJSON(network, coin) {
witness: this.witness.toJSON(),
sequence: this.sequence,
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
* @constructor
* @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))
return new MemBlock(options);
return new MemBlock();
AbstractBlock.call(this, options);
this.memory = true;
this.coinbaseHeight = -1;
this.raw = null;
if (options)
this.fromOptions(options);
this._cbHeight = -1;
this._raw = null;
}
util.inherits(MemBlock, AbstractBlock);
/**
* 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);
};
MemBlock.prototype.memory = true;
/**
* Serialize the block headers.
@ -84,7 +53,7 @@ MemBlock.fromOptions = function fromOptions(options) {
*/
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() {
return this.raw.length;
return this._raw.length;
};
/**
@ -115,7 +84,7 @@ MemBlock.prototype._verify = function _verify(ret) {
*/
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.raw = br.data;
this._cbHeight = height;
this._raw = br.data;
return this;
};
@ -175,7 +144,7 @@ MemBlock.fromRaw = function fromRaw(data) {
*/
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() {
return this.raw;
return this._raw;
};
/**
@ -195,11 +164,11 @@ MemBlock.prototype.toNormal = function toNormal() {
*/
MemBlock.prototype.toBlock = function toBlock() {
var block = Block.fromRaw(this.raw);
var block = Block.fromRaw(this._raw);
block._hash = this._hash;
block._cbHeight = this.coinbaseHeight;
block._cbHeight = this._cbHeight;
block._validHeaders = this._validHeaders;
this.raw = null;
this._raw = null;
return block;
};

View File

@ -100,7 +100,7 @@ MerkleBlock.prototype.getSize = function getSize() {
/**
* Add a transaction to the block's tx vector.
* @param {TX} tx
* @returns {TX}
* @returns {Number}
*/
MerkleBlock.prototype.addTX = function addTX(tx) {
@ -108,9 +108,8 @@ MerkleBlock.prototype.addTX = function addTX(tx) {
var index = this.map[hash];
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() {
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 {
type: 'merkleblock',
hash: this.rhash(),
height: this.height,
height: height != null ? height : -1,
date: util.date(this.ts),
version: util.hex32(this.version),
prevBlock: util.revHex(this.prevBlock),
@ -433,17 +443,29 @@ MerkleBlock.fromRaw = function fromRaw(data, enc) {
/**
* 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.
* for JSON serialization.
* @returns {Object}
*/
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 {
type: 'merkleblock',
hash: this.rhash(),
height: this.height,
height: height,
version: this.version,
prevBlock: util.revHex(this.prevBlock),
merkleRoot: util.revHex(this.merkleRoot),
@ -468,7 +490,6 @@ MerkleBlock.prototype.fromJSON = function fromJSON(json) {
var i, hash;
assert(json, 'MerkleBlock data is required.');
assert.equal(json.type, 'merkleblock');
assert(Array.isArray(json.hashes));
assert(typeof json.flags === 'string');
@ -640,7 +661,6 @@ MerkleBlock.fromMatches = function fromMatches(block, matches) {
merkle.bits = block.bits;
merkle.nonce = block.nonce;
merkle.totalTX = totalTX;
merkle.height = block.height;
merkle.hashes = hashes;
merkle.flags = flags;
merkle.txs = txs;

View File

@ -33,7 +33,6 @@ var encoding = require('../utils/encoding');
* @constructor
* @param {Object} options
* @param {Number?} options.version
* @param {Number?} options.ps
* @param {Number?} options.changeIndex
* @param {Input[]?} options.inputs
* @param {Output[]?} options.outputs
@ -45,16 +44,6 @@ var encoding = require('../utils/encoding');
* @property {Input[]} inputs
* @property {Output[]} outputs
* @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
*/
@ -110,11 +99,6 @@ MTX.prototype.fromOptions = function fromOptions(options) {
this.locktime = options.locktime;
}
if (options.ps != null) {
assert(util.isNumber(options.ps));
this.ps = options.ps;
}
if (options.changeIndex != null) {
assert(util.isNumber(options.changeIndex));
this.changeIndex = options.changeIndex;

View File

@ -49,15 +49,6 @@ var BAD_NONSTD_P2WSH = 3;
* @property {Input[]} inputs
* @property {Output[]} outputs
* @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) {
@ -69,12 +60,6 @@ function TX(options) {
this.inputs = [];
this.outputs = [];
this.locktime = 0;
this.ts = 0;
this.block = null;
this.index = -1;
this.ps = util.now();
this.height = -1;
this.mutable = false;
this._hash = null;
@ -133,30 +118,6 @@ TX.prototype.fromOptions = function fromOptions(options) {
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;
};
@ -179,30 +140,6 @@ TX.prototype.clone = function clone() {
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.
* @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));
};
/**
* 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.
* @returns {Hash[]} Outpoint hashes.
@ -1955,17 +1873,6 @@ TX.prototype.isWatched = function isWatched(filter) {
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.
* @returns {Hash}
@ -2025,12 +1932,18 @@ TX.prototype.inspect = function inspect() {
* Inspect the transaction and return a more
* user-friendly representation of the data.
* @param {CoinView} view
* @param {ChainEntry} entry
* @param {Number} index
* @returns {Object}
*/
TX.prototype.format = function format(view) {
TX.prototype.format = function format(view, entry, index) {
var rate = 0;
var fee = 0;
var height = -1;
var block = null;
var ts = 0;
var date = null;
if (view) {
fee = this.getFee(view);
@ -2041,21 +1954,30 @@ TX.prototype.format = function format(view) {
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 {
hash: this.txid(),
witnessHash: this.wtxid(),
size: this.getSize(),
virtualSize: this.getVirtualSize(),
height: this.height,
value: Amount.btc(this.getOutputValue()),
fee: Amount.btc(fee),
rate: Amount.btc(rate),
minFee: Amount.btc(this.getMinFee()),
date: util.date(this.ts || this.ps),
block: this.block ? util.revHex(this.block) : null,
ts: this.ts,
ps: this.ps,
index: this.index,
height: height,
block: block,
ts: ts,
date: date,
index: index,
version: this.version,
flag: this.flag,
inputs: this.inputs.map(function(input) {
@ -2084,12 +2006,13 @@ TX.prototype.toJSON = function toJSON() {
* of little-endian uint256s.
* @param {Network} network
* @param {CoinView} view
* @param {ChainEntry} entry
* @param {Number} index
* @returns {Object}
*/
TX.prototype.getJSON = function getJSON(network, view) {
var rate = 0;
var fee = 0;
TX.prototype.getJSON = function getJSON(network, view, entry, index) {
var rate, fee, height, block, ts, date;
if (view) {
fee = this.getFee(view);
@ -2098,6 +2021,16 @@ TX.prototype.getJSON = function getJSON(network, view) {
// Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate))
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);
@ -2105,14 +2038,14 @@ TX.prototype.getJSON = function getJSON(network, view) {
return {
hash: this.txid(),
witnessHash: this.wtxid(),
height: this.height,
block: this.rblock(),
ts: this.ts,
ps: this.ps,
date: util.date(this.ts || this.ps),
index: this.index,
fee: Amount.btc(fee),
rate: Amount.btc(rate),
fee: fee,
rate: rate,
ps: util.now(),
height: height,
block: block,
ts: ts,
date: date,
index: index,
version: this.version,
flag: this.flag,
inputs: this.inputs.map(function(input) {
@ -2136,23 +2069,11 @@ TX.prototype.fromJSON = function fromJSON(json) {
var i, input, output;
assert(json, 'TX data is required.');
assert.equal(json.type, 'tx');
assert(util.isNumber(json.version));
assert(util.isNumber(json.flag));
assert(Array.isArray(json.inputs));
assert(Array.isArray(json.outputs));
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;
@ -2455,87 +2376,6 @@ TX.isWitness = function isWitness(br) {
&& 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.
* @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) {
var br, block, hash;
var br, block, hash, height;
if (typeof data === 'string')
data = new Buffer(data, 'hex');
@ -343,11 +343,10 @@ function parseEntry(data, enc) {
br = new BufferReader(data);
block = Headers.fromAbbr(br);
block.height = br.readU32();
height = br.readU32();
hash = block.hash('hex');
return new BlockMeta(hash, block.height, block.ts);
return new BlockMeta(hash, height, block.ts);
}
function parseBlock(entry, txs) {
@ -358,10 +357,6 @@ function parseBlock(entry, txs) {
for (i = 0; i < txs.length; i++) {
tx = txs[i];
tx = parseTX(tx);
tx.block = block.hash;
tx.height = block.height;
tx.ts = block.ts;
tx.index = -1;
out.push(tx);
}

View File

@ -6,11 +6,12 @@
'use strict';
var util = require('../utils/util');
var assert = require('assert');
var util = require('../utils/util');
var constants = require('../protocol/constants');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var TX = require('../primitives/tx');
/**
* Chain State
@ -470,6 +471,150 @@ PathMapRecord.fromRaw = function fromRaw(hash, 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
*/
@ -516,5 +661,6 @@ exports.BlockMapRecord = BlockMapRecord;
exports.TXMapRecord = TXMapRecord;
exports.OutpointMapRecord = OutpointMapRecord;
exports.PathMapRecord = PathMapRecord;
exports.TXRecord = TXRecord;
module.exports = exports;

View File

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

View File

@ -1652,24 +1652,26 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
*/
Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase) {
var tx = yield this.getTX(hash);
var i, oldFee, fee, path, input, output, change;
var wtx = yield this.getTX(hash);
var i, tx, view, oldFee, fee, path, input, output, change;
if (!tx)
if (!wtx)
throw new Error('Transaction not found.');
tx = wtx.tx;
if (tx.isCoinbase())
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.');
if (!util.isUInt32(rate))
throw new Error('Rate must be a number.');
oldFee = tx.getFee();
oldFee = tx.getFee(view);
fee = tx.getMinFee(null, rate);
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.');
tx = MTX.fromRaw(tx.toRaw());
tx.view = view;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
@ -1715,7 +1718,7 @@ Wallet.prototype.increaseFee = co(function* increaseFee(hash, rate, passphrase)
if (change.value < 0)
throw new Error('Fee is too high.');
if (change.isDust(constants.tx.MIN_RELAY)) {
if (change.isDust()) {
tx.outputs.splice(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
* @returns {Promise} - Returns {@link TX}.
* @returns {Promise} - Returns {@link CoinView}.
*/
Wallet.prototype.fillHistory = function fillHistory(tx) {
return this.txdb.fillHistory(tx);
Wallet.prototype.getCoinView = function getCoinView(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.
* @param {TX} tx
* @param {TXRecord} wtx
* @returns {Promise} - Returns {@link Details}.
*/
Wallet.prototype.toDetails = function toDetails(tx) {
return this.txdb.toDetails(tx);
Wallet.prototype.toDetails = function toDetails(wtx) {
return this.txdb.toDetails(wtx);
};
/**

View File

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

View File

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