Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make channelClose & channelSettle success actions confirmable #1017

Merged
merged 3 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion raiden-ts/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export const ConfirmableActions = [
ChannelsActions.channelOpen.success,
ChannelsActions.channelDeposit.success,
ChannelsActions.channelWithdrawn,
ChannelsActions.channelClose.success,
ChannelsActions.channelSettle.success,
];
/**
* Union of codecs of actions above
Expand All @@ -85,8 +87,12 @@ export const ConfirmableAction = t.union([
ChannelsActions.channelOpen.success.codec,
ChannelsActions.channelDeposit.success.codec,
ChannelsActions.channelWithdrawn.codec,
ChannelsActions.channelClose.success.codec,
ChannelsActions.channelSettle.success.codec,
]);
export type ConfirmableAction =
| ChannelsActions.channelOpen.success
| ChannelsActions.channelDeposit.success
| ChannelsActions.channelWithdrawn;
| ChannelsActions.channelWithdrawn
| ChannelsActions.channelClose.success
| ChannelsActions.channelSettle.success;
15 changes: 13 additions & 2 deletions raiden-ts/src/channels/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ export const channelClose = createAsyncAction(
'channel/close/success',
'channel/close/failure',
t.union([t.partial({ subkey: t.boolean }), t.undefined]),
t.type({ id: t.number, participant: Address, closeBlock: t.number, txHash: Hash }),
t.type({
id: t.number,
participant: Address,
txHash: Hash,
txBlock: t.number,
confirmed: t.union([t.undefined, t.boolean]),
}),
);

export namespace channelClose {
Expand All @@ -132,7 +138,12 @@ export const channelSettle = createAsyncAction(
'channel/settle/success',
'channel/settle/failure',
t.union([t.partial({ subkey: t.boolean }), t.undefined]),
t.type({ id: t.number, settleBlock: t.number, txHash: Hash }),
t.type({
id: t.number,
txHash: Hash,
txBlock: t.number,
confirmed: t.union([t.undefined, t.boolean]),
}),
);

export namespace channelSettle {
Expand Down
20 changes: 12 additions & 8 deletions raiden-ts/src/channels/epics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,10 @@ export const channelMonitoredEpic = (
// fetch Channel's pastEvents since channelOpen.success blockNumber as fromBlock$
action.payload.fromBlock ? of(action.payload.fromBlock) : undefined,
).pipe(
mergeMap(function*(data) {
map(data => {
if (isEvent<ChannelNewDepositEvent>(depositFilter, data)) {
const [id, participant, totalDeposit, event] = data;
yield channelDeposit.success(
return channelDeposit.success(
{
id: id.toNumber(),
participant,
Expand All @@ -409,7 +409,7 @@ export const channelMonitoredEpic = (
);
} else if (isEvent<ChannelWithdrawEvent>(withdrawFilter, data)) {
const [id, participant, totalWithdraw, event] = data;
yield channelWithdrawn(
return channelWithdrawn(
{
id: id.toNumber(),
participant,
Expand All @@ -422,34 +422,37 @@ export const channelMonitoredEpic = (
);
} else if (isEvent<ChannelClosedEvent>(closedFilter, data)) {
const [id, participant, , , event] = data;
yield channelClose.success(
return channelClose.success(
{
id: id.toNumber(),
participant,
closeBlock: event.blockNumber!,
txHash: event.transactionHash! as Hash,
txBlock: event.blockNumber!,
confirmed: undefined,
},
action.meta,
);
} else if (isEvent<ChannelSettledEvent>(settledFilter, data)) {
const [id, , , , , event] = data;
yield channelSettle.success(
return channelSettle.success(
{
id: id.toNumber(),
settleBlock: event.blockNumber!,
txHash: event.transactionHash! as Hash,
txBlock: event.blockNumber!,
confirmed: undefined,
},
action.meta,
);
}
}),
filter(isntNil),
// takeWhile tends to broad input to generic Action. We need to narrow it explicitly
takeWhile<
| channelDeposit.success
| channelWithdrawn
| channelClose.success
| channelSettle.success
>(negate(isActionOf(channelSettle.success)), true),
>(negate(channelSettle.success.is), true),
);
}),
),
Expand Down Expand Up @@ -941,6 +944,7 @@ export const confirmationEpic = (
state$.pipe(pluck('pendingTxs')),
config$.pipe(pluckDistinct('confirmationBlocks')),
).pipe(
filter(([, pendingTxs]) => pendingTxs.length > 0),
// exhaust will ignore blocks while concat$ is busy
exhaustMap(([blockNumber, pendingTxs, confirmationBlocks]) =>
concat$(
Expand Down
15 changes: 12 additions & 3 deletions raiden-ts/src/channels/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function channelUpdateOnchainBalanceStateReducer(
state: RaidenState['channels'],
action: channelDeposit.success | channelWithdrawn,
): RaidenState['channels'] {
// ignore channelDeposit.success if unconfirmed or removed
// ignore event if unconfirmed or removed
if (!action.payload.confirmed) return state;
const path = [action.meta.tokenNetwork, action.meta.partner];
let channel: Channel | undefined = get(path, state);
Expand Down Expand Up @@ -121,7 +121,12 @@ function channelCloseSuccessReducer(
channel.id !== action.payload.id
)
return state;
channel = { ...channel, state: ChannelState.closed, closeBlock: action.payload.closeBlock };
// even on non-confirmed action, already set channel state as closing, so it can't be used for new transfers
if (action.payload.confirmed === undefined && channel.state === ChannelState.open)
channel = { ...channel, state: ChannelState.closing };
else if (action.payload.confirmed)
channel = { ...channel, state: ChannelState.closed, closeBlock: action.payload.txBlock };
else return state;
return set(path, channel, state);
}

Expand Down Expand Up @@ -158,7 +163,11 @@ function channelSettleSuccessReducer(
channel.id !== action.payload.id
)
return state;
return unset(path, state);
// even on non-confirmed action, already set channel as settling
if (action.payload.confirmed === undefined && channel.state !== ChannelState.settling)
andrevmatos marked this conversation as resolved.
Show resolved Hide resolved
return set(path, { ...channel, state: ChannelState.settling }, state);
else if (action.payload.confirmed) return unset(path, state);
else return state;
}

// handles all channel actions and requests
Expand Down
4 changes: 2 additions & 2 deletions raiden-ts/src/raiden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ export class Raiden {
assert(!subkey || this.deps.main, "Can't send tx from subkey if not set");

const meta = { tokenNetwork, partner };
const promise = asyncActionToPromise(channelClose, meta, this.action$).then(
const promise = asyncActionToPromise(channelClose, meta, this.action$, true).then(
({ txHash }) => txHash,
);
this.store.dispatch(channelClose.request(subkey ? { subkey } : undefined, meta));
Expand Down Expand Up @@ -625,7 +625,7 @@ export class Raiden {

// wait for the corresponding success or error action
const meta = { tokenNetwork, partner };
const promise = asyncActionToPromise(channelSettle, meta, this.action$).then(
const promise = asyncActionToPromise(channelSettle, meta, this.action$, true).then(
({ txHash }) => txHash,
);
this.store.dispatch(channelSettle.request(subkey ? { subkey } : undefined, meta));
Expand Down
31 changes: 17 additions & 14 deletions raiden-ts/src/transfers/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Reducer } from 'redux';
import { get, set, unset, mapValues } from 'lodash/fp';
import { get, set, unset } from 'lodash/fp';
import { Zero, HashZero } from 'ethers/constants';
import { hexlify } from 'ethers/utils';

Expand Down Expand Up @@ -206,7 +206,7 @@ function transferStateReducer(
} else {
return state;
}

if (state.sent[secrethash][key]) return state;
return {
...state,
sent: {
Expand All @@ -223,18 +223,19 @@ function channelCloseSuccessReducer(
state: RaidenState,
action: channelClose.success,
): RaidenState {
return {
...state,
sent: mapValues(
(v: SentTransfer): SentTransfer =>
// if transfer was on this channel, persist CloseChannel txHash, else pass
v.transfer[1].channel_identifier.eq(action.payload.id) &&
v.transfer[1].recipient === action.meta.partner &&
v.transfer[1].token_network_address === action.meta.tokenNetwork
? { ...v, channelClosed: timed(action.payload.txHash) }
: v,
)(state.sent),
};
let sent = state.sent;
for (const [secrethash, v] of Object.entries(sent)) {
const transfer = v.transfer[1];
if (
!transfer.channel_identifier.eq(action.payload.id) ||
transfer.recipient !== action.meta.partner ||
transfer.token_network_address !== action.meta.tokenNetwork
)
continue;
sent = { ...sent, [secrethash]: { ...v, channelClosed: timed(action.payload.txHash) } };
}
if (sent === state.sent) return state;
return { ...state, sent };
}

function transferClearReducer(state: RaidenState, action: transferClear): RaidenState {
Expand All @@ -249,6 +250,8 @@ function withdrawReceiveSuccessReducer(
state: RaidenState,
action: withdrawReceive.success,
): RaidenState {
// TODO: subtract this pending withdraw request from partner's capacity (maybe some pending
// withdraws state), revert upon expiration or consolidate on confirmed channelWithdrawn
const message = action.payload.message;
const channelPath = ['channels', action.meta.tokenNetwork, action.meta.partner];
let channel: Channel | undefined = get(channelPath, state);
Expand Down
2 changes: 1 addition & 1 deletion raiden-ts/tests/e2e/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export class TestProvider extends Web3Provider {

public async mineUntil(block: number): Promise<number> {
const blockNumber = await this.getBlockNumber();
block = Math.max(block, blockNumber + 1);
console.debug(`mining until block=${block} from ${blockNumber}`);
if (blockNumber >= block) return blockNumber;
const promise = new Promise<number>(resolve => {
const cb = (b: number): void => {
if (b < block) return;
Expand Down
18 changes: 4 additions & 14 deletions raiden-ts/tests/e2e/raiden.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { timer } from 'rxjs';
import { first, filter, takeUntil } from 'rxjs/operators';
import { Zero } from 'ethers/constants';
import { parseEther, parseUnits, bigNumberify, BigNumber, keccak256, Network } from 'ethers/utils';
Expand All @@ -13,7 +14,7 @@ import 'raiden-ts/polyfills';
import { Raiden } from 'raiden-ts/raiden';
import { ShutdownReason } from 'raiden-ts/constants';
import { makeInitialState, RaidenState } from 'raiden-ts/state';
import { raidenShutdown } from 'raiden-ts/actions';
import { raidenShutdown, ConfirmableActions } from 'raiden-ts/actions';
import { newBlock, tokenMonitored } from 'raiden-ts/channels/actions';
import { ChannelState } from 'raiden-ts/channels/state';
import { Storage, Secret, Address } from 'raiden-ts/utils/types';
Expand All @@ -25,7 +26,6 @@ import { makeSecret, getSecrethash } from 'raiden-ts/transfers/utils';
import { matrixSetup } from 'raiden-ts/transport/actions';
import { losslessStringify } from 'raiden-ts/utils/data';
import { ServiceRegistryFactory } from 'raiden-ts/contracts/ServiceRegistryFactory';
import { timer } from 'rxjs';

describe('Raiden', () => {
const provider = new TestProvider();
Expand Down Expand Up @@ -68,18 +68,8 @@ describe('Raiden', () => {
);
raiden.action$
.pipe(
filter(
(
a: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): a is {
payload: { txHash: string; txBlock: number; confirmed: undefined | boolean };
} =>
a &&
a.payload?.['txHash'] &&
a.payload['txBlock'] &&
'confirmed' in a.payload &&
a.payload.confirmed === undefined,
),
filter(isActionOf(ConfirmableActions)),
filter(a => a.payload.confirmed === undefined),
)
.subscribe(a =>
provider.mineUntil(a.payload.txBlock + raiden.config.confirmationBlocks + 1),
Expand Down
48 changes: 41 additions & 7 deletions raiden-ts/tests/unit/epics/channels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ describe('channels epic', () => {
{
id: channelId,
participant: depsMock.address,
closeBlock,
txHash,
txBlock: closeBlock,
confirmed: true,
},
{ tokenNetwork, partner },
),
Expand Down Expand Up @@ -531,7 +532,13 @@ describe('channels epic', () => {

await expect(promise).resolves.toEqual(
channelClose.success(
{ id: channelId, participant: partner, closeBlock, txHash },
{
id: channelId,
participant: partner,
txHash,
txBlock: closeBlock,
confirmed: undefined,
},
{ tokenNetwork, partner },
),
);
Expand All @@ -552,7 +559,13 @@ describe('channels epic', () => {
{ tokenNetwork, partner },
),
channelClose.success(
{ id: channelId, participant: depsMock.address, closeBlock, txHash },
{
id: channelId,
participant: depsMock.address,
txHash,
txBlock: closeBlock,
confirmed: true,
},
{ tokenNetwork, partner },
), // channel is in "closed" state already
].reduce(raidenReducer, state);
Expand All @@ -579,7 +592,10 @@ describe('channels epic', () => {
);

await expect(promise).resolves.toEqual(
channelSettle.success({ id: channelId, settleBlock, txHash }, { tokenNetwork, partner }),
channelSettle.success(
{ id: channelId, txHash, txBlock: settleBlock, confirmed: undefined },
{ tokenNetwork, partner },
),
);

// ensure ChannelSettledAction completed channel monitoring and unsubscribed from events
Expand Down Expand Up @@ -946,7 +962,13 @@ describe('channels epic', () => {
),
newBlock({ blockNumber: closeBlock }),
channelClose.success(
{ id: channelId, participant: depsMock.address, closeBlock, txHash },
{
id: channelId,
participant: depsMock.address,
txHash,
txBlock: closeBlock,
confirmed: true,
},
{ tokenNetwork, partner },
),
].reduce(raidenReducer, state);
Expand Down Expand Up @@ -977,7 +999,13 @@ describe('channels epic', () => {
),
newBlock({ blockNumber: closeBlock }),
channelClose.success(
{ id: channelId, participant: depsMock.address, closeBlock, txHash },
{
id: channelId,
participant: depsMock.address,
txHash,
txBlock: closeBlock,
confirmed: true,
},
{ tokenNetwork, partner },
),
newBlock({ blockNumber: settleBlock }),
Expand Down Expand Up @@ -1024,7 +1052,13 @@ describe('channels epic', () => {
),
newBlock({ blockNumber: closeBlock }),
channelClose.success(
{ id: channelId, participant: depsMock.address, closeBlock, txHash },
{
id: channelId,
participant: depsMock.address,
txHash,
txBlock: closeBlock,
confirmed: true,
},
{ tokenNetwork, partner },
),
newBlock({ blockNumber: settleBlock }),
Expand Down
2 changes: 1 addition & 1 deletion raiden-ts/tests/unit/epics/path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ describe('PFS: pathFindServiceEpic', () => {
state$.next(
[
channelClose.success(
{ id: channelId, participant: partner, closeBlock: 126, txHash },
{ id: channelId, participant: partner, txHash, txBlock: 126, confirmed: true },
{ tokenNetwork, partner },
),
].reduce(raidenReducer, state$.value),
Expand Down
Loading