From 2ef47124fccdb79e20dbfa187e493bd1da8b87bd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 9 Jan 2018 14:30:37 -0800 Subject: [PATCH] handshake: wallet work. --- lib/coins/coinentry.js | 3 +- lib/covenants/auction.js | 9 + lib/covenants/db.js | 11 +- lib/covenants/rules.js | 12 +- lib/node/http.js | 2 +- lib/node/rpc.js | 53 +++++ lib/primitives/covenant.js | 21 ++ lib/wallet/http.js | 2 +- lib/wallet/layout.js | 8 +- lib/wallet/rpc.js | 101 +++++++++- lib/wallet/txdb.js | 391 +++++++++++++++++++++++++++---------- lib/wallet/wallet.js | 82 +++++--- test/util/memwallet.js | 4 - 13 files changed, 545 insertions(+), 154 deletions(-) diff --git a/lib/coins/coinentry.js b/lib/coins/coinentry.js index 05a04ddf..4aba3750 100644 --- a/lib/coins/coinentry.js +++ b/lib/coins/coinentry.js @@ -108,8 +108,9 @@ class CoinEntry { this.version = coin.version; this.height = coin.height; this.coinbase = coin.coinbase; - this.output.address = coin.address; this.output.value = coin.value; + this.output.address = coin.address; + this.output.covenant = coin.covenant; return this; } diff --git a/lib/covenants/auction.js b/lib/covenants/auction.js index 4517e466..31c6f2dd 100644 --- a/lib/covenants/auction.js +++ b/lib/covenants/auction.js @@ -171,6 +171,15 @@ class Auction { auction.bids = br.readU32(); return auction; } + + toJSON() { + return { + name: this.name.toString('ascii'), + owner: this.owner.toJSON(), + height: this.height, + renewal: this.renewal + }; + } } Auction.states = states; diff --git a/lib/covenants/db.js b/lib/covenants/db.js index 932a1e21..4ddf20f7 100644 --- a/lib/covenants/db.js +++ b/lib/covenants/db.js @@ -140,6 +140,7 @@ class NameDB { } async prove(root, key) { + return this.trie.prove(key); const trie = this.trie.snapshot(root); return trie.prove(key); } @@ -210,6 +211,11 @@ class NameDB { auction.ops.length = 0; } + async getDataByName(name, height) { + const key = blake2b.digest(name); + return this.getData(key, height); + } + async getData(key, height) { const auction = await this.getAuction(key); @@ -275,10 +281,11 @@ class NameDB { return false; // XXX util.revHex - // Must commit to last 4 bytes of the block hash. + // Must commit to last 8 bytes of the block hash. + // Presigning a renewal would require 1.1151 exabytes of storage. const tail = covenant.items[4].toString('hex'); - if (entry.slice(0, 8) !== tail) + if (entry.slice(0, 16) !== tail) return false; return true; diff --git a/lib/covenants/rules.js b/lib/covenants/rules.js index f206edfb..d55f8869 100644 --- a/lib/covenants/rules.js +++ b/lib/covenants/rules.js @@ -164,8 +164,8 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) { if (i >= tx.inputs.length) return false; - // Should contain a nonce and height. - if (covenant.items.length !== 3) + // Should contain a nonce. + if (covenant.items.length !== 2) return false; // Name must be valid. @@ -176,10 +176,6 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) { if (covenant.items[1].length !== 32) return false; - // Height the user bid at. - if (covenant.items[2].length !== 4) - return false; - break; } case types.REGISTER: { @@ -362,10 +358,6 @@ exports.verifyCovenants = function verifyCovenants(tx, view, height) { if (!blind.equals(uc.items[1])) return false; - // Height must match their bid height. - if (covenant.items[2].readUInt32LE(0, true) !== entry.height) - return false; - // If they lied to us, they can // never redeem their money. if (coin.value < output.value) diff --git a/lib/node/http.js b/lib/node/http.js index 84ab4b85..e9be0b0b 100644 --- a/lib/node/http.js +++ b/lib/node/http.js @@ -80,7 +80,7 @@ class HTTP extends Server { */ initRouter() { - this.use(this.cors()); + // this.use(this.cors()); if (!this.options.noAuth) { this.use(this.basicAuth({ diff --git a/lib/node/rpc.js b/lib/node/rpc.js index 55a8b4cf..56673622 100644 --- a/lib/node/rpc.js +++ b/lib/node/rpc.js @@ -33,6 +33,8 @@ const Output = require('../primitives/output'); const TX = require('../primitives/tx'); const consensus = require('../protocol/consensus'); const pkg = require('../pkg'); +const rules = require('../covenants/rules'); +const {TrieProof} = require('thc/trie/proof'); const {EXP} = consensus; const RPCBase = bweb.RPC; const RPCError = bweb.RPCError; @@ -228,6 +230,8 @@ class RPC extends RPCBase { this.add('getmemoryinfo', this.getMemoryInfo); this.add('setloglevel', this.setLogLevel); + this.add('getnamestatus', this.getNameStatus); + this.add('getnameproof', this.getNameProof); } /* @@ -2223,6 +2227,55 @@ class RPC extends RPCBase { return null; } + async getNameStatus(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getnamestatus "name"'); + + const valid = new Validator(args); + const name = valid.str(0); + + if (!name) + throw new RPCError(errs.TYPE_ERROR, 'Invalid name.'); + + const raw = Buffer.from(name, 'ascii'); + const week = blake2b.digest(raw)[0] % 52; + const start = week * rules.ROLLOUT_INTERVAL; + const auction = await this.chain.cdb.getAuctionByName(raw); + const data = await this.chain.cdb.getDataByName(raw); + + return { + start, + auction: auction ? auction.toJSON() : null, + data: data ? data.toString('hex') : null + }; + } + + async getNameProof(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getnameproof "name"'); + + const valid = new Validator(args); + const name = valid.str(0); + + if (!name) + throw new RPCError(errs.TYPE_ERROR, 'Invalid name.'); + + const raw = Buffer.from(name, 'ascii'); + const key = blake2b.digest(raw); + const height = this.chain.tip.height; + const root = this.chain.tip.trieRoot; + const nodes = await this.chain.cdb.prove(root, key); + const data = await this.chain.cdb.getData(key); + + const proof = new TrieProof(root, key, nodes); + proof.height = height; + + if (data) + proof.data = data; + + return proof.toJSON(); + } + /* * Helpers */ diff --git a/lib/primitives/covenant.js b/lib/primitives/covenant.js index 6160a144..1cef2991 100644 --- a/lib/primitives/covenant.js +++ b/lib/primitives/covenant.js @@ -78,6 +78,27 @@ class Covenant { return this.type > rules.MAX_COVENANT_TYPE; } + getString(index, enc) { + assert(index < this.items.length); + return this.items[index].toString(enc || 'ascii'); + } + + getU32(index) { + assert(index < this.items.length); + assert(this.items[index].length === 4); + return this.items[index].readUInt32LE(0, true); + } + + getU64(index) { + assert(index < this.items.length); + assert(this.items[index].length === 8); + try { + return encoding.readU64(this.items[index], 0); + } catch (e) { + return -1; + } + } + /** * Convert witness to an array of buffers. * @returns {Buffer[]} diff --git a/lib/wallet/http.js b/lib/wallet/http.js index d1e211ec..a196c7a0 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -77,7 +77,7 @@ class HTTP extends Server { */ initRouter() { - this.use(this.cors()); + // this.use(this.cors()); if (!this.options.noAuth) { this.use(this.basicAuth({ diff --git a/lib/wallet/layout.js b/lib/wallet/layout.js index 2d70e2f9..a973345f 100644 --- a/lib/wallet/layout.js +++ b/lib/wallet/layout.js @@ -93,7 +93,9 @@ exports.txdb = { // Auction records A: bdb.key('A', ['ascii']), // Bids - B: bdb.key('B', ['ascii']), - // Values - v: bdb.key('v', ['ascii']) + i: bdb.key('i', ['ascii', 'hash256', 'uint32']), + // Reveals + B: bdb.key('B', ['ascii', 'hash256', 'uint32']), + // Blinds + v: bdb.key('v', ['hash256']) }; diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index c7dd1243..87a27a1d 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -162,6 +162,9 @@ class RPC extends RPCBase { this.add('selectwallet', this.selectWallet); this.add('getmemoryinfo', this.getMemoryInfo); this.add('setloglevel', this.setLogLevel); + this.add('getbids', this.getBids); + this.add('getauctions', this.getAuctions); + this.add('getauctioninfo', this.getAuctionInfo); this.add('sendbid', this.sendBid); this.add('sendreveal', this.sendReveal); this.add('sendregister', this.sendRegister); @@ -1618,6 +1621,102 @@ class RPC extends RPCBase { return null; } + async getBids(args, help) { + if (help || args.length > 1) + throw new RPCError(errs.MISC_ERROR, 'getbids "name"'); + + const wallet = this.wallet; + const valid = new Validator(args); + const name = valid.str(0); + + const bids = await wallet.getBids(name); + const items = []; + + for (const bid of bids) { + items.push({ + name: !name ? bid.name : undefined, + prevout: bid.prevout.toJSON(), + blind: bid.blind.toString('hex'), + lockup: bid.lockup, + value: bid.value, + nonce: bid.nonce ? bid.nonce.toString('hex') : null + }); + } + + return items; + } + + async getAuctions(args, help) { + if (help || args.length !== 0) + throw new RPCError(errs.MISC_ERROR, 'getauctions'); + + const wallet = this.wallet; + const valid = new Validator(args); + const name = valid.str(0); + + const auctions = await wallet.getAuctions(); + const items = []; + + for (const auction of auctions) { + items.push({ + name: auction.name, + owner: auction.owner.toJSON(), + state: auction.state, + height: auction.height + }); + } + + return items; + } + + async getAuctionInfo(args, help) { + if (help || args.length !== 1) + throw new RPCError(errs.MISC_ERROR, 'getauctioninfo "name"'); + + const wallet = this.wallet; + const valid = new Validator(args); + const name = valid.str(0); + + const auction = await wallet.getAuction(name); + + if (!auction) + throw new RPCError(errs.MISC_ERROR, 'Auction not found.'); + + const bids = await wallet.getBids(name); + const reveals = await wallet.getReveals(name); + const items = []; + + const info = { + name, + owner: auction.owner.toJSON(), + state: auction.state, + height: auction.height, + bids: [], + reveals: [] + }; + + for (const bid of bids) { + info.bids.push({ + prevout: bid.prevout.toJSON(), + blind: bid.blind.toString('hex'), + lockup: bid.lockup, + value: bid.value, + nonce: bid.nonce ? bid.nonce.toString('hex') : null + }); + } + + for (const reveal of reveals) { + info.reveals.push({ + prevout: reveal.prevout.toJSON(), + value: reveal.value, + height: reveal.height, + own: reveal.own + }); + } + + return info; + } + async sendBid(args, help) { if (help || args.length !== 3) throw new RPCError(errs.MISC_ERROR, 'sendbid "name" bid value'); @@ -1660,7 +1759,7 @@ class RPC extends RPCBase { async sendRegister(args, help) { if (help || args.length !== 2) - throw new RPCError(errs.MISC_ERROR, 'sendregister "name"'); + throw new RPCError(errs.MISC_ERROR, 'sendregister "name" "data"'); const wallet = this.wallet; const valid = new Validator(args); diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 48330e8f..42e95fe5 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -553,7 +553,7 @@ class TXDB { const output = tx.outputs[i]; const path = await this.getPath(output); - await this.connectCovenant(tx, i, path, height); + await this.insertCovenant(b, state, tx, i, path, height); if (!path) continue; @@ -648,29 +648,137 @@ class TXDB { if (height === 0xffffffff) height = -1; - return { - owner, - state, - height - }; + return { owner, state, height }; + } + + async getAuctions() { + const iter = this.bucket.iterator({ + gte: layout.A.min(), + lte: layout.A.max(), + values: true + }); + + const auctions = []; + + await iter.each((key, raw) => { + const name = layout.A.parse(key); + const br = bio.read(raw); + const owner = Outpoint.fromReader(br); + const state = br.readU8(); + + let height = br.readU32(); + + if (height === 0xffffffff) + height = -1; + + auctions.push({ + name, + owner, + state, + height + }); + }); + + return auctions; } putAuction(b, name, owner, state, height) { + if (!owner) + owner = new Outpoint(); + const bw = bio.write(36 + 1 + 4); owner.toWriter(bw); - bw.writeU8(state); if (height === -1) height = 0xffffffff; + bw.writeU8(state); bw.writeU32(height); b.put(layout.A.build(name), bw.render()); } removeAuction(b, name) { - b.del(layout.B.build(name)); + b.del(layout.A.build(name)); + } + + async hasBid(name, {hash, index}) { + return this.bucket.has(layout.i.build(name, hash, index)); + } + + async getBid(name, {hash, index}) { + const raw = await this.bucket.get(layout.i.build(name, hash, index)); + + if (!raw) + return null; + + const br = bio.read(raw); + const blind = br.readBytes(32); + const value = br.readU64(); + + return { + name, + prevout: new Outpoint(hash, index), + blind, + value + }; + } + + putBid(b, name, {hash, index}, blind, value) { + const bw = bio.write(40); + + bw.writeBytes(blind); + bw.writeU64(value); + + b.put(layout.i.build(name, hash, index), bw.render()); + } + + async getBids(name) { + const iter = this.bucket.iterator({ + gte: name ? layout.i.min(name) : layout.i.min(), + lte: name ? layout.i.max(name) : layout.i.max(), + values: true + }); + + const bids = []; + + await iter.each(async (key, raw) => { + const [name, hash, index] = layout.i.parse(key); + const prevout = new Outpoint(hash, index); + const br = bio.read(raw); + const blind = br.readBytes(32); + const lockup = br.readU64(); + + let value = -1; + let nonce = null; + + const b = await this.getBlind(blind); + if (b) { + value = b.value; + nonce = b.nonce; + } + + bids.push({ + name, + prevout, + blind, + lockup, + value, + nonce + }); + }); + + return bids; + } + + async removeBids(b, name) { + const iter = this.bucket.iterator({ + gte: layout.i.min(name), + lte: layout.i.max(name) + }); + + await iter.each(k => b.del(k)); } async hasReveal(name, {hash, index}) { @@ -710,6 +818,46 @@ class TXDB { b.put(layout.B.build(name, hash, index), bw.render()); } + async saveReveal(name, outpoint, value, height) { + const b = this.bucket.batch(); + this.putReveal(b, name, outpoint, value, height); + await b.write(); + } + + async getReveals(name) { + const iter = this.bucket.iterator({ + gte: name ? layout.B.min(name) : layout.B.min(), + lte: name ? layout.B.max(name) : layout.B.max(), + values: true + }); + + const reveals = []; + + await iter.each(async (key, raw) => { + const [name, hash, index] = layout.B.parse(key); + const prevout = new Outpoint(hash, index); + const br = bio.read(raw); + const value = br.readU64(); + + let height = br.readU32(); + + if (height === 0xffffffff) + height = -1; + + const own = await this.hasCoin(hash, index); + + reveals.push({ + name, + prevout, + value, + height, + own + }); + }); + + return reveals; + } + async removeReveals(b, name) { const iter = this.bucket.iterator({ gte: layout.B.min(name), @@ -719,42 +867,7 @@ class TXDB { await iter.each(k => b.del(k)); } - async hasValue(name) { - return this.bucket.has(layout.v.build(name)); - } - - async getValue(name) { - const raw = await this.bucket.get(layout.v.build(name)); - - if (!raw) - return null; - - const br = bio.read(raw); - const value = br.readU64(); - const nonce = br.readBytes(32); - - return { - value, - nonce - }; - } - - putValue(b, name, value, nonce) { - const bw = bio.write(8 + 32); - - bw.writeU64(value); - bw.writeBytes(nonce); - - b.put(layout.v.build(name), bw.render()); - } - - async saveValue(name, value, nonce) { - const b = this.bucket.batch(); - this.putValue(b, name, value, nonce); - await b.write(); - } - - async pickWinner(name) { + async getWinningReveal(name) { const iter = this.bucket.iterator({ gte: layout.B.min(name), lte: layout.B.max(name), @@ -765,7 +878,7 @@ class TXDB { let winner = null; await iter.each((key, data) => { - const value = br.read(data).readU64(); + const value = bio.read(data).readU64(); if (value >= best) { const [, hash, index] = layout.B.parse(key); @@ -780,16 +893,51 @@ class TXDB { return winner; } - async isWinner(name, owner) { - const winner = await this.pickWinner(name); + async isWinningReveal(name, owner) { + const winner = await this.getWinningReveal(name); return winner.equals(owner); } - async connectCovenant(b, tx, i, path, height) { + async hasBlind(blind) { + return this.bucket.has(layout.v.build(blind)); + } + + async getBlind(blind) { + const raw = await this.bucket.get(layout.v.build(blind)); + + if (!raw) + return null; + + const br = bio.read(raw); + const value = br.readU64(); + const nonce = br.readBytes(32); + + return { + value, + nonce + }; + } + + putBlind(b, blind, value, nonce) { + const bw = bio.write(8 + 32); + + bw.writeU64(value); + bw.writeBytes(nonce); + + b.put(layout.v.build(blind), bw.render()); + } + + async saveBlind(blind, value, nonce) { + const b = this.bucket.batch(); + this.putBlind(b, blind, value, nonce); + await b.write(); + } + + async insertCovenant(b, state, tx, i, path, height) { const output = tx.outputs[i]; const {covenant} = output; - if (covenant.type === rules.NONE) + if (covenant.type === rules.types.NONE) return; if (covenant.type > rules.MAX_COVENANT_TYPE) @@ -800,51 +948,80 @@ class TXDB { const name = output.covenant.items[0]; switch (covenant.type) { - case rules.BID: { + case rules.types.BID: { if (!path) break; - this.putAuction(b, name, outpoint, rules.BID, height); - state.locked(path, output.value); - - break; - } - case rules.REVEAL: { - if (!await this.hasAuction(name)) - break; - - this.putReveal(b, name, outpoint, output.value, height); - - if (path) { - const nonce = output.covenant.items[1]; - const height = output.covenant.items[2].readUInt32LE(0, true); - const {hash, index} = tx.inputs[i].prevout; - const {coin} = await this.getCredit(hash, index); - state.locked(path, -coin.value); - state.locked(path, output.value); - this.putValue(b, name, output.value, nonce); - this.putAuction(b, name, outpoint, rules.REVEAL, height); + if (!await this.hasAuction(name)) { + this.putAuction(b, name, null, rules.types.BID, height); + await this.addNameMap(b, name); } + const blind = covenant.items[1]; + + this.putBid(b, name, outpoint, blind, output.value); + + state.locked(path, output.value, height); + break; } - case rules.REDEEM: { - if (!await this.hasAuction(name)) + case rules.types.REVEAL: { + const auction = await this.getAuction(name); + + if (!auction) break; - if (!path) + if (!path) { + const b = this.bucket.batch(); + this.putReveal(b, + name, + outpoint, + output.value, + height + ); + this.putAuction(b, name, null, rules.types.REVEAL, auction.height); + await b.write(); + break; + } + + const {prevout} = tx.inputs[i]; + const {hash, index} = prevout; + const coin = await this.getCoin(hash, index); + assert(coin); + + state.locked(path, -coin.value + output.value, height); + + const nonce = output.covenant.items[1]; + const blind = coin.covenant.items[1]; + + this.putBid(b, name, prevout, blind, coin.value); + this.putBlind(b, blind, output.value, nonce); + this.putReveal(b, name, outpoint, output.value, height); + this.putAuction(b, name, null, rules.types.REVEAL, auction.height); + + break; + } + case rules.types.REDEEM: { + const auction = await this.getAuction(name); + + if (!auction) break; - state.locked(path, -output.value); + if (!path) { + const b = this.bucket.batch(); + this.putAuction(b, name, null, rules.types.REGISTER, auction.height); + await b.write(); + break; + } + + state.locked(path, -output.value, height); this.removeAuction(b, name); + await this.removeBids(b, name); await this.removeReveals(b, name); break; } - case rules.REGISTER: { - if (!await this.hasAuction(name)) - break; - + case rules.types.REGISTER: { if (!path) { // Somebody else registered. this.removeAuction(b, name); @@ -852,30 +1029,25 @@ class TXDB { break; } - this.putAuction(b, name, outpoint, rules.REGISTER, -1); + this.putAuction(b, name, outpoint, rules.types.REGISTER, -1); + await this.removeBids(b, name); await this.removeReveals(b, name); break; } - case rules.TRANSFER: { - if (!await this.hasAuction(name)) - break; - + case rules.types.TRANSFER: { if (!path) break; - this.putAuction(b, name, outpoint, rules.TRANSFER, -1); + this.putAuction(b, name, outpoint, rules.types.TRANSFER, -1); break; } - case rules.REVOKE: { - if (!await this.hasAuction(name)) - break; - + case rules.types.REVOKE: { if (!path) break; - this.putAuction(b, name, outpoint, rules.REVOKE, -1); + this.putAuction(b, name, outpoint, rules.types.REVOKE, -1); break; } @@ -2346,7 +2518,8 @@ class Balance { this.coin = 0; this.unconfirmed = 0; this.confirmed = 0; - this.locked = 0; + this.ulocked = 0; + this.clocked = 0; } /** @@ -2359,12 +2532,15 @@ class Balance { balance.coin += this.coin; balance.unconfirmed += this.unconfirmed; balance.confirmed += this.confirmed; + balance.ulocked += this.ulocked; + balance.clocked += this.clocked; assert(balance.tx >= 0); assert(balance.coin >= 0); assert(balance.unconfirmed >= 0); assert(balance.confirmed >= 0); - assert(balance.locked >= 0); + assert(balance.ulocked >= 0); + assert(balance.clocked >= 0); } /** @@ -2373,13 +2549,14 @@ class Balance { */ toRaw() { - const bw = bio.write(40); + const bw = bio.write(48); bw.writeU64(this.tx); bw.writeU64(this.coin); bw.writeU64(this.unconfirmed); bw.writeU64(this.confirmed); - bw.writeU64(this.locked); + bw.writeU64(this.ulocked); + bw.writeU64(this.clocked); return bw.render(); } @@ -2397,7 +2574,8 @@ class Balance { this.coin = br.readU64(); this.unconfirmed = br.readU64(); this.confirmed = br.readU64(); - this.locked = br.readU64(); + this.ulocked = br.readU64(); + this.clocked = br.readU64(); return this; } @@ -2425,7 +2603,8 @@ class Balance { coin: this.coin, unconfirmed: this.unconfirmed, confirmed: this.confirmed, - locked: this.locked + lockedUnconfirmed: this.ulocked, + lockedConfirmed: this.clocked }; } @@ -2440,7 +2619,8 @@ class Balance { + ` coin=${this.coin}` + ` unconfirmed=${Amount.coin(this.unconfirmed)}` + ` confirmed=${Amount.coin(this.confirmed)}` - + ` locked=${Amount.coin(this.locked)}` + + ` lockedUnconfirmed=${Amount.coin(this.ulocked)}` + + ` lockedConfirmed=${Amount.coin(this.clocked)}` + '>'; } } @@ -2500,10 +2680,23 @@ class BalanceDelta { this.wallet.confirmed += value; } - locked(path, value) { + ulocked(path, value) { const account = this.get(path); - account.locked += value; - this.wallet.locked += value; + account.ulocked += value; + this.wallet.ulocked += value; + } + + clocked(path, value) { + const account = this.get(path); + account.clocked += value; + this.wallet.clocked += value; + } + + locked(path, value, height) { + if (height === -1) + this.ulocked(path, value); + else + this.clocked(path, value); } } diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index c3c34449..7c5d8ab5 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -15,6 +15,7 @@ const bio = require('bufio'); const hash160 = require('bcrypto/lib/hash160'); const hash256 = require('bcrypto/lib/hash256'); const cleanse = require('bcrypto/lib/cleanse'); +const random = require('bcrypto/lib/random'); const TXDB = require('./txdb'); const Path = require('./path'); const common = require('./common'); @@ -1128,14 +1129,31 @@ class Wallet extends EventEmitter { assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); } - async createBid(name, bid, value, options) { - if (!options) - options = {}; + async getAuctions() { + return this.txdb.getAuctions(); + } - const auction = await this.txdb.getAuction(name); + async getAuction(name) { + return this.txdb.getAuction(name); + } + + async getBlind(blind) { + return this.txdb.getBlind(blind); + } + + async getBids(name) { + return this.txdb.getBids(name); + } + + async getReveals(name) { + return this.txdb.getReveals(name); + } + + async createBid(name, bid, value, options) { + const auction = await this.getAuction(name); if (auction && auction.state !== rules.types.BID) - throw new Error('Auction not found.'); + throw new Error('Bidding has closed.'); if (bid > value) throw new Error('Bid exceeds lockup value.'); @@ -1154,9 +1172,7 @@ class Wallet extends EventEmitter { output.covenant.items.push(raw); output.covenant.items.push(blind); - // XXX POssibly index by value hash. - // Maybe don't need to store state on auction? Use UTXO? - await this.txdb.saveValue(name, bid, nonce); + await this.txdb.saveBlind(blind, bid, nonce); const mtx = new MTX(); mtx.outputs.push(output); @@ -1174,29 +1190,34 @@ class Wallet extends EventEmitter { } async createReveal(name, options) { - const auction = await this.txdb.getAuction(name); + const auction = await this.getAuction(name); if (!auction) throw new Error('Auction not found.'); - const reveal = await this.txdb.getValue(name); - - if (!reveal) - throw new Error('Reveal value not found.'); - const raw = Buffer.from(name, 'ascii'); const {owner, state} = auction; - const {value, nonce} = reveal; if (state !== rules.types.BID) throw new Error('Already revealed.'); - const {hash, index} = owner; - const coin = await this.getCoin(hash, index) + const bids = await this.getBids(); + + if (bids.length === 0) + throw new Error('No bids found.'); + + const {prevout} = bids[0]; + const {hash, index} = prevout; + const coin = await this.getCoin(hash, index); assert(coin); - const height = Buffer.allocUnsafe(4); - height.writeUInt32LE(coin.height, 0, true); + const blind = coin.covenant.items[1]; + const reveal = await this.getBlind(blind); + + if (!reveal) + throw new Error('Reveal value not found.'); + + const {value, nonce} = reveal; const output = new Output(); output.address = coin.address; @@ -1204,7 +1225,6 @@ class Wallet extends EventEmitter { output.covenant.type = rules.types.REVEAL; output.covenant.items.push(raw); output.covenant.items.push(nonce); - output.covenant.items.push(height); const mtx = new MTX(); mtx.addOutpoint(prevout); @@ -1223,19 +1243,14 @@ class Wallet extends EventEmitter { } async createRegister(name, data, options, renew = false) { - const auction = await this.txdb.getAuction(name); + const auction = await this.getAuction(name); if (!auction) throw new Error('Auction not found.'); - const reveal = await this.txdb.getValue(name); - - if (!reveal) - throw new Error('Reveal value not found.'); - const raw = Buffer.from(name, 'ascii'); - const {owner, state} = auction; - const {value} = reveal; + + let {owner, state} = auction; if (state !== rules.types.REVEAL && state !== rules.types.REGISTER @@ -1244,8 +1259,11 @@ class Wallet extends EventEmitter { } if (state === rules.types.REVEAL) { - if (!await this.txdb.isWinner(name, owner)) + const winner = await this.txdb.getWinningReveal(name); + const {hash, index} = winner; + if (!await this.txdb.hasCoin(hash, index)) throw new Error('Wallet did not win the auction.'); + owner = winner; } const output = new Output(); @@ -1254,7 +1272,7 @@ class Wallet extends EventEmitter { assert(coin); output.address = coin.address; - output.value = value; + output.value = coin.value; output.covenant.type = rules.types.REGISTER; output.covenant.items.push(raw); output.covenant.items.push(data); @@ -1283,7 +1301,7 @@ class Wallet extends EventEmitter { } const mtx = new MTX(); - mtx.addOutpoint(prevout); + mtx.addOutpoint(owner); mtx.outputs.push(output); await this._fund(mtx, options); @@ -1437,7 +1455,7 @@ class Wallet extends EventEmitter { // Consensus sanity checks. assert(mtx.isSane(), 'TX failed sanity check.'); - assert(mtx.verifyInputs(this.wdb.state.height + 1), + assert(mtx.verifyInputs(this.wdb.height + 1), 'TX failed context check.'); const total = await this.template(mtx); diff --git a/test/util/memwallet.js b/test/util/memwallet.js index ed1c1c6c..3fefe9d0 100644 --- a/test/util/memwallet.js +++ b/test/util/memwallet.js @@ -552,16 +552,12 @@ class MemWallet { const coin = this.getCoin(prevout.toKey()) assert(coin); - const height = Buffer.allocUnsafe(4); - height.writeUInt32LE(coin.height, 0, true); - const output = new Output(); output.address = coin.address; output.value = value; output.covenant.type = 2; output.covenant.items.push(raw); output.covenant.items.push(nonce); - output.covenant.items.push(height); const mtx = new MTX(); mtx.addOutpoint(prevout);