covenants: refactor a number of things.

This commit is contained in:
Christopher Jeffrey 2018-01-05 20:37:19 -08:00
parent 0d08a19353
commit 2ffbbded2e
No known key found for this signature in database
GPG key ID: 8962AB9DE6666BBD
4 changed files with 284 additions and 146 deletions

View file

@ -211,7 +211,6 @@ class NameDB {
const {prevout} = input;
const coin = view.getOutput(prevout);
assert(coin);
const uc = coin.covenant;
const index = input.link;
@ -219,10 +218,12 @@ class NameDB {
const {covenant} = output;
if (uc.type === types.BID) {
assert(covenant.type === types.REVEAL);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);
if (auction.state(height, network) !== states.REVEAL)
if (auction.state(height, network) > states.REVEAL)
return false;
auction.removeBid(prevout.hash, prevout.index);
@ -233,6 +234,10 @@ class NameDB {
}
if (uc.type === types.REVEAL) {
assert(covenant.type === types.REDEEM
|| covenant.type === types.UPDATE
|| covenant.type === types.RELEASE);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);
@ -244,7 +249,7 @@ class NameDB {
if (owner.isNull())
owner = await this.pickWinner(auction.nameHash);
if (covenant.type !== types.UPDATE) {
if (covenant.type === types.REDEEM) {
// Must be the loser in order
// to redeem the money now.
if (prevout.equals(owner))
@ -253,23 +258,29 @@ class NameDB {
continue;
}
assert(covenant.type === types.UPDATE);
// Must be the winner in order
// to update the name record.
if (!prevout.equals(owner))
return false;
auction.removeReveal(prevout.hash, prevout.index);
auction.setOwner(hash, index);
auction.setData(covenant.items[1]);
auction.save();
auction.queue();
if (covenant.type === types.UPDATE) {
auction.setOwner(hash, index);
auction.setData(covenant.items[1]);
auction.save();
auction.queue();
} else {
auction.remove();
}
continue;
}
if (uc.type === types.UPDATE) {
assert(covenant.type === types.UPDATE
|| covenant.type === types.RELEASE);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);
@ -280,10 +291,15 @@ class NameDB {
if (!prevout.equals(auction.owner))
return false;
auction.setOwner(hash, index);
auction.setData(covenant.items[1]);
auction.save();
auction.queue();
if (covenant.type === types.UPDATE) {
auction.setOwner(hash, index);
auction.setData(covenant.items[1]);
auction.save();
auction.queue();
} else {
auction.remove();
auction.unqueue();
}
continue;
}
@ -299,6 +315,9 @@ class NameDB {
const auction =
await view.ensureAuction(this, name, nameHash, height);
if (!auction.owner.isNull())
return false;
if (auction.state(height, network) !== states.BIDDING)
return false;
@ -365,9 +384,11 @@ class NameDB {
const {covenant} = output;
if (uc.type === types.BID) {
assert(covenant.type === types.REVEAL);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);
assert(auction.state(height, network) === states.REVEAL);
assert(auction.state(height, network) <= states.REVEAL);
auction.removeReveal(hash, index);
auction.save();
@ -376,18 +397,26 @@ class NameDB {
}
if (uc.type === types.REVEAL) {
assert(covenant.type === types.REDEEM
|| covenant.type === types.UPDATE
|| covenant.type === types.RELEASE);
// XXX Figure out what to do here:
// Add a `released` property to auction object!
assert(covenant.type !== types.RELEASE);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);
assert(auction.state(height, network) === states.CLOSED);
if (covenant.type !== types.UPDATE) {
if (covenant.type === types.REDEEM) {
assert(!prevout.equals(auction.owner));
auction.addReveal(prevout.hash, prevout.index, coin.value);
auction.save();
continue;
}
assert(!prevout.equals(auction.owner));
assert(prevout.equals(auction.owner));
// Switch back to previous owner and data.
auction.addReveal(prevout.hash, prevout.index, coin.value);
@ -400,7 +429,12 @@ class NameDB {
}
if (uc.type === types.UPDATE) {
assert(covenant.type === types.UPDATE);
assert(covenant.type === types.UPDATE
|| covenant.type === types.RELEASE);
// XXX Figure out what to do here:
// Add a `released` property to auction object!
assert(covenant.type !== types.RELEASE);
const auction = await view.getAuctionFor(this, prevout);
assert(auction);

View file

@ -4,13 +4,17 @@ const assert = require('assert');
const bio = require('bufio');
const blake2b = require('bcrypto/lib/blake2b');
// TODO:
// locktime for transfer
// no duplicate names
// early reveals
const types = {
NONE: 0,
BID: 1,
REVEAL: 2,
UPDATE: 3,
REDEEM: 4,
// locktime for transfer
RELEASE: 5
};
@ -19,7 +23,7 @@ exports.types = types;
exports.MAX_NAME_SIZE = 255;
exports.MAX_RECORD_SIZE = 512;
exports.MAX_COVENANT_SIZE = 1 + exports.MAX_RECORD_SIZE;
exports.MAX_COVENANT_TYPE = types.UPDATE;
exports.MAX_COVENANT_TYPE = types.RELEASE;
exports.BIDDING_PERIOD = 1;
exports.REVEAL_PERIOD = 1;
@ -123,6 +127,9 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
for (const {covenant} of tx.outputs) {
if (covenant.type !== types.NONE)
return false;
if (covenant.items.length !== 0)
return false;
}
return true;
@ -148,14 +155,19 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
const {covenant} = tx.outputs[i];
switch (covenant.type) {
case types.NONE:
case types.NONE: {
// No inputs can be linked.
if (links.has(i))
return false;
// Just a regular payment.
// Can come from a payment or a reveal (loser).
if (covenant.items.length !== 0)
return false;
break;
case types.BID:
}
case types.BID: {
// No inputs can be linked.
if (links.has(i))
return false;
@ -173,7 +185,8 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
return false;
break;
case types.REVEAL:
}
case types.REVEAL: {
// Has to come from a BID.
if (!links.has(i))
return false;
@ -191,7 +204,8 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
return false;
break;
case types.UPDATE:
}
case types.UPDATE: {
// Has to come from an UPDATE or REVEAL.
if (!links.has(i))
return false;
@ -209,7 +223,8 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
return false;
break;
case types.REDEEM:
}
case types.REDEEM: {
// Has to come from a REVEAL.
if (!links.has(i))
return false;
@ -223,7 +238,8 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
return false;
break;
case types.RELEASE:
}
case types.RELEASE: {
// Has to come from an UPDATE or REVEAL.
if (!links.has(i))
return false;
@ -237,10 +253,12 @@ exports.hasSaneCovenants = function hasSaneCovenants(tx) {
return false;
break;
default:
}
default: {
// Unknown covenant.
// Don't enforce anything.
break;
}
}
}
@ -259,27 +277,27 @@ exports.verifyCovenants = function verifyCovenants(tx, view) {
const uc = coin.covenant;
// XXX More verification here?
if (input.link === 0xffffffff)
continue;
assert(input.link < tx.outputs.length);
// Output the covenant is linked to.
const output = tx.outputs[input.link];
const {covenant} = output;
if (input.link !== 0xffffffff)
assert(input.link < tx.outputs.length);
switch (uc.type) {
case types.NONE: {
// Payment has to go to either
// another payment, or a bid.
if (covenant.type !== types.NONE
&& covenant.type !== types.BID) {
case types.NONE:
case types.REDEEM: {
// Cannot be linked.
if (input.link !== 0xffffffff)
return false;
}
break;
}
case types.BID: {
// Must be be linked.
if (input.link === 0xffffffff)
return false;
// Output the covenant is linked to.
const output = tx.outputs[input.link];
const {covenant} = output;
// Bid has to go to a reveal.
if (covenant.type !== types.REVEAL)
return false;
@ -304,58 +322,76 @@ exports.verifyCovenants = function verifyCovenants(tx, view) {
break;
}
case types.REVEAL: {
// Must be be linked.
if (input.link === 0xffffffff)
return false;
// Output the covenant is linked to.
const output = tx.outputs[input.link];
const {covenant} = output;
// Reveal has to go to an update, or
// a redeem (in the case of the loser).
if (covenant.type !== types.UPDATE
&& covenant.type !== types.REDEEM) {
return false;
}
switch (covenant.type) {
case types.UPDATE:
case types.RELEASE: {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
return false;
// Money is now locked up forever.
if (covenant.type === types.UPDATE) {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
return false;
// Money is now locked up forever.
if (output.value !== coin.value)
return false;
if (output.value !== coin.value)
return false;
}
break;
}
case types.REDEEM: {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
return false;
if (covenant.type === types.REDEEM) {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
break;
}
default: {
return false;
}
}
break;
}
case types.UPDATE: {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
// Must be be linked.
if (input.link === 0xffffffff)
return false;
// Money is now locked up forever.
if (output.value !== coin.value)
return false;
// Output the covenant is linked to.
const output = tx.outputs[input.link];
const {covenant} = output;
if (covenant.type !== types.UPDATE)
return false;
// Can only send to another update or release.
switch (covenant.type) {
case types.UPDATE:
case types.RELEASE: {
// Names must match.
if (!covenant.items[0].equals(uc.items[0]))
return false;
break;
}
case types.REDEEM: {
// Can go anywhere.
if (covenant.items.length !== 0)
return false;
// Money is now locked up forever.
if (output.value !== coin.value)
return false;
break;
}
default: {
return false;
}
}
break;
}
case types.RELEASE: {
// Can go anywhere.
if (covenant.items.length !== 0)
return false;
break;
// Releases are perma-burned.
return false;
}
default: {
// Unknown covenant.

View file

@ -1125,38 +1125,8 @@ class MTX extends TX {
for (const coin of select.chosen)
this.addCoin(coin);
// XXX
const map = new Map();
for (let i = 0; i < this.outputs.length; i++) {
const {covenant} = this.outputs[i];
if (covenant.items.length < 1)
continue;
const name = covenant.string(0);
map.set(name, i);
}
for (let i = 0; i < select.chosen.length; i++) {
if (map.size === 0)
break;
const {covenant} = select.chosen[i];
if (covenant.items.length < 1)
continue;
const name = covenant.string(0);
const link = map.get(name);
if (link == null)
continue;
map.delete(name);
this.inputs[i].link = link;
}
if (select.prevout)
this.inputs[0].link = select.link;
// Attempt to subtract fee.
if (select.subtractFee) {
@ -1411,8 +1381,8 @@ class CoinSelector {
this.maxFee = -1;
this.round = false;
this.changeAddress = null;
// XXX
this.map = new Map();
this.prevout = null;
this.link = -1;
// Needed for size estimation.
this.estimate = null;
@ -1508,6 +1478,12 @@ class CoinSelector {
this.estimate = options.estimate;
}
if (options.covenant) {
assert(typeof options.covenant === 'object');
this.prevout = options.covenant.prevout;
this.link = options.covenant.link;
}
return this;
}
@ -1524,17 +1500,6 @@ class CoinSelector {
this.change = 0;
this.fee = CoinSelector.MIN_FEE;
this.tx.inputs.length = 0;
this.map.clear();
// XXX
for (let i = 0; i < this.tx.outputs.length; i++) {
const {covenant} = this.tx.outputs[i];
if (covenant.items.length < 1)
continue;
this.map.set(covenant.string(0), i);
}
switch (this.selection) {
case 'all':
@ -1624,6 +1589,22 @@ class CoinSelector {
return Math.min(fee, CoinSelector.MAX_FEE);
}
findCoin(prevout) {
for (let i = 0; i < this.coins.length; i++) {
const {hash, index} = this.coins[i];
if (index !== prevout.index)
continue;
if (hash !== prevout.hash)
continue;
return i;
}
return -1;
}
/**
* Fund the transaction with more
* coins if the `output value + fee`
@ -1631,26 +1612,11 @@ class CoinSelector {
*/
fund() {
// XXX
for (let i = 0; i < this.coins.length; i++) {
if (this.map.size === 0)
break;
const coin = this.coins[i];
const {covenant} = coin;
if (covenant.items.length < 1)
continue;
const name = covenant.string(0);
if (!this.map.has(name))
continue;
this.map.delete(name);
this.coins.splice(i, 1);
i -= 1;
if (this.prevout && this.chosen.length === 0) {
const index = this.findCoin(this.prevout);
assert(index !== -1, 'Coin not found.');
const coin = this.coins[index];
this.coins.splice(index, 1);
this.tx.addCoin(coin);
this.chosen.push(coin);
}

View file

@ -340,6 +340,29 @@ class MemWallet {
this.auctions.set(name, [new Outpoint(hash, i), 3]);
break;
}
case 4: {
if (!path)
break;
const name = covenant.string(0);
// We lost.
this.auctions.delete(name);
this.bids.delete(name);
this.values.delete(name);
break;
}
case 5: {
const name = covenant.string(0);
// Someone released it.
this.auctions.delete(name);
this.bids.delete(name);
this.values.delete(name);
break;
}
}
@ -388,6 +411,75 @@ class MemWallet {
const op = input.prevout.toKey();
const coin = this.getUndo(op);
switch (covenant.type) {
case 1: {
if (!coin)
break;
const name = covenant.string(0);
this.auctions.delete(name);
break;
}
case 2: {
const name = covenant.string(0);
const nonce = covenant.items[1];
if (!this.auctions.has(name))
break;
if (!this.bids.has(name))
break;
const key = Outpoint.toKey(hash, i);
const bids = this.bids.get(name);
bids.delete(key);
if (bids.size === 0)
this.bids.delete(name);
if (!coin)
break;
this.values.delete(name);
this.auctions.set(name, [new Outpoint(hash, i), 1]);
break;
}
case 3: {
if (!coin)
break;
const name = covenant.string(0);
this.auctions.set(name, [new Outpoint(hash, i), 2]);
break;
}
case 4: {
if (!coin)
break;
const name = covenant.string(0);
// We lost.
this.auctions.set(name, [new Outpoint(hash, i), 2]);
break;
}
case 5: {
const name = covenant.string(0);
// Someone released it.
this.auctions.set(name, [new Outpoint(hash, i), 3]);
break;
}
}
if (!coin)
continue;
@ -479,7 +571,10 @@ class MemWallet {
const mtx = new MTX();
mtx.outputs.push(output);
return this._create(mtx, options, prevout);
return this._create(mtx, options, {
prevout,
link: 0
});
}
async registerName(name, data, options) {
@ -511,7 +606,10 @@ class MemWallet {
const mtx = new MTX();
mtx.outputs.push(output);
return this._create(mtx, options, prevout);
return this._create(mtx, options, {
prevout,
link: 0
});
}
async redeemName(name, options) {
@ -531,7 +629,10 @@ class MemWallet {
const mtx = new MTX();
mtx.outputs.push(output);
return this._create(mtx, options);
return this._create(mtx, options, {
prevout,
link: 0
});
}
isWinner(name) {
@ -556,7 +657,7 @@ class MemWallet {
return this.coins.has(winner);
}
fund(mtx, options) {
fund(mtx, options, covenant) {
const coins = this.getCoins();
if (!options)
@ -571,7 +672,8 @@ class MemWallet {
changeAddress: this.getChange(),
height: -1,
rate: options.rate,
maxFee: options.maxFee
maxFee: options.maxFee,
covenant
});
}
@ -586,8 +688,8 @@ class MemWallet {
mtx.sign(keys);
}
async _create(mtx, options) {
await this.fund(mtx, options);
async _create(mtx, options, covenant) {
await this.fund(mtx, options, covenant);
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');