Skip to content

Commit

Permalink
wallet: add FINALIZE all to batch + other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
pinheadmz committed Aug 15, 2022
1 parent 1a10677 commit 0e2eb39
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 21 deletions.
12 changes: 8 additions & 4 deletions lib/wallet/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2604,11 +2604,15 @@ class RPC extends RPCBase {
}
case 'FINALIZE': {
assert(
action.length === 1,
'FINALIZE action requires 1 argument: name'
action.length === 0 || action.length === 1,
'FINALIZE can only have 1 argument: name'
);
const {name} = this._validateFinalize(action);
actions.push([type, name]);
if (action.length === 1) {
const {name} = this._validateFinalize(action);
actions.push([type, name]);
} else {
actions.push([type]);
}
break;
}
case 'CANCEL': {
Expand Down
105 changes: 93 additions & 12 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -2066,8 +2066,8 @@ class Wallet extends EventEmitter {

// Keep batches below policy size limit
if (this.isOversizedBatch(mtx, witnessSize)) {
mtx.inputs.shift().getSize();
mtx.outputs.shift().getSize();
mtx.inputs.pop();
mtx.outputs.pop();
break;
}

Expand Down Expand Up @@ -2336,8 +2336,8 @@ class Wallet extends EventEmitter {

// Keep batches below policy size limit
if (this.isOversizedBatch(mtx, witnessSize)) {
mtx.inputs.shift();
mtx.outputs.shift();
mtx.inputs.pop();
mtx.outputs.pop();
break;
}

Expand Down Expand Up @@ -2725,14 +2725,14 @@ class Wallet extends EventEmitter {
let expiring = [];
for (const ns of names) {
// Easiest check is for expiring time, do that first
const {blocksUntilExpire} = ns.toStats(height, network);

if (!blocksUntilExpire || blocksUntilExpire < 0)
if (ns.isExpired(height, network))
continue;

// TODO: Should this factor of 8 be user-configurable?
// About 90 days on main (1.75 years after REGISTER)
// 625 blocks on regtest (4375 blocks after REGISTER)
if (blocksUntilExpire >= network.names.renewalWindow / 8)
const blocksLeft = (ns.renewal + network.names.renewalWindow) - height;
if (blocksLeft >= network.names.renewalWindow / 8)
continue;

if (height < ns.renewal + network.names.treeInterval)
Expand Down Expand Up @@ -2782,8 +2782,8 @@ class Wallet extends EventEmitter {

// Keep batches below policy size limit
if (this.isOversizedBatch(mtx, witnessSize)) {
mtx.inputs.shift();
mtx.outputs.shift();
mtx.inputs.pop();
mtx.outputs.pop();
break;
}
}
Expand Down Expand Up @@ -3205,6 +3205,83 @@ class Wallet extends EventEmitter {
return mtx;
}

/**
* Make a finazling MTX for all transferring names
* @private
* @param {MTX?} mtx
* @param {Number?} witnessSize
* @returns {MTX}
*/

async makeFinalizeAll(mtx, witnessSize) {
// Only allowed in makeBatch
assert(mtx, 'Batch MTX required for makeFinalizeAll.');
assert(witnessSize, 'Witness size required for batch size estimation.');
const height = this.wdb.height + 1;
const network = this.network;
const names = await this.getNames();

let finalizes = 0;
for (const ns of names) {
// Easiest check is for transfer state, do that first
if (!ns.transfer)
continue;

const blocksLeft = (ns.transfer + network.names.transferLockup) - height;
if (blocksLeft > 0)
continue;

// Then check for expiration
if (ns.isExpired(height, network))
continue;

// Now do the db lookups to see if we own the name
const {hash, index} = ns.owner;
const coin = await this.getUnspentCoin(hash, index);
if (!coin)
continue;

const version = coin.covenant.getU8(2);
const addr = coin.covenant.get(3);
const address = Address.fromHash(addr, version);

let flags = 0;

if (ns.weak)
flags |= 1;

const output = new Output();
output.address = address;
output.value = coin.value;
output.covenant.type = types.FINALIZE;
output.covenant.pushHash(ns.nameHash);
output.covenant.pushU32(ns.height);
output.covenant.push(Buffer.from(ns.name, 'ascii'));
output.covenant.pushU8(flags);
output.covenant.pushU32(ns.claimed);
output.covenant.pushU32(ns.renewals);
output.covenant.pushHash(await this.wdb.getRenewalBlock());

mtx.addOutpoint(new Outpoint(coin.hash, coin.index));
mtx.outputs.push(output);

// Keep batches below policy size limit
if (this.isOversizedBatch(mtx, witnessSize)) {
mtx.inputs.pop();
mtx.outputs.pop();
break;
}

// TODO: Should this factor of 6 be user-configurable?
// Enforce consensus limit per block at a maxmium
finalizes++;
if (finalizes > consensus.MAX_BLOCK_RENEWALS / 6)
break;
}

return mtx;
}

/**
* Create and finalize a finalize
* MTX without a lock.
Expand Down Expand Up @@ -3628,8 +3705,12 @@ class Wallet extends EventEmitter {
await this.makeTransfer(...action, acct, mtx);
break;
case 'FINALIZE':
assert(action.length === 1, 'Bad arguments for FINALIZE.');
await this.makeFinalize(...action, acct, mtx);
if (action.length === 1) {
await this.makeFinalize(...action, acct, mtx);
} else {
assert(action.length === 0, 'Bad arguments for FINALIZE.');
await this.makeFinalizeAll(mtx, witnessSize);
}
break;
case 'CANCEL':
assert(action.length === 1, 'Bad arguments for CANCEL.');
Expand Down
82 changes: 77 additions & 5 deletions test/wallet-auction-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,23 @@ describe('Wallet Auction', function() {
this.timeout(30000);
let name;

it('should reset wallet', async () => {
const newWallet = await wdb.create();
const address = await newWallet.receiveAddress();
const bal = await wallet.getBalance();
const value = Math.floor(bal.confirmed / 5);
await wallet.send({
outputs: [
{address, value},
{address, value},
{address, value},
{address, value}
]
});
await mineBlocks(1);
wallet = newWallet;
});

it('should OPEN', async () => {
name = rules.grindName(4, chain.height, network);
await wallet.sendBatch([['OPEN', name]]);
Expand Down Expand Up @@ -867,7 +884,7 @@ describe('Wallet Auction', function() {
let reveals = 0;
for (;;) {
try {
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW']]);
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]);
reveals += tx.outputs.length - 1; // Don't count change output
} catch (e) {
assert.strictEqual(e.message, 'Nothing to do.');
Expand All @@ -884,7 +901,7 @@ describe('Wallet Auction', function() {
let redeems = 0;
for (;;) {
try {
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW']]);
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]);
redeems += tx.outputs.length - 1; // Don't count change output
} catch (e) {
assert.strictEqual(e.message, 'Nothing to do.');
Expand Down Expand Up @@ -939,14 +956,14 @@ describe('Wallet Auction', function() {
);

await assert.rejects(
wallet.sendBatch([['RENEW']]),
wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]),
{message: 'Nothing to do.'}
);
});

it('should not batch too many RENEWs', async () => {
const batch = [];
for (let i = 0; i < consensus.MAX_BLOCK_UPDATES + 1; i++)
for (let i = 0; i < consensus.MAX_BLOCK_RENEWALS + 1; i++)
batch.push(['RENEW', names[i]]);

await assert.rejects(
Expand All @@ -960,7 +977,7 @@ describe('Wallet Auction', function() {

let renewals = 0;
for (;;) {
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW']]);
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]);
await mineBlocks(1);

if (!renewals) {
Expand All @@ -975,6 +992,61 @@ describe('Wallet Auction', function() {
}
assert.strictEqual(renewals, 800);
});

it('should not batch too many TRANSFERs', async () => {
const batch = [];
for (const name of names)
batch.push(['TRANSFER', name, new Address()]);

await assert.rejects(
wallet.createBatch(batch),
{message: 'Too many UPDATEs.'} // Might exceed wallet lookahead also
);
});

it('should send batches of TRANSFERs', async () => {
const addr = Address.fromProgram(0, Buffer.alloc(20, 0xd0));
let count = 0;
for (let i = 1; i <= 8; i++) {
const batch = [];
for (let j = 1; j <= 100; j++) {
batch.push(['TRANSFER', names[count++], addr]);
}
await wallet.sendBatch(batch);
await mineBlocks(1);
}
});

it('should not FINALIZE any names too early', async () => {
await mineBlocks(network.names.lockupPeriod - 9);

await assert.rejects(
wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]),
{message: 'Nothing to do.'}
);
});

it('should send all the batches of FINALIZEs it needs to', async () => {
await mineBlocks(8); // All names ready for finalize

let finalizes = 0;
for (;;) {
const tx = await wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]);
await mineBlocks(1);

finalizes += tx.outputs.length - 1; // Don't count change output
if (finalizes === 800)
break;
}
assert.strictEqual(finalizes, 800);
});

it('should have nothing to do', async () => {
await assert.rejects(
wallet.sendBatch([['REVEAL'], ['REDEEM'], ['RENEW'], ['FINALIZE']]),
{message: 'Nothing to do.'}
);
});
});
});
});

0 comments on commit 0e2eb39

Please sign in to comment.