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

Commit

Permalink
Merge pull request #357 from keep-network/uncaught-errors
Browse files Browse the repository at this point in the history
Catch moar errors

This adds error handling to the deposit restoreState saga. Before recent tbtc.js
work, a UTXO error would occur occasionally and sometimes not surface to
the UI. This was due to the error arising during the restoreState saga in the
finalCalls logic, which was not wrapped in any try/catch blocks.

In addition to adding error handling to restoreState, this removes the finalCalls logic
and moves that to the onStateRestored saga.

Any error caused by state restoration is now tracked in the redux state and is
shown either in the <Loadable> component or any relevant view that is wrapped
in <Loadable>. We pass the error to the child views, because the view is revealed
before the restoreState saga totally completes.
  • Loading branch information
Shadowfiend authored Sep 17, 2020
2 parents 2743831 + dd894b0 commit 008e702
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 98 deletions.
3 changes: 2 additions & 1 deletion src/components/deposit/Confirming.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ const mapStateToProps = ({
btcConfirmingError,
requiredConfirmations,
confirmations,
stateRestorationError,
},
}) => {
return {
signerFee: formatSatsToBtc(signerFeeInSatoshis),
error: btcConfirmingError,
error: btcConfirmingError || stateRestorationError,
requiredConfirmations,
confirmations,
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/deposit/GetAddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const GetAddress = ({ status, error }) => {
</div>
<div className="page-body">
<div className="step">Step 2/5</div>
<div className="title">Initiating deposit</div>
<div className="title">
{error ? "Error initiating deposit" : "Initiating deposit"}
</div>
<hr />
<Description error={error}>{statusText}</Description>
</div>
Expand All @@ -36,7 +38,7 @@ GetAddress.propTypes = {
const mapStateToProps = (state, ownProps) => {
return {
status: state.deposit.invoiceStatus,
error: state.deposit.btcAddressError,
error: state.deposit.btcAddressError || state.deposit.stateRestorationError,
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/deposit/Pay.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ const mapStateToProps = (state) => {
lotInSatoshis,
signerFeeInSatoshis,
btcTxError,
stateRestorationError,
} = state.deposit

return {
btcAddress,
btcAmount: formatSatsToBtc(lotInSatoshis),
signerFee: formatSatsToBtc(signerFeeInSatoshis),
error: btcTxError,
error: btcTxError || stateRestorationError,
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/deposit/Prove.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ ProveComponent.propTypes = {
const mapStateToProps = (state, ownProps) => {
return {
provingDeposit: state.deposit.provingDeposit,
error: state.deposit.proveDepositError,
error:
state.deposit.proveDepositError || state.deposit.stateRestorationError,
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/redemption/Confirming.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Confirming.propTypes = {
const mapStateToProps = (state) => {
return {
txHash: state.redemption.txHash,
error: state.redemption.confirmationError,
error:
state.redemption.confirmationError || state.deposit.stateRestorationError,
btcNetwork: state.tbtc.btcNetwork,
requiredConfirmations: state.redemption.requiredConfirmations,
confirmations: state.redemption.confirmations,
Expand Down
8 changes: 6 additions & 2 deletions src/components/redemption/Prove.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const Prove = ({ error }) => (
</div>
<div className="page-body">
<div className="step">Step 5/6</div>
<div className="title">Confirmed</div>
<div className="title">
{error ? "Error proving redemption" : "Confirmed"}
</div>
<hr />
<Description error={error}>
Finally, we are submitting proof to the sidechain and get you your BTC.
Expand All @@ -28,7 +30,9 @@ Prove.propTypes = {
const mapStateToProps = (state, ownProps) => {
return {
provingRedemption: state.redemption.provingRedemption,
error: state.redemption.proveRedemptionError,
error:
state.redemption.proveRedemptionError ||
state.deposit.stateRestorationError,
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/components/redemption/Redeeming.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ RedeemingComponent.propTypes = {
}

const mapStateToProps = (state, ownProps) => ({
error: state.redemption.requestRedemptionError,
error:
state.redemption.requestRedemptionError ||
state.deposit.stateRestorationError,
})

const mapDispatchToProps = (dispatch) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/redemption/Signing.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Signing.propTypes = {
}

const mapStateToProps = (state) => ({
error: state.redemption.signTxError,
error: state.redemption.signTxError || state.deposit.stateRestorationError,
})

export default connect(mapStateToProps)(Signing)
4 changes: 4 additions & 0 deletions src/css/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ a {

.page-top {
margin-bottom: 25px;

.error {
color: $alert;
}
}
}

8 changes: 8 additions & 0 deletions src/reducers/deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
DEPOSIT_REQUEST_BEGIN,
DEPOSIT_RESOLVED,
DEPOSIT_STATE_RESTORED,
DEPOSIT_STATE_RESTORATION_ERROR,
DEPOSIT_AVAILABLE_LOT_SIZES_REQUESTED,
DEPOSIT_AVAILABLE_LOT_SIZES_ERROR,
} from "../sagas/deposit"
Expand Down Expand Up @@ -56,6 +57,12 @@ const deposit = (state = initialState, action) => {
return {
...state,
isStateReady: true,
stateRestorationError: null,
}
case DEPOSIT_STATE_RESTORATION_ERROR:
return {
...state,
stateRestorationError: action.payload.error,
}
case DEPOSIT_AVAILABLE_LOT_SIZES_REQUESTED:
return {
Expand Down Expand Up @@ -187,6 +194,7 @@ const deposit = (state = initialState, action) => {
btcTxError: null,
btcConfirmingError: null,
proveDepositError: null,
stateRestorationError: null,
}
default:
return state
Expand Down
172 changes: 86 additions & 86 deletions src/sagas/deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const DEPOSIT_RESOLVED = "DEPOSIT_RESOLVED"
export const DEPOSIT_BTC_ADDRESS = "DEPOSIT_BTC_ADDRESS"
export const DEPOSIT_BTC_ADDRESS_ERROR = "DEPOSIT_BTC_ADDRESS_ERROR"
export const DEPOSIT_STATE_RESTORED = "DEPOSIT_STATE_RESTORED"
export const DEPOSIT_STATE_RESTORATION_ERROR = "DEPOSIT_STATE_RESTORATION_ERROR"
export const DEPOSIT_BTC_AMOUNTS = "DEPOSIT_BTC_AMOUNTS"
export const DEPOSIT_BTC_AMOUNTS_ERROR = "DEPOSIT_BTC_AMOUNTS_ERROR"
export const DEPOSIT_AUTO_SUBMIT_PROOF = "DEPOSIT_AUTO_SUBMIT_PROOF"
Expand All @@ -50,97 +51,95 @@ function* restoreState(nextStepMap, stateKey) {
/** @type {TBTC} */
const tbtc = yield TBTCLoaded

const depositAddress = yield select((state) => state[stateKey].depositAddress)
const deposit = yield tbtc.Deposit.withAddress(depositAddress)
yield put({
type: DEPOSIT_RESOLVED,
payload: {
deposit,
},
})

/** @type {BN} */
const depositState = yield call([deposit, deposit.getCurrentState])

let finalCalls = null
const nextStep = nextStepMap[depositState]

switch (depositState) {
case tbtc.Deposit.State.START:
throw new Error("Unexpected state.")
try {
const depositAddress = yield select(
(state) => state[stateKey].depositAddress
)
const deposit = yield tbtc.Deposit.withAddress(depositAddress)
yield put({
type: DEPOSIT_RESOLVED,
payload: {
deposit,
},
})

case tbtc.Deposit.State.AWAITING_WITHDRAWAL_PROOF:
finalCalls = resumeRedemption
// Explicitly fall through.
/** @type {BN} */
const depositState = yield call([deposit, deposit.getCurrentState])

const nextStep = nextStepMap[depositState]

switch (depositState) {
case tbtc.Deposit.State.START:
throw new Error("Unexpected state.")

case tbtc.Deposit.State.AWAITING_WITHDRAWAL_PROOF:
case tbtc.Deposit.State.AWAITING_WITHDRAWAL_SIGNATURE:
case tbtc.Deposit.State.AWAITING_BTC_FUNDING_PROOF:
case tbtc.Deposit.State.REDEEMED:
case tbtc.Deposit.State.ACTIVE:
const btcAddress = yield call([deposit, deposit.getBitcoinAddress])
yield put({
type: DEPOSIT_BTC_ADDRESS,
payload: {
btcAddress,
},
})

const lotInSatoshis = yield call([deposit, deposit.getLotSizeSatoshis])
const signerFeeTbtc = yield call([deposit, deposit.getSignerFeeTBTC])
const signerFeeInSatoshis = signerFeeTbtc.div(tbtc.satoshisPerTbtc)
yield put({
type: DEPOSIT_BTC_AMOUNTS,
payload: {
lotInSatoshis,
signerFeeInSatoshis,
},
})

// FIXME Check to see if Electrum has already seen a tx for payment
// FIXME and fast-forward to /pay/confirming if so.
//
// FIXME Check to see if we have a transaction in the mempool for
// FIXME submitting funding proof, and update state accordingly.

// Explicitly fall through.

case tbtc.Deposit.State.AWAITING_SIGNER_SETUP:
yield put({
type: DEPOSIT_STATE_RESTORED,
})

const inVendingMachine = yield call([deposit, deposit.inVendingMachine])
if (depositState === tbtc.Deposit.State.ACTIVE && !inVendingMachine) {
yield call([deposit, deposit.mintTBTC])
}

// TODO Fork on active vs await
yield put(navigateTo("/deposit/" + depositAddress + nextStep))

yield* onStateRestored(tbtc, depositState)

break

// Funding failure states
case tbtc.Deposit.State.FRAUD_AWAITING_BTC_FUNDING_PROOF:
case tbtc.Deposit.State.FAILED_SETUP:
// TODO Update deposit state to reflect situation.
break

default:
throw new Error(`Unexpected state ${depositState}.`)
}

case tbtc.Deposit.State.AWAITING_WITHDRAWAL_SIGNATURE:
case tbtc.Deposit.State.AWAITING_BTC_FUNDING_PROOF:
case tbtc.Deposit.State.REDEEMED:
case tbtc.Deposit.State.ACTIVE:
const btcAddress = yield call([deposit, deposit.getBitcoinAddress])
yield put({
type: DEPOSIT_BTC_ADDRESS,
payload: {
btcAddress,
},
})

const lotInSatoshis = yield call([deposit, deposit.getLotSizeSatoshis])
const signerFeeTbtc = yield call([deposit, deposit.getSignerFeeTBTC])
const signerFeeInSatoshis = signerFeeTbtc.div(tbtc.satoshisPerTbtc)
yield put({
type: DEPOSIT_BTC_AMOUNTS,
payload: {
lotInSatoshis,
signerFeeInSatoshis,
},
})

if (finalCalls) {
yield* finalCalls()
}

// FIXME Check to see if Electrum has already seen a tx for payment
// FIXME and fast-forward to /pay/confirming if so.
// Here, we need to look at the logs. getDepositBtcAddress submits a
// signed tx to Metamask, so that's not what we need.
//
// FIXME Check to see if we have a transaction in the mempool for
// FIXME submitting funding proof, and update state accordingly.

// Explicitly fall through.

case tbtc.Deposit.State.AWAITING_SIGNER_SETUP:
yield put({
type: DEPOSIT_STATE_RESTORED,
})
// Then, we need to dispatch an update to the state.

const inVendingMachine = yield call([deposit, deposit.inVendingMachine])
if (depositState === tbtc.Deposit.State.ACTIVE && !inVendingMachine) {
yield call([deposit, deposit.mintTBTC])
}

// TODO Fork on active vs await
yield put(navigateTo("/deposit/" + depositAddress + nextStep))

yield* onStateRestored(tbtc, depositState)

break

// Funding failure states
case tbtc.Deposit.State.FRAUD_AWAITING_BTC_FUNDING_PROOF:
case tbtc.Deposit.State.FAILED_SETUP:
// TODO Update deposit state to reflect situation.
break

default:
throw new Error(`Unexpected state ${depositState}.`)
// yield put({ type: })
} catch (error) {
yield* logError(DEPOSIT_STATE_RESTORATION_ERROR, error)
}

// Here, we need to look at the logs. getDepositBtcAddress submits a
// signed tx to Metamask, so that's not what we need.
//
// Then, we need to dispatch an update to the state.

// yield put({ type: })
}

export function* restoreDepositState() {
Expand Down Expand Up @@ -181,6 +180,7 @@ export function* onStateRestored(tbtc, depositState) {
yield* autoSubmitDepositProof()
break
case tbtc.Deposit.State.AWAITING_WITHDRAWAL_SIGNATURE:
case tbtc.Deposit.State.AWAITING_WITHDRAWAL_PROOF:
yield* resumeRedemption()
break
default:
Expand Down
9 changes: 7 additions & 2 deletions src/wrappers/loadable.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function LoadableBase({
restoreDepositState,
restoreRedemptionState,
restorer,
error,
}) {
// Wait for web3 connected
const { active: web3Active } = useWeb3React()
Expand Down Expand Up @@ -46,7 +47,7 @@ function LoadableBase({
return (
<div className="pay">
<div className="page-top">
<p>Loading...</p>
{error ? <p className="error">{error}</p> : <p>Loading...</p>}
</div>
<div className="page-body"></div>
</div>
Expand All @@ -56,6 +57,10 @@ function LoadableBase({
return children
}

const mapStateToProps = (state) => ({
error: state.deposit.stateRestorationError,
})

const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{
Expand All @@ -66,6 +71,6 @@ const mapDispatchToProps = (dispatch) => {
)
}

const Loadable = connect(null, mapDispatchToProps)(LoadableBase)
const Loadable = connect(mapStateToProps, mapDispatchToProps)(LoadableBase)

export default Loadable

0 comments on commit 008e702

Please sign in to comment.