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

Lots of node test fixes #1386

Draft
wants to merge 13 commits into
base: staging
Choose a base branch
from
10 changes: 10 additions & 0 deletions modules/node/src/appInstance/appInstance.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ export class AppInstanceRepository extends Repository<AppInstance> {
.getMany();
}

async findInstalledAppsAcrossChannelsByAppDefinition(
appDefinition: string,
): Promise<AppInstance[]> {
return this.createQueryBuilder("app_instances")
.leftJoinAndSelect("app_instances.channel", "channel")
.where("app_instances.type = :type", { type: AppType.INSTANCE })
.andWhere("app_instances.appDefinition = :appDefinition", { appDefinition })
.getMany();
}

async findTransferAppsByAppDefinitionPaymentIdAndType(
paymentId: string,
appDefinition: string,
Expand Down
155 changes: 155 additions & 0 deletions modules/node/src/test/e2e/hashlockTransfer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { ColorfulLogger, logTime, getRandomBytes32 } from "@connext/utils";
import { INestApplication } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import {
IConnextClient,
ConditionalTransferCreatedEventData,
ConditionalTransferTypes,
EventNames,
CONVENTION_FOR_ETH_ASSET_ID,
} from "@connext/types";
import { utils, BigNumber } from "ethers";

import { AppModule } from "../../app.module";
import { ConfigService } from "../../config/config.service";
import {
env,
ethProviderUrl,
expect,
MockConfigService,
getClient,
AssetOptions,
fundChannel,
ethProvider,
ETH_AMOUNT_SM,
} from "../utils";
import { TIMEOUT_BUFFER } from "../../constants";
import { TransferService } from "../../transfer/transfer.service";

const { soliditySha256 } = utils;

// Define helper functions
const sendHashlockTransfer = async (
sender: IConnextClient,
receiver: IConnextClient,
transfer: AssetOptions & { preImage: string; timelock: string },
): Promise<ConditionalTransferCreatedEventData<"HashLockTransferApp">> => {
// Fund sender channel
await fundChannel(sender, transfer.amount, transfer.assetId);

// Create transfer parameters
const expiry = BigNumber.from(transfer.timelock).add(await ethProvider.getBlockNumber());
const lockHash = soliditySha256(["bytes32"], [transfer.preImage]);

const receiverPromise = receiver.waitFor(EventNames.CONDITIONAL_TRANSFER_CREATED_EVENT, 10_000);
// sender result
const senderResult = await sender.conditionalTransfer({
amount: transfer.amount.toString(),
conditionType: ConditionalTransferTypes.HashLockTransfer,
lockHash,
timelock: transfer.timelock,
assetId: transfer.assetId,
meta: { foo: "bar" },
recipient: receiver.publicIdentifier,
});
const receiverEvent = await receiverPromise;
const paymentId = soliditySha256(["address", "bytes32"], [transfer.assetId, lockHash]);
const expectedVals = {
amount: transfer.amount,
assetId: transfer.assetId,
paymentId,
recipient: receiver.publicIdentifier,
sender: sender.publicIdentifier,
transferMeta: {
timelock: transfer.timelock,
lockHash,
expiry: expiry.sub(TIMEOUT_BUFFER),
},
};
// verify the receiver event
expect(receiverEvent).to.containSubset({
...expectedVals,
type: ConditionalTransferTypes.HashLockTransfer,
});

// verify sender return value
expect(senderResult).to.containSubset({
...expectedVals,
transferMeta: {
...expectedVals.transferMeta,
expiry,
},
});
return receiverEvent as ConditionalTransferCreatedEventData<"HashLockTransferApp">;
};

describe.only("Hashlock Transfer", () => {
const log = new ColorfulLogger("TestStartup", env.logLevel, true, "T");

let app: INestApplication;
let configService: ConfigService;
let transferService: TransferService;
let senderClient: IConnextClient;
let receiverClient: IConnextClient;

before(async () => {
const start = Date.now();
const currBlock = await ethProvider.getBlockNumber();
if (currBlock <= TIMEOUT_BUFFER) {
log.warn(`Mining until there are at least ${TIMEOUT_BUFFER} blocks`);
for (let index = currBlock; index <= TIMEOUT_BUFFER + 1; index++) {
await ethProvider.send("evm_mine", []);
}
}

const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(ConfigService)
.useClass(MockConfigService)
.compile();

app = moduleFixture.createNestApplication();
await app.init();
configService = moduleFixture.get<ConfigService>(ConfigService);
await app.listen(configService.getPort());

log.info(`node: ${await configService.getSignerAddress()}`);
log.info(`ethProviderUrl: ${ethProviderUrl}`);

senderClient = await getClient("A");

receiverClient = await getClient("B");

logTime(log, start, "Done setting up test env");
transferService = moduleFixture.get<TransferService>(TransferService);
log.warn(`Finished before() in ${Date.now() - start}ms`);
});

after(async () => {
try {
await app.close();
log.info(`Application was shutdown successfully`);
} catch (e) {
log.warn(`Application was shutdown unsuccessfully: ${e.message}`);
}
});

it("cleans up expired hashlock transfers ", async () => {
const transfer: AssetOptions = { amount: ETH_AMOUNT_SM, assetId: CONVENTION_FOR_ETH_ASSET_ID };
const preImage = getRandomBytes32();
const timelock = (101).toString();
const opts = { ...transfer, preImage, timelock };

const { paymentId } = await sendHashlockTransfer(senderClient, receiverClient, opts);

expect(paymentId).to.be.ok;

const appsBeforePrune = await receiverClient.getAppInstances();
expect(appsBeforePrune.length).to.eq(1);
await transferService.pruneChannels();
const appsAfterPrune = await receiverClient.getAppInstances();
console.log("receiverClientappsAfterPrune: ", appsAfterPrune[0]);
expect(appsAfterPrune.length).to.eq(0);
});
});
82 changes: 82 additions & 0 deletions modules/node/src/test/utils/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { connect } from "@connext/client";
import { ColorfulLogger, getRandomChannelSigner } from "@connext/utils";
import { BigNumber } from "ethers";
import { getMemoryStore } from "@connext/store";
import {
ClientOptions,
IConnextClient,
AssetId,
CONVENTION_FOR_ETH_ASSET_ID,
} from "@connext/types";

import { env, ethProviderUrl, expect, ethProvider, sugarDaddy } from ".";
import { parseEther } from "ethers/lib/utils";

export const TEN = "10";
export const TWO = "2";
export const ONE = "1";
export const ZERO_ONE = "0.1";
export const ZERO_ZERO_TWO = "0.02";
export const ZERO_ZERO_ONE = "0.01";
export const ZERO_ZERO_ZERO_FIVE = "0.005";
export const ZERO_ZERO_ZERO_ONE = "0.001";

export const TEN_ETH = parseEther(TEN);
export const TWO_ETH = parseEther(TWO);
export const ONE_ETH = parseEther(ONE);
export const ZERO_ONE_ETH = parseEther(ZERO_ONE);
export const ZERO_ZERO_TWO_ETH = parseEther(ZERO_ZERO_TWO);
export const ZERO_ZERO_ONE_ETH = parseEther(ZERO_ZERO_ONE);
export const ZERO_ZERO_ZERO_FIVE_ETH = parseEther(ZERO_ZERO_ZERO_FIVE);
export const ZERO_ZERO_ZERO_ONE_ETH = parseEther(ZERO_ZERO_ZERO_ONE);

export const ETH_AMOUNT_SM = ZERO_ZERO_ONE_ETH;
export const ETH_AMOUNT_MD = ZERO_ONE_ETH;
export const ETH_AMOUNT_LG = ONE_ETH;
export const TOKEN_AMOUNT = TEN_ETH;
export const TOKEN_AMOUNT_SM = ONE_ETH;

export const getClient = async (
id: string = "",
overrides: Partial<ClientOptions> = {},
fundAmount: BigNumber = ETH_AMOUNT_MD,
): Promise<IConnextClient> => {
const log = new ColorfulLogger("getClient", env.logLevel, true, "T");
const client = await connect({
store: getMemoryStore(),
signer: getRandomChannelSigner(ethProvider),
ethProviderUrl,
messagingUrl: env.messagingUrl,
nodeUrl: env.nodeUrl,
loggerService: new ColorfulLogger("", env.logLevel, true, id),
...overrides,
});

if (fundAmount.gt(0)) {
const tx = await sugarDaddy.sendTransaction({
to: client.signerAddress,
value: fundAmount,
});
await ethProvider.waitForTransaction(tx.hash);

log.info(`Created client: ${client.publicIdentifier}`);
expect(client.signerAddress).to.be.a("string");
}

return client;
};

export const fundChannel = async (
client: IConnextClient,
amount: BigNumber = ETH_AMOUNT_SM,
assetId: AssetId = CONVENTION_FOR_ETH_ASSET_ID,
) => {
const { [client.signerAddress]: preBalance } = await client.getFreeBalance(assetId);
const depositRes = await client.deposit({
assetId,
amount,
});
expect(depositRes.transaction).to.be.ok;
const postBalance = await depositRes.completed();
expect(preBalance.add(amount)).to.eq(postBalance.freeBalance[client.signerAddress]);
};
3 changes: 3 additions & 0 deletions modules/node/src/test/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const env = {

export const ethProviderUrl = env.chainProviders[env.defaultChain];

export const ethProvider = new providers.JsonRpcProvider(ethProviderUrl);
export const sugarDaddy = Wallet.fromMnemonic(env.mnemonic!).connect(ethProvider);

export const defaultSigner = new ChannelSigner(
Wallet.fromMnemonic(env.mnemonic!).privateKey,
ethProviderUrl,
Expand Down
2 changes: 2 additions & 0 deletions modules/node/src/test/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./cfCore";
export * from "./client";
export * from "./config";
export * from "./eth";
export * from "./expect";
export * from "./transfer";
6 changes: 6 additions & 0 deletions modules/node/src/test/utils/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { BigNumber } from "ethers";

export interface AssetOptions {
amount: BigNumber;
assetId: string;
}
4 changes: 4 additions & 0 deletions modules/node/src/transfer/transfer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ export class TransferService {
@Interval(3600_000)
async pruneChannels() {
const channels = await this.channelRepository.findAll();
const addresses = this.configService.getAddressBook();
Object.entries(addresses).map((addrs) => addrs);
// const hashLockApps = await this.appInstanceRepository.findInstalledAppsByAppDefinition();
console.log("channels: ", channels);
for (const channel of channels) {
await this.pruneExpiredApps(channel);
}
Expand Down