From a0d109f52d9a7e6383709662bad8a0264abdf036 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 10 Aug 2016 19:33:32 -0700 Subject: [PATCH] rpc: gettxoutproof and verifytxoutproof. --- lib/bcoin/http/rpc.js | 106 +++++++++++++++++++++++++++++++++++++-- lib/bcoin/merkleblock.js | 59 ++++++++++++++++++++-- 2 files changed, 157 insertions(+), 8 deletions(-) diff --git a/lib/bcoin/http/rpc.js b/lib/bcoin/http/rpc.js index fefff9a7..a1325e68 100644 --- a/lib/bcoin/http/rpc.js +++ b/lib/bcoin/http/rpc.js @@ -1157,11 +1157,108 @@ RPC.prototype.gettxout = function gettxout(args, callback) { }; RPC.prototype.gettxoutproof = function gettxoutproof(args, callback) { - callback(new Error('Not implemented.')); + var self = this; + var uniq = {}; + var i, txids, block, hash, last; + + if (args.help || (args.length !== 1 && args.length !== 2)) { + return callback(new RPCError('gettxoutproof' + + ' ["txid",...] ( blockhash )')); + } + + txids = args[0]; + block = args[1]; + + if (!Array.isArray(txids) || txids.length === 0) + return callback(new RPCError('Invalid parameter.')); + + if (block) { + if (!utils.isHex(block) || block.length !== 64) + return callback(new RPCError('Invalid parameter.')); + block = utils.revHex(block); + } + + for (i = 0; i < txids.length; i++) { + hash = txids[i]; + if (!utils.isHex(hash) || hash.length !== 64) + return callback(new RPCError('Invalid parameter.')); + hash = utils.revHex(hash); + if (uniq[hash]) + return callback(new RPCError('Duplicate txid.')); + uniq[hash] = true; + txids[i] = hash; + last = hash; + } + + function getBlock(callback) { + if (hash) + return self.chain.db.getBlock(hash, callback); + + if (self.chain.options.indexTX) { + return self.chain.db.getTX(last, function(err, tx) { + if (err) + return callback(err); + if (!tx) + return callback(); + self.chain.db.getBlock(tx.block, callback); + }); + } + + self.chain.db.getCoins(last, function(err, coins) { + if (err) + return callback(err); + + if (!coins) + return callback(); + + self.chain.db.getBlock(coins.height, callback); + }); + } + + getBlock(function(err, block) { + if (err) + return callback(err); + + if (!block) + return callback(new RPCError('Block not found.')); + + for (i = 0; i < txids.length; i++) { + if (!block.hasTX(txids[i])) + return callback(new RPCError('Block does not contain all txids.')); + } + + block = bcoin.merkleblock.fromHashes(block, txids); + + callback(null, block.toRaw().toString('hex')); + }); }; RPC.prototype.verifytxoutproof = function verifytxoutproof(args, callback) { - callback(new Error('Not implemented.')); + var res = []; + var i, block, hash; + + if (args.help || args.length !== 1) + return callback(new RPCError('verifytxoutproof "proof"')); + + block = bcoin.merkleblock.fromRaw(String(args[0]), 'hex'); + + if (!block.verify()) + return callback(null, res); + + this.chain.db.get(block.hash('hex'), function(err, entry) { + if (err) + return callback(err); + + if (!entry) + return callback(new RPCError('Block not found in chain.')); + + for (i = 0; i < block.matches.length; i++) { + hash = block.matches[i]; + res.push(utils.revHex(hash)); + } + + callback(null, res); + }); }; RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args, callback) { @@ -1446,6 +1543,9 @@ RPC.prototype.getnetworkhashps = function getnetworkhashps(args, callback) { lookup = args.length > 0 ? Number(args[0]) : 120; height = args.length > 1 ? Number(args[1]) : -1; + if (!utils.isNumber(lookup) || !utils.isNumber(height)) + return callback(new RPCError('Invalid parameter.')); + return this._hashps(lookup, height, callback); }; @@ -1461,7 +1561,7 @@ RPC.prototype.submitblock = function submitblock(args, callback) { + ' ( "jsonparametersobject" )')); } - block = bcoin.block.fromRaw(args[0], 'hex'); + block = bcoin.block.fromRaw(String(args[0]), 'hex'); this.chain.add(block, function(err, total) { if (err) diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index 8720a201..85d17624 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -448,6 +448,59 @@ MerkleBlock.fromJSON = function fromJSON(json) { MerkleBlock.fromBlock = function fromBlock(block, filter) { var matches = []; + var i, tx; + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + if (tx.isWatched(filter)) + matches.push(1); + else + matches.push(0); + } + + return MerkleBlock.fromMatches(block, matches); +}; + +/** + * Create a merkleblock from an array of txids. + * This will build the partial merkle tree. + * @param {Block} block + * @param {Hash[]} hashes + * @returns {MerkleBlock} + */ + +MerkleBlock.fromHashes = function fromHashes(block, hashes) { + var filter = {}; + var matches = []; + var i, tx, hash; + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + if (Buffer.isBuffer(hash)) + hash = hash.toString('hex'); + filter[hash] = true; + } + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + if (filter[tx.hash('hex')]) + matches.push(1); + else + matches.push(0); + } + + return MerkleBlock.fromMatches(block, matches); +}; + +/** + * Create a merkleblock from an array of matches. + * This will build the partial merkle tree. + * @param {Block} block + * @param {Number[]} matches + * @returns {MerkleBlock} + */ + +MerkleBlock.fromMatches = function fromMatches(block, matches) { var txs = []; var leaves = []; var bits = []; @@ -456,12 +509,8 @@ MerkleBlock.fromBlock = function fromBlock(block, filter) { for (i = 0; i < block.txs.length; i++) { tx = block.txs[i]; - if (tx.isWatched(filter)) { - matches.push(1); + if (matches[i]) txs.push(tx); - } else { - matches.push(0); - } leaves.push(tx.hash()); }