Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Add additional spend status check #862

Merged
merged 2 commits into from
Jan 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/mobile/src/libs/progressSteps.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
'progressSteps:preparingTransfers',
'progressSteps:gettingTransactionsToApprove',
'progressSteps:proofOfWork',
'progressSteps:validatingTransactionAddresses',
'progressSteps:broadcasting',
],
zeroValueTransaction: [
Expand Down
4 changes: 2 additions & 2 deletions src/shared/__tests__/actions/transfers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ describe('actions: transfers', () => {
});

describe('when transaction is successful', () => {
it('should create eight actions of type IOTA/PROGRESS/SET_NEXT_STEP_AS_ACTIVE', () => {
it('should create nine actions of type IOTA/PROGRESS/SET_NEXT_STEP_AS_ACTIVE', () => {
const wereAddressesSpentFrom = sinon.stub(iota.api, 'wereAddressesSpentFrom').yields(null, [false]);
const store = mockStore({ accounts });

Expand Down Expand Up @@ -425,7 +425,7 @@ describe('actions: transfers', () => {
.getActions()
.map((action) => action.type)
.filter((type) => type === 'IOTA/PROGRESS/SET_NEXT_STEP_AS_ACTIVE').length,
).to.equal(8);
).to.equal(9);

syncAccountAfterSpending.restore();
syncAccount.restore();
Expand Down
107 changes: 107 additions & 0 deletions src/shared/__tests__/libs/iota/addresses.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1467,4 +1467,111 @@ describe('libs: iota/addresses', () => {
expect(result).to.eql([true, false, false]);
});
});

describe('#isAnyAddressSpent', () => {
let addresses;

before(() => {
addresses = map(['U', 'A', 'S', '9'], (char) => char.repeat(81));
});

describe('when all addresses are unspent', () => {
beforeEach(() => {
nock('http://localhost:14265', {
reqheaders: {
'Content-Type': 'application/json',
'X-IOTA-API-Version': IRI_API_VERSION,
},
})
.filteringRequestBody(() => '*')
.persist()
.post('/', '*')
.reply(200, (_, body) => {
const { addresses, command } = body;

if (command === 'wereAddressesSpentFrom') {
return { states: map(addresses, () => false) };
}

return {};
});
});

afterEach(() => {
nock.cleanAll();
});

it('should return false', () => {
return addressesUtils
.isAnyAddressSpent()(addresses)
.then((isSpent) => expect(isSpent).to.equal(false));
});
});

describe('when all addresses are spent', () => {
beforeEach(() => {
nock('http://localhost:14265', {
reqheaders: {
'Content-Type': 'application/json',
'X-IOTA-API-Version': IRI_API_VERSION,
},
})
.filteringRequestBody(() => '*')
.persist()
.post('/', '*')
.reply(200, (_, body) => {
const { addresses, command } = body;

if (command === 'wereAddressesSpentFrom') {
return { states: map(addresses, () => true) };
}

return {};
});
});

afterEach(() => {
nock.cleanAll();
});

it('should return true', () => {
return addressesUtils
.isAnyAddressSpent()(addresses)
.then((isSpent) => expect(isSpent).to.equal(true));
});
});

describe('when some addresses are spent', () => {
beforeEach(() => {
nock('http://localhost:14265', {
reqheaders: {
'Content-Type': 'application/json',
'X-IOTA-API-Version': IRI_API_VERSION,
},
})
.filteringRequestBody(() => '*')
.persist()
.post('/', '*')
.reply(200, (_, body) => {
const { addresses, command } = body;

if (command === 'wereAddressesSpentFrom') {
return { states: map(addresses, (_, idx) => idx % 2 === 0) };
}

return {};
});
});

afterEach(() => {
nock.cleanAll();
});

it('should return true', () => {
return addressesUtils
.isAnyAddressSpent()(addresses)
.then((isSpent) => expect(isSpent).to.equal(true));
});
});
});
});
45 changes: 31 additions & 14 deletions src/shared/actions/transfers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import some from 'lodash/some';
import size from 'lodash/size';
import every from 'lodash/every';
import includes from 'lodash/includes';
import uniq from 'lodash/uniq';
import { iota } from '../libs/iota';
import {
replayBundleAsync,
Expand Down Expand Up @@ -55,11 +56,7 @@ import {
markBundleBroadcastStatusComplete,
markBundleBroadcastStatusPending,
} from './accounts';
import {
shouldAllowSendingToAddress,
getAddressesUptoRemainder,
categoriseAddressesBySpentStatus,
} from '../libs/iota/addresses';
import { isAnyAddressSpent, getAddressesUptoRemainder, categoriseAddressesBySpentStatus } from '../libs/iota/addresses';
import { getStartingSearchIndexToPrepareInputs, getUnspentInputs } from '../libs/iota/inputs';
import {
generateAlert,
Expand Down Expand Up @@ -434,17 +431,17 @@ export const makeTransaction = (seedStore, receiveAddress, value, message, accou
// Progressbar step => (Validating receive address)
dispatch(setNextStepAsActive());

// Make sure that the address a user is about to send to is not already used.
return shouldAllowSendingToAddress()([address])
.then((shouldAllowSending) => {
if (shouldAllowSending) {
// Progressbar step => (Syncing account)
dispatch(setNextStepAsActive());

return syncAccount()(accountState, seedStore);
// Make sure that the address a user is about to send to is not already spent.
return isAnyAddressSpent()([address])
.then((isSpent) => {
if (isSpent) {
throw new Error(Errors.KEY_REUSE);
}

throw new Error(Errors.KEY_REUSE);
// Progressbar step => (Syncing account)
dispatch(setNextStepAsActive());

return syncAccount()(accountState, seedStore);
})
.then((newState) => {
// Assign latest account but do not update the local store yet.
Expand Down Expand Up @@ -653,6 +650,26 @@ export const makeTransaction = (seedStore, receiveAddress, value, message, accou
});
});
})
// Re-check spend statuses of all addresses in bundle
.then(({ trytes, transactionObjects }) => {
// Skip this check if it's a zero value transaction
if (isZeroValue) {
return Promise.resolve({ trytes, transactionObjects });
}

// Progressbar step => (Validating transaction addresses)
dispatch(setNextStepAsActive());

const addresses = uniq(map(transactionObjects, (transaction) => transaction.address));

return isAnyAddressSpent()(addresses).then((isSpent) => {
if (isSpent) {
throw new Error(Errors.KEY_REUSE);
}

return { trytes, transactionObjects };
});
})
.then(({ trytes, transactionObjects }) => {
cached.trytes = trytes;
cached.transactionObjects = transactionObjects;
Expand Down
1 change: 1 addition & 0 deletions src/shared/containers/wallet/Send.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default function withSendData(SendComponent) {
t('progressSteps:preparingTransfers'),
t('progressSteps:gettingTransactionsToApprove'),
t('progressSteps:proofOfWork'),
t('progressSteps:validatingTransactionAddresses'),
t('progressSteps:broadcasting'),
];

Expand Down
14 changes: 6 additions & 8 deletions src/shared/libs/iota/addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,19 +405,17 @@ export const filterSpentAddresses = (provider) => (inputs, addressData, normalis
};

/**
* Communicates with ledger and checks if the addresses are spent from.
* Communicates with ledger and checks if the any address is spent.
*
* @method shouldAllowSendingToAddress
* @method isAnyAddressSpent
* @param {string} [provider]
*
* @returns {function(array): Promise<boolean>}
**/
export const shouldAllowSendingToAddress = (provider) => (addresses) => {
return wereAddressesSpentFromAsync(provider)(addresses).then((wereSpent) => {
const spentAddresses = filter(addresses, (address, idx) => wereSpent[idx]);

return !spentAddresses.length;
});
export const isAnyAddressSpent = (provider) => (addresses) => {
return wereAddressesSpentFromAsync(provider)(addresses).then((spendStatuses) =>
some(spendStatuses, (spendStatus) => spendStatus === true),
);
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/shared/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@
"progressSteps": {
"checkingNodeHealth": "Checking node health",
"validatingReceiveAddress": "Validating receive address",
"validatingTransactionAddresses": "Validating transaction addresses",
"syncingAccount": "Syncing account",
"preparingInputs": "Preparing inputs",
"preparingTransfers": "Preparing transfers",
Expand Down