handshake: wallet work.
This commit is contained in:
parent
5c581b4eb3
commit
2ef47124fc
13 changed files with 545 additions and 154 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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[]}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue