Bug #747: Don't use getLastTxNonce to fetch safe nonce (#750)

* don't use getLastTxNonce to fetch safe nonce

* fetch safe nonce in checkAndUpdateSafe

* checkAndUpdateSafe refactor

* remove nonce update logic from UPDATE_SAFE reducer

* handle the case when localSafe returns undefined

* handle the case when localSafe returns undefined in buildTransactionFrom

* bump package json version to 1.9.4
This commit is contained in:
Mikhail Mikheev 2020-04-09 17:58:58 +04:00 committed by GitHub
parent b0849ee97f
commit b1f50c7f3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 65 additions and 772 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "safe-react", "name": "safe-react",
"version": "1.9.3", "version": "1.9.4",
"description": "Allowing crypto users manage funds in a safer way", "description": "Allowing crypto users manage funds in a safer way",
"homepage": "https://github.com/gnosis/safe-react#readme", "homepage": "https://github.com/gnosis/safe-react#readme",
"bugs": { "bugs": {

View File

@ -29,7 +29,7 @@ export type Actions = {
fetchLatestMasterContractVersion: typeof fetchLatestMasterContractVersion, fetchLatestMasterContractVersion: typeof fetchLatestMasterContractVersion,
activateTokensByBalance: typeof activateTokensByBalance, activateTokensByBalance: typeof activateTokensByBalance,
activateAssetsByBalance: typeof activateAssetsByBalance, activateAssetsByBalance: typeof activateAssetsByBalance,
checkAndUpdateSafeOwners: typeof checkAndUpdateSafe, checkAndUpdateSafe: typeof checkAndUpdateSafe,
fetchCurrencyValues: typeof fetchCurrencyValues, fetchCurrencyValues: typeof fetchCurrencyValues,
loadAddressBook: typeof loadAddressBookFromStorage, loadAddressBook: typeof loadAddressBookFromStorage,
updateAddressBookEntry: typeof updateAddressBookEntry, updateAddressBookEntry: typeof updateAddressBookEntry,
@ -50,7 +50,7 @@ export default {
fetchEtherBalance, fetchEtherBalance,
fetchLatestMasterContractVersion, fetchLatestMasterContractVersion,
fetchCurrencyValues, fetchCurrencyValues,
checkAndUpdateSafeOwners: checkAndUpdateSafe, checkAndUpdateSafe,
loadAddressBook: loadAddressBookFromStorage, loadAddressBook: loadAddressBookFromStorage,
updateAddressBookEntry, updateAddressBookEntry,
addViewedSafe, addViewedSafe,

View File

@ -121,14 +121,14 @@ class SafeView extends React.Component<Props, State> {
checkForUpdates() { checkForUpdates() {
const { const {
activeTokens, activeTokens,
checkAndUpdateSafeOwners, checkAndUpdateSafe,
fetchEtherBalance, fetchEtherBalance,
fetchTokenBalances, fetchTokenBalances,
fetchTransactions, fetchTransactions,
safe, safe,
safeUrl, safeUrl,
} = this.props } = this.props
checkAndUpdateSafeOwners(safeUrl) checkAndUpdateSafe(safeUrl)
fetchTokenBalances(safeUrl, activeTokens) fetchTokenBalances(safeUrl, activeTokens)
fetchEtherBalance(safe) fetchEtherBalance(safe)
fetchTransactions(safeUrl) fetchTransactions(safeUrl)

View File

@ -67,40 +67,47 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: ReduxDis
// Check if the owner's safe did change and update them // Check if the owner's safe did change and update them
const [gnosisSafe, localSafe] = await Promise.all([getGnosisSafeInstanceAt(safeAddress), getLocalSafe(safeAddress)]) const [gnosisSafe, localSafe] = await Promise.all([getGnosisSafeInstanceAt(safeAddress), getLocalSafe(safeAddress)])
const remoteOwners = await gnosisSafe.getOwners() const [remoteOwners, remoteNonce, remoteThreshold] = await Promise.all([
gnosisSafe.getOwners(),
gnosisSafe.nonce(),
gnosisSafe.getThreshold(),
])
// Converts from [ { address, ownerName} ] to address array // Converts from [ { address, ownerName} ] to address array
const localOwners = localSafe.owners.map((localOwner) => localOwner.address) const localOwners = localSafe ? localSafe.owners.map((localOwner) => localOwner.address) : undefined
const localThreshold = localSafe.threshold const localThreshold = localSafe ? localSafe.threshold : undefined
const localNonce = localSafe ? localSafe.nonce : undefined
// Updates threshold values if (localNonce !== remoteNonce.toNumber()) {
const remoteThreshold = await gnosisSafe.getThreshold() dispatch(updateSafe({ address: safeAddress, nonce: remoteNonce.toNumber() }))
localSafe.threshold = remoteThreshold.toNumber() }
if (localThreshold !== remoteThreshold.toNumber()) { if (localThreshold !== remoteThreshold.toNumber()) {
dispatch(updateSafe({ address: safeAddress, threshold: remoteThreshold.toNumber() })) dispatch(updateSafe({ address: safeAddress, threshold: remoteThreshold.toNumber() }))
} }
// If the remote owners does not contain a local address, we remove that local owner // If the remote owners does not contain a local address, we remove that local owner
localOwners.forEach((localAddress) => { if (localOwners) {
const remoteOwnerIndex = remoteOwners.findIndex((remoteAddress) => sameAddress(remoteAddress, localAddress)) localOwners.forEach((localAddress) => {
if (remoteOwnerIndex === -1) { const remoteOwnerIndex = remoteOwners.findIndex((remoteAddress) => sameAddress(remoteAddress, localAddress))
dispatch(removeSafeOwner({ safeAddress, ownerAddress: localAddress })) if (remoteOwnerIndex === -1) {
} dispatch(removeSafeOwner({ safeAddress, ownerAddress: localAddress }))
}) }
})
// If the remote has an owner that we don't have locally, we add it // If the remote has an owner that we don't have locally, we add it
remoteOwners.forEach((remoteAddress) => { remoteOwners.forEach((remoteAddress) => {
const localOwnerIndex = localOwners.findIndex((localAddress) => sameAddress(remoteAddress, localAddress)) const localOwnerIndex = localOwners.findIndex((localAddress) => sameAddress(remoteAddress, localAddress))
if (localOwnerIndex === -1) { if (localOwnerIndex === -1) {
dispatch( dispatch(
addSafeOwner({ addSafeOwner({
safeAddress, safeAddress,
ownerAddress: remoteAddress, ownerAddress: remoteAddress,
ownerName: 'UNKNOWN', ownerName: 'UNKNOWN',
}), }),
) )
} }
}) })
}
} }
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return

View File

@ -25,7 +25,6 @@ import { ZERO_ADDRESS, sameAddress } from '~/logic/wallets/ethAddresses'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { addCancellationTransactions } from '~/routes/safe/store/actions/addCancellationTransactions' import { addCancellationTransactions } from '~/routes/safe/store/actions/addCancellationTransactions'
import updateSafe from '~/routes/safe/store/actions/updateSafe'
import { makeConfirmation } from '~/routes/safe/store/models/confirmation' import { makeConfirmation } from '~/routes/safe/store/models/confirmation'
import { type IncomingTransaction, makeIncomingTransaction } from '~/routes/safe/store/models/incomingTransaction' import { type IncomingTransaction, makeIncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
import { makeOwner } from '~/routes/safe/store/models/owner' import { makeOwner } from '~/routes/safe/store/models/owner'
@ -75,14 +74,14 @@ type IncomingTxServiceModel = {
} }
export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel): Promise<Transaction> => { export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel): Promise<Transaction> => {
const { owners } = await getLocalSafe(safeAddress) const localSafe = await getLocalSafe(safeAddress)
const confirmations = List( const confirmations = List(
tx.confirmations.map((conf: ConfirmationServiceModel) => { tx.confirmations.map((conf: ConfirmationServiceModel) => {
let ownerName = 'UNKNOWN' let ownerName = 'UNKNOWN'
if (owners) { if (localSafe && localSafe.owners) {
const storedOwner = owners.find((owner) => sameAddress(conf.owner, owner.address)) const storedOwner = localSafe.owners.find((owner) => sameAddress(conf.owner, owner.address))
if (storedOwner) { if (storedOwner) {
ownerName = storedOwner.name ownerName = storedOwner.name
@ -349,45 +348,16 @@ export const loadSafeIncomingTransactions = async (safeAddress: string) => {
return Map().set(safeAddress, List(incomingTxsRecord)) return Map().set(safeAddress, List(incomingTxsRecord))
} }
/**
* Returns nonce from the last tx returned by the server or defaults to 0
* @param outgoingTxs
* @returns {number|*}
*/
const getLastTxNonce = (outgoingTxs) => {
if (!outgoingTxs) {
return 0
}
const mostRecentNonce = outgoingTxs.get(0).nonce
// if nonce is null, then we are in front of the creation-tx
if (mostRecentNonce === null) {
const tx = outgoingTxs.get(1)
if (tx) {
// if there's other tx than the creation one, we return its nonce
return tx.nonce
} else {
return 0
}
}
return mostRecentNonce
}
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => { export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
web3 = await getWeb3() web3 = await getWeb3()
const transactions: SafeTransactionsType | undefined = await loadSafeTransactions(safeAddress) const transactions: SafeTransactionsType | undefined = await loadSafeTransactions(safeAddress)
if (transactions) { if (transactions) {
const { cancel, outgoing } = transactions const { cancel, outgoing } = transactions
const nonce = getLastTxNonce(outgoing && outgoing.get(safeAddress))
batch(() => { batch(() => {
dispatch(addCancellationTransactions(cancel)) dispatch(addCancellationTransactions(cancel))
dispatch(addTransactions(outgoing)) dispatch(addTransactions(outgoing))
dispatch(updateSafe({ address: safeAddress, nonce }))
}) })
} }

View File

@ -48,14 +48,6 @@ export default handleActions<SafeReducerState, *>(
[UPDATE_SAFE]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => { [UPDATE_SAFE]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
const safe = action.payload const safe = action.payload
const safeAddress = safe.address const safeAddress = safe.address
const isNonceUpdate = safe.nonce !== undefined
if (isNonceUpdate && safe.nonce <= state.getIn([SAFE_REDUCER_ID, safeAddress, 'nonce'])) {
// update only when nonce is greater than the one already stored
// this will prevent undesired changes in the safe's state when
// txs pagination is implemented
return state
}
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => prevSafe.merge(safe)) return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => prevSafe.merge(safe))
}, },

726
yarn.lock

File diff suppressed because it is too large Load Diff