Skip to content

Commit

Permalink
feat: mark inputs as used (#484)
Browse files Browse the repository at this point in the history
* feat: mark inputs as used
  • Loading branch information
r4mmer authored Sep 23, 2024
1 parent 31d66b1 commit a1698c0
Show file tree
Hide file tree
Showing 11 changed files with 581 additions and 13 deletions.
179 changes: 179 additions & 0 deletions __tests__/mark_utxos_selected_as_input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Storage, Transaction, Input, Output } from '@hathor/wallet-lib';
import TestUtils from './test-utils';

const walletId = 'stub_mark_inputs_as_used';

function createCustomTxHex() {
const txId0 = '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74';
const txId1 = 'fb2fbe0385bc0bc8e9a255a8d530f7b3bdcebcd5ccdae5e154e6c3d57cbcd143';
const txId2 = '11835fae291c60fc58314c61d27dc644b9e029c363bbe458039b2b0186144275';
const tx = new Transaction(
[new Input(txId0, 0), new Input(txId1, 1), new Input(txId2, 2)],
[new Output(100, Buffer.from('0463616665ac', 'hex'))],
{
timestamp: 123,
parents: ['f6c83e3641a08ec21aebc01296ff12f5a46780f0fbadb1c8101309123b95d2c6'],
},
);

return tx.toHex();
}

describe('mark utxos selected_as_input api', () => {
let selectSpy;
const txHex = createCustomTxHex();

beforeAll(async () => {
selectSpy = jest.spyOn(Storage.prototype, 'utxoSelectAsInput');
await TestUtils.startWallet({ walletId });
});

beforeEach(() => {
selectSpy.mockReset();
selectSpy.mockImplementation(jest.fn(async () => {}));
});

afterAll(async () => {
selectSpy.mockRestore();
await TestUtils.stopWallet({ walletId });
});

it('should fail if txHex is not a hex string', async () => {
let response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex: 123 })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);

response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex: '0123g' })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(selectSpy).toHaveBeenCalledTimes(0);
});

it('should fail if mark is not a boolean', async () => {
let response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: '123' })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);

response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: 123 })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);

response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: 'abc' })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should fail if ttl is not a number', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, ttl: '123a' })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});

it('should fail if txHex is an invalid transaction', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex: '0123456789abcdef' })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(false);
expect(selectSpy).toHaveBeenCalledTimes(0);
});

it('should mark the inputs as selected on the storage', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);

expect(selectSpy).toHaveBeenCalledTimes(3);
expect(selectSpy).toHaveBeenCalledWith(
{ index: 0, txId: '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74' },
true,
undefined,
);
});

it('should mark the inputs as selected on the storage with ttl', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, ttl: 123 })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);

expect(selectSpy).toHaveBeenCalledTimes(3);
expect(selectSpy).toHaveBeenCalledWith(
{ index: 0, txId: '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74' },
true,
123,
);
});

it('should mark the inputs as selected on the storage with mark false', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: false })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);

expect(selectSpy).toHaveBeenCalledTimes(3);
expect(selectSpy).toHaveBeenCalledWith(
{ index: 0, txId: '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74' },
false,
undefined,
);
});

it('should mark the inputs as selected on the storage with mark true', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: true })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);

expect(selectSpy).toHaveBeenCalledTimes(3);
expect(selectSpy).toHaveBeenCalledWith(
{ index: 0, txId: '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74' },
true,
undefined,
);
});

it('should mark the inputs as selected on the storage with all options', async () => {
const response = await TestUtils.request
.put('/wallet/utxos-selected-as-input')
.send({ txHex, mark_as_used: false, ttl: 456 })
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);

expect(selectSpy).toHaveBeenCalledTimes(3);
expect(selectSpy).toHaveBeenCalledWith(
{ index: 0, txId: '5db0a8c77f818c51cb107532fc1a36785adfa700d81d973fd1f23438b2f3dd74' },
false,
456,
);
});
});
26 changes: 26 additions & 0 deletions __tests__/p2sh/tx-proposal-create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,30 @@ describe('create tx-proposal api', () => {
expect(response.status).toBe(200);
expect(response.body.success).toBe(false);
});

it('should mark utxos as used when sending mark_inputs_as_used', async () => {
const markSpy = jest.spyOn(hathorLib.Storage.prototype, 'utxoSelectAsInput').mockImplementation(jest.fn(async () => {}));
try {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal')
.send({
outputs: [
{ address: 'WPynsVhyU6nP7RSZAkqfijEutC88KgAyFc', value: 1 },
{ address: 'wcUZ6J7t2B1s8bqRYiyuZAftcdCGRSiiau', value: 1 },
],
mark_inputs_as_used: true,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.txHex).toBeDefined();
expect(response.body.success).toBe(true);
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['WPynsVhyU6nP7RSZAkqfijEutC88KgAyFc', 'wcUZ6J7t2B1s8bqRYiyuZAftcdCGRSiiau']));

expect(markSpy).toHaveBeenCalledTimes(1);
} finally {
markSpy.mockRestore();
}
});
});
25 changes: 25 additions & 0 deletions __tests__/p2sh/tx-proposal-melt-tokens.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,29 @@ describe('melt-tokens tx-proposal api', () => {
});
}
});

it('should mark utxos as used when sending mark_inputs_as_used', async () => {
const markSpy = jest.spyOn(hathorLib.Storage.prototype, 'utxoSelectAsInput').mockImplementation(jest.fn(async () => {}));
try {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/melt-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
mark_inputs_as_used: true,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils
.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG']));

expect(markSpy).toHaveBeenCalledTimes(2);
} finally {
markSpy.mockRestore();
}
});
});
30 changes: 30 additions & 0 deletions __tests__/p2sh/tx-proposal-mint-tokens.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,34 @@ describe('mint-tokens tx-proposal api', () => {
expect(authorityOutputs).toHaveLength(1);
expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MINT);
});

it('should mark utxos as used when sending mark_inputs_as_used', async () => {
const markSpy = jest.spyOn(hathorLib.Storage.prototype, 'utxoSelectAsInput').mockImplementation(jest.fn(async () => {}));
try {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/mint-tokens')
.send({
token: '0000073b972162f70061f61cf0082b7a47263cc1659a05976aca5cd01b3351ee',
amount: 1,
mark_inputs_as_used: true,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils
.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining(['wbe2eJdyZVimA7nJjmBQnKYJSXmpnpMKgG']));
expect(tx.inputs).toEqual(expect.not.arrayContaining([
expect.objectContaining({
data: expect.any(Object),
}),
]));

expect(markSpy).toHaveBeenCalledTimes(2);
} finally {
markSpy.mockRestore();
}
});
});
30 changes: 30 additions & 0 deletions __tests__/p2sh/tx-proposal-new-token.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,34 @@ describe('create-token tx-proposal api', () => {
expect(authorityOutputs[0].value).toBe(AUTHORITY_VALUE.MINT);
expect(authorityOutputs[1].value).toBe(AUTHORITY_VALUE.MELT);
});

it('should mark utxos as used when sending mark_inputs_as_used', async () => {
const markSpy = jest.spyOn(hathorLib.Storage.prototype, 'utxoSelectAsInput').mockImplementation(jest.fn(async () => {}));
try {
const response = await TestUtils.request
.post('/wallet/p2sh/tx-proposal/create-token')
.send({
name: 'My Custom Token',
symbol: 'MCT',
amount: 1,
mark_inputs_as_used: true,
})
.set({ 'x-wallet-id': walletId });
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.txHex).toBeDefined();
const tx = hathorLib.helpersUtils.createTxFromHex(response.body.txHex, new hathorLib.Network('testnet'));
expect(tx.outputs.map(o => o.decodedScript.address.base58))
.toEqual(expect.arrayContaining([TestUtils.multisigAddresses[1]]));
expect(tx.inputs).toEqual(expect.not.arrayContaining([
expect.objectContaining({
data: expect.any(Object),
}),
]));

expect(markSpy).toHaveBeenCalledTimes(1);
} finally {
markSpy.mockRestore();
}
});
});
Loading

0 comments on commit a1698c0

Please sign in to comment.