raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
143 lines • 8.32 kB
JavaScript
;
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