UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

249 lines 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const pick_1 = __importDefault(require("lodash/fp/pick")); const actions_1 = require("../channels/actions"); const state_1 = require("../channels/state"); const utils_1 = require("../channels/utils"); const utils_2 = require("../messages/utils"); const state_2 = require("../state"); const actions_2 = require("../utils/actions"); const types_1 = require("../utils/types"); const actions_3 = require("./actions"); const state_3 = require("./state"); const utils_3 = require("./utils"); const END = { [state_3.Direction.SENT]: 'own', [state_3.Direction.RECEIVED]: 'partner' }; // Reducers for different actions function transferSecretReducer(state, action) { const key = (0, utils_3.transferKey)(action.meta); const transferState = state.transfers[key]; if (!transferState) return state; // store when seeing unconfirmed, but registerBlock only after confirmation if (!transferState.secret) state = { ...state, transfers: { ...state.transfers, [key]: { ...state.transfers[key], secret: action.payload.secret, }, }, }; // don't overwrite registerBlock if secret already stored with it if (actions_3.transferSecretRegister.success.is(action) && action.payload.confirmed && action.payload.txBlock !== transferState.secretRegistered?.txBlock && // TokenNetwork.sol::getLockedAmountFromLock() action.payload.txTimestamp < transferState.expiration) state = { ...state, transfers: { ...state.transfers, [key]: { ...state.transfers[key], // special-case secretRegistered: instead of `timed`, use txTimestamp (to millis) as `ts` secretRegistered: (0, types_1.timed)({ txHash: action.payload.txHash, txBlock: action.payload.txBlock }, action.payload.txTimestamp * 1e3), }, }, }; return state; } function transferEnvelopeReducer(state, action) { const message = action.payload.message; const tKey = (0, utils_3.transferKey)(action.meta); const partner = action.payload.partner; const cKey = (0, utils_1.channelKey)({ tokenNetwork: message.token_network_address, partner }); const end = END[action.meta.direction]; let channel = state.channels[cKey]; let transferState = state.transfers[tKey]; const field = actions_3.transferUnlock.success.is(action) ? 'unlock' : 'expired'; // nonce must be next, otherwise we already processed this message; validation happens on epic const isSignedAndNoState = actions_3.transferSigned.is(action) && transferState; const isntSignedAndState = !actions_3.transferSigned.is(action) && (!transferState || field in transferState); if (isSignedAndNoState || isntSignedAndState || channel?.state !== state_1.ChannelState.open || !message.nonce.eq(channel[end].nextNonce)) return state; let locks; switch (action.type) { case actions_3.transferSigned.type: locks = [...channel[end].locks, action.payload.message.lock]; // append lock transferState = { _id: tKey, channel: (0, utils_1.channelUniqueKey)(channel), ...action.meta, transfer: (0, types_1.timed)(action.payload.message), fee: action.payload.fee, expiration: action.payload.message.lock.expiration.toNumber(), partner, cleared: 0, }; // initialize transfer state on transferSigned break; case actions_3.transferUnlock.success.type: case actions_3.transferExpire.success.type: locks = channel[end].locks.filter((l) => l.secrethash !== action.meta.secrethash); // pop lock transferState = { [field]: (0, types_1.timed)(action.payload.message), ...transferState, // don't overwrite previous [field] }; // set unlock or expired members on existing tranferState break; } // switch without default helps secure against incomplete casing channel = { ...channel, [end]: { ...channel[end], locks, // set current/latest channel[end].balanceProof balanceProof: (0, utils_2.getBalanceProofFromEnvelopeMessage)(message), nextNonce: channel[end].nextNonce.add(1), // always increment nextNonce }, }; // both transfer's and channel end's state changes done atomically return { ...state, channels: { ...state.channels, [cKey]: channel }, transfers: { ...state.transfers, [tKey]: transferState, }, }; } const fieldMap = { [actions_3.transferSecretRequest.type]: 'secretRequest', [actions_3.transferSecretReveal.type]: 'secretReveal', [actions_3.transferProcessed.type]: 'transferProcessed', [actions_3.transferUnlockProcessed.type]: 'unlockProcessed', [actions_3.transferExpireProcessed.type]: 'expiredProcessed', }; function transferMessagesReducer(state, action) { const key = (0, utils_3.transferKey)(action.meta); const field = fieldMap[action.type]; const transferState = state.transfers[key]; if (transferState && (actions_3.transferSecretRequest.is(action) || !(field in transferState))) state = { ...state, transfers: { ...state.transfers, [key]: { ...transferState, [field]: (0, types_1.timed)(action.payload.message), }, }, }; return state; } function transferClearReducer(state, action) { const key = (0, utils_3.transferKey)(action.meta); if (key in state.transfers) { const { [key]: _cleared, ...transfers } = state.transfers; state = { ...state, transfers }; } return state; } function transferLoadReducer(state, action) { const key = (0, utils_3.transferKey)(action.meta); if (!(key in state.transfers)) state = { ...state, transfers: { ...state.transfers, [key]: { ...action.payload, cleared: 0 }, }, }; return state; } function channelCloseSettleReducer(state, action) { if (!action.payload.confirmed) return state; const field = actions_1.channelClose.success.is(action) ? 'channelClosed' : 'channelSettled'; const channel = (0, utils_1.channelUniqueKey)({ id: action.payload.id, ...action.meta }); for (const transferState of Object.values(state.transfers)) { if (transferState.channel !== channel) continue; state = { ...state, transfers: { ...state.transfers, [(0, utils_3.transferKey)(transferState)]: { ...transferState, [field]: (0, types_1.timed)((0, pick_1.default)(['txHash', 'txBlock'], action.payload)), }, }, }; } return state; } function withdrawReducer(state, action) { const message = action.payload.message; const key = (0, utils_1.channelKey)(action.meta); let channel = state.channels[key]; // messages always update sender's nonce, i.e. requestee's for confirmations, else requester's const senderEnd = (action.meta.direction === state_3.Direction.RECEIVED) !== actions_3.withdrawMessage.success.is(action) ? 'partner' : 'own'; // nonce must be next, otherwise already processed message, skip if (channel?.state !== state_1.ChannelState.open || !message.nonce.eq(channel[senderEnd].nextNonce)) return state; channel = { ...channel, [senderEnd]: { ...channel[senderEnd], nextNonce: channel[senderEnd].nextNonce.add(1), // no BP, but increment nextNonce }, }; // all messages are stored in 'pendingWithdraws' array on requester's/withdrawer's side const withdrawerEnd = action.meta.direction === state_3.Direction.RECEIVED ? 'partner' : 'own'; const pendingWithdraws = [...channel[withdrawerEnd].pendingWithdraws, action.payload.message]; // senderEnd == withdrawerEnd for request & expiration, and the other for confirmation channel = { ...channel, [withdrawerEnd]: { ...channel[withdrawerEnd], pendingWithdraws, }, }; return { ...state, channels: { ...state.channels, [key]: channel } }; } function withdrawCompletedReducer(state, action) { const key = (0, utils_1.channelKey)(action.meta); let channel = state.channels[key]; if (channel?.state !== state_1.ChannelState.open) return state; const end = action.meta.direction === state_3.Direction.RECEIVED ? 'partner' : 'own'; // filters out all withdraw messages matching meta const pendingWithdraws = channel[end].pendingWithdraws.filter((req) => !req.expiration.eq(action.meta.expiration) || !req.total_withdraw.eq(action.meta.totalWithdraw)); channel = { ...channel, [end]: { ...channel[end], pendingWithdraws, }, }; return { ...state, channels: { ...state.channels, [key]: channel } }; } /** * Handles all transfers actions and requests */ const transfersReducer = (0, actions_2.createReducer)(state_2.initialState) .handle([actions_3.transferSecret, actions_3.transferSecretRegister.success], transferSecretReducer) .handle([actions_3.transferSigned, actions_3.transferUnlock.success, actions_3.transferExpire.success], transferEnvelopeReducer) .handle([ actions_3.transferProcessed, actions_3.transferUnlockProcessed, actions_3.transferExpireProcessed, actions_3.transferSecretRequest, actions_3.transferSecretReveal, ], transferMessagesReducer) .handle(actions_3.transferClear, transferClearReducer) .handle(actions_3.transferLoad, transferLoadReducer) .handle([actions_1.channelClose.success, actions_1.channelSettle.success], channelCloseSettleReducer) .handle([actions_3.withdrawMessage.request, actions_3.withdrawMessage.success, actions_3.withdrawExpire.success], withdrawReducer) .handle(actions_3.withdrawCompleted, withdrawCompletedReducer); exports.default = transfersReducer; //# sourceMappingURL=reducer.js.map