diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index f83555c1..6a982745 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -1893,6 +1893,17 @@ Chain.prototype.getTips = function getTips() { return this.db.getTips(); }; +/** + * Get range of hashes. + * @param {Number} [start=-1] + * @param {Number} [end=-1] + * @returns {Promise} + */ + +Chain.prototype.getHashes = function getHashes(start = -1, end = -1) { + return this.db.getHashes(start, end); +}; + /** * Get a coin (unspents only). * @private diff --git a/lib/http/server.js b/lib/http/server.js index b38d7534..5eb7b550 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -452,6 +452,14 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { return entry.toRaw(); }); + socket.hook('get hashes', (args) => { + const valid = new Validator([args]); + const start = valid.i32(0, -1); + const end = valid.i32(1, -1); + + return await this.chain.getHashes(start, end); + }); + socket.hook('add filter', (args) => { const valid = new Validator([args]); const chunks = valid.array(0); diff --git a/lib/wallet/client.js b/lib/wallet/client.js index e5281f92..9b5393f0 100644 --- a/lib/wallet/client.js +++ b/lib/wallet/client.js @@ -226,6 +226,19 @@ WalletClient.prototype.getEntry = function getEntry(block) { }); }; +/** + * Get hashes. + * @param {Number} [start=-1] + * @param {Number} [end=-1] + * @returns {Promise} + */ + +WalletClient.prototype.getHashes = function getHashes(start = -1, end = -1) { + return new Promise((resolve, reject) => { + this.socket.emit('get hashes', start, end, wrap(resolve, reject)); + }); +}; + /** * Send a transaction. Do not wait for promise. * @param {TX} tx diff --git a/lib/wallet/nodeclient.js b/lib/wallet/nodeclient.js index 145e8d89..acfbc569 100644 --- a/lib/wallet/nodeclient.js +++ b/lib/wallet/nodeclient.js @@ -178,7 +178,7 @@ NodeClient.prototype.estimateFee = async function estimateFee(blocks) { */ NodeClient.prototype.getHashes = async function getHashes(start = -1, end = -1) { - return this.node.chain.db.getHashes(start, end); + return this.node.chain.getHashes(start, end); }; /** diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index b4ad789e..a88d58aa 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -335,12 +335,12 @@ RPC.prototype.getAccountAddress = async function getAccountAddress(args, help) { if (!name) name = 'default'; - const account = await wallet.getAccount(name); + const addr = await wallet.receiveAddress(name); - if (!account) + if (!addr) return ''; - return account.receive.getAddress('string'); + return addr.toString(this.network); }; RPC.prototype.getAccount = async function getAccount(args, help) { diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index bf7c43d2..d408ac19 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -227,9 +227,9 @@ TXDB.prototype.removeCredit = async function removeCredit(b, credit, path) { */ TXDB.prototype.spendCredit = function spendCredit(b, credit, tx, index) { - const prevout = tx.inputs[index].prevout; + const {hash, index} = tx.inputs[index].prevout; const spender = Outpoint.fromTX(tx, index); - b.put(layout.s(prevout.hash, prevout.index), spender.toRaw()); + b.put(layout.s(hash, index), spender.toRaw()); b.put(layout.d(spender.hash, spender.index), credit.coin.toRaw()); }; @@ -240,12 +240,37 @@ TXDB.prototype.spendCredit = function spendCredit(b, credit, tx, index) { */ TXDB.prototype.unspendCredit = function unspendCredit(b, tx, index) { - const prevout = tx.inputs[index].prevout; + const {hash, index} = tx.inputs[index].prevout; const spender = Outpoint.fromTX(tx, index); - b.del(layout.s(prevout.hash, prevout.index)); + b.del(layout.s(hash, index)); b.del(layout.d(spender.hash, spender.index)); }; +/** + * Write input record. + * @param {TX} tx + * @param {Number} index + */ + +TXDB.prototype.writeInput = async function writeInput(tx, index) { + const {hash, index} = tx.inputs[index].prevout; + const spender = Outpoint.fromTX(tx, index); + this.put(layout.s(hash, index), spender.toRaw()); + return this.addOutpointMap(b, hash, index); +}; + +/** + * Remove input record. + * @param {TX} tx + * @param {Number} index + */ + +TXDB.prototype.removeInput = async function removeInput(tx, index) { + const {hash, index} = tx.inputs[index].prevout; + this.del(layout.s(hash, index)); + return this.removeOutpointMap(b, hash, index); +}; + /** * Test a whether a coin has been spent. * @param {Hash} hash @@ -421,25 +446,6 @@ TXDB.prototype.removeBlock = async function removeBlock(b, hash, height) { b.put(key, raw); }; -/** - * Append to the global block record. - * @param {Hash} hash - * @param {BlockMeta} meta - * @returns {Promise} - */ - -TXDB.prototype.addBlockSlow = async function addBlockSlow(b, hash, meta) { - let block = await this.getBlock(meta.height); - - if (!block) - block = BlockRecord.fromMeta(meta); - - if (!block.add(hash)) - return; - - b.put(layout.b(meta.height), block.toRaw()); -}; - /** * Remove from the global block record. * @param {Hash} hash @@ -447,7 +453,7 @@ TXDB.prototype.addBlockSlow = async function addBlockSlow(b, hash, meta) { * @returns {Promise} */ -TXDB.prototype.removeBlockSlow = async function removeBlockSlow(b, hash, height) { +TXDB.prototype.spliceBlock = async function spliceBlock(b, hash, height) { const block = await this.getBlock(height); if (!block) @@ -488,7 +494,7 @@ TXDB.prototype.add = async function add(tx, block) { return null; // Confirm transaction. - return await this._confirm(existing, block); + return await this.confirm(existing, block); } const wtx = TXRecord.fromTX(tx, block); @@ -533,8 +539,13 @@ TXDB.prototype.insert = async function insert(wtx, block) { const {hash, index} = input.prevout; const credit = await this.getCredit(hash, index); - if (!credit) + if (!credit) { + // Watch all inputs for incoming txs. + // This allows us to check for double spends. + if (!block) + await this.writeInput(tx, i); continue; + } const coin = credit.coin; const path = await this.getPath(coin); @@ -668,28 +679,6 @@ TXDB.prototype.insert = async function insert(wtx, block) { return details; }; -/** - * Attempt to confirm a transaction. - * @private - * @param {TX} tx - * @param {BlockMeta} block - * @returns {Promise} - */ - -TXDB.prototype.confirm = async function confirm(hash, block) { - const wtx = await this.getTX(hash); - - if (!wtx) - return null; - - if (wtx.height !== -1) - throw new Error('TX is already confirmed.'); - - assert(block); - - return this._confirm(wtx, block); -}; - /** * Attempt to confirm a transaction. * @private @@ -698,7 +687,7 @@ TXDB.prototype.confirm = async function confirm(hash, block) { * @returns {Promise} */ -TXDB.prototype._confirm = async function _confirm(wtx, block) { +TXDB.prototype.confirm = async function confirm(wtx, block) { const b = this.bucket(); const state = this.state.clone(); const tx = wtx.tx; @@ -724,6 +713,8 @@ TXDB.prototype._confirm = async function _confirm(wtx, block) { // There may be new credits available // that we haven't seen yet. if (!credit) { + await this.removeInput(tx, i); + credit = await this.getCredit(hash, index); if (!credit) @@ -860,8 +851,11 @@ TXDB.prototype.erase = async function erase(wtx, block) { for (let i = 0; i < tx.inputs.length; i++) { const credit = credits[i]; - if (!credit) + if (!credit) { + if (!block) + await this.removeInput(tx, i); continue; + } const coin = credit.coin; const path = await this.getPath(coin); @@ -933,7 +927,7 @@ TXDB.prototype.erase = async function erase(wtx, block) { // Update block records. if (block) { await this.removeBlockMap(b, height); - await this.removeBlockSlow(b, hash, height); + await this.spliceBlock(b, hash, height); } // Update the transaction counter @@ -1053,8 +1047,10 @@ TXDB.prototype.disconnect = async function disconnect(wtx, block) { for (let i = 0; i < tx.inputs.length; i++) { const credit = credits[i]; - if (!credit) + if (!credit) { + await this.writeInput(tx, i); continue; + } const coin = credit.coin; @@ -1183,10 +1179,10 @@ TXDB.prototype.removeConflicts = async function removeConflicts(tx, conf) { // Gather all spent records first. for (let i = 0; i < tx.inputs.length; i++) { const input = tx.inputs[i]; - const prevout = input.prevout; + const {hash, index} = input.prevout; // Is it already spent? - const spent = await this.getSpent(prevout.hash, prevout.index); + const spent = await this.getSpent(hash, index); if (!spent) continue; @@ -1198,21 +1194,16 @@ TXDB.prototype.removeConflicts = async function removeConflicts(tx, conf) { const spender = await this.getTX(spent.hash); assert(spender); - const block = spender.getBlock(); - - if (conf && block) + if (conf && spender.height !== -1) return false; - spends[i] = spender; + spends.push(spender); } // Once we know we're not going to // screw things up, remove the double // spenders. for (const spender of spends) { - if (!spender) - continue; - // Remove the double spender. await this.removeConflict(spender); } @@ -2383,12 +2374,7 @@ Credit.prototype.fromRaw = function fromRaw(data) { const br = new BufferReader(data); this.coin.fromReader(br); this.spent = br.readU8() === 1; - this.own = true; - - // Note: soft-fork - if (br.left() > 0) - this.own = br.readU8() === 1; - + this.own = br.readU8() === 1; return this; }; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 2e512b39..a86395f8 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -1559,8 +1559,8 @@ WalletDB.prototype.getPathMap = function getPathMap(hash) { * @param {Number} wid */ -WalletDB.prototype.addPathMap = function addPathMap(b, hash, wid) { - this.addHash(hash); +WalletDB.prototype.addPathMap = async function addPathMap(b, hash, wid) { + await this.addHash(hash); return this.addMap(b, layout.p(hash), wid); };