Skip to content

Commit d952bcb

Browse files
committed
wallet: check account ownership of name before making update TX
Uses new method txdb.hasCoinByAccount() to verify that not only is a name-owning coin owned by the wallet, but also by the specified account, when making an UPDATE TX.
1 parent ec2bdc8 commit d952bcb

File tree

3 files changed

+354
-198
lines changed

3 files changed

+354
-198
lines changed

lib/wallet/wallet.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,13 +2329,18 @@ class Wallet extends EventEmitter {
23292329
* @returns {MTX}
23302330
*/
23312331

2332-
async makeUpdate(name, resource) {
2332+
async makeUpdate(name, resource, acct) {
23332333
assert(typeof name === 'string');
23342334
assert(resource instanceof Resource);
23352335

23362336
if (!rules.verifyName(name))
23372337
throw new Error('Invalid name.');
23382338

2339+
if (acct != null) {
2340+
assert((acct >>> 0) === acct || typeof acct === 'string');
2341+
acct = await this.getAccountIndex(acct);
2342+
}
2343+
23392344
const rawName = Buffer.from(name, 'ascii');
23402345
const nameHash = rules.hashName(rawName);
23412346
const ns = await this.getNameState(nameHash);
@@ -2351,6 +2356,9 @@ class Wallet extends EventEmitter {
23512356
if (!coin)
23522357
throw new Error(`Wallet does not own: "${name}".`);
23532358

2359+
if (acct != null && !await this.txdb.hasCoinByAccount(acct, hash, index))
2360+
throw new Error(`Account does not own: "${name}".`);
2361+
23542362
if (coin.covenant.isReveal() || coin.covenant.isClaim())
23552363
return this._makeRegister(name, resource);
23562364

@@ -2403,7 +2411,8 @@ class Wallet extends EventEmitter {
24032411
*/
24042412

24052413
async _createUpdate(name, resource, options) {
2406-
const mtx = await this.makeUpdate(name, resource);
2414+
const acct = options ? options.account : null;
2415+
const mtx = await this.makeUpdate(name, resource, acct);
24072416
await this.fill(mtx, options);
24082417
return this.finalize(mtx, options);
24092418
}
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
/* eslint-env mocha */
2+
/* eslint prefer-arrow-callback: "off" */
3+
4+
'use strict';
5+
6+
const assert = require('bsert');
7+
const Network = require('../lib/protocol/network');
8+
const FullNode = require('../lib/node/fullnode');
9+
const Address = require('../lib/primitives/address');
10+
const rules = require('../lib/covenants/rules');
11+
const Resource = require('../lib/dns/resource');
12+
const {WalletClient} = require('hs-client');
13+
14+
const network = Network.get('regtest');
15+
16+
const node = new FullNode({
17+
memory: true,
18+
network: 'regtest',
19+
plugins: [require('../lib/wallet/plugin')]
20+
});
21+
22+
// Prevent mempool from sending duplicate TXs back to the walletDB and txdb.
23+
// This will prevent a race condition when we need to remove spent (but
24+
// unconfirmed) outputs from the wallet so they can be reused in other tests.
25+
node.mempool.emit = () => {};
26+
27+
const wclient = new WalletClient({
28+
port: network.walletPort
29+
});
30+
31+
const {wdb} = node.require('walletdb');
32+
33+
const name = rules.grindName(5, 1, network);
34+
let wallet, alice, bob, aliceReceive, bobReceive;
35+
36+
async function mineBlocks(n, addr) {
37+
addr = addr ? addr : new Address().toString('regtest');
38+
for (let i = 0; i < n; i++) {
39+
const block = await node.miner.mineBlock(null, addr);
40+
await node.chain.add(block);
41+
}
42+
}
43+
44+
describe('Multiple accounts participating in same auction', function() {
45+
before(async () => {
46+
await node.open();
47+
await wclient.open();
48+
49+
wallet = await wdb.create();
50+
51+
// We'll use an account number for alice and a string for bob
52+
// to ensure that both types work as options.
53+
alice = await wallet.getAccount(0);
54+
bob = await wallet.createAccount({name: 'bob'});
55+
56+
aliceReceive = await alice.receiveAddress();
57+
bobReceive = await bob.receiveAddress();
58+
});
59+
60+
after(async () => {
61+
await wclient.close();
62+
await node.close();
63+
});
64+
65+
it('should fund both accounts', async () => {
66+
await mineBlocks(2, aliceReceive);
67+
await mineBlocks(2, bobReceive);
68+
69+
// Wallet rescan is an effective way to ensure that
70+
// wallet and chain are synced before proceeding.
71+
await wdb.rescan(0);
72+
73+
const aliceBal = await wallet.getBalance(0);
74+
const bobBal = await wallet.getBalance('bob');
75+
assert(aliceBal.confirmed === 2000 * 2 * 1e6);
76+
assert(bobBal.confirmed === 2000 * 2 * 1e6);
77+
});
78+
79+
it('should open an auction and proceed to REVEAL phase', async () => {
80+
await wallet.sendOpen(name, false, {account: 0});
81+
await mineBlocks(network.names.treeInterval + 2);
82+
let ns = await node.chain.db.getNameStateByName(name);
83+
assert(ns.isBidding(node.chain.height, network));
84+
85+
await wdb.rescan(0);
86+
87+
await wallet.sendBid(name, 100000, 200000, {account: 0});
88+
await wallet.sendBid(name, 50000, 200000, {account: 'bob'});
89+
await mineBlocks(network.names.biddingPeriod);
90+
ns = await node.chain.db.getNameStateByName(name);
91+
assert(ns.isReveal(node.chain.height, network));
92+
93+
await wdb.rescan(0);
94+
95+
const walletBids = await wallet.getBidsByName(name);
96+
assert.strictEqual(walletBids.length, 2);
97+
98+
for (const bid of walletBids)
99+
assert(bid.own);
100+
101+
assert.strictEqual(node.mempool.map.size, 0);
102+
});
103+
104+
describe('REVEAL', function() {
105+
describe('Library methods', function() {
106+
it('one tx per account', async () => {
107+
const tx1 = await wallet.sendReveal(name, {account: 0});
108+
assert(tx1);
109+
110+
const tx2 = await wallet.sendReveal(name, {account: 'bob'});
111+
assert(tx2);
112+
113+
// Reset for next test
114+
await wallet.abandon(tx1.hash());
115+
await wallet.abandon(tx2.hash());
116+
117+
assert.strictEqual(node.mempool.map.size, 2);
118+
await node.mempool.reset();
119+
assert.strictEqual(node.mempool.map.size, 0);
120+
});
121+
122+
it('all accounts in one tx', async () => {
123+
const tx = await wallet.sendRevealAll();
124+
assert(tx);
125+
126+
// Reset for next test
127+
await wallet.abandon(tx.hash());
128+
129+
assert.strictEqual(node.mempool.map.size, 1);
130+
await node.mempool.reset();
131+
assert.strictEqual(node.mempool.map.size, 0);
132+
});
133+
});
134+
135+
describe('HTTP API', function () {
136+
it('one tx per account', async () => {
137+
const tx1 = await wclient.post(`/wallet/${wallet.id}/reveal`, {
138+
name: name,
139+
account: 'default'
140+
});
141+
assert(tx1);
142+
143+
const tx2 = await wclient.post(`/wallet/${wallet.id}/reveal`, {
144+
name: name,
145+
account: 'bob'
146+
});
147+
assert(tx2);
148+
149+
// Reset for next test
150+
await wallet.abandon(Buffer.from(tx1.hash, 'hex'));
151+
await wallet.abandon(Buffer.from(tx2.hash, 'hex'));
152+
153+
assert.strictEqual(node.mempool.map.size, 2);
154+
await node.mempool.reset();
155+
assert.strictEqual(node.mempool.map.size, 0);
156+
});
157+
158+
it('all accounts in one tx', async () => {
159+
const tx = await wclient.post(`/wallet/${wallet.id}/reveal`, {
160+
name: name
161+
});
162+
assert(tx);
163+
164+
// Reset for next test
165+
await wallet.abandon(Buffer.from(tx.hash, 'hex'));
166+
167+
assert.strictEqual(node.mempool.map.size, 1);
168+
await node.mempool.reset();
169+
assert.strictEqual(node.mempool.map.size, 0);
170+
});
171+
});
172+
173+
describe('RPC API', function() {
174+
it('one tx per account', async () => {
175+
await wclient.execute('selectwallet', [wallet.id]);
176+
177+
const tx1 = await wclient.execute('sendreveal', [name, 'default']);
178+
assert(tx1);
179+
180+
const tx2 = await wclient.execute('sendreveal', [name, 'bob']);
181+
assert(tx2);
182+
183+
// Reset for next test
184+
await wallet.abandon(Buffer.from(tx1.hash, 'hex'));
185+
await wallet.abandon(Buffer.from(tx2.hash, 'hex'));
186+
187+
assert.strictEqual(node.mempool.map.size, 2);
188+
await node.mempool.reset();
189+
assert.strictEqual(node.mempool.map.size, 0);
190+
});
191+
192+
it('all accounts in one tx', async () => {
193+
const tx = await wclient.execute('sendreveal', [name]);
194+
assert(tx);
195+
196+
// Do not reset for next test, time to move on to REGISTER
197+
});
198+
});
199+
});
200+
201+
describe('UPDATE', function() {
202+
const aliceResource = Resource.fromJSON({text: ['ALICE']});
203+
const bobResource = Resource.fromJSON({text: ['BOB']});
204+
205+
it('should advance auction to REGISTER phase', async () => {
206+
await mineBlocks(network.names.revealPeriod);
207+
const ns = await node.chain.db.getNameStateByName(name);
208+
assert(ns.isClosed(node.chain.height, network));
209+
210+
await wdb.rescan(0);
211+
212+
// Alice is the winner
213+
const {hash, index} = ns.owner;
214+
assert(await wallet.txdb.hasCoinByAccount(0, hash, index));
215+
216+
// ...not Bob (sanity check)
217+
assert(!await wallet.txdb.hasCoinByAccount(1, hash, index));
218+
});
219+
220+
describe('Library methods', function() {
221+
it('reject from wrongly specified account', async () => {
222+
await assert.rejects(async () => {
223+
await wallet.sendUpdate(name, bobResource, {account: 'bob'});
224+
}, {
225+
name: 'Error',
226+
message: `Account does not own: "${name}".`
227+
});
228+
});
229+
230+
it('send from correctly specified account', async () => {
231+
const tx = await wallet.sendUpdate(name, aliceResource, {account: 0});
232+
assert(tx);
233+
234+
await wallet.abandon(tx.hash());
235+
236+
assert.strictEqual(node.mempool.map.size, 1);
237+
await node.mempool.reset();
238+
assert.strictEqual(node.mempool.map.size, 0);
239+
});
240+
241+
it('send from correct account automatically', async () => {
242+
const tx = await wallet.sendUpdate(name, aliceResource);
243+
assert(tx);
244+
245+
await wallet.abandon(tx.hash());
246+
247+
assert.strictEqual(node.mempool.map.size, 1);
248+
await node.mempool.reset();
249+
assert.strictEqual(node.mempool.map.size, 0);
250+
});
251+
});
252+
253+
describe('HTTP API', function () {
254+
it('reject from wrongly specified account', async () => {
255+
await assert.rejects(async () => {
256+
await wclient.post(`wallet/${wallet.id}/update`, {
257+
name: name,
258+
data: bobResource,
259+
account: 'bob'
260+
});
261+
}, {
262+
name: 'Error',
263+
message: `Account does not own: "${name}".`
264+
});
265+
});
266+
267+
it('send from correctly specified account', async () => {
268+
const tx = await wclient.post(`wallet/${wallet.id}/update`, {
269+
name: name,
270+
data: aliceResource,
271+
account: 'default'
272+
});
273+
assert(tx);
274+
275+
await wallet.abandon(Buffer.from(tx.hash, 'hex'));
276+
277+
assert.strictEqual(node.mempool.map.size, 1);
278+
await node.mempool.reset();
279+
assert.strictEqual(node.mempool.map.size, 0);
280+
});
281+
282+
it('send from correct account automatically', async () => {
283+
const tx = await wclient.post(`wallet/${wallet.id}/update`, {
284+
name: name,
285+
data: aliceResource
286+
});
287+
assert(tx);
288+
289+
await wallet.abandon(Buffer.from(tx.hash, 'hex'));
290+
291+
assert.strictEqual(node.mempool.map.size, 1);
292+
await node.mempool.reset();
293+
assert.strictEqual(node.mempool.map.size, 0);
294+
});
295+
});
296+
297+
describe('RPC API', function() {
298+
it('reject from wrongly specified account', async () => {
299+
await wclient.execute('selectwallet', [wallet.id]);
300+
301+
await assert.rejects(async () => {
302+
await wclient.execute('sendupdate', [
303+
name,
304+
bobResource,
305+
'bob'
306+
]);
307+
}, {
308+
name: 'Error',
309+
message: `Account does not own: "${name}".`
310+
});
311+
});
312+
313+
it('send from correctly specified account', async () => {
314+
const tx = await wclient.execute('sendupdate', [
315+
name,
316+
aliceResource,
317+
'default'
318+
]);
319+
assert(tx);
320+
321+
await wallet.abandon(Buffer.from(tx.hash, 'hex'));
322+
323+
assert.strictEqual(node.mempool.map.size, 1);
324+
await node.mempool.reset();
325+
assert.strictEqual(node.mempool.map.size, 0);
326+
});
327+
328+
it('send from correct account automatically', async () => {
329+
const tx = await wclient.execute('sendupdate', [
330+
name,
331+
aliceResource
332+
]);
333+
assert(tx);
334+
335+
await wallet.abandon(Buffer.from(tx.hash, 'hex'));
336+
337+
assert.strictEqual(node.mempool.map.size, 1);
338+
await node.mempool.reset();
339+
assert.strictEqual(node.mempool.map.size, 0);
340+
});
341+
});
342+
});
343+
});

0 commit comments

Comments
 (0)