UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

143 lines 8.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.channelUpdateEpic = exports.channelCloseEpic = void 0; const bytes_1 = require("@ethersproject/bytes"); const constants_1 = require("@ethersproject/constants"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const config_1 = require("../../config"); const utils_1 = require("../../messages/utils"); const actions_1 = require("../../utils/actions"); const data_1 = require("../../utils/data"); const error_1 = require("../../utils/error"); const rx_1 = require("../../utils/rx"); const actions_2 = require("../actions"); const state_1 = require("../state"); const utils_2 = require("../utils"); /** * A ChannelClose action requested by user * Needs to be called on an opened or closing (for retries) channel. * If tx goes through successfuly, stop as ChannelClosed success action will instead be detected * and reacted by channelEventsEpic. * If anything detectable goes wrong, fires a ChannelCloseActionFailed instead * * @param action$ - Observable of channelClose actions * @param state$ - Observable of RaidenStates * @param deps - RaidenEpicDeps members * @returns Observable of channelClose.failure actions */ function channelCloseEpic(action$, state$, deps) { const { log, signer, address, network, getTokenNetworkContract, config$ } = deps; return action$.pipe((0, operators_1.filter)((0, actions_1.isActionOf)(actions_2.channelClose.request)), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => { const { tokenNetwork, partner } = action.meta; const channel = state.channels[(0, utils_2.channelKey)(action.meta)]; if (channel?.state !== state_1.ChannelState.open && channel?.state !== state_1.ChannelState.closing) { const error = new error_1.RaidenError(error_1.ErrorCodes.CNL_NO_OPEN_OR_CLOSING_CHANNEL_FOUND, action.meta); return (0, rxjs_1.of)(actions_2.channelClose.failure(error, action.meta)); } const balanceProof = channel.partner.balanceProof; const balanceHash = (0, utils_1.createBalanceHash)(balanceProof); const nonce = balanceProof.nonce; const additionalHash = balanceProof.additionalHash; const nonClosingSignature = balanceProof.signature; const closingMessage = (0, bytes_1.concat)([ (0, data_1.encode)(tokenNetwork, 20), (0, data_1.encode)(network.chainId, 32), (0, data_1.encode)(utils_1.MessageTypeId.BALANCE_PROOF, 32), (0, data_1.encode)(channel.id, 32), (0, data_1.encode)(balanceHash, 32), (0, data_1.encode)(nonce, 32), (0, data_1.encode)(additionalHash, 32), (0, data_1.encode)(nonClosingSignature, 65), // partner's signature for this balance proof ]); // UInt8Array of 277 bytes // sign counter balance proof, then send closeChannel transaction with our signature return (0, rxjs_1.from)(signer.signMessage(closingMessage)).pipe((0, operators_1.mergeMap)((closingSignature) => (0, utils_2.transact)(getTokenNetworkContract(tokenNetwork), 'closeChannel', [ channel.id, partner, address, balanceHash, nonce, additionalHash, nonClosingSignature, closingSignature, ], deps, { error: error_1.ErrorCodes.CNL_CLOSECHANNEL_FAILED }).pipe((0, rx_1.retryWhile)((0, config_1.intervalFromConfig)(config$), { onErrors: error_1.commonAndFailTxErrors, log: log.info, }), // if channel gets closed while retrying (e.g. by partner), give up (0, operators_1.takeUntil)(action$.pipe((0, operators_1.filter)(actions_2.channelClose.success.is), (0, operators_1.filter)((action) => action.meta.tokenNetwork === tokenNetwork && action.meta.partner === partner))))), // if succeeded, return a empty/completed observable // actual ChannelClosedAction will be detected and handled by channelEventsEpic // if any error happened on tx call/pipeline, catchError will then emit the // channelClose.failure action instead (0, operators_1.ignoreElements)(), (0, operators_1.catchError)((error) => (0, rxjs_1.of)(actions_2.channelClose.failure(error, action.meta)))); })); } exports.channelCloseEpic = channelCloseEpic; /** * When detecting a ChannelClosed event, calls updateNonClosingBalanceProof with partner's balance * proof, iff there's any * TODO: do it only if economically viable (and define what that means) * * @param action$ - Observable of channelClose.success|newBlock actions * @param state$ - Observable of RaidenStates * @param deps - RaidenEpicDeps members * @returns Empty observable */ function channelUpdateEpic(action$, state$, deps) { const { log, signer, address, network, getTokenNetworkContract, config$ } = deps; return action$.pipe((0, operators_1.filter)((0, actions_1.isActionOf)(actions_2.channelClose.success)), (0, operators_1.filter)((action) => !!action.payload.confirmed), // wait a newBlock go through after channelClose confirmation, to ensure any pending // channelSettle could have been processed (0, operators_1.delayWhen)(() => action$.pipe((0, operators_1.filter)(actions_2.newBlock.is))), (0, operators_1.withLatestFrom)(state$), (0, operators_1.filter)(([action, state]) => { const channel = state.channels[(0, utils_2.channelKey)(action.meta)]; return (channel?.state === state_1.ChannelState.closed && channel.id === action.payload.id && channel.partner.balanceProof.transferredAmount .add(channel.partner.balanceProof.lockedAmount) .gt(constants_1.Zero) && // there's partners balanceProof (i.e. received transfers) channel.closeParticipant !== address // we're not the closing end ); }), (0, operators_1.mergeMap)(([action, state]) => { const { tokenNetwork, partner } = action.meta; const channel = state.channels[(0, utils_2.channelKey)(action.meta)]; // checked in filter const balanceHash = (0, utils_1.createBalanceHash)(channel.partner.balanceProof); const nonce = channel.partner.balanceProof.nonce; const additionalHash = channel.partner.balanceProof.additionalHash; const closingSignature = channel.partner.balanceProof.signature; const nonClosingMessage = (0, bytes_1.concat)([ (0, data_1.encode)(tokenNetwork, 20), (0, data_1.encode)(network.chainId, 32), (0, data_1.encode)(utils_1.MessageTypeId.BALANCE_PROOF_UPDATE, 32), (0, data_1.encode)(channel.id, 32), (0, data_1.encode)(balanceHash, 32), (0, data_1.encode)(nonce, 32), (0, data_1.encode)(additionalHash, 32), (0, data_1.encode)(closingSignature, 65), // partner's signature for this balance proof ]); // UInt8Array of 277 bytes // send updateNonClosingBalanceProof transaction return (0, rxjs_1.defer)(() => signer.signMessage(nonClosingMessage)).pipe((0, operators_1.mergeMap)((nonClosingSignature) => (0, utils_2.transact)(getTokenNetworkContract(tokenNetwork), 'updateNonClosingBalanceProof', [ channel.id, partner, address, balanceHash, nonce, additionalHash, closingSignature, nonClosingSignature, ], deps, { error: error_1.ErrorCodes.CNL_UPDATE_NONCLOSING_BP_FAILED })), (0, operators_1.tap)({ next: (v) => log.info('Updated channel', { channel, v }), error: (error) => log.info('Error updating channel', { channel, error }), }), (0, rx_1.retryWhile)((0, config_1.intervalFromConfig)(config$), { onErrors: error_1.commonAndFailTxErrors, log: log.info, }), // if succeeded, return a empty/completed observable (0, operators_1.ignoreElements)(), (0, operators_1.catchError)((error) => { log.error('Error updating non-closing balance-proof, ignoring', error); return rxjs_1.EMPTY; })); })); } exports.channelUpdateEpic = channelUpdateEpic; //# sourceMappingURL=close.js.map