From 59645ac3ec949b36ae2b831d085f21c1a3d912d2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 11 Dec 2016 05:34:12 -0800 Subject: [PATCH] refactor: remove extra properties from tx and block. --- .jshintrc | 2 + browser/index.js | 9 +- lib/blockchain/chain.js | 45 +- lib/blockchain/chaindb.js | 135 ++-- lib/http/rpc.js | 1033 +++++++++++++++---------------- lib/http/server.js | 38 +- lib/mempool/mempool.js | 75 ++- lib/mining/miner.js | 12 +- lib/mining/minerblock.js | 3 +- lib/net/bip152.js | 1 - lib/net/peer.js | 11 +- lib/node/fullnode.js | 90 ++- lib/primitives/abstractblock.js | 31 +- lib/primitives/block.js | 30 +- lib/primitives/coin.js | 22 +- lib/primitives/headers.js | 70 ++- lib/primitives/index.js | 1 + lib/primitives/input.js | 4 +- lib/primitives/memblock.js | 61 +- lib/primitives/merkleblock.js | 44 +- lib/primitives/mtx.js | 16 - lib/primitives/tx.js | 248 ++------ lib/primitives/txmeta.js | 280 +++++++++ lib/wallet/client.js | 11 +- lib/wallet/records.js | 148 ++++- lib/wallet/txdb.js | 437 +++++++------ lib/wallet/wallet.js | 41 +- lib/wallet/walletdb.js | 31 +- test/wallet-test.js | 62 +- 29 files changed, 1725 insertions(+), 1266 deletions(-) create mode 100644 lib/primitives/txmeta.js 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'); }));