Skip to content

Commit

Permalink
fix(channel): reestablish flow
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Sep 4, 2024
1 parent d048a89 commit 66c7a72
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 25 deletions.
11 changes: 9 additions & 2 deletions src/channel/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
ChannelMessage,
ChannelEvents,
} from './internal';
import { ChannelError } from '../utils/errors';
import { ChannelError, IllegalArgumentError } from '../utils/errors';
import { Encoded } from '../utils/encoder';
import { TxUnpacked } from '../tx/builder/schema.generated';
import { EntryTag } from '../tx/builder/entry/constants';
Expand Down Expand Up @@ -108,9 +108,16 @@ export default class Channel {
}

static async _initialize<T extends Channel>(channel: T, options: ChannelOptions): Promise<T> {
const reconnect = (options.existingFsmId ?? options.existingChannelId) != null;
if (reconnect && (options.existingFsmId == null || options.existingChannelId == null)) {
throw new IllegalArgumentError('`existingChannelId`, `existingFsmId` should be both provided or missed');

Check warning on line 113 in src/channel/Base.ts

View check run for this annotation

Codecov / codecov/patch

src/channel/Base.ts#L113

Added line #L113 was not covered by tests
}
const reconnectHandler = handlers[
options.reestablish === true ? 'awaitingReestablish' : 'awaitingReconnection'
];
await initialize(
channel,
options.existingFsmId != null ? handlers.awaitingReconnection : handlers.awaitingConnection,
reconnect ? reconnectHandler : handlers.awaitingConnection,
handlers.channelOpen,
options,
);
Expand Down
45 changes: 37 additions & 8 deletions src/channel/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,40 @@ export function awaitingConnection(
}
}

export async function awaitingReestablish(
channel: Channel,
message: ChannelMessage,
state: ChannelState,
): Promise<ChannelFsm> {
if (message.method === 'channels.info' && message.params.data.event === 'fsm_up') {
channel._fsmId = message.params.data.fsm_id;
return {
handler: function awaitingChannelReestablished(
_: Channel,
message2: ChannelMessage,
state2: ChannelState,
): ChannelFsm | undefined {
if (
message2.method === 'channels.info'
&& message2.params.data.event === 'channel_reestablished'
) return { handler: awaitingOpenConfirmation };
return handleUnexpectedMessage(channel, message2, state2);

Check warning on line 163 in src/channel/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/channel/handlers.ts#L163

Added line #L163 was not covered by tests
},
};
}
return handleUnexpectedMessage(channel, message, state);

Check warning on line 167 in src/channel/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/channel/handlers.ts#L167

Added line #L167 was not covered by tests
}

export async function awaitingReconnection(
channel: Channel,
message: ChannelMessage,
state: ChannelState,
): Promise<ChannelFsm> {
if (message.method === 'channels.info') {
if (message.params.data.event === 'fsm_up') {
channel._fsmId = message.params.data.fsm_id;
const { signedTx } = await channel.state();
changeState(channel, signedTx == null ? '' : buildTx(signedTx));
return { handler: channelOpen };
}
if (message.method === 'channels.info' && message.params.data.event === 'fsm_up') {
channel._fsmId = message.params.data.fsm_id;
const { signedTx } = await channel.state();
changeState(channel, signedTx == null ? '' : buildTx(signedTx));
return { handler: channelOpen };
}
return handleUnexpectedMessage(channel, message, state);
}
Expand Down Expand Up @@ -220,18 +242,25 @@ export function awaitingOnChainTx(
function awaitingOpenConfirmation(
channel: Channel,
message: ChannelMessage,
state: ChannelState,
): ChannelFsm | undefined {
if (message.method === 'channels.info' && message.params.data.event === 'open') {
channel._channelId = message.params.channel_id;
return {
handler(_: Channel, message2: ChannelMessage): ChannelFsm | undefined {
handler: function awaitingChannelsUpdate(
_: Channel,
message2: ChannelMessage,
state2: ChannelState,
): ChannelFsm | undefined {
if (message2.method === 'channels.update') {
changeState(channel, message2.params.data.state);
return { handler: channelOpen };
}
return handleUnexpectedMessage(channel, message2, state2);

Check warning on line 259 in src/channel/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/channel/handlers.ts#L259

Added line #L259 was not covered by tests
},
};
}
return handleUnexpectedMessage(channel, message, state);

Check warning on line 263 in src/channel/handlers.ts

View check run for this annotation

Codecov / codecov/patch

src/channel/handlers.ts#L263

Added line #L263 was not covered by tests
}

export async function channelOpen(
Expand Down
5 changes: 5 additions & 0 deletions src/channel/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ interface CommonChannelOptions {
* Existing FSM id (required if reestablishing a channel)
*/
existingFsmId?: Encoded.Bytearray;
/**
* Needs to be provided if reconnecting with calling `leave` before
*/
// TODO: remove after solving https://github.com/aeternity/aeternity/issues/4399
reestablish?: boolean;
/**
* The time waiting for a new event to be initiated (default: 600000)
*/
Expand Down
16 changes: 11 additions & 5 deletions test/integration/channel-other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,15 @@ describe('Channel other', () => {
.should.be.equal(true);
}).timeout(timeoutBlock);

// https://github.com/aeternity/protocol/blob/d634e7a3f3110657900759b183d0734e61e5803a/node/api/channels_api_usage.md#reestablish
it('can reconnect', async () => {
it('can reconnect a channel without leave', async () => {
expect(initiatorCh.round()).to.be.equal(1);
const result = await initiatorCh.update(
await initiatorCh.update(
initiator.address,
responder.address,
100,
initiatorSign,
);
expect(result.accepted).to.equal(true);
expect(initiatorCh.round()).to.be.equal(2);
const channelId = initiatorCh.id();
const fsmId = initiatorCh.fsmId();
initiatorCh.disconnect();
Expand All @@ -188,9 +187,16 @@ describe('Channel other', () => {
expect(ch.fsmId()).to.be.equal(fsmId);
expect(ch.round()).to.be.equal(2);
const state = await ch.state();
ch.disconnect();
assertNotNull(state.signedTx);
expect(state.signedTx.encodedTx.tag).to.be.equal(Tag.ChannelOffChainTx);
await ch.update(
initiator.address,
responder.address,
100,
initiatorSign,
);
expect(ch.round()).to.be.equal(3);
ch.disconnect();
});

it('can post backchannel update', async () => {
Expand Down
19 changes: 9 additions & 10 deletions test/integration/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,33 +572,32 @@ describe('Channel', () => {
// TODO: check `initiatorAmountFinal` and `responderAmountFinal`
});

let existingChannelId: Encoded.Channel;
let offchainTx: Encoded.Transaction;
it('can leave a channel', async () => {
initiatorCh.disconnect();
responderCh.disconnect();
[initiatorCh, responderCh] = await initializeChannels(initiatorParams, responderParams);
initiatorCh.round(); // existingChannelRound
await initiatorCh.update(initiator.address, responder.address, 1, initiatorSign);
const result = await initiatorCh.leave();
expect(result.channelId).to.satisfy((t: string) => t.startsWith('ch_'));
expect(result.signedTx).to.satisfy((t: string) => t.startsWith('tx_'));
existingChannelId = result.channelId;
offchainTx = result.signedTx;
});

// https://github.com/aeternity/protocol/blob/d634e7a3f3110657900759b183d0734e61e5803a/node/api/channels_api_usage.md#reestablish
it('can reestablish a channel', async () => {
expect(initiatorCh.round()).to.be.equal(2);
initiatorCh = await Channel.initialize({
...sharedParams,
...initiatorParams,
// @ts-expect-error TODO: use existingChannelId instead existingFsmId
existingFsmId: existingChannelId,
offchainTx,
reestablish: true,
existingChannelId: initiatorCh.id(),
existingFsmId: initiatorCh.fsmId(),
});
await waitForChannel(initiatorCh, ['open']);
// TODO: why node doesn't return signed_tx when channel is reestablished?
// initiatorCh.round().should.equal(existingChannelRound)
expect(initiatorCh.round()).to.be.equal(2);
sinon.assert.notCalled(initiatorSignTag);
sinon.assert.notCalled(responderSignTag);
await initiatorCh.update(initiator.address, responder.address, 1, initiatorSign);
expect(initiatorCh.round()).to.be.equal(3);
});

describe('throws errors', () => {
Expand Down

0 comments on commit 66c7a72

Please sign in to comment.