Skip to content

Commit

Permalink
Merge c969e8f into 4c901f4
Browse files Browse the repository at this point in the history
  • Loading branch information
HDardenne-HNS committed Feb 2, 2021
2 parents 4c901f4 + c969e8f commit e1fb21c
Show file tree
Hide file tree
Showing 5 changed files with 534 additions and 4 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# HSD Release Notes & Changelog

## unreleased

- Adds new wallet HTTP endpoint `/wallet/:id/auction` based on `POST /wallet/:id/bid`.
It requires an additional parameter `broadcastBid` set to either true or false.
This action returns a bid and its corresponding reveal, the reveal being prepared in advance.
The bid will be broadcasted either during the creation (`broadcastBid=true`) or at a later time
(`broadcastBid=false`).
The reveal will have to be broadcasted at a later time, during the REVEAL phase.
The lockup must include a blind big enough to ensure the BID will be the only input of the REVEAL
transaction.

## v2.3.0

### Node changes
Expand Down
39 changes: 39 additions & 0 deletions lib/wallet/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,45 @@ class HTTP extends Server {
return res.json(200, mtx.getJSON(this.network));
});

// Create auction-related transactions in advance (bid and reveal for now)
this.post('/wallet/:id/auction', async (req, res) => {
const valid = Validator.fromRequest(req);
const name = valid.str('name');
const bid = valid.u64('bid');
const lockup = valid.u64('lockup');
const passphrase = valid.str('passphrase');
const sign = valid.bool('sign', true);
const broadcastBid = valid.bool('broadcastBid');

assert(name, 'Name is required.');
assert(bid != null, 'Bid is required.');
assert(lockup != null, 'Lockup is required.');
assert(broadcastBid != null, 'broadcastBid is required.');
assert(broadcastBid ? sign : true, 'Must sign when broadcasting.');

const options = TransactionOptions.fromValidator(valid);
const auctionTxs = await req.wallet.createAuctionTxs(
name,
bid,
lockup,
options
);

if (sign) {
if (broadcastBid) {
auctionTxs.bid = await req.wallet.sendMTX(auctionTxs.bid, passphrase);
} else {
await req.wallet.sign(auctionTxs.bid, passphrase);
}
await req.wallet.sign(auctionTxs.reveal, passphrase);
}

return res.json(200, {
bid: auctionTxs.bid.getJSON(this.network),
reveal: auctionTxs.reveal.getJSON(this.network)
});
});

// Create Reveal
this.post('/wallet/:id/reveal', async (req, res) => {
const valid = Validator.fromRequest(req);
Expand Down
83 changes: 79 additions & 4 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const {states} = require('../covenants/namestate');
const {types} = rules;
const {Mnemonic} = HD;
const {BufferSet} = require('buffer-map');
const Coin = require('../primitives/coin');
const Outpoint = require('../primitives/outpoint');

/*
* Constants
Expand Down Expand Up @@ -1195,12 +1197,15 @@ class Wallet extends EventEmitter {
if (rate == null)
rate = await this.wdb.estimateFee(options.blocks);

let coins;
let coins = options.coins || [];
assert(Array.isArray(coins));
if (options.smart) {
coins = await this.getSmartCoins(options.account);
const smartCoins = await this.getSmartCoins(options.account);
coins = coins.concat(smartCoins);
} else {
coins = await this.getCoins(options.account);
coins = this.txdb.filterLocked(coins);
let availableCoins = await this.getCoins(options.account);
availableCoins = this.txdb.filterLocked(availableCoins);
coins = coins.concat(availableCoins);
}

await mtx.fund(coins, {
Expand Down Expand Up @@ -1820,6 +1825,76 @@ class Wallet extends EventEmitter {
}
}

/**
* Create and finalize a bid & a reveal (in advance)
* MTX with a lock.
* @param {String} name
* @param {Number} value
* @param {Number} lockup
* @param {Object} options
* @returns {{ bid: MTX; reveal: MTX;}} {bid, reveal}
*/

async createAuctionTxs(name, value, lockup, options) {
const unlock = await this.fundLock.lock();
try {
return await this._createAuctionTxs(name, value, lockup, options);
} finally {
unlock();
}
}

/**
* Create and finalize a bid & a reveal (in advance)
* MTX without a lock.
* @param {String} name
* @param {Number} value
* @param {Number} lockup
* @param {Object} options
* @returns {{ bid: MTX; reveal: MTX;}} {bid, reveal}
*/

async _createAuctionTxs(name, value, lockup, options) {
const bid = await this._createBid(name, value, lockup, options);

const bidOuputIndex = bid.outputs.findIndex(o => o.covenant.isBid());
const bidOutput = bid.outputs[bidOuputIndex];
const bidCoin = Coin.fromTX(bid, bidOuputIndex, -1);

// Prepare the data needed to make the reveal in advance
const nameHash = bidOutput.covenant.getHash(0);
const height = bidOutput.covenant.getU32(1);

const coins = [];
coins.push(bidCoin);

const blind = bidOutput.covenant.getHash(3);
const bv = await this.getBlind(blind);
if (!bv)
throw new Error('Blind value not found.');
const { nonce } = bv;

const reveal = new MTX();
const output = new Output();
output.address = bidCoin.address;
output.value = value;
output.covenant.type = types.REVEAL;
output.covenant.pushHash(nameHash);
output.covenant.pushU32(height);
output.covenant.pushHash(nonce);
reveal.addOutpoint(Outpoint.fromTX(bid, bidOuputIndex));
reveal.outputs.push(output);

await this.fill(reveal, { ...options, coins: coins });
assert(
reveal.inputs.length === 1,
'Pre-signed REVEAL must not require additional inputs'
);

const finalReveal = await this.finalize(reveal, options);
return { bid, reveal: finalReveal };
}

/**
* Make a reveal MTX.
* @param {String} name
Expand Down
65 changes: 65 additions & 0 deletions test/wallet-http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,71 @@ describe('Wallet HTTP', function() {
matchTxId(auction.reveals, state.reveals[1].hash);
});

it('should create a bid and a reveal (reveal in advance)', async () => {
const balanceBeforeTest = await wallet.getBalance();
const lockConfirmedBeforeTest = balanceBeforeTest.lockedConfirmed;
const lockUnconfirmedBeforeTest = balanceBeforeTest.lockedUnconfirmed;

await wallet.createOpen({ name: name });

await mineBlocks(treeInterval + 2, cbAddress);

const balanceBeforeBid = await wallet.getBalance();
assert.equal(balanceBeforeBid.lockedConfirmed - lockConfirmedBeforeTest, 0);
assert.equal(
balanceBeforeBid.lockedUnconfirmed - lockUnconfirmedBeforeTest,
0
);

const bidValue = 1000000;
const lockupValue = 5000000;

const auctionTxs = await wallet.client.post(
`/wallet/${wallet.id}/auction`,
{
name: name,
bid: 1000000,
lockup: 5000000,
broadcastBid: true
}
);

await mineBlocks(biddingPeriod + 1, cbAddress);

let walletAuction = await wallet.getAuctionByName(name);
const bidFromWallet = walletAuction.bids.find(
b => b.prevout.hash === auctionTxs.bid.hash
);
assert(bidFromWallet);

const { info } = await nclient.execute('getnameinfo', [name]);
assert.equal(info.name, name);
assert.equal(info.state, 'REVEAL');

const b5 = await wallet.getBalance();
assert.equal(b5.lockedConfirmed - lockConfirmedBeforeTest, lockupValue);
assert.equal(b5.lockedUnconfirmed - lockUnconfirmedBeforeTest, lockupValue);

await nclient.broadcast(auctionTxs.reveal.hex);
await mineBlocks(1, cbAddress);

walletAuction = await wallet.getAuctionByName(name);
const revealFromWallet = walletAuction.reveals.find(
b => b.prevout.hash === auctionTxs.reveal.hash
);
assert(revealFromWallet);

const b6 = await wallet.getBalance();
assert.equal(b6.lockedConfirmed - lockConfirmedBeforeTest, bidValue);
assert.equal(b6.lockedUnconfirmed - lockUnconfirmedBeforeTest, bidValue);

await mineBlocks(revealPeriod + 1, cbAddress);

const ns = await nclient.execute('getnameinfo', [name]);
const coin = await wallet.getCoin(ns.info.owner.hash, ns.info.owner.index);
assert.ok(coin);
});

it('should create a redeem', async () => {
await wallet.createOpen({
name: name
Expand Down

0 comments on commit e1fb21c

Please sign in to comment.