Skip to content

Commit

Permalink
wallet: watch open earlier.
Browse files Browse the repository at this point in the history
This allows to track double OPEN issue from the outside of the wallet.
Should reduce number of stale OPEN txs.
  • Loading branch information
nodech committed Nov 24, 2023
1 parent c74d528 commit a03c49a
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 24 deletions.
4 changes: 4 additions & 0 deletions lib/wallet/records.js
Expand Up @@ -422,6 +422,10 @@ class MapRecord extends bio.Struct {
return this.wids.delete(wid);
}

has(wid) {
return this.wids.has(wid);
}

write(bw) {
bw.writeU32(this.wids.size);

Expand Down
32 changes: 28 additions & 4 deletions lib/wallet/txdb.js
Expand Up @@ -1045,6 +1045,7 @@ class TXDB {
}

await this.saveCredit(b, credit, path);
await this.watchOpensEarly(b, output);
}

// Handle names.
Expand Down Expand Up @@ -1852,12 +1853,36 @@ class TXDB {
}
}

/**
* Start tracking OPENs right away.
* This does not check if the name is owned by the wallet.
* @private
* @param {Batch} b
* @param {Output} tx
* @param {Path} path
* @returns {Promise}
*/

async watchOpensEarly(b, output) {
const {covenant} = output;

if (!covenant.isName())
return;

if (!covenant.isOpen())
return;

const nameHash = covenant.getHash(0);

if (!await this.wdb.hasNameMap(nameHash, this.wid))
await this.addNameMap(b, nameHash);
}

/**
* Handle incoming covenant.
* @param {Object} b
* @param {TX} tx
* @param {Number} i
* @param {Path} path
* @param {CoinView} view
* @param {Number} height
* @returns {Promise<Boolean>} updated
*/
Expand Down Expand Up @@ -1916,8 +1941,7 @@ class TXDB {
case types.OPEN: {
if (!path) {
// Are we "watching" this name?
const map = await this.wdb.getNameMap(nameHash);
if (!map || !map.wids.has(this.wid))
if (!await this.wdb.hasNameMap(nameHash, this.wid))
break;

const name = covenant.get(2);
Expand Down
27 changes: 27 additions & 0 deletions lib/wallet/walletdb.js
Expand Up @@ -1925,6 +1925,22 @@ class WalletDB extends EventEmitter {
return MapRecord.decode(data);
}

/**
* Does wdb have wallet map.
* @param {Buffer} key
* @param {Number} wid
* @returns {Promise<Boolean>}
*/

async hasMap(key, wid) {
const map = await this.getMap(key);

if (!map)
return false;

return map.has(wid);
}

/**
* Add wid to a wallet map.
* @param {Wallet} wallet
Expand Down Expand Up @@ -2116,6 +2132,17 @@ class WalletDB extends EventEmitter {
return this.getMap(layout.N.encode(nameHash));
}

/**
* Has wid in the wallet map.
* @param {Buffer} nameHash
* @param {Number} wid
* @returns {Promise<Boolean>}
*/

async hasNameMap(nameHash, wid) {
return this.hasMap(layout.N.encode(nameHash), wid);
}

/**
* Add wid to a wallet map.
* @param {Wallet} wallet
Expand Down
4 changes: 2 additions & 2 deletions test/mempool-invalidation-test.js
Expand Up @@ -137,8 +137,8 @@ describe('Mempool Invalidation', function() {
assert.strictEqual(await getNameState(name), states.OPENING);

assert.strictEqual(node.mempool.map.size, 0);
// we don't want coins to get stuck in the wallet.
wallet2.abandon(memopen.hash());
const pending = await wallet2.getPending();
assert.strictEqual(pending.length, 0);
});

it('should invalidate bids', async () => {
Expand Down
70 changes: 52 additions & 18 deletions test/wallet-auction-test.js
Expand Up @@ -18,7 +18,6 @@ const {Resource} = require('../lib/dns/resource');
const {forEvent} = require('./util/common');

const network = Network.get('regtest');
const NAME1 = rules.grindName(10, 2, network);
const {
treeInterval,
biddingPeriod,
Expand Down Expand Up @@ -58,7 +57,10 @@ const wdb = new WalletDB({
});

describe('Wallet Auction', function() {
let wallet;
let wallet, wallet2;

const name1 = rules.grindName(10, 2, network);
const name2 = rules.grindName(10, 2, network);

before(async () => {
// Open
Expand All @@ -70,6 +72,7 @@ describe('Wallet Auction', function() {

// Set up wallet
wallet = await wdb.create();
wallet2 = await wdb.create();
chain.on('connect', async (entry, block) => {
await wdb.addBlock(entry, block.txs);
});
Expand All @@ -79,10 +82,16 @@ describe('Wallet Auction', function() {
});

// Generate blocks to roll out name and fund wallet
let winnerAddr = await wallet.createReceive();
winnerAddr = winnerAddr.getAddress().toString(network);
for (let i = 0; i < 10; i++) {
const block = await cpu.mineBlock(null, winnerAddr);
let walletAddr = await wallet.createReceive();
walletAddr = walletAddr.getAddress().toString(network);
for (let i = 0; i < 5; i++) {
const block = await cpu.mineBlock(null, walletAddr);
await chain.add(block);
}

walletAddr = (await wallet2.createReceive()).getAddress().toString(network);
for (let i = 0; i < 5; i++) {
const block = await cpu.mineBlock(null, walletAddr);
await chain.add(block);
}
});
Expand All @@ -108,7 +117,7 @@ describe('Wallet Auction', function() {

it('should open auction', async () => {
for (let i = 0; i < OPENS1; i++) {
const open = await wallet.createOpen(NAME1);
const open = await wallet.createOpen(name1);
await wallet.sign(open);

assert.strictEqual(open.inputs.length, 1);
Expand All @@ -133,13 +142,13 @@ describe('Wallet Auction', function() {
it('should fail to create duplicate open', async () => {
let err;
try {
await wallet.createOpen(NAME1);
await wallet.createOpen(name1);
} catch (e) {
err = e;
}

assert(err);
assert.strictEqual(err.message, `Already sent an open for: ${NAME1}.`);
assert.strictEqual(err.message, `Already sent an open for: ${name1}.`);
});

it('should not accept own duplicate open', async () => {
Expand Down Expand Up @@ -183,13 +192,13 @@ describe('Wallet Auction', function() {
it('should fail to re-open auction during OPEN phase', async () => {
let err;
try {
await wallet.createOpen(NAME1);
await wallet.createOpen(name1);
} catch (e) {
err = e;
}

assert(err);
assert.strictEqual(err.message, `Name is already opening: ${NAME1}.`);
assert.strictEqual(err.message, `Name is already opening: ${name1}.`);
});

it('should mine enough blocks to enter BIDDING phase', async () => {
Expand All @@ -201,7 +210,7 @@ describe('Wallet Auction', function() {
});

it('should fail to send bid to null address', async () => {
const mtx = await wallet.makeBid(NAME1, 1000, 2000, 0);
const mtx = await wallet.makeBid(name1, 1000, 2000, 0);
mtx.outputs[0].address = new Address();
await wallet.fill(mtx);
await wallet.finalize(mtx);
Expand All @@ -214,13 +223,13 @@ describe('Wallet Auction', function() {
it('should fail to re-open auction during BIDDING phase', async () => {
let err;
try {
await wallet.createOpen(NAME1);
await wallet.createOpen(name1);
} catch (e) {
err = e;
}

assert(err);
assert.strictEqual(err.message, `Name is not available: ${NAME1}.`);
assert.strictEqual(err.message, `Name is not available: ${name1}.`);
});

it('should mine enough blocks to expire auction', async () => {
Expand All @@ -241,13 +250,13 @@ describe('Wallet Auction', function() {
it('should fail to create duplicate open (again)', async () => {
let err;
try {
await wallet.createOpen(NAME1);
await wallet.createOpen(name1);
} catch (e) {
err = e;
}

assert(err);
assert.strictEqual(err.message, `Already sent an open for: ${NAME1}.`);
assert.strictEqual(err.message, `Already sent an open for: ${name1}.`);
});

it('should confirm OPEN transaction', async () => {
Expand All @@ -259,7 +268,7 @@ describe('Wallet Auction', function() {
const block = await job.mineAsync();
assert(await chain.add(block));

let ns = await chain.db.getNameStateByName(NAME1);
let ns = await chain.db.getNameStateByName(name1);
let state = ns.state(chain.height, network);
assert.strictEqual(state, states.OPENING);

Expand All @@ -269,7 +278,7 @@ describe('Wallet Auction', function() {
assert(await chain.add(block));
}

ns = await chain.db.getNameStateByName(NAME1);
ns = await chain.db.getNameStateByName(name1);
state = ns.state(chain.height, network);
assert.strictEqual(state, states.BIDDING);
});
Expand Down Expand Up @@ -355,6 +364,31 @@ describe('Wallet Auction', function() {
const secondTX = await wallet.getTX(openTXs[insertIndexes[2]].hash());
assert.notStrictEqual(secondTX, null);
});

it('should handle foreign double open after sending open', async () => {
const open = await wallet.createOpen(name2);
await wallet.sign(open);

const open2 = await wallet2.createOpen(name2);
await wallet2.sign(open2);

// try to open.
await wdb.addTX(open.toTX());
const pending1 = await wallet.getPending();
assert.strictEqual(pending1.length, 1);
assert.bufferEqual(pending1[0].hash, open.hash());

const job = await cpu.createJob();
const [tx, view] = open2.commit();
job.addTX(tx, view);
job.refresh();

const block = await job.mineAsync();
assert(await chain.add(block));

const pending1after = await wallet.getPending();
assert.strictEqual(pending1after.length, 0);
});
});

describe('Batch TXs', function() {
Expand Down

0 comments on commit a03c49a

Please sign in to comment.