raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
249 lines • 10.7 kB
JavaScript
"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