Skip to content
This repository has been archived by the owner on May 28, 2021. It is now read-only.

39 cooperative cancel #1310

Merged
merged 43 commits into from
Jul 19, 2020
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9e3da08
make
LayneHaber Jul 10, 2020
a16966f
event bugfix
LayneHaber Jul 10, 2020
cd4a5f1
heavy test refactor
LayneHaber Jul 10, 2020
c605e76
prettier
LayneHaber Jul 10, 2020
7d9a96c
wrong secret still finalized
LayneHaber Jul 10, 2020
5e69e1c
create test
LayneHaber Jul 10, 2020
b0603a3
enforce turn taker at app level
LayneHaber Jul 10, 2020
38cfa0b
add turntaker test
LayneHaber Jul 10, 2020
7973319
fix failure message
LayneHaber Jul 10, 2020
6f5b306
improve error
LayneHaber Jul 10, 2020
bd208b0
use correct turn taker in test
LayneHaber Jul 10, 2020
b803560
fix turn taker in test
LayneHaber Jul 10, 2020
a1a4634
fix turn taker in linked transfer test
LayneHaber Jul 10, 2020
081e723
client side working
LayneHaber Jul 11, 2020
bfee042
reset
LayneHaber Jul 11, 2020
12cb1f2
tests created
LayneHaber Jul 11, 2020
4759ddd
node side
LayneHaber Jul 11, 2020
4673c8b
Merge branch 'staging' into 39-cooperative-cancel
LayneHaber Jul 11, 2020
41a3c29
use expiry
LayneHaber Jul 11, 2020
ecd4e08
types
LayneHaber Jul 11, 2020
5bb436c
handle cancellation
LayneHaber Jul 11, 2020
4f798ca
handle proposal case better
LayneHaber Jul 11, 2020
587577a
handle case where receiver app expired, sender did not
LayneHaber Jul 11, 2020
859ffa3
reset
LayneHaber Jul 11, 2020
8dc030f
fix index and logs
LayneHaber Jul 11, 2020
c4f7093
tests passing
LayneHaber Jul 11, 2020
bd5bc69
remove .only, fix test
LayneHaber Jul 11, 2020
44d5f13
fix casting
LayneHaber Jul 11, 2020
25e4237
prettier
LayneHaber Jul 13, 2020
b9ef947
handle cancellation explicitly
LayneHaber Jul 13, 2020
d33884f
set preimage on cancel
LayneHaber Jul 13, 2020
a928d45
add cancellation tests
LayneHaber Jul 13, 2020
006a7c9
allow cancellation before expiry
LayneHaber Jul 13, 2020
f34ff43
remove special case flow
LayneHaber Jul 13, 2020
3e6f0ed
fix types
LayneHaber Jul 14, 2020
b07b323
make reset
LayneHaber Jul 14, 2020
6a25aa8
Merge branch 'staging' into 39-cooperative-cancel
LayneHaber Jul 14, 2020
1b3c4cc
Merge branch 'staging' into 39-cooperative-cancel
LayneHaber Jul 14, 2020
6219d8b
fix build
LayneHaber Jul 14, 2020
661ddc3
Merge branch '39-cooperative-cancel' of http://github.com/connext/ind…
LayneHaber Jul 14, 2020
11f199b
Merge branch 'staging' into 39-cooperative-cancel
ArjunBhuptani Jul 15, 2020
a18a35e
Merge branch 'staging' into 39-cooperative-cancel
Jul 19, 2020
5c2a601
Merge branch 'staging' into 39-cooperative-cancel
Jul 19, 2020
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
43 changes: 43 additions & 0 deletions modules/cf-core/src/models/app-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,55 @@ export class AppInstance {
return this.computeOutcome(this.state, provider, bytecode);
}

public async computeTurnTaker(
provider: providers.JsonRpcProvider,
bytecode?: HexString,
): Promise<string> {
let turnTaker: undefined | string = undefined;
// attempt evm if available
if (bytecode) {
try {
const functionData = appInterface.encodeFunctionData("getTurnTaker", [
this.encodedLatestState,
this.participants,
]);
const output = await execEvmBytecode(bytecode, functionData);
turnTaker = appInterface.decodeFunctionResult("getTurnTaker", output)[0];
} catch (e) {}
rhlsthrm marked this conversation as resolved.
Show resolved Hide resolved
}
if (turnTaker) {
return turnTaker;
}
// otherwise, if err or if no bytecode, execute read fn
turnTaker = (await this.toEthersContract(provider).getTurnTaker(
this.encodedLatestState,
this.participants,
)) as string;
return turnTaker;
}

public async isCorrectTurnTaker(
attemptedTurnTaker: string,
provider: providers.JsonRpcProvider,
bytecode?: HexString,
) {
const turnTaker = await this.computeTurnTaker(provider, bytecode);
return attemptedTurnTaker === turnTaker;
}

public async computeStateTransition(
actionTaker: Address,
action: SolidityValueType,
provider: providers.JsonRpcProvider,
bytecode?: HexString,
): Promise<SolidityValueType> {
let computedNextState: SolidityValueType;
const turnTaker = await this.computeTurnTaker(provider, bytecode);
if (actionTaker !== turnTaker) {
throw new Error(
`Cannot compute state transition, got invalid turn taker for action on app at ${this.appDefinition}. Expected ${turnTaker}, got ${actionTaker}`,
);
}
if (bytecode) {
try {
const functionData = appInterface.encodeFunctionData("applyAction", [
Expand Down
4 changes: 3 additions & 1 deletion modules/cf-core/src/protocol/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
AppInstanceJson,
} from "@connext/types";
import { Context, ProtocolExecutionFlow, PersistStateChannelType } from "../types";
import { stringify, logTime, toBN } from "@connext/utils";
import { stringify, logTime, toBN, getSignerAddressFromPublicIdentifier } from "@connext/utils";
import { stateChannelClassFromStoreByMultisig, getPureBytecode } from "./utils";
import { StateChannel, AppInstance, FreeBalanceClass } from "../models";
import {
Expand Down Expand Up @@ -182,6 +182,7 @@ export const SYNC_PROTOCOL: ProtocolExecutionFlow = {
postSyncStateChannel = postSyncStateChannel.setState(
app,
await app.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
affectedApp!.latestAction,
provider,
getPureBytecode(app.appDefinition, contractAddresses),
Expand Down Expand Up @@ -391,6 +392,7 @@ export const SYNC_PROTOCOL: ProtocolExecutionFlow = {
postSyncStateChannel = postSyncStateChannel.setState(
app,
await app.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
affectedApp!.latestAction,
provider,
getPureBytecode(app.appDefinition, contractAddresses),
Expand Down
13 changes: 5 additions & 8 deletions modules/cf-core/src/protocol/take-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const TAKE_ACTION_PROTOCOL: ProtocolExecutionFlow = {
responderIdentifier,
action,
stateTimeout,
initiatorIdentifier,
} = params as ProtocolParams.TakeAction;

if (!preProtocolStateChannel) {
Expand Down Expand Up @@ -67,12 +68,10 @@ export const TAKE_ACTION_PROTOCOL: ProtocolExecutionFlow = {
const postProtocolStateChannel = preProtocolStateChannel.setState(
preAppInstance,
await preAppInstance.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
action,
network.provider,
getPureBytecode(
preAppInstance.appDefinition,
network.contractAddresses,
),
getPureBytecode(preAppInstance.appDefinition, network.contractAddresses),
),
stateTimeout,
);
Expand Down Expand Up @@ -200,12 +199,10 @@ export const TAKE_ACTION_PROTOCOL: ProtocolExecutionFlow = {
const postProtocolStateChannel = preProtocolStateChannel.setState(
preAppInstance,
await preAppInstance.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
action,
network.provider,
getPureBytecode(
preAppInstance.appDefinition,
network.contractAddresses,
),
getPureBytecode(preAppInstance.appDefinition, network.contractAddresses),
),
stateTimeout,
);
Expand Down
3 changes: 3 additions & 0 deletions modules/cf-core/src/protocol/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const UNINSTALL_PROTOCOL: ProtocolExecutionFlow = {
appIdentityHash,
action,
stateTimeout,
initiatorIdentifier,
} = params as ProtocolParams.Uninstall;

if (!preProtocolStateChannel) {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const UNINSTALL_PROTOCOL: ProtocolExecutionFlow = {
// apply action
substart = Date.now();
const newState = await appToUninstall.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
action,
network.provider,
getPureBytecode(appToUninstall.appDefinition, network.contractAddresses),
Expand Down Expand Up @@ -205,6 +207,7 @@ export const UNINSTALL_PROTOCOL: ProtocolExecutionFlow = {
// apply action
substart = Date.now();
const newState = await appToUninstall.computeStateTransition(
getSignerAddressFromPublicIdentifier(initiatorIdentifier),
action,
network.provider,
getPureBytecode(appToUninstall.appDefinition, network.contractAddresses),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CFCore } from "../../cfCore";
import { INVALID_ACTION } from "../../errors";

import { TestContractAddresses } from "../contracts";
import { constructTakeActionRpc, createChannel, installApp } from "../utils";
Expand Down Expand Up @@ -35,7 +34,7 @@ describe("Node method follows spec - fails with improper action taken", () => {
const takeActionReq = constructTakeActionRpc(appIdentityHash, multisigAddress, validAction);

await expect(nodeA.rpcRouter.dispatch(takeActionReq)).to.eventually.be.rejectedWith(
INVALID_ACTION,
"Cannot compute state transition",
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ describe("Node method follows spec - toke action", () => {

let appsTakenActionOn = 0;

nodeB.on(EventNames.UPDATE_STATE_EVENT, () => {
nodeA.on(EventNames.UPDATE_STATE_EVENT, () => {
appsTakenActionOn += 1;
if (appsTakenActionOn === 2) done();
});

nodeA.rpcRouter.dispatch(
nodeB.rpcRouter.dispatch(
constructTakeActionRpc(appIdentityHashes[0], multisigAddress, validAction),
);
nodeA.rpcRouter.dispatch(
nodeB.rpcRouter.dispatch(
constructTakeActionRpc(appIdentityHashes[1], multisigAddress, validAction),
);
});
Expand Down
2 changes: 1 addition & 1 deletion modules/cf-core/src/testing/scenarios/take-action.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe("Node method follows spec - takeAction", () => {
result: {
result: { newState },
},
} = await nodeA.rpcRouter.dispatch(takeActionReq);
} = await nodeB.rpcRouter.dispatch(takeActionReq);
// allow nodeA to confirm its messages
await new Promise((resolve) => {
nodeA.once(EventNames.UPDATE_STATE_EVENT, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SimpleLinkedTransferAppName,
AppActions,
} from "@connext/types";
import { getRandomBytes32, getSignerAddressFromPublicIdentifier } from "@connext/utils";
import { constants, utils } from "ethers";

import { CFCore } from "../../cfCore";
Expand All @@ -26,7 +27,6 @@ import {
getAppInstance,
} from "../utils";
import { AppInstance } from "../../models";
import { getRandomBytes32 } from "@connext/utils";
import { expect } from "../assertions";

const { One, Two, Zero, HashZero } = constants;
Expand Down Expand Up @@ -100,18 +100,25 @@ describe("Node A and B install an app, then uninstall with a given action", () =
);
const appPreUninstall = AppInstance.fromJson(await getAppInstance(nodeA, appIdentityHash));
const expected = appPreUninstall
.setState(await appPreUninstall.computeStateTransition(action, provider), Zero)
.setState(
await appPreUninstall.computeStateTransition(
getSignerAddressFromPublicIdentifier(nodeB.publicIdentifier),
action,
provider,
),
Zero,
)
.toJson();

await Promise.all([
new Promise(async (resolve, reject) => {
nodeB.on(EventNames.UNINSTALL_EVENT, async (msg) => {
nodeA.on(EventNames.UNINSTALL_EVENT, async (msg) => {
if (msg.data.appIdentityHash !== appIdentityHash) {
return;
}
try {
assertUninstallMessage(
nodeA.publicIdentifier,
nodeB.publicIdentifier,
multisigAddress,
appIdentityHash,
expected,
Expand All @@ -131,7 +138,7 @@ describe("Node A and B install an app, then uninstall with a given action", () =
}),
new Promise(async (resolve, reject) => {
try {
await nodeA.rpcRouter.dispatch(
await nodeB.rpcRouter.dispatch(
constructUninstallRpc(appIdentityHash, multisigAddress, action),
);

Expand Down
97 changes: 52 additions & 45 deletions modules/client/src/connext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,11 @@ export class ConnextClient implements IConnextClient {
// object might not be in the store yet. We should still wait for at least 1 withdrawal
const withdrawalsToFind = (await this.getUserWithdrawals()).length || 1;

this.log.info(`Watching for ${withdrawalsToFind} withdrawal${withdrawalsToFind === 1 ? "s" : ""} starting at block ${startingBlock}`);
this.log.info(
`Watching for ${withdrawalsToFind} withdrawal${
withdrawalsToFind === 1 ? "s" : ""
} starting at block ${startingBlock}`,
);

const getTransactionResponse = async (
tx: MinimalTransaction,
Expand Down Expand Up @@ -424,17 +428,39 @@ export class ConnextClient implements IConnextClient {
return responses;
};

return new Promise(async (resolve: any, reject: any): Promise<any> => {

// First, start listener & process the next n blocks. If no withdrawal found, reject.
this.ethProvider.on(
"block",
async (blockNumber: number): Promise<void> => {
// in the `WithdrawalController` the user does not store the
// commitment until `takeAction` happens, so this may be 0
// meaning the withdrawal has not been saved to the store yet
(await checkForUserWithdrawals(blockNumber)).forEach(async ([storedValue, tx]) => {
if (tx) { // && !transactions.some(t => t.hash === tx.hash)) {
return new Promise(
async (resolve: any, reject: any): Promise<any> => {
// First, start listener & process the next n blocks. If no withdrawal found, reject.
this.ethProvider.on(
"block",
async (blockNumber: number): Promise<void> => {
// in the `WithdrawalController` the user does not store the
// commitment until `takeAction` happens, so this may be 0
// meaning the withdrawal has not been saved to the store yet
(await checkForUserWithdrawals(blockNumber)).forEach(async ([storedValue, tx]) => {
if (tx) {
// && !transactions.some(t => t.hash === tx.hash)) {
this.log.info(`Found new tx at block ${tx.blockNumber} for withdrawal: ${tx.hash}`);
transactions.push(tx);
await this.channelProvider.send(ChannelMethods.chan_setUserWithdrawal, {
withdrawalObject: storedValue,
remove: true,
});
}
});
if (blockNumber - startingBlock > blocksAhead) {
this.ethProvider.removeAllListeners("block");
return reject(`More than ${blocksAhead} have passed`);
}
},
);

// Second, look for withdrawals in the previous n blocks
for (let i = 0; i < blocksBehind; i++) {
// eslint-disable-next-line no-loop-func
(await checkForUserWithdrawals(startingBlock - i)).forEach(async ([storedValue, tx]) => {
if (tx) {
// && !transactions.some(t => t.hash === tx.hash)) {
this.log.info(`Found new tx at block ${tx.blockNumber} for withdrawal: ${tx.hash}`);
transactions.push(tx);
await this.channelProvider.send(ChannelMethods.chan_setUserWithdrawal, {
Expand All @@ -443,41 +469,23 @@ export class ConnextClient implements IConnextClient {
});
}
});
if (blockNumber - startingBlock > blocksAhead) {
this.ethProvider.removeAllListeners("block");
return reject(`More than ${blocksAhead} have passed`);
}
},
);
}

// Second, look for withdrawals in the previous n blocks
for (let i = 0; i < blocksBehind; i++) {
// eslint-disable-next-line no-loop-func
(await checkForUserWithdrawals(startingBlock - i)).forEach(async ([storedValue, tx]) => {
if (tx) { // && !transactions.some(t => t.hash === tx.hash)) {
this.log.info(`Found new tx at block ${tx.blockNumber} for withdrawal: ${tx.hash}`);
transactions.push(tx);
await this.channelProvider.send(ChannelMethods.chan_setUserWithdrawal, {
withdrawalObject: storedValue,
remove: true,
});
// Third, wait until the previous two steps have found all the withdrawals
while (true) {
const withdrawals = await this.getUserWithdrawals();
if (transactions.length > 0 && withdrawals.length < 1) {
this.log.info(
`Found ${transactions.length} transactions, done looking for withdrawals`,
);
this.ethProvider.removeAllListeners("block");
return resolve(transactions);
} else {
await delay(500);
}
});
}

// Third, wait until the previous two steps have found all the withdrawals
while (true) {
const withdrawals = await this.getUserWithdrawals();
if (transactions.length > 0 && withdrawals.length < 1) {
this.log.info(`Found ${transactions.length} transactions, done looking for withdrawals`);
this.ethProvider.removeAllListeners("block");
return resolve(transactions);
} else {
await delay(500);
}
}

});
},
);
};

////////////////////////////////////////
Expand Down Expand Up @@ -995,5 +1003,4 @@ export class ConnextClient implements IConnextClient {
}
return undefined;
};

}
Loading