-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Wallet] Fix web3 syncing sagas #809
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,13 @@ | ||
import { expectSaga } from 'redux-saga-test-plan' | ||
import { call, delay, select } from 'redux-saga/effects' | ||
import { pincodeSelector } from 'src/account/reducer' | ||
import { waitForGethConnectivity } from 'src/geth/saga' | ||
import { navigateToError } from 'src/navigator/NavigationService' | ||
import { setLatestBlockNumber, updateWeb3SyncProgress } from 'src/web3/actions' | ||
import { | ||
getLatestBlock, | ||
setLatestBlockNumber, | ||
setSyncProgress, | ||
updateWeb3SyncProgress, | ||
} from 'src/web3/actions' | ||
import { | ||
_CHECK_SYNC_PROGRESS_TIMEOUT, | ||
_checkWeb3SyncProgressClaim, | ||
checkWeb3Sync, | ||
checkWeb3SyncProgress, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martinvol I do like the convention of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, no problem. Disabling VS code import organizing might not be a bad a idea since the pre-commit hook also sorts imports. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah but the way it de-dupes imports for you and removes unused ones is really nice |
||
createNewAccount, | ||
SYNC_TIMEOUT, | ||
waitForWeb3Sync, | ||
} from 'src/web3/saga' | ||
import { currentAccountSelector } from 'src/web3/selectors' | ||
import { createMockStore, sleep } from 'test/utils' | ||
|
@@ -37,6 +31,7 @@ jest.mock('src/web3/contracts', () => ({ | |
.fn() | ||
.mockReturnValueOnce({ startingBlock: 0, currentBlock: 10, highestBlock: 100 }) | ||
.mockReturnValueOnce(false), | ||
getBlock: jest.fn(() => ({ number: LAST_BLOCK_NUMBER })), | ||
}, | ||
}, | ||
})) | ||
|
@@ -66,40 +61,37 @@ describe(createNewAccount, () => { | |
}) | ||
}) | ||
|
||
describe(checkWeb3Sync, () => { | ||
describe(waitForWeb3Sync, () => { | ||
it('reports connection successfully', async () => { | ||
await expectSaga(checkWeb3Sync) | ||
await expectSaga(waitForWeb3Sync) | ||
.withState(state) | ||
.provide([ | ||
[call(waitForGethConnectivity), true], | ||
// needs this async function to win the race with a delay | ||
[call(_checkWeb3SyncProgressClaim), call(async () => true)], | ||
[call(getLatestBlock), { number: LAST_BLOCK_NUMBER }], | ||
[call(checkWeb3SyncProgress), call(async () => true)], | ||
]) | ||
.put(setLatestBlockNumber(LAST_BLOCK_NUMBER)) | ||
.returns(true) | ||
.run() | ||
}) | ||
|
||
it('times out', async () => { | ||
await expectSaga(checkWeb3Sync) | ||
await expectSaga(waitForWeb3Sync) | ||
.withState(state) | ||
.provide([ | ||
[call(waitForGethConnectivity), true], | ||
[call(_checkWeb3SyncProgressClaim), call(sleep, 100)], // sleep so timeout always wins the race | ||
[delay(_CHECK_SYNC_PROGRESS_TIMEOUT), true], | ||
[call(getLatestBlock), { number: LAST_BLOCK_NUMBER }], | ||
[call(checkWeb3SyncProgress), call(sleep, 100)], // sleep so timeout always wins the race | ||
[delay(SYNC_TIMEOUT), true], | ||
]) | ||
.returns(false) | ||
.run() | ||
expect(navigateToError).toHaveBeenCalled() | ||
}) | ||
}) | ||
|
||
describe(_checkWeb3SyncProgressClaim, () => { | ||
describe(checkWeb3SyncProgress, () => { | ||
it('reports web3 status correctly', async () => { | ||
await expectSaga(_checkWeb3SyncProgressClaim) | ||
await expectSaga(checkWeb3SyncProgress) | ||
.withState(state) | ||
.put(updateWeb3SyncProgress({ startingBlock: 0, currentBlock: 10, highestBlock: 100 })) // is syncing the first time | ||
.put(setSyncProgress(100)) // finished syncing the second time | ||
.put(setLatestBlockNumber(LAST_BLOCK_NUMBER)) // finished syncing the second time | ||
.returns(true) | ||
.run() | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,6 @@ import { currentLanguageSelector } from 'src/app/reducers' | |
import { getWordlist } from 'src/backup/utils' | ||
import { UNLOCK_DURATION } from 'src/geth/consts' | ||
import { deleteChainData } from 'src/geth/geth' | ||
import { waitForGethConnectivity } from 'src/geth/saga' | ||
import { navigateToError } from 'src/navigator/NavigationService' | ||
import { waitWeb3LastBlock } from 'src/networkInfo/saga' | ||
import Logger from 'src/utils/Logger' | ||
|
@@ -23,7 +22,6 @@ import { | |
setAccount, | ||
setLatestBlockNumber, | ||
setPrivateCommentKey, | ||
setSyncProgress, | ||
unlockAccount, | ||
updateWeb3SyncProgress, | ||
} from 'src/web3/actions' | ||
|
@@ -36,68 +34,67 @@ const MNEMONIC_BIT_LENGTH = (ETH_PRIVATE_KEY_LENGTH * 8) / 2 | |
|
||
const TAG = 'web3/saga' | ||
// The timeout for web3 to complete syncing and the latestBlock to be > 0 | ||
const CHECK_SYNC_PROGRESS_TIMEOUT = 60000 | ||
export const SYNC_TIMEOUT = 60000 | ||
martinvol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const BLOCK_CHAIN_CORRUPTION_ERROR = "Error: CONNECTION ERROR: Couldn't connect to node on IPC." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remove this to a json as nice to have? |
||
|
||
// checks if web3 claims it is currently syncing or not | ||
function* checkWeb3SyncProgressClaim() { | ||
// checks if web3 claims it is currently syncing and attempts to wait for it to complete | ||
export function* checkWeb3SyncProgress() { | ||
while (true) { | ||
try { | ||
const syncProgress = yield web3.eth.isSyncing() // returns true when it's still syncing and thus not ready | ||
if (typeof syncProgress === 'boolean' && !syncProgress) { | ||
Logger.debug(TAG, 'checkWeb3SyncProgressClaim', 'sync complete') | ||
Logger.debug(TAG, 'checkWeb3SyncProgress', 'Checking sync progress') | ||
|
||
// isSyncing returns a syncProgress object when it's still syncing, false otherwise | ||
const syncProgress = yield web3.eth.isSyncing() | ||
|
||
yield put(setSyncProgress(100)) | ||
return true | ||
if (typeof syncProgress === 'boolean' && !syncProgress) { | ||
Logger.debug(TAG, 'checkWeb3SyncProgress', 'Sync maybe complete, checking') | ||
|
||
const latestBlock: Block = yield call(getLatestBlock) | ||
martinvol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (latestBlock && latestBlock.number > 0) { | ||
yield put(setLatestBlockNumber(latestBlock.number)) | ||
Logger.debug(TAG, 'checkWeb3SyncProgress', 'Sync is complete') | ||
return true | ||
} else { | ||
Logger.debug(TAG, 'checkWeb3SyncProgress', 'Sync not actually complete, still waiting') | ||
} | ||
} else { | ||
yield put(updateWeb3SyncProgress(syncProgress)) | ||
} | ||
Logger.debug(TAG, 'checkWeb3SyncProgressClaim', 'sync in progress') | ||
yield put(updateWeb3SyncProgress(syncProgress)) | ||
// not ready yet, keep looping | ||
|
||
yield delay(100) // wait 100ms while web3 syncs | ||
} catch (error) { | ||
if (error.toString().toLowerCase() === BLOCK_CHAIN_CORRUPTION_ERROR.toLowerCase()) { | ||
CeloAnalytics.track(CustomEventNames.blockChainCorruption, {}, true) | ||
const deleted = yield call(deleteChainData) | ||
if (deleted) { | ||
navigateToError('corruptedChainDeleted') | ||
} | ||
throw new Error('Corrupted chain data encountered') | ||
} else { | ||
Logger.error(TAG, `checking web3 sync progress: ${error}`) | ||
Logger.error(TAG, 'Unexpected sync error', error) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martinvol it's better to provide the error as the 3rd param to Logger.error, which has special logic to extract and format the error message nicely |
||
} | ||
return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martinvol previously, you were not throwing or returning false in this case, which allowed the calling function to finish as though syncing were done. |
||
} | ||
} | ||
} | ||
|
||
// The worker listening to sync progress requests | ||
export function* checkWeb3Sync() { | ||
export function* waitForWeb3Sync() { | ||
try { | ||
yield call(waitForGethConnectivity) | ||
try { | ||
const { timeout } = yield race({ | ||
checkProgress: call(checkWeb3SyncProgressClaim), | ||
timeout: delay(CHECK_SYNC_PROGRESS_TIMEOUT), | ||
}) | ||
|
||
if (timeout) { | ||
Logger.error(TAG, 'Could not complete sync progress check') | ||
navigateToError('web3FailedToSync') | ||
} | ||
|
||
const latestBlock: Block = yield call(getLatestBlock) | ||
if (latestBlock && latestBlock.number > 0) { | ||
yield put(setLatestBlockNumber(latestBlock.number)) | ||
} else { | ||
Logger.error( | ||
TAG, | ||
`web3 indicated sync complete, yet the latest block is ${JSON.stringify(latestBlock)}` | ||
) | ||
} | ||
} catch (error) { | ||
Logger.error(TAG, 'checkWeb3Sync', error) | ||
navigateToError('errorDuringSync') | ||
const { syncComplete, timeout } = yield race({ | ||
syncComplete: call(checkWeb3SyncProgress), | ||
timeout: delay(SYNC_TIMEOUT), | ||
}) | ||
|
||
if (timeout || !syncComplete) { | ||
Logger.error(TAG, 'Could not complete sync') | ||
navigateToError('web3FailedToSync') | ||
return false | ||
} | ||
|
||
return true | ||
} catch (error) { | ||
Logger.error(TAG, 'checkWeb3Sync saga error', error) | ||
Logger.error(TAG, 'checkWeb3Sync', error) | ||
navigateToError('errorDuringSync') | ||
return false | ||
} | ||
} | ||
|
||
|
@@ -218,7 +215,3 @@ export function* getConnectedUnlockedAccount() { | |
throw new Error(ErrorMessages.INCORRECT_PIN) | ||
} | ||
} | ||
|
||
// exported for testing | ||
export const _checkWeb3SyncProgressClaim = checkWeb3SyncProgressClaim | ||
export const _CHECK_SYNC_PROGRESS_TIMEOUT = CHECK_SYNC_PROGRESS_TIMEOUT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmcewen IIRC you recommended calling this
check
butwait
is what it's really doing. Mind if we rename?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Volpe and I had a discussion about this - the reason I don't like using wait is that it actually makes a call to get the latest block to the geth node. Since I think we treat the
waitFor
generators as free in terms of cost, I would be wary of someone just adding this elsewhere and we end up doing a bunch of duplicate polling. I thinkwaitFor
should imply solely waiting on state changes.