diff --git a/.jshintrc b/.jshintrc
index 2a7b0344..0339c34f 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -41,6 +41,8 @@
"laxbreak": true,
"laxcomma": true,
+ "noyield": true,
+
"predef": [
"it",
"describe",
diff --git a/browser/index.js b/browser/index.js
index da08d81d..ff04163f 100644
--- a/browser/index.js
+++ b/browser/index.js
@@ -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('' + tx.rhash + ' (' + tx.height
- + ' - ' + kb(tx.getSize()) + ')');
+ + item.rhash() + '">' + item.rhash() + ' (' + height
+ + ' - ' + kb(item.getSize()) + ')');
tdiv.appendChild(el);
- setMouseup(el, tx);
+ setMouseup(el, item);
items.push(el);
diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js
index 20f5171b..30785105 100644
--- a/lib/blockchain/chain.js
+++ b/lib/blockchain/chain.js
@@ -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()`
diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js
index 9d04381d..8cbcd202 100644
--- a/lib/blockchain/chaindb.js
+++ b/lib/blockchain/chaindb.js
@@ -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));
diff --git a/lib/http/rpc.js b/lib/http/rpc.js
index 5173b016..4c55633c 100644
--- a/lib/http/rpc.js
+++ b/lib/http/rpc.js
@@ -6,6 +6,9 @@
'use strict';
+var fs = require('fs');
+var EventEmitter = require('events').EventEmitter;
+
var util = require('../utils/util');
var co = require('../utils/co');
var crypto = require('../crypto/crypto');
@@ -17,7 +20,6 @@ var NetworkAddress = require('../primitives/netaddress');
var Script = require('../script/script');
var Address = require('../primitives/address');
var Block = require('../primitives/block');
-var Coin = require('../primitives/coin');
var Headers = require('../primitives/headers');
var Input = require('../primitives/input');
var KeyRing = require('../primitives/keyring');
@@ -27,11 +29,13 @@ var MTX = require('../primitives/mtx');
var Network = require('../protocol/network');
var Outpoint = require('../primitives/outpoint');
var Output = require('../primitives/output');
-var BufferReader = require('../utils/reader');
var TX = require('../primitives/tx');
var Logger = require('../node/logger');
-var EventEmitter = require('events').EventEmitter;
-var fs = require('fs');
+
+/**
+ * RPC
+ * @constructor
+ */
function RPC(node) {
if (!(this instanceof RPC))
@@ -300,7 +304,7 @@ RPC.prototype.execute = function execute(json) {
return this.setloglevel(json.params);
default:
- return Promise.reject(new Error('Method not found: ' + json.method + '.'));
+ return Promise.reject(new Error('Not found: ' + json.method + '.'));
}
};
@@ -336,11 +340,11 @@ RPC.prototype.getinfo = co(function* getinfo(args) {
};
});
-RPC.prototype.help = function help(args) {
+RPC.prototype.help = co(function* help(args) {
var json;
if (args.length === 0)
- return Promise.resolve('Select a command.');
+ return 'Select a command.';
json = {
method: args[0],
@@ -349,27 +353,27 @@ RPC.prototype.help = function help(args) {
json.params.help = true;
- return this.execute(json);
-};
+ return yield this.execute(json);
+});
-RPC.prototype.stop = function stop(args) {
+RPC.prototype.stop = co(function* stop(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('stop'));
+ throw new RPCError('stop');
this.node.close();
- return Promise.resolve('Stopping.');
-};
+ return 'Stopping.';
+});
/*
* P2P networking
*/
-RPC.prototype.getnetworkinfo = function getnetworkinfo(args) {
+RPC.prototype.getnetworkinfo = co(function* getnetworkinfo(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getnetworkinfo'));
+ throw new RPCError('getnetworkinfo');
- return Promise.resolve({
+ return {
version: constants.USER_VERSION,
subversion: constants.USER_AGENT,
protocolversion: constants.VERSION,
@@ -380,14 +384,14 @@ RPC.prototype.getnetworkinfo = function getnetworkinfo(args) {
relayfee: Amount.btc(this.network.getMinRelay(), true),
localaddresses: [],
warnings: ''
- });
-};
+ };
+});
-RPC.prototype.addnode = function addnode(args) {
+RPC.prototype.addnode = co(function* addnode(args) {
var i, node, cmd, seed, addr, peer;
if (args.help || args.length !== 2)
- return Promise.reject(new RPCError('addnode "node" "add|remove|onetry"'));
+ throw new RPCError('addnode "node" "add|remove|onetry"');
node = toString(args[0]);
cmd = toString(args[1]);
@@ -414,14 +418,14 @@ RPC.prototype.addnode = function addnode(args) {
break;
}
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.disconnectnode = function disconnectnode(args) {
+RPC.prototype.disconnectnode = co(function* disconnectnode(args) {
var node, addr, peer;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('disconnectnode "node"'));
+ throw new RPCError('disconnectnode "node"');
node = toString(args[0]);
addr = NetworkAddress.fromHostname(node, this.network);
@@ -430,22 +434,22 @@ RPC.prototype.disconnectnode = function disconnectnode(args) {
if (peer)
peer.destroy();
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.getaddednodeinfo = function getaddednodeinfo(args) {
+RPC.prototype.getaddednodeinfo = co(function* getaddednodeinfo(args) {
var out = [];
var i, host, addr, peer, peers;
if (args.help || args.length < 1 || args.length > 2)
- return Promise.reject(new RPCError('getaddednodeinfo dummy ( "node" )'));
+ throw new RPCError('getaddednodeinfo dummy ( "node" )');
if (args.length === 2) {
host = toString(args[1]);
addr = NetworkAddress.fromHostname(host, this.network);
peer = this.pool.peers.get(addr);
if (!peer)
- return Promise.reject(new RPCError('Node has not been added.'));
+ throw new RPCError('Node has not been added.');
peers = [peer];
} else {
peers = this.pool.peers.all;
@@ -467,24 +471,23 @@ RPC.prototype.getaddednodeinfo = function getaddednodeinfo(args) {
});
}
- return Promise.resolve(out);
-};
+ return out;
+});
-RPC.prototype.getconnectioncount = function getconnectioncount(args) {
+RPC.prototype.getconnectioncount = co(function* getconnectioncount(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getconnectioncount'));
+ throw new RPCError('getconnectioncount');
- return Promise.resolve(this.pool.peers.all.length);
-};
+ return this.pool.peers.all.length;
+});
-RPC.prototype.getnettotals = function getnettotals(args) {
- var i, sent, recv, peer;
+RPC.prototype.getnettotals = co(function* getnettotals(args) {
+ var sent = 0;
+ var recv = 0;
+ var i, peer;
if (args.help || args.length > 0)
- return Promise.reject(new RPCError('getnettotals'));
-
- sent = 0;
- recv = 0;
+ throw new RPCError('getnettotals');
for (i = 0; i < this.pool.peers.all.length; i++) {
peer = this.pool.peers.all[i];
@@ -492,19 +495,19 @@ RPC.prototype.getnettotals = function getnettotals(args) {
recv += peer.socket.bytesRead;
}
- return Promise.resolve({
+ return {
totalbytesrecv: recv,
totalbytessent: sent,
timemillis: util.ms()
- });
-};
+ };
+});
-RPC.prototype.getpeerinfo = function getpeerinfo(args) {
+RPC.prototype.getpeerinfo = co(function* getpeerinfo(args) {
var peers = [];
var i, peer;
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getpeerinfo'));
+ throw new RPCError('getpeerinfo');
for (i = 0; i < this.pool.peers.all.length; i++) {
peer = this.pool.peers.all[i];
@@ -531,29 +534,29 @@ RPC.prototype.getpeerinfo = function getpeerinfo(args) {
});
}
- return Promise.resolve(peers);
-};
+ return peers;
+});
-RPC.prototype.ping = function ping(args) {
+RPC.prototype.ping = co(function* ping(args) {
var i;
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('ping'));
+ throw new RPCError('ping');
for (i = 0; i < this.pool.peers.all.length; i++)
this.pool.peers.all[i].sendPing();
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.setban = function setban(args) {
+RPC.prototype.setban = co(function* setban(args) {
var host, ip;
if (args.help
|| args.length < 2
|| (args[1] !== 'add' && args[1] !== 'remove')) {
- return Promise.reject(new RPCError(
- 'setban "ip(/netmask)" "add|remove" (bantime) (absolute)'));
+ throw new RPCError('setban "ip(/netmask)"'
+ + ' "add|remove" (bantime) (absolute)');
}
host = toString(args[0]);
@@ -568,14 +571,14 @@ RPC.prototype.setban = function setban(args) {
break;
}
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.listbanned = function listbanned(args) {
+RPC.prototype.listbanned = co(function* listbanned(args) {
var i, banned, keys, host, time;
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('listbanned'));
+ throw new RPCError('listbanned');
banned = [];
keys = Object.keys(this.pool.hosts.misbehaving);
@@ -591,17 +594,17 @@ RPC.prototype.listbanned = function listbanned(args) {
});
}
- return Promise.resolve(banned);
-};
+ return banned;
+});
-RPC.prototype.clearbanned = function clearbanned(args) {
+RPC.prototype.clearbanned = co(function* clearbanned(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('clearbanned'));
+ throw new RPCError('clearbanned');
this.pool.hosts.clear();
- return Promise.resolve();
-};
+ return null;
+});
RPC.prototype._deployment = function _deployment(id, version, status) {
return {
@@ -707,19 +710,19 @@ RPC.prototype._getDifficulty = function getDifficulty(entry) {
return diff;
};
-RPC.prototype.getbestblockhash = function getbestblockhash(args) {
+RPC.prototype.getbestblockhash = co(function* getbestblockhash(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getbestblockhash'));
+ throw new RPCError('getbestblockhash');
- return Promise.resolve(this.chain.tip.rhash());
-};
+ return this.chain.tip.rhash();
+});
-RPC.prototype.getblockcount = function getblockcount(args) {
+RPC.prototype.getblockcount = co(function* getblockcount(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getblockcount'));
+ throw new RPCError('getblockcount');
- return Promise.resolve(this.chain.tip.height);
-};
+ return this.chain.tip.height;
+});
RPC.prototype.getblock = co(function* getblock(args) {
var hash, verbose, entry, block;
@@ -760,7 +763,7 @@ RPC.prototype.getblock = co(function* getblock(args) {
return yield this._blockToJSON(entry, block, false);
});
-RPC.prototype._txToJSON = function _txToJSON(tx) {
+RPC.prototype._txToJSON = function _txToJSON(tx, entry) {
var self = this;
return {
txid: tx.txid(),
@@ -796,10 +799,10 @@ RPC.prototype._txToJSON = function _txToJSON(tx) {
scriptPubKey: self._scriptToJSON(output.script, true)
};
}),
- blockhash: tx.block || null,
+ blockhash: entry ? entry.rhash() : null,
confirmations: tx.getConfirmations(this.chain.height),
- time: tx.ts,
- blocktime: tx.ts
+ time: entry ? entry.ts : 0,
+ blocktime: entry ? entry.ts : 0
};
};
@@ -908,7 +911,7 @@ RPC.prototype._blockToJSON = co(function* _blockToJSON(entry, block, txDetails)
merkleroot: util.revHex(entry.merkleRoot),
tx: block.txs.map(function(tx) {
if (txDetails)
- return self._txToJSON(tx);
+ return self._txToJSON(tx, entry);
return tx.txid();
}),
time: entry.ts,
@@ -960,42 +963,42 @@ RPC.prototype._findFork = co(function* _findFork(entry) {
throw new Error('Fork not found.');
});
-RPC.prototype.getdifficulty = function getdifficulty(args) {
+RPC.prototype.getdifficulty = co(function* getdifficulty(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getdifficulty'));
+ throw new RPCError('getdifficulty');
- return Promise.resolve(this._getDifficulty());
-};
+ return this._getDifficulty();
+});
-RPC.prototype.getmempoolinfo = function getmempoolinfo(args) {
+RPC.prototype.getmempoolinfo = co(function* getmempoolinfo(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getmempoolinfo'));
+ throw new RPCError('getmempoolinfo');
if (!this.mempool)
- return Promise.reject(new RPCError('No mempool available.'));
+ throw new RPCError('No mempool available.');
- return Promise.resolve({
+ return {
size: this.mempool.totalTX,
bytes: this.mempool.getSize(),
usage: this.mempool.getSize(),
maxmempool: constants.mempool.MAX_MEMPOOL_SIZE,
mempoolminfee: Amount.btc(this.mempool.minRelay, true)
- });
-};
+ };
+});
-RPC.prototype.getmempoolancestors = function getmempoolancestors(args) {
+RPC.prototype.getmempoolancestors = co(function* getmempoolancestors(args) {
var i, hash, verbose, entry, entries;
if (args.help || args.length < 1 || args.length > 2)
- return Promise.reject(new RPCError('getmempoolancestors txid (verbose)'));
+ throw new RPCError('getmempoolancestors txid (verbose)');
if (!this.mempool)
- return Promise.reject(new RPCError('No mempool available.'));
+ throw new RPCError('No mempool available.');
hash = toHash(args[0]);
if (!hash)
- return Promise.reject(new RPCError('Invalid parameter.'));
+ throw new RPCError('Invalid parameter.');
if (args.length > 1)
verbose = toBool(args[1], false);
@@ -1003,7 +1006,7 @@ RPC.prototype.getmempoolancestors = function getmempoolancestors(args) {
entry = this.mempool.getEntry(hash);
if (!entry)
- return Promise.reject(new RPCError('Transaction not in mempool.'));
+ throw new RPCError('Transaction not in mempool.');
entries = this.mempool.getAncestors(entry.tx);
@@ -1015,22 +1018,22 @@ RPC.prototype.getmempoolancestors = function getmempoolancestors(args) {
entries[i] = entries[i].tx.txid();
}
- return Promise.resolve(entries);
-};
+ return entries;
+});
-RPC.prototype.getmempooldescendants = function getmempooldescendants(args) {
+RPC.prototype.getmempooldescendants = co(function* getmempooldescendants(args) {
var i, hash, verbose, entry, entries;
if (args.help || args.length < 1 || args.length > 2)
- return Promise.reject(new RPCError('getmempooldescendants txid (verbose)'));
+ throw new RPCError('getmempooldescendants txid (verbose)');
if (!this.mempool)
- return Promise.reject(new RPCError('No mempool available.'));
+ throw new RPCError('No mempool available.');
hash = toHash(args[0]);
if (!hash)
- return Promise.reject(new RPCError('Invalid parameter.'));
+ throw new RPCError('Invalid parameter.');
if (args.length > 1)
verbose = toBool(args[1], false);
@@ -1038,7 +1041,7 @@ RPC.prototype.getmempooldescendants = function getmempooldescendants(args) {
entry = this.mempool.getEntry(hash);
if (!entry)
- return Promise.reject(new RPCError('Transaction not in mempool.'));
+ throw new RPCError('Transaction not in mempool.');
entries = this.mempool.getDescendants(entry.tx);
@@ -1050,36 +1053,36 @@ RPC.prototype.getmempooldescendants = function getmempooldescendants(args) {
entries[i] = entries[i].tx.txid();
}
- return Promise.resolve(entries);
-};
+ return entries;
+});
-RPC.prototype.getmempoolentry = function getmempoolentry(args) {
+RPC.prototype.getmempoolentry = co(function* getmempoolentry(args) {
var hash, entry;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('getmempoolentry txid'));
+ throw new RPCError('getmempoolentry txid');
if (!this.mempool)
- return Promise.reject(new RPCError('No mempool available.'));
+ throw new RPCError('No mempool available.');
hash = toHash(args[0]);
if (!hash)
- return Promise.reject(new RPCError('Invalid parameter.'));
+ throw new RPCError('Invalid parameter.');
entry = this.mempool.getEntry(hash);
if (!entry)
- return Promise.reject(new RPCError('Transaction not in mempool.'));
+ throw new RPCError('Transaction not in mempool.');
- return Promise.resolve(this._entryToJSON(entry));
-};
+ return this._entryToJSON(entry);
+});
-RPC.prototype.getrawmempool = function getrawmempool(args) {
+RPC.prototype.getrawmempool = co(function* getrawmempool(args) {
var verbose;
if (args.help || args.length > 1)
- return Promise.reject(new RPCError('getrawmempool ( verbose )'));
+ throw new RPCError('getrawmempool ( verbose )');
verbose = false;
@@ -1087,7 +1090,7 @@ RPC.prototype.getrawmempool = function getrawmempool(args) {
verbose = toBool(args[0], false);
return this._mempoolToJSON(verbose);
-};
+});
RPC.prototype._mempoolToJSON = function _mempoolToJSON(verbose) {
var out = {};
@@ -1216,7 +1219,7 @@ RPC.prototype.gettxoutproof = co(function* gettxoutproof(args) {
if (hash) {
block = yield this.chain.db.getBlock(hash);
} else if (this.chain.options.indexTX) {
- tx = yield this.chain.db.getTX(txid);
+ tx = yield this.chain.db.getMeta(txid);
if (!tx)
return;
block = yield this.chain.db.getBlock(tx.block);
@@ -1265,14 +1268,14 @@ RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) {
return res;
});
-RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args) {
+RPC.prototype.gettxoutsetinfo = co(function* gettxoutsetinfo(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('gettxoutsetinfo'));
+ throw new RPCError('gettxoutsetinfo');
if (this.chain.db.options.spv)
- return Promise.reject(new RPCError('Chainstate not available (SPV mode).'));
+ throw new RPCError('Chainstate not available (SPV mode).');
- return Promise.resolve({
+ return {
height: this.chain.height,
bestblock: this.chain.tip.rhash(),
transactions: this.chain.db.state.tx,
@@ -1280,21 +1283,21 @@ RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args) {
bytes_serialized: 0,
hash_serialized: 0,
total_amount: Amount.btc(this.chain.db.state.value, true)
- });
-};
+ };
+});
-RPC.prototype.verifychain = function verifychain(args) {
+RPC.prototype.verifychain = co(function* verifychain(args) {
if (args.help || args.length > 2)
- return Promise.reject(new RPCError('verifychain ( checklevel numblocks )'));
+ throw new RPCError('verifychain ( checklevel numblocks )');
if (this.chain.db.options.spv)
- return Promise.reject(new RPCError('Cannot verify chain in SPV mode.'));
+ throw new RPCError('Cannot verify chain in SPV mode.');
if (this.chain.db.options.prune)
- return Promise.reject(new RPCError('Cannot verify chain when pruned.'));
+ throw new RPCError('Cannot verify chain when pruned.');
return null;
-};
+});
/*
* Mining
@@ -1786,12 +1789,12 @@ RPC.prototype.getmininginfo = co(function* getmininginfo(args) {
};
});
-RPC.prototype.getnetworkhashps = function getnetworkhashps(args) {
+RPC.prototype.getnetworkhashps = co(function* getnetworkhashps(args) {
var lookup = 120;
var height = -1;
if (args.help || args.length > 2)
- return Promise.reject(new RPCError('getnetworkhashps ( blocks height )'));
+ throw new RPCError('getnetworkhashps ( blocks height )');
if (args.length > 0)
lookup = toNumber(args[0], 120);
@@ -1799,34 +1802,34 @@ RPC.prototype.getnetworkhashps = function getnetworkhashps(args) {
if (args.length > 1)
height = toNumber(args[1], -1);
- return this._hashps(lookup, height);
-};
+ return yield this._hashps(lookup, height);
+});
-RPC.prototype.prioritisetransaction = function prioritisetransaction(args) {
+RPC.prototype.prioritisetransaction = co(function* prioritisetransaction(args) {
var hash, pri, fee, entry;
if (args.help || args.length !== 3) {
- return Promise.reject(new RPCError('prioritisetransaction'
- + ' '));
+ throw new RPCError('prioritisetransaction'
+ + ' ');
}
if (!this.mempool)
- return Promise.reject(new RPCError('No mempool available.'));
+ throw new RPCError('No mempool available.');
hash = toHash(args[0]);
pri = args[1];
fee = args[2];
if (!hash)
- return Promise.reject(new RPCError('Invalid parameter'));
+ throw new RPCError('Invalid parameter');
if (!util.isNumber(pri) || !util.isNumber(fee))
- return Promise.reject(new RPCError('Invalid parameter'));
+ throw new RPCError('Invalid parameter');
entry = this.mempool.getEntry(hash);
if (!entry)
- return Promise.reject(new RPCError('Transaction not in mempool.'));
+ throw new RPCError('Transaction not in mempool.');
entry.priority += pri;
entry.fee += fee;
@@ -1837,8 +1840,8 @@ RPC.prototype.prioritisetransaction = function prioritisetransaction(args) {
if (entry.fee < 0)
entry.fee = 0;
- return Promise.resolve(true);
-};
+ return true;
+});
RPC.prototype._hashps = co(function* _hashps(lookup, height) {
var tip = this.chain.tip;
@@ -1884,11 +1887,11 @@ RPC.prototype._hashps = co(function* _hashps(lookup, height) {
* Coin generation
*/
-RPC.prototype.getgenerate = function getgenerate(args) {
+RPC.prototype.getgenerate = co(function* getgenerate(args) {
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getgenerate'));
- return Promise.resolve(this.mining);
-};
+ throw new RPCError('getgenerate');
+ return this.mining;
+});
RPC.prototype.setgenerate = co(function* setgenerate(args) {
if (args.help || args.length < 1 || args.length > 2)
@@ -1963,30 +1966,30 @@ RPC.prototype._generatetoaddress = co(function* generatetoaddress(args) {
* Raw transactions
*/
-RPC.prototype.createrawtransaction = function createrawtransaction(args) {
+RPC.prototype.createrawtransaction = co(function* createrawtransaction(args) {
var inputs, sendTo, tx, locktime;
var i, input, output, hash, index, sequence;
var keys, addrs, key, value, address, b58;
if (args.help || args.length < 2 || args.length > 3) {
- return Promise.reject(new RPCError('createrawtransaction'
+ throw new RPCError('createrawtransaction'
+ ' [{"txid":"id","vout":n},...]'
+ ' {"address":amount,"data":"hex",...}'
- + ' ( locktime )'));
+ + ' ( locktime )');
}
inputs = toArray(args[0]);
sendTo = toObject(args[1]);
if (!inputs || !sendTo)
- return Promise.reject(new RPCError('Invalid parameter'));
+ throw new RPCError('Invalid parameter');
tx = new TX();
if (args.length > 2 && args[2] != null) {
locktime = toNumber(args[2]);
- if (locktime < 0 || locktime > 0xffffffff)
- return Promise.reject(new RPCError('Locktime out of range'));
+ if (!util.isUInt32(locktime))
+ throw new RPCError('Locktime out of range');
tx.locktime = locktime;
}
@@ -1994,7 +1997,7 @@ RPC.prototype.createrawtransaction = function createrawtransaction(args) {
input = inputs[i];
if (!input)
- return Promise.reject(new RPCError('Invalid parameter'));
+ throw new RPCError('Invalid parameter');
hash = toHash(input.txid);
index = input.vout;
@@ -2003,16 +2006,13 @@ RPC.prototype.createrawtransaction = function createrawtransaction(args) {
if (tx.locktime)
sequence--;
- if (!hash
- || !util.isNumber(index)
- || index < 0) {
- return Promise.reject(new RPCError('Invalid parameter'));
- }
+ if (!hash || !util.isUInt32(index))
+ throw new RPCError('Invalid parameter');
if (util.isNumber(input.sequence)) {
sequence = toNumber(input.sequence);
- if (input.sequence < 0 || input.sequence > 0xffffffff)
- return Promise.reject(new RPCError('Invalid parameter'));
+ if (!util.isUInt32(sequence))
+ throw new RPCError('Invalid parameter');
}
input = new Input({
@@ -2047,7 +2047,7 @@ RPC.prototype.createrawtransaction = function createrawtransaction(args) {
b58 = address.toBase58(this.network);
if (addrs[b58])
- return Promise.reject(new RPCError('Duplicate address'));
+ throw new RPCError('Duplicate address');
addrs[b58] = true;
@@ -2059,25 +2059,25 @@ RPC.prototype.createrawtransaction = function createrawtransaction(args) {
tx.outputs.push(output);
}
- return Promise.resolve(tx.toRaw().toString('hex'));
-};
+ return tx.toRaw().toString('hex');
+});
-RPC.prototype.decoderawtransaction = function decoderawtransaction(args) {
+RPC.prototype.decoderawtransaction = co(function* decoderawtransaction(args) {
var tx;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('decoderawtransaction "hexstring"'));
+ throw new RPCError('decoderawtransaction "hexstring"');
tx = TX.fromRaw(toString(args[0]), 'hex');
- return Promise.resolve(this._txToJSON(tx));
-};
+ return this._txToJSON(tx);
+});
-RPC.prototype.decodescript = function decodescript(args) {
+RPC.prototype.decodescript = co(function* decodescript(args) {
var data, script, hash, address;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('decodescript "hex"'));
+ throw new RPCError('decodescript "hex"');
data = toString(args[0]);
script = new Script();
@@ -2091,8 +2091,8 @@ RPC.prototype.decodescript = function decodescript(args) {
script = this._scriptToJSON(script);
script.p2sh = address.toBase58(this.network);
- return Promise.resolve(script);
-};
+ return script;
+});
RPC.prototype.getrawtransaction = co(function* getrawtransaction(args) {
var hash, verbose, json, tx;
@@ -2116,7 +2116,7 @@ RPC.prototype.getrawtransaction = co(function* getrawtransaction(args) {
throw new RPCError('Transaction not found.');
if (!verbose)
- throw tx.toRaw().toString('hex');
+ return tx.toRaw().toString('hex');
json = this._txToJSON(tx);
json.hex = tx.toRaw().toString('hex');
@@ -2124,26 +2124,25 @@ RPC.prototype.getrawtransaction = co(function* getrawtransaction(args) {
return json;
});
-RPC.prototype.sendrawtransaction = function sendrawtransaction(args) {
+RPC.prototype.sendrawtransaction = co(function* sendrawtransaction(args) {
var tx;
- if (args.help || args.length < 1 || args.length > 2) {
- return Promise.reject(new RPCError('sendrawtransaction'
- + ' "hexstring" ( allowhighfees )'));
- }
+ if (args.help || args.length < 1 || args.length > 2)
+ throw new RPCError('sendrawtransaction "hexstring" ( allowhighfees )');
if (!util.isHex(args[0]))
- return Promise.reject(new RPCError('Invalid parameter'));
+ throw new RPCError('Invalid parameter');
tx = TX.fromRaw(args[0], 'hex');
- this.node.sendTX(tx);
+ this.node.sendTX(tx).catch(util.nop);
return tx.txid();
-};
+});
RPC.prototype.signrawtransaction = co(function* signrawtransaction(args) {
- var raw, br, txs, merged;
+ var wallet = this.wallet;
+ var tx;
if (args.help || args.length < 1 || args.length > 4) {
throw new RPCError('signrawtransaction'
@@ -2156,44 +2155,27 @@ RPC.prototype.signrawtransaction = co(function* signrawtransaction(args) {
if (!util.isHex(args[0]))
throw new RPCError('Invalid parameter');
- raw = new Buffer(args[0], 'hex');
- br = new BufferReader(raw);
- txs = [];
+ tx = MTX.fromRaw(args[0], 'hex');
+ tx.view = yield wallet.getCoinView(tx);
- while (br.left())
- txs.push(MTX.fromReader(br));
-
- merged = txs[0];
-
- yield this._fillCoins(merged);
- yield this.wallet.fillCoins(merged);
-
- return yield this._signrawtransaction(merged, txs, args);
+ return yield this._signrawtransaction(wallet, tx, args);
});
-RPC.prototype._fillCoins = function _fillCoins(tx) {
- if (this.chain.db.options.spv)
- return Promise.resolve();
-
- return this.node.fillCoins(tx);
-};
-
-RPC.prototype._signrawtransaction = co(function* signrawtransaction(merged, txs, args) {
+RPC.prototype._signrawtransaction = co(function* signrawtransaction(wallet, tx, args) {
var type = constants.hashType.ALL;
var keys = [];
var keyMap = {};
- var coins = [];
var i, j, k, secret, key;
var coin, prevout, prev;
var hash, index, script, value;
- var redeem, op, parts, tx;
+ var redeem, op, parts;
if (args.length > 2 && Array.isArray(args[2])) {
k = args[2];
for (i = 0; i < k.length; i++) {
secret = k[i];
- if (!util.isBase58(secret))
+ if (typeof secret !== 'string')
throw new RPCError('Invalid parameter');
key = KeyRing.fromSecret(secret);
@@ -2208,7 +2190,7 @@ RPC.prototype._signrawtransaction = co(function* signrawtransaction(merged, txs,
for (i = 0; i < prevout.length; i++) {
prev = prevout[i];
- if (!prev)
+ if (!(prev && typeof prev === 'object'))
throw new RPCError('Invalid parameter');
hash = toHash(prev.txid);
@@ -2217,22 +2199,18 @@ RPC.prototype._signrawtransaction = co(function* signrawtransaction(merged, txs,
value = toSatoshi(prev.amount);
if (!hash
- || !util.isNumber(index)
- || index < 0
+ || !util.isUInt32(index)
|| !util.isHex(script)) {
throw new RPCError('Invalid parameter');
}
script = Script.fromRaw(script, 'hex');
- coin = new Coin();
- coin.hash = util.revHex(hash);
- coin.index = index;
+ coin = new Output();
coin.script = script;
coin.value = value;
- coin.coinbase = false;
- coin.height = -1;
- coins.push(coin);
+
+ tx.view.addOutput(hash, index, coin);
if (keys.length === 0 || !util.isHex(prev.redeemScript))
continue;
@@ -2255,8 +2233,6 @@ RPC.prototype._signrawtransaction = co(function* signrawtransaction(merged, txs,
}
}
}
-
- tx.fillCoins(coins);
}
if (args.length > 3) {
@@ -2276,26 +2252,26 @@ RPC.prototype._signrawtransaction = co(function* signrawtransaction(merged, txs,
}
}
- for (i = 0; i < keys.length; i++) {
- key = keys[i];
- merged.sign(key, type);
- }
-
- yield this.wallet.sign(merged, { type: type });
+ yield tx.signAsync(keys, type);
+ yield wallet.sign(tx, { type: type });
return {
- hex: merged.toRaw().toString('hex'),
- complete: merged.isSigned()
+ hex: tx.toRaw().toString('hex'),
+ complete: tx.isSigned()
};
});
RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args) {
+ var wallet = this.wallet;
var tx, options, changeAddress, feeRate;
if (args.help || args.length < 1 || args.length > 2)
- throw new RPCError('fundrawtransaction "hexstring" ( options )');
+ throw new RPCError('fundrawtransaction "hexstring" ( options )');
- tx = MTX.fromRaw(toString(args[0]), 'hex');
+ if (!util.isHex(args[0]))
+ throw new RPCError('Invalid parameter.');
+
+ tx = MTX.fromRaw(args[0], 'hex');
if (tx.outputs.length === 0)
throw new RPCError('TX must have at least one output.');
@@ -2318,7 +2294,7 @@ RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args) {
changeAddress: changeAddress
};
- yield this.wallet.fund(tx, options);
+ yield wallet.fund(tx, options);
return {
hex: tx.toRaw().toString('hex'),
@@ -2328,6 +2304,7 @@ RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args) {
});
RPC.prototype._createRedeem = co(function* _createRedeem(args) {
+ var wallet = this.wallet;
var i, m, n, keys, hash, script, key, ring;
if (!util.isNumber(args[0])
@@ -2357,7 +2334,7 @@ RPC.prototype._createRedeem = co(function* _createRedeem(args) {
if (!hash)
throw new RPCError('Invalid key.');
- ring = yield this.wallet.getKey(hash);
+ ring = yield wallet.getKey(hash);
if (!ring)
throw new RPCError('Invalid key.');
@@ -2395,23 +2372,24 @@ RPC.prototype.createmultisig = co(function* createmultisig(args) {
};
});
-RPC.prototype.createwitnessaddress = function createwitnessaddress(args) {
+RPC.prototype.createwitnessaddress = co(function* createwitnessaddress(args) {
var raw, script, program;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('createwitnessaddress "script"'));
+ throw new RPCError('createwitnessaddress "script"');
raw = toString(args[1]);
script = Script.fromRaw(raw, 'hex');
program = script.forWitness();
- return Promise.resolve({
+ return {
address: program.getAddress().toBase58(this.network),
witnessScript: program.toJSON()
- });
-};
+ };
+});
RPC.prototype.validateaddress = co(function* validateaddress(args) {
+ var wallet = this.wallet;
var b58, address, json, path;
if (args.help || args.length !== 1)
@@ -2427,14 +2405,14 @@ RPC.prototype.validateaddress = co(function* validateaddress(args) {
};
}
- path = yield this.wallet.getPath(address);
+ path = yield wallet.getPath(address);
json = {
isvalid: true,
address: address.toBase58(this.network),
scriptPubKey: address.toScript().toJSON(),
ismine: path ? true : false,
- iswatchonly: path ? this.wallet.watchOnly : false
+ iswatchonly: path ? wallet.watchOnly : false
};
if (!path)
@@ -2448,13 +2426,11 @@ RPC.prototype.validateaddress = co(function* validateaddress(args) {
RPC.magic = 'Bitcoin Signed Message:\n';
-RPC.prototype.verifymessage = function verifymessage(args) {
+RPC.prototype.verifymessage = co(function* verifymessage(args) {
var address, sig, msg, key;
- if (args.help || args.length !== 3) {
- return Promise.reject(new RPCError('verifymessage'
- + ' "bitcoinaddress" "signature" "message"'));
- }
+ if (args.help || args.length !== 3)
+ throw new RPCError('verifymessage "bitcoinaddress" "signature" "message"');
address = toString(args[0]);
sig = toString(args[1]);
@@ -2463,7 +2439,7 @@ RPC.prototype.verifymessage = function verifymessage(args) {
address = Address.getHash(address);
if (!address)
- return Promise.reject(new RPCError('Invalid address.'));
+ throw new RPCError('Invalid address.');
sig = new Buffer(sig, 'base64');
msg = new Buffer(RPC.magic + msg, 'utf8');
@@ -2472,20 +2448,18 @@ RPC.prototype.verifymessage = function verifymessage(args) {
key = ec.recover(msg, sig, 0, true);
if (!key)
- return Promise.resolve(false);
+ return false;
key = crypto.hash160(key);
- return Promise.resolve(crypto.ccmp(key, address));
-};
+ return crypto.ccmp(key, address);
+});
-RPC.prototype.signmessagewithprivkey = function signmessagewithprivkey(args) {
+RPC.prototype.signmessagewithprivkey = co(function* signmessagewithprivkey(args) {
var key, msg, sig;
- if (args.help || args.length !== 2) {
- return Promise.reject(new RPCError(
- 'signmessagewithprivkey "privkey" "message"'));
- }
+ if (args.help || args.length !== 2)
+ throw new RPCError('signmessagewithprivkey "privkey" "message"');
key = toString(args[0]);
msg = toString(args[1]);
@@ -2496,17 +2470,17 @@ RPC.prototype.signmessagewithprivkey = function signmessagewithprivkey(args) {
sig = key.sign(msg);
- return Promise.resolve(sig.toString('base64'));
-};
+ return sig.toString('base64');
+});
-RPC.prototype.estimatefee = function estimatefee(args) {
+RPC.prototype.estimatefee = co(function* estimatefee(args) {
var blocks, fee;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('estimatefee nblocks'));
+ throw new RPCError('estimatefee nblocks');
if (!this.fees)
- return Promise.reject(new RPCError('Fee estimation not available.'));
+ throw new RPCError('Fee estimation not available.');
blocks = toNumber(args[0], 1);
@@ -2520,17 +2494,17 @@ RPC.prototype.estimatefee = function estimatefee(args) {
else
fee = Amount.btc(fee, true);
- return Promise.resolve(fee);
-};
+ return fee;
+});
-RPC.prototype.estimatepriority = function estimatepriority(args) {
+RPC.prototype.estimatepriority = co(function* estimatepriority(args) {
var blocks, pri;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('estimatepriority nblocks'));
+ throw new RPCError('estimatepriority nblocks');
if (!this.fees)
- return Promise.reject(new RPCError('Priority estimation not available.'));
+ throw new RPCError('Priority estimation not available.');
blocks = toNumber(args[0], 1);
@@ -2539,17 +2513,17 @@ RPC.prototype.estimatepriority = function estimatepriority(args) {
pri = this.fees.estimatePriority(blocks, false);
- return Promise.resolve(pri);
-};
+ return pri;
+});
-RPC.prototype.estimatesmartfee = function estimatesmartfee(args) {
+RPC.prototype.estimatesmartfee = co(function* estimatesmartfee(args) {
var blocks, fee;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('estimatesmartfee nblocks'));
+ throw new RPCError('estimatesmartfee nblocks');
if (!this.fees)
- return Promise.reject(new RPCError('Fee estimation not available.'));
+ throw new RPCError('Fee estimation not available.');
blocks = toNumber(args[0], 1);
@@ -2563,20 +2537,20 @@ RPC.prototype.estimatesmartfee = function estimatesmartfee(args) {
else
fee = Amount.btc(fee, true);
- return Promise.resolve({
+ return {
fee: fee,
blocks: blocks
- });
-};
+ };
+});
-RPC.prototype.estimatesmartpriority = function estimatesmartpriority(args) {
+RPC.prototype.estimatesmartpriority = co(function* estimatesmartpriority(args) {
var blocks, pri;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('estimatesmartpriority nblocks'));
+ throw new RPCError('estimatesmartpriority nblocks');
if (!this.fees)
- return Promise.reject(new RPCError('Priority estimation not available.'));
+ throw new RPCError('Priority estimation not available.');
blocks = toNumber(args[0], 1);
@@ -2585,74 +2559,75 @@ RPC.prototype.estimatesmartpriority = function estimatesmartpriority(args) {
pri = this.fees.estimatePriority(blocks, true);
- return Promise.resolve({
+ return {
priority: pri,
blocks: blocks
- });
-};
+ };
+});
-RPC.prototype.invalidateblock = function invalidateblock(args) {
+RPC.prototype.invalidateblock = co(function* invalidateblock(args) {
var hash;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('invalidateblock "hash"'));
+ throw new RPCError('invalidateblock "hash"');
hash = toHash(args[0]);
if (!hash)
- return Promise.reject(new RPCError('Block not found.'));
+ throw new RPCError('Block not found.');
this.chain.setInvalid(hash);
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.reconsiderblock = function reconsiderblock(args) {
+RPC.prototype.reconsiderblock = co(function* reconsiderblock(args) {
var hash;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('reconsiderblock "hash"'));
+ throw new RPCError('reconsiderblock "hash"');
hash = toHash(args[0]);
if (!hash)
- return Promise.reject(new RPCError('Block not found.'));
+ throw new RPCError('Block not found.');
this.chain.removeInvalid(hash);
- return Promise.resolve();
-};
+ return null;
+});
-RPC.prototype.setmocktime = function setmocktime(args) {
+RPC.prototype.setmocktime = co(function* setmocktime(args) {
var ts, delta;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('setmocktime timestamp'));
+ throw new RPCError('setmocktime timestamp');
ts = toNumber(args[0]);
if (ts < 0)
- return Promise.reject(new RPCError('Invalid parameter.'));
+ throw new RPCError('Invalid parameter.');
delta = this.network.now() - ts;
this.network.time.offset = -delta;
- return Promise.resolve();
-};
+ return null;
+});
/*
* Wallet
*/
RPC.prototype.resendwallettransactions = co(function* resendwallettransactions(args) {
+ var wallet = this.wallet;
var hashes = [];
var i, tx, txs;
if (args.help || args.length !== 0)
throw new RPCError('resendwallettransactions');
- txs = yield this.wallet.resend();
+ txs = yield wallet.resend();
for (i = 0; i < txs.length; i++) {
tx = txs[i];
@@ -2662,21 +2637,23 @@ RPC.prototype.resendwallettransactions = co(function* resendwallettransactions(a
return hashes;
});
-RPC.prototype.addmultisigaddress = function addmultisigaddress(args) {
+RPC.prototype.addmultisigaddress = co(function* addmultisigaddress(args) {
if (args.help || args.length < 2 || args.length > 3) {
- return Promise.reject(new RPCError('addmultisigaddress'
- + ' nrequired ["key",...] ( "account" )'));
+ throw new RPCError('addmultisigaddress'
+ + ' nrequired ["key",...] ( "account" )');
}
- // Impossible to implement in bcoin (no address book).
- Promise.reject(new Error('Not implemented.'));
-};
-RPC.prototype.addwitnessaddress = function addwitnessaddress(args) {
+ // Impossible to implement in bcoin (no address book).
+ throw new Error('Not implemented.');
+});
+
+RPC.prototype.addwitnessaddress = co(function* addwitnessaddress(args) {
if (args.help || args.length < 1 || args.length > 1)
- return Promise.reject(new RPCError('addwitnessaddress "address"'));
+ throw new RPCError('addwitnessaddress "address"');
+
// Unlikely to be implemented.
- Promise.reject(new Error('Not implemented.'));
-};
+ throw new Error('Not implemented.');
+});
RPC.prototype.backupwallet = co(function* backupwallet(args) {
var dest;
@@ -2692,6 +2669,7 @@ RPC.prototype.backupwallet = co(function* backupwallet(args) {
});
RPC.prototype.dumpprivkey = co(function* dumpprivkey(args) {
+ var wallet = this.wallet;
var hash, ring;
if (args.help || args.length !== 1)
@@ -2702,18 +2680,16 @@ RPC.prototype.dumpprivkey = co(function* dumpprivkey(args) {
if (!hash)
throw new RPCError('Invalid address.');
- ring = yield this.wallet.getKey(hash);
+ ring = yield wallet.getPrivateKey(hash);
if (!ring)
throw new RPCError('Key not found.');
- if (!this.wallet.master.key)
- throw new RPCError('Wallet is locked.');
-
return ring.toSecret();
});
RPC.prototype.dumpwallet = co(function* dumpwallet(args) {
+ var wallet = this.wallet;
var i, file, time, address, fmt, str, out, hash, hashes, ring;
if (args.help || args.length !== 1)
@@ -2734,18 +2710,15 @@ RPC.prototype.dumpwallet = co(function* dumpwallet(args) {
''
];
- hashes = yield this.wallet.getAddressHashes();
+ hashes = yield wallet.getAddressHashes();
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
- ring = yield this.wallet.getKey(hash);
+ ring = yield wallet.getPrivateKey(hash);
if (!ring)
continue;
- if (!this.wallet.master.key)
- throw new RPCError('Wallet is locked.');
-
address = ring.getAddress('base58');
fmt = '%s %s label= addr=%s';
@@ -2772,12 +2745,13 @@ RPC.prototype.dumpwallet = co(function* dumpwallet(args) {
});
RPC.prototype.encryptwallet = co(function* encryptwallet(args) {
+ var wallet = this.wallet;
var passphrase;
- if (!this.wallet.master.encrypted && (args.help || args.help !== 1))
+ if (!wallet.master.encrypted && (args.help || args.help !== 1))
throw new RPCError('encryptwallet "passphrase"');
- if (this.wallet.master.encrypted)
+ if (wallet.master.encrypted)
throw new RPCError('Already running with an encrypted wallet');
passphrase = toString(args[0]);
@@ -2785,12 +2759,13 @@ RPC.prototype.encryptwallet = co(function* encryptwallet(args) {
if (passphrase.length < 1)
throw new RPCError('encryptwallet "passphrase"');
- yield this.wallet.setPassphrase(passphrase);
+ yield wallet.setPassphrase(passphrase);
return 'wallet encrypted; we do not need to stop!';
});
RPC.prototype.getaccountaddress = co(function* getaccountaddress(args) {
+ var wallet = this.wallet;
var account;
if (args.help || args.length !== 1)
@@ -2801,7 +2776,7 @@ RPC.prototype.getaccountaddress = co(function* getaccountaddress(args) {
if (!account)
account = 'default';
- account = yield this.wallet.getAccount(account);
+ account = yield wallet.getAccount(account);
if (!account)
return '';
@@ -2810,6 +2785,7 @@ RPC.prototype.getaccountaddress = co(function* getaccountaddress(args) {
});
RPC.prototype.getaccount = co(function* getaccount(args) {
+ var wallet = this.wallet;
var hash, path;
if (args.help || args.length !== 1)
@@ -2820,7 +2796,7 @@ RPC.prototype.getaccount = co(function* getaccount(args) {
if (!hash)
throw new RPCError('Invalid address.');
- path = yield this.wallet.getPath(hash);
+ path = yield wallet.getPath(hash);
if (!path)
return '';
@@ -2829,6 +2805,7 @@ RPC.prototype.getaccount = co(function* getaccount(args) {
});
RPC.prototype.getaddressesbyaccount = co(function* getaddressesbyaccount(args) {
+ var wallet = this.wallet;
var i, path, account, addrs, paths;
if (args.help || args.length !== 1)
@@ -2841,7 +2818,7 @@ RPC.prototype.getaddressesbyaccount = co(function* getaddressesbyaccount(args) {
addrs = [];
- paths = yield this.wallet.getPaths(account);
+ paths = yield wallet.getPaths(account);
for (i = 0; i < paths.length; i++) {
path = paths[i];
@@ -2852,6 +2829,7 @@ RPC.prototype.getaddressesbyaccount = co(function* getaddressesbyaccount(args) {
});
RPC.prototype.getbalance = co(function* getbalance(args) {
+ var wallet = this.wallet;
var minconf = 0;
var account, value, balance;
@@ -2860,8 +2838,10 @@ RPC.prototype.getbalance = co(function* getbalance(args) {
if (args.length >= 1) {
account = toString(args[0]);
+
if (!account)
account = 'default';
+
if (account === '*')
account = null;
}
@@ -2869,7 +2849,7 @@ RPC.prototype.getbalance = co(function* getbalance(args) {
if (args.length >= 2)
minconf = toNumber(args[1], 0);
- balance = yield this.wallet.getBalance(account);
+ balance = yield wallet.getBalance(account);
if (minconf)
value = balance.confirmed;
@@ -2880,6 +2860,7 @@ RPC.prototype.getbalance = co(function* getbalance(args) {
});
RPC.prototype.getnewaddress = co(function* getnewaddress(args) {
+ var wallet = this.wallet;
var account, address;
if (args.help || args.length > 1)
@@ -2891,28 +2872,30 @@ RPC.prototype.getnewaddress = co(function* getnewaddress(args) {
if (!account)
account = 'default';
- address = yield this.wallet.createReceive(account);
+ address = yield wallet.createReceive(account);
return address.getAddress('base58');
});
RPC.prototype.getrawchangeaddress = co(function* getrawchangeaddress(args) {
+ var wallet = this.wallet;
var address;
if (args.help || args.length > 1)
throw new RPCError('getrawchangeaddress');
- address = yield this.wallet.createChange();
+ address = yield wallet.createChange();
return address.getAddress('base58');
});
RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args) {
+ var wallet = this.wallet;
var minconf = 0;
var total = 0;
var filter = {};
var lastConf = -1;
- var i, j, path, tx, output, conf, hash, account, paths, txs;
+ var i, j, path, wtx, output, conf, hash, account, paths, txs;
if (args.help || args.length < 1 || args.length > 2)
throw new RPCError('getreceivedbyaccount "account" ( minconf )');
@@ -2925,32 +2908,28 @@ RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args) {
if (args.length === 2)
minconf = toNumber(args[1], 0);
- paths = yield this.wallet.getPaths(account);
+ paths = yield wallet.getPaths(account);
for (i = 0; i < paths.length; i++) {
path = paths[i];
filter[path.hash] = true;
}
- txs = yield this.wallet.getHistory(account);
+ txs = yield wallet.getHistory(account);
for (i = 0; i < txs.length; i++) {
- tx = txs[i];
+ wtx = txs[i];
- if (minconf) {
- if (tx.height === -1)
- continue;
- if (!(this.chain.height - tx.height + 1 >= minconf))
- continue;
- }
+ conf = wtx.getConfirmations(this.chain.height);
- conf = tx.getConfirmations(this.chain.height);
+ if (conf < minconf)
+ continue;
if (lastConf === -1 || conf < lastConf)
lastConf = conf;
- for (j = 0; j < tx.outputs.length; j++) {
- output = tx.outputs[j];
+ for (j = 0; j < wtx.tx.outputs.length; j++) {
+ output = wtx.tx.outputs[j];
hash = output.getHash('hex');
if (hash && filter[hash])
total += output.value;
@@ -2961,9 +2940,10 @@ RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args) {
});
RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args) {
+ var wallet = this.wallet;
var minconf = 0;
var total = 0;
- var i, j, hash, tx, output, txs;
+ var i, j, hash, wtx, output, txs;
if (args.help || args.length < 1 || args.length > 2)
throw new RPCError('getreceivedbyaddress "bitcoinaddress" ( minconf )');
@@ -2976,18 +2956,16 @@ RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args) {
if (args.length === 2)
minconf = toNumber(args[1], 0);
- txs = yield this.wallet.getHistory();
+ txs = yield wallet.getHistory();
for (i = 0; i < txs.length; i++) {
- tx = txs[i];
- if (minconf) {
- if (tx.height === -1)
- continue;
- if (!(this.chain.height - tx.height + 1 >= minconf))
- continue;
- }
- for (j = 0; j < tx.outputs.length; j++) {
- output = tx.outputs[j];
+ wtx = txs[i];
+
+ if (wtx.getConfirmations(this.chain.height) < minconf)
+ continue;
+
+ for (j = 0; j < wtx.tx.outputs.length; j++) {
+ output = wtx.tx.outputs[j];
if (output.getHash('hex') === hash)
total += output.value;
}
@@ -2996,19 +2974,18 @@ RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args) {
return Amount.btc(total, true);
});
-RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) {
- var i, det, receive, member, sent, received, json, details;
-
- details = yield this.wallet.toDetails(tx);
+RPC.prototype._toWalletTX = co(function* _toWalletTX(wtx) {
+ var wallet = this.wallet;
+ var details = yield wallet.toDetails(wtx);
+ var det = [];
+ var sent = 0;
+ var received = 0;
+ var receive = true;
+ var i, member;
if (!details)
throw new RPCError('TX not found.');
- det = [];
- sent = 0;
- received = 0;
- receive = true;
-
for (i = 0; i < details.inputs.length; i++) {
member = details.inputs[i];
if (member.path) {
@@ -3055,7 +3032,7 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) {
sent += member.value;
}
- json = {
+ return {
amount: Amount.btc(receive ? received : -sent, true),
confirmations: details.confirmations,
blockhash: details.block ? util.revHex(details.block) : null,
@@ -3069,12 +3046,11 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) {
details: det,
hex: details.tx.toRaw().toString('hex')
};
-
- return json;
});
RPC.prototype.gettransaction = co(function* gettransaction(args) {
- var hash, tx;
+ var wallet = this.wallet;
+ var hash, wtx;
if (args.help || args.length < 1 || args.length > 2)
throw new RPCError('gettransaction "txid" ( includeWatchonly )');
@@ -3084,15 +3060,16 @@ RPC.prototype.gettransaction = co(function* gettransaction(args) {
if (!hash)
throw new RPCError('Invalid parameter');
- tx = yield this.wallet.getTX(hash);
+ wtx = yield wallet.getTX(hash);
- if (!tx)
+ if (!wtx)
throw new RPCError('TX not found.');
- return yield this._toWalletTX(tx);
+ return yield this._toWalletTX(wtx);
});
RPC.prototype.abandontransaction = co(function* abandontransaction(args) {
+ var wallet = this.wallet;
var hash, result;
if (args.help || args.length !== 1)
@@ -3103,7 +3080,7 @@ RPC.prototype.abandontransaction = co(function* abandontransaction(args) {
if (!hash)
throw new RPCError('Invalid parameter.');
- result = yield this.wallet.abandon(hash);
+ result = yield wallet.abandon(hash);
if (!result)
throw new RPCError('Transaction not in wallet.');
@@ -3112,33 +3089,35 @@ RPC.prototype.abandontransaction = co(function* abandontransaction(args) {
});
RPC.prototype.getunconfirmedbalance = co(function* getunconfirmedbalance(args) {
+ var wallet = this.wallet;
var balance;
if (args.help || args.length > 0)
throw new RPCError('getunconfirmedbalance');
- balance = yield this.wallet.getBalance();
+ balance = yield wallet.getBalance();
return Amount.btc(balance.unconfirmed, true);
});
RPC.prototype.getwalletinfo = co(function* getwalletinfo(args) {
+ var wallet = this.wallet;
var balance;
if (args.help || args.length !== 0)
throw new RPCError('getwalletinfo');
- balance = yield this.wallet.getBalance();
+ balance = yield wallet.getBalance();
return {
- walletid: this.wallet.id,
+ walletid: wallet.id,
walletversion: 6,
balance: Amount.btc(balance.unconfirmed, true),
unconfirmed_balance: Amount.btc(balance.unconfirmed, true),
- txcount: this.wallet.txdb.state.tx,
+ txcount: wallet.txdb.state.tx,
keypoololdest: 0,
keypoolsize: 0,
- unlocked_until: this.wallet.master.until,
+ unlocked_until: wallet.master.until,
paytxfee: this.feeRate != null
? Amount.btc(this.feeRate, true)
: 0
@@ -3146,6 +3125,7 @@ RPC.prototype.getwalletinfo = co(function* getwalletinfo(args) {
});
RPC.prototype.importprivkey = co(function* importprivkey(args) {
+ var wallet = this.wallet;
var secret, label, rescan, key;
if (args.help || args.length < 1 || args.length > 3)
@@ -3164,7 +3144,7 @@ RPC.prototype.importprivkey = co(function* importprivkey(args) {
key = KeyRing.fromSecret(secret);
- yield this.wallet.importKey(0, key);
+ yield wallet.importKey(0, key);
if (rescan)
yield this.walletdb.rescan(0);
@@ -3173,6 +3153,7 @@ RPC.prototype.importprivkey = co(function* importprivkey(args) {
});
RPC.prototype.importwallet = co(function* importwallet(args) {
+ var wallet = this.wallet;
var file, keys, lines, line, parts;
var i, secret, time, label, addr;
var data, key, rescan;
@@ -3221,7 +3202,7 @@ RPC.prototype.importwallet = co(function* importwallet(args) {
for (i = 0; i < keys.length; i++) {
key = keys[i];
- yield this.wallet.importKey(0, key);
+ yield wallet.importKey(0, key);
}
if (rescan)
@@ -3231,12 +3212,11 @@ RPC.prototype.importwallet = co(function* importwallet(args) {
});
RPC.prototype.importaddress = co(function* importaddress(args) {
+ var wallet = this.wallet;
var addr, label, rescan, p2sh;
- if (args.help || args.length < 1 || args.length > 4) {
- return Promise.reject(new RPCError(
- 'importaddress "address" ( "label" rescan p2sh )'));
- }
+ if (args.help || args.length < 1 || args.length > 4)
+ throw new RPCError('importaddress "address" ( "label" rescan p2sh )');
addr = toString(args[0]);
@@ -3254,7 +3234,7 @@ RPC.prototype.importaddress = co(function* importaddress(args) {
addr = Address.fromBase58(addr);
- yield this.wallet.importAddress(0, addr);
+ yield wallet.importAddress(0, addr);
if (rescan)
yield this.walletdb.rescan(0);
@@ -3263,6 +3243,7 @@ RPC.prototype.importaddress = co(function* importaddress(args) {
});
RPC.prototype.importpubkey = co(function* importpubkey(args) {
+ var wallet = this.wallet;
var pubkey, label, rescan, key;
if (args.help || args.length < 1 || args.length > 4)
@@ -3271,7 +3252,7 @@ RPC.prototype.importpubkey = co(function* importpubkey(args) {
pubkey = toString(args[0]);
if (!util.isHex(pubkey))
- throw new RPCError('Invalid paremeter.');
+ throw new RPCError('Invalid parameter.');
if (args.length > 1)
label = toString(args[1]);
@@ -3286,7 +3267,7 @@ RPC.prototype.importpubkey = co(function* importpubkey(args) {
key = KeyRing.fromPublic(pubkey, this.network);
- yield this.wallet.importKey(0, key);
+ yield wallet.importKey(0, key);
if (rescan)
yield this.walletdb.rescan(0);
@@ -3294,43 +3275,45 @@ RPC.prototype.importpubkey = co(function* importpubkey(args) {
return null;
});
-RPC.prototype.keypoolrefill = function keypoolrefill(args) {
+RPC.prototype.keypoolrefill = co(function* keypoolrefill(args) {
if (args.help || args.length > 1)
- return Promise.reject(new RPCError('keypoolrefill ( newsize )'));
- return Promise.resolve();
-};
+ throw new RPCError('keypoolrefill ( newsize )');
+ return null;
+});
RPC.prototype.listaccounts = co(function* listaccounts(args) {
+ var wallet = this.wallet;
var i, map, accounts, account, balance;
if (args.help || args.length > 2)
throw new RPCError('listaccounts ( minconf includeWatchonly)');
map = {};
- accounts = yield this.wallet.getAccounts();
+ accounts = yield wallet.getAccounts();
for (i = 0; i < accounts.length; i++) {
account = accounts[i];
- balance = yield this.wallet.getBalance(account);
+ balance = yield wallet.getBalance(account);
map[account] = Amount.btc(balance.unconfirmed, true);
}
return map;
});
-RPC.prototype.listaddressgroupings = function listaddressgroupings(args) {
+RPC.prototype.listaddressgroupings = co(function* listaddressgroupings(args) {
if (args.help)
- return Promise.reject(new RPCError('listaddressgroupings'));
- return Promise.resolve(new Error('Not implemented.'));
-};
+ throw new RPCError('listaddressgroupings');
+ throw new Error('Not implemented.');
+});
-RPC.prototype.listlockunspent = function listlockunspent(args) {
+RPC.prototype.listlockunspent = co(function* listlockunspent(args) {
+ var wallet = this.wallet;
var i, outpoints, outpoint, out;
if (args.help || args.length > 0)
- return Promise.reject(new RPCError('listlockunspent'));
+ throw new RPCError('listlockunspent');
- outpoints = this.wallet.getLocked();
+ outpoints = wallet.getLocked();
out = [];
for (i = 0; i < outpoints.length; i++) {
@@ -3341,16 +3324,16 @@ RPC.prototype.listlockunspent = function listlockunspent(args) {
});
}
- return Promise.resolve(out);
-};
+ return out;
+});
-RPC.prototype.listreceivedbyaccount = function listreceivedbyaccount(args) {
+RPC.prototype.listreceivedbyaccount = co(function* listreceivedbyaccount(args) {
var minconf = 0;
var includeEmpty = false;
if (args.help || args.length > 3) {
- return Promise.reject(new RPCError(
- 'listreceivedbyaccount ( minconf includeempty includeWatchonly )'));
+ throw new RPCError('listreceivedbyaccount'
+ + ' ( minconf includeempty includeWatchonly )');
}
if (args.length > 0)
@@ -3359,16 +3342,16 @@ RPC.prototype.listreceivedbyaccount = function listreceivedbyaccount(args) {
if (args.length > 1)
includeEmpty = toBool(args[1], false);
- return this._listReceived(minconf, includeEmpty, true);
-};
+ return yield this._listReceived(minconf, includeEmpty, true);
+});
-RPC.prototype.listreceivedbyaddress = function listreceivedbyaddress(args) {
+RPC.prototype.listreceivedbyaddress = co(function* listreceivedbyaddress(args) {
var minconf = 0;
var includeEmpty = false;
if (args.help || args.length > 3) {
- return Promise.reject(new RPCError(
- 'listreceivedbyaddress ( minconf includeempty includeWatchonly )'));
+ throw new RPCError('listreceivedbyaddress'
+ + ' ( minconf includeempty includeWatchonly )');
}
if (args.length > 0)
@@ -3377,22 +3360,22 @@ RPC.prototype.listreceivedbyaddress = function listreceivedbyaddress(args) {
if (args.length > 1)
includeEmpty = toBool(args[1], false);
- return this._listReceived(minconf, includeEmpty, false);
-};
+ return yield this._listReceived(minconf, includeEmpty, false);
+});
RPC.prototype._listReceived = co(function* _listReceived(minconf, empty, account) {
+ var wallet = this.wallet;
var out = [];
var result = [];
var map = {};
- var i, j, path, tx, output, conf, hash;
- var entry, address, keys, key, item, paths, txs;
-
- paths = yield this.wallet.getPaths();
+ var paths = yield wallet.getPaths();
+ var i, j, path, wtx, output, conf, hash;
+ var entry, address, keys, key, item, txs;
for (i = 0; i < paths.length; i++) {
path = paths[i];
map[path.hash] = {
- involvesWatchonly: this.wallet.watchOnly,
+ involvesWatchonly: wallet.watchOnly,
address: path.toAddress().toBase58(this.network),
account: path.name,
amount: 0,
@@ -3401,22 +3384,18 @@ RPC.prototype._listReceived = co(function* _listReceived(minconf, empty, account
};
}
- txs = yield this.wallet.getHistory();
+ txs = yield wallet.getHistory();
for (i = 0; i < txs.length; i++) {
- tx = txs[i];
+ wtx = txs[i];
- if (minconf) {
- if (tx.height === -1)
- continue;
- if (!(this.chain.height - tx.height + 1 >= minconf))
- continue;
- }
+ conf = wtx.getConfirmations(this.chain.height);
- conf = tx.getConfirmations(this.chain.height);
+ if (conf < minconf)
+ continue;
- for (j = 0; j < tx.outputs.length; j++) {
- output = tx.outputs[j];
+ for (j = 0; j < wtx.tx.outputs.length; j++) {
+ output = wtx.tx.outputs[j];
address = output.getAddress();
if (!address)
@@ -3483,8 +3462,11 @@ RPC.prototype._listReceived = co(function* _listReceived(minconf, empty, account
});
RPC.prototype.listsinceblock = co(function* listsinceblock(args) {
- var block, conf, out, highest;
- var i, height, txs, tx, json;
+ var wallet = this.wallet;
+ var minconf = 0;
+ var out = [];
+ var i, block, highest, height;
+ var txs, wtx, json;
if (args.help) {
throw new RPCError('listsinceblock'
@@ -3497,33 +3479,29 @@ RPC.prototype.listsinceblock = co(function* listsinceblock(args) {
throw new RPCError('Invalid parameter.');
}
- conf = 0;
-
if (args.length > 1)
- conf = toNumber(args[1], 0);
-
- out = [];
+ minconf = toNumber(args[1], 0);
height = yield this.chain.db.getHeight(block);
if (height === -1)
height = this.chain.height;
- txs = yield this.wallet.getHistory();
+ txs = yield wallet.getHistory();
for (i = 0; i < txs.length; i++) {
- tx = txs[i];
+ wtx = txs[i];
- if (tx.height < height)
+ if (wtx.height < height)
continue;
- if (tx.getConfirmations(this.chain.height) < conf)
+ if (wtx.getConfirmations(this.chain.height) < minconf)
continue;
- if (!highest || tx.height > highest)
- highest = tx;
+ if (!highest || wtx.height > highest)
+ highest = wtx;
- json = yield this._toListTX(tx);
+ json = yield this._toListTX(wtx);
out.push(json);
}
@@ -3536,21 +3514,18 @@ RPC.prototype.listsinceblock = co(function* listsinceblock(args) {
};
});
-RPC.prototype._toListTX = co(function* _toListTX(tx) {
- var i, receive, member, det, sent, received, index;
- var sendMember, recMember, sendIndex, recIndex, json;
- var details;
-
- details = yield this.wallet.toDetails(tx);
+RPC.prototype._toListTX = co(function* _toListTX(wtx) {
+ var wallet = this.wallet;
+ var sent = 0;
+ var received = 0;
+ var receive = true;
+ var sendMember, recMember, sendIndex, recIndex;
+ var i, member, index;
+ var details = yield wallet.toDetails(wtx);
if (!details)
throw new RPCError('TX not found.');
- det = [];
- sent = 0;
- received = 0;
- receive = true;
-
for (i = 0; i < details.inputs.length; i++) {
member = details.inputs[i];
if (member.path) {
@@ -3591,7 +3566,7 @@ RPC.prototype._toListTX = co(function* _toListTX(tx) {
index = recIndex;
}
- json = {
+ return {
account: member.path ? member.path.name : '',
address: member.address
? member.address.toBase58(this.network)
@@ -3610,12 +3585,11 @@ RPC.prototype._toListTX = co(function* _toListTX(tx) {
timereceived: details.ps,
'bip125-replaceable': 'no'
};
-
- return json;
});
RPC.prototype.listtransactions = co(function* listtransactions(args) {
- var i, account, count, txs, tx, json;
+ var wallet = this.wallet;
+ var i, account, count, txs, wtx, json;
if (args.help || args.length > 4) {
throw new RPCError(
@@ -3638,13 +3612,13 @@ RPC.prototype.listtransactions = co(function* listtransactions(args) {
if (count < 0)
count = 10;
- txs = yield this.wallet.getHistory();
+ txs = yield wallet.getHistory();
sortTX(txs);
for (i = 0; i < txs.length; i++) {
- tx = txs[i];
- json = yield this._toListTX(tx);
+ wtx = txs[i];
+ json = yield this._toListTX(wtx);
txs[i] = json;
}
@@ -3652,6 +3626,7 @@ RPC.prototype.listtransactions = co(function* listtransactions(args) {
});
RPC.prototype.listunspent = co(function* listunspent(args) {
+ var wallet = this.wallet;
var minDepth = 1;
var maxDepth = 9999999;
var out = [];
@@ -3687,7 +3662,7 @@ RPC.prototype.listunspent = co(function* listunspent(args) {
}
}
- coins = yield this.wallet.getCoins();
+ coins = yield wallet.getCoins();
sortCoins(coins);
@@ -3713,7 +3688,7 @@ RPC.prototype.listunspent = co(function* listunspent(args) {
continue;
}
- ring = yield this.wallet.getKey(hash);
+ ring = yield wallet.getKey(hash);
out.push({
txid: util.revHex(coin.hash),
@@ -3726,7 +3701,7 @@ RPC.prototype.listunspent = co(function* listunspent(args) {
scriptPubKey: coin.script.toJSON(),
amount: Amount.btc(coin.value, true),
confirmations: depth,
- spendable: !this.wallet.isLocked(coin),
+ spendable: !wallet.isLocked(coin),
solvable: true
});
}
@@ -3734,58 +3709,62 @@ RPC.prototype.listunspent = co(function* listunspent(args) {
return out;
});
-RPC.prototype.lockunspent = function lockunspent(args) {
+RPC.prototype.lockunspent = co(function* lockunspent(args) {
+ var wallet = this.wallet;
var i, unlock, outputs, output, outpoint;
if (args.help || args.length < 1 || args.length > 2) {
- return Promise.reject(new RPCError('lockunspent'
- + ' unlock ([{"txid":"txid","vout":n},...])'));
+ throw new RPCError('lockunspent'
+ + ' unlock ([{"txid":"txid","vout":n},...])');
}
unlock = toBool(args[0]);
if (args.length === 1) {
if (unlock)
- this.wallet.unlockCoins();
- return Promise.resolve(true);
+ wallet.unlockCoins();
+ return true;
}
outputs = toArray(args[1]);
if (!outputs)
- return Promise.reject(new RPCError('Invalid paremeter.'));
+ throw new RPCError('Invalid parameter.');
for (i = 0; i < outputs.length; i++) {
output = outputs[i];
if (!output || typeof output !== 'object')
- return Promise.reject(new RPCError('Invalid paremeter.'));
+ throw new RPCError('Invalid parameter.');
outpoint = new Outpoint();
outpoint.hash = toHash(output.txid);
outpoint.index = toNumber(output.vout);
if (!outpoint.hash)
- return Promise.reject(new RPCError('Invalid paremeter.'));
+ throw new RPCError('Invalid parameter.');
if (outpoint.index < 0)
- return Promise.reject(new RPCError('Invalid paremeter.'));
+ throw new RPCError('Invalid parameter.');
- if (unlock)
- this.wallet.unlockCoin(outpoint);
- else
- this.wallet.lockCoin(outpoint);
+ if (unlock) {
+ wallet.unlockCoin(outpoint);
+ continue;
+ }
+
+ wallet.lockCoin(outpoint);
}
- return Promise.resolve(true);
-};
+ return true;
+});
-RPC.prototype.move = function move(args) {
+RPC.prototype.move = co(function* move(args) {
// Not implementing: stupid and deprecated.
- return Promise.reject(new Error('Not implemented.'));
-};
+ throw new Error('Not implemented.');
+});
RPC.prototype._send = co(function* _send(account, address, amount, subtractFee) {
+ var wallet = this.wallet;
var tx, options;
options = {
@@ -3798,18 +3777,18 @@ RPC.prototype._send = co(function* _send(account, address, amount, subtractFee)
}]
};
- tx = yield this.wallet.send(options);
+ tx = yield wallet.send(options);
return tx.txid();
});
-RPC.prototype.sendfrom = function sendfrom(args) {
+RPC.prototype.sendfrom = co(function* sendfrom(args) {
var account, address, amount;
if (args.help || args.length < 3 || args.length > 6) {
- return Promise.reject(new RPCError('sendfrom'
+ throw new RPCError('sendfrom'
+ ' "fromaccount" "tobitcoinaddress"'
- + ' amount ( minconf "comment" "comment-to" )'));
+ + ' amount ( minconf "comment" "comment-to" )');
}
account = toString(args[0]);
@@ -3819,23 +3798,26 @@ RPC.prototype.sendfrom = function sendfrom(args) {
if (!account)
account = 'default';
- return this._send(account, address, amount, false);
-};
+ return yield this._send(account, address, amount, false);
+});
RPC.prototype.sendmany = co(function* sendmany(args) {
- var account, sendTo, minDepth, comment, subtractFee;
- var i, outputs, keys, uniq, tx;
- var key, value, address, hash, output, options;
+ var wallet = this.wallet;
+ var minconf = 1;
+ var outputs = [];
+ var uniq = {};
+ var account, sendTo, comment, subtractFee;
+ var i, keys, tx, key, value, address;
+ var hash, output, options;
if (args.help || args.length < 2 || args.length > 5) {
- return Promise.reject(new RPCError('sendmany'
+ throw new RPCError('sendmany'
+ ' "fromaccount" {"address":amount,...}'
- + ' ( minconf "comment" ["address",...] )'));
+ + ' ( minconf "comment" ["address",...] )');
}
account = toString(args[0]);
sendTo = toObject(args[1]);
- minDepth = 1;
if (!account)
account = 'default';
@@ -3844,7 +3826,7 @@ RPC.prototype.sendmany = co(function* sendmany(args) {
throw new RPCError('Invalid parameter.');
if (args.length > 2)
- minDepth = toNumber(args[2], 1);
+ minconf = toNumber(args[2], 1);
if (args.length > 3)
comment = toString(args[3]);
@@ -3852,9 +3834,7 @@ RPC.prototype.sendmany = co(function* sendmany(args) {
if (args.length > 4)
subtractFee = toArray(args[4]);
- outputs = [];
keys = Object.keys(sendTo);
- uniq = {};
for (i = 0; i < keys.length; i++) {
key = keys[i];
@@ -3877,50 +3857,50 @@ RPC.prototype.sendmany = co(function* sendmany(args) {
outputs: outputs,
subtractFee: subtractFee,
account: account,
- confirmations: minDepth
+ confirmations: minconf
};
- tx = yield this.wallet.send(options);
+ tx = yield wallet.send(options);
return tx.txid();
});
-RPC.prototype.sendtoaddress = function sendtoaddress(args) {
+RPC.prototype.sendtoaddress = co(function* sendtoaddress(args) {
var address, amount, subtractFee;
if (args.help || args.length < 2 || args.length > 5) {
- return Promise.reject(new RPCError('sendtoaddress'
+ throw new RPCError('sendtoaddress'
+ ' "bitcoinaddress" amount'
+ ' ( "comment" "comment-to"'
- + ' subtractfeefromamount )'));
+ + ' subtractfeefromamount )');
}
address = Address.fromBase58(toString(args[0]));
amount = toSatoshi(args[1]);
subtractFee = toBool(args[4]);
- return this._send(null, address, amount, subtractFee);
-};
+ return yield this._send(null, address, amount, subtractFee);
+});
+
+RPC.prototype.setaccount = co(function* setaccount(args) {
+ if (args.help || args.length < 1 || args.length > 2)
+ throw new RPCError('setaccount "bitcoinaddress" "account"');
-RPC.prototype.setaccount = function setaccount(args) {
- if (args.help || args.length < 1 || args.length > 2) {
- return Promise.reject(new RPCError(
- 'setaccount "bitcoinaddress" "account"'));
- }
// Impossible to implement in bcoin:
- return Promise.reject(new Error('Not implemented.'));
-};
+ throw new Error('Not implemented.');
+});
-RPC.prototype.settxfee = function settxfee(args) {
+RPC.prototype.settxfee = co(function* settxfee(args) {
if (args.help || args.length < 1 || args.length > 1)
- return Promise.reject(new RPCError('settxfee amount'));
+ throw new RPCError('settxfee amount');
this.feeRate = toSatoshi(args[0]);
- return Promise.resolve(true);
-};
+ return true;
+});
RPC.prototype.signmessage = co(function* signmessage(args) {
+ var wallet = this.wallet;
var address, msg, sig, ring;
if (args.help || args.length !== 2)
@@ -3934,12 +3914,12 @@ RPC.prototype.signmessage = co(function* signmessage(args) {
if (!address)
throw new RPCError('Invalid address.');
- ring = yield this.wallet.getKey(address);
+ ring = yield wallet.getKey(address);
if (!ring)
throw new RPCError('Address not found.');
- if (!this.wallet.master.key)
+ if (!wallet.master.key)
throw new RPCError('Wallet is locked.');
msg = new Buffer(RPC.magic + msg, 'utf8');
@@ -3951,26 +3931,29 @@ RPC.prototype.signmessage = co(function* signmessage(args) {
});
RPC.prototype.walletlock = co(function* walletlock(args) {
- if (args.help || (this.wallet.master.encrypted && args.length !== 0))
+ var wallet = this.wallet;
+
+ if (args.help || (wallet.master.encrypted && args.length !== 0))
throw new RPCError('walletlock');
- if (!this.wallet.master.encrypted)
+ if (!wallet.master.encrypted)
throw new RPCError('Wallet is not encrypted.');
- yield this.wallet.lock();
+ yield wallet.lock();
return null;
});
RPC.prototype.walletpassphrasechange = co(function* walletpassphrasechange(args) {
+ var wallet = this.wallet;
var old, new_;
- if (args.help || (this.wallet.master.encrypted && args.length !== 2)) {
+ if (args.help || (wallet.master.encrypted && args.length !== 2)) {
throw new RPCError('walletpassphrasechange'
+ ' "oldpassphrase" "newpassphrase"');
}
- if (!this.wallet.master.encrypted)
+ if (!wallet.master.encrypted)
throw new RPCError('Wallet is not encrypted.');
old = toString(args[0]);
@@ -3979,18 +3962,19 @@ RPC.prototype.walletpassphrasechange = co(function* walletpassphrasechange(args)
if (old.length < 1 || new_.length < 1)
throw new RPCError('Invalid parameter');
- yield this.wallet.setPassphrase(old, new_);
+ yield wallet.setPassphrase(old, new_);
return null;
});
RPC.prototype.walletpassphrase = co(function* walletpassphrase(args) {
+ var wallet = this.wallet;
var passphrase, timeout;
- if (args.help || (this.wallet.master.encrypted && args.length !== 2))
+ if (args.help || (wallet.master.encrypted && args.length !== 2))
throw new RPCError('walletpassphrase "passphrase" timeout');
- if (!this.wallet.master.encrypted)
+ if (!wallet.master.encrypted)
throw new RPCError('Wallet is not encrypted.');
passphrase = toString(args[0]);
@@ -4002,13 +3986,13 @@ RPC.prototype.walletpassphrase = co(function* walletpassphrase(args) {
if (timeout < 0)
throw new RPCError('Invalid parameter');
- yield this.wallet.unlock(passphrase, timeout);
+ yield wallet.unlock(passphrase, timeout);
return null;
});
RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) {
- var tx, block, label, height;
+ var tx, block, hash, label, height;
if (args.help || args.length < 2 || args.length > 3) {
throw new RPCError('importprunedfunds'
@@ -4023,6 +4007,7 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) {
tx = TX.fromRaw(tx, 'hex');
block = MerkleBlock.fromRaw(block, 'hex');
+ hash = block.hash('hex');
if (args.length === 3)
label = toString(args[2]);
@@ -4033,23 +4018,25 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) {
if (!block.hasTX(tx))
throw new RPCError('Invalid proof.');
- height = yield this.chain.db.getHeight(block.hash('hex'));
+ height = yield this.chain.db.getHeight(hash);
if (height === -1)
throw new RPCError('Invalid proof.');
- tx.index = block.indexOf(tx);
- tx.block = block.hash('hex');
- tx.ts = block.ts;
- tx.height = height;
+ block = {
+ hash: hash,
+ ts: block.ts,
+ height: height
+ };
- if (!(yield this.walletdb.addTX(tx)))
+ if (!(yield this.walletdb.addTX(tx, block)))
throw new RPCError('No tracked address for TX.');
return null;
});
RPC.prototype.removeprunedfunds = co(function* removeprunedfunds(args) {
+ var wallet = this.wallet;
var hash;
if (args.help || args.length !== 1)
@@ -4060,30 +4047,30 @@ RPC.prototype.removeprunedfunds = co(function* removeprunedfunds(args) {
if (!hash)
throw new RPCError('Invalid parameter.');
- if (!(yield this.wallet.remove(hash)))
+ if (!(yield wallet.remove(hash)))
throw new RPCError('Transaction not in wallet.');
return null;
});
-RPC.prototype.getmemory = function getmemory(args) {
+RPC.prototype.getmemory = co(function* getmemory(args) {
var mem;
if (args.help || args.length !== 0)
- return Promise.reject(new RPCError('getmemory'));
+ throw new RPCError('getmemory');
if (!process.memoryUsage)
- return Promise.resolve({});
+ return {};
mem = process.memoryUsage();
- return Promise.resolve({
+ return {
rss: util.mb(mem.rss),
jsheap: util.mb(mem.heapUsed),
jsheaptotal: util.mb(mem.heapTotal),
nativeheap: util.mb(mem.rss - mem.heapTotal)
- });
-};
+ };
+});
RPC.prototype.selectwallet = co(function* selectwallet(args) {
var id, wallet;
@@ -4102,22 +4089,22 @@ RPC.prototype.selectwallet = co(function* selectwallet(args) {
return null;
});
-RPC.prototype.setloglevel = function setloglevel(args) {
+RPC.prototype.setloglevel = co(function* setloglevel(args) {
var name, level;
if (args.help || args.length !== 1)
- return Promise.reject(new RPCError('setloglevel "level"'));
+ throw new RPCError('setloglevel "level"');
name = toString(args[0]);
level = Logger.levels[name];
if (level == null)
- return Promise.reject(new RPCError('Bad log level.'));
+ throw new RPCError('Bad log level.');
this.logger.level = level;
- return Promise.resolve(null);
-};
+ return null;
+});
/*
* Helpers
diff --git a/lib/http/server.js b/lib/http/server.js
index a947ebca..17365078 100644
--- a/lib/http/server.js
+++ b/lib/http/server.js
@@ -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) {
diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js
index c8a07bb0..37dcde2e 100644
--- a/lib/mempool/mempool.js
+++ b/lib/mempool/mempool.js
@@ -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');
diff --git a/lib/mining/miner.js b/lib/mining/miner.js
index c207510f..d1ff5920 100644
--- a/lib/mining/miner.js
+++ b/lib/mining/miner.js
@@ -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];
diff --git a/lib/mining/minerblock.js b/lib/mining/minerblock.js
index 737547bb..5816e762 100644
--- a/lib/mining/minerblock.js
+++ b/lib/mining/minerblock.js
@@ -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
diff --git a/lib/net/bip152.js b/lib/net/bip152.js
index df53d9f8..03456147 100644
--- a/lib/net/bip152.js
+++ b/lib/net/bip152.js
@@ -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;
}
diff --git a/lib/net/peer.js b/lib/net/peer.js
index 74e737d4..c91c3fe4 100644
--- a/lib/net/peer.js
+++ b/lib/net/peer.js
@@ -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);
diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js
index 9b2a0e7a..c43a3b42 100644
--- a/lib/node/fullnode.js
+++ b/lib/node/fullnode.js
@@ -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
*/
diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js
index 1733742d..08be9641 100644
--- a/lib/primitives/abstractblock.js
+++ b/lib/primitives/abstractblock.js
@@ -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}
diff --git a/lib/primitives/block.js b/lib/primitives/block.js
index e2832033..da38d0d2 100644
--- a/lib/primitives/block.js
+++ b/lib/primitives/block.js
@@ -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)
};
};
diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js
index 3d0731d7..9cf4688c 100644
--- a/lib/primitives/coin.js
+++ b/lib/primitives/coin.js
@@ -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
diff --git a/lib/primitives/headers.js b/lib/primitives/headers.js
index 950308e4..d8de2685 100644
--- a/lib/primitives/headers.js
+++ b/lib/primitives/headers.js
@@ -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),
diff --git a/lib/primitives/index.js b/lib/primitives/index.js
index f67150d8..180338c9 100644
--- a/lib/primitives/index.js
+++ b/lib/primitives/index.js
@@ -15,3 +15,4 @@ exports.NetworkAddress = require('./netaddress');
exports.Outpoint = require('./outpoint');
exports.Output = require('./output');
exports.TX = require('./tx');
+exports.TXMeta = require('./txmeta');
diff --git a/lib/primitives/input.js b/lib/primitives/input.js
index fc05e88e..5b7b89d3 100644
--- a/lib/primitives/input.js
+++ b/lib/primitives/input.js
@@ -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
};
};
diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js
index b8e3ae59..73ab7c5b 100644
--- a/lib/primitives/memblock.js
+++ b/lib/primitives/memblock.js
@@ -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;
};
diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js
index 8fa1fc58..3eb9779f 100644
--- a/lib/primitives/merkleblock.js
+++ b/lib/primitives/merkleblock.js
@@ -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;
diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js
index 7d7ee2bb..799e7781 100644
--- a/lib/primitives/mtx.js
+++ b/lib/primitives/mtx.js
@@ -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;
diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js
index 6df894f2..9f6fa809 100644
--- a/lib/primitives/tx.js
+++ b/lib/primitives/tx.js
@@ -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
diff --git a/lib/primitives/txmeta.js b/lib/primitives/txmeta.js
new file mode 100644
index 00000000..53258c6e
--- /dev/null
+++ b/lib/primitives/txmeta.js
@@ -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;
diff --git a/lib/wallet/client.js b/lib/wallet/client.js
index 9dcbad72..02ec3268 100644
--- a/lib/wallet/client.js
+++ b/lib/wallet/client.js
@@ -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);
}
diff --git a/lib/wallet/records.js b/lib/wallet/records.js
index 6172411a..06285270 100644
--- a/lib/wallet/records.js
+++ b/lib/wallet/records.js
@@ -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;
diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js
index b0c2409d..069b161e 100644
--- a/lib/wallet/txdb.js
+++ b/lib/wallet/txdb.js
@@ -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
*/
diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js
index bc229096..7487d493 100644
--- a/lib/wallet/wallet.js
+++ b/lib/wallet/wallet.js
@@ -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);
};
/**
diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js
index 012f008b..7310754f 100644
--- a/lib/wallet/walletdb.js
+++ b/lib/wallet/walletdb.js
@@ -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();
}
diff --git a/test/wallet-test.js b/test/wallet-test.js
index 399bd6ac..c3993e89 100644
--- a/test/wallet-test.js
+++ b/test/wallet-test.js
@@ -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');
}));