Skip to content

Commit

Permalink
Merge branch 'LibertyDSNP:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mattheworris authored Nov 9, 2023
2 parents 114af9b + 8f55a11 commit 14c0700
Show file tree
Hide file tree
Showing 45 changed files with 4,524 additions and 3,317 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/verify-pr-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,9 @@ jobs:
- name: Install NPM Modules
run: npm ci
working-directory: e2e
- name: Lint
run: npm run lint
working-directory: e2e
- name: Run E2E Tests
working-directory: e2e
env:
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ start-manual:
start-interval:
./scripts/init.sh start-frequency-interval

start-interval-1:
./scripts/init.sh start-frequency-interval-1
start-interval-short:
./scripts/init.sh start-frequency-interval 1

.PHONY: stop
stop-relay:
Expand Down Expand Up @@ -236,6 +236,9 @@ test:
e2e-tests:
./scripts/run_e2e_tests.sh

e2e-tests-serial:
./scripts/run_e2e_tests.sh -c serial

e2e-tests-only:
./scripts/run_e2e_tests.sh -s

Expand Down
24 changes: 24 additions & 0 deletions e2e/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-env node */
module.exports = {
ignorePatterns: ['dist'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:mocha/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'mocha'],
root: true,
env: {
mocha: true,
node: true,
},
rules: {
'@typescript-eslint/no-explicit-any': 'off', // Don't worry about an any in a test
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
};
18 changes: 7 additions & 11 deletions e2e/.load-test.mocharc.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
{
"exit": true,
"extension": ["ts"],
"parallel": false,
"require": [
"scaffolding/globalHooks.ts",
"scaffolding/rootHooks.ts",
"scaffolding/extrinsicHelpers.ts"
],
"loader": "ts-node/esm",
"spec": ["./load-tests/**/*.test.ts"],
"timeout": 60000
"exit": true,
"extension": ["ts"],
"parallel": false,
"require": ["scaffolding/globalHooks.ts", "scaffolding/rootHooks.ts", "scaffolding/extrinsicHelpers.ts"],
"loader": "ts-node/esm",
"spec": ["./load-tests/**/*.test.ts"],
"timeout": 60000
}
18 changes: 7 additions & 11 deletions e2e/.mocharc.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
{
"exit": true,
"extension": ["ts"],
"parallel": true,
"require": [
"scaffolding/globalHooks.ts",
"scaffolding/rootHooks.ts",
"scaffolding/extrinsicHelpers.ts"
],
"loader": "ts-node/esm",
"spec": ["./{,!(node_modules|load-tests)/**}/*.test.ts"],
"timeout": 10000
"exit": true,
"extension": ["ts"],
"parallel": true,
"require": ["scaffolding/globalHooks.ts", "scaffolding/rootHooks.ts", "scaffolding/extrinsicHelpers.ts"],
"loader": "ts-node/esm",
"spec": ["./{,!(node_modules|load-tests)/**}/*.test.ts"],
"timeout": 20000
}
3 changes: 3 additions & 0 deletions e2e/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# package.json is formatted by package managers, so we ignore it here
package.json
package-lock.json
build/*
dist/*
coverage/*
multimodules/*
/*.d.ts
/*.map
/*.js
**/*.md
9 changes: 8 additions & 1 deletion e2e/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
{}
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"useTabs": false,
"printWidth": 120
}
22 changes: 7 additions & 15 deletions e2e/.relay-chain.mocharc.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
{
"exit": true,
"extension": [
"ts"
],
"parallel": true,
"require": [
"scaffolding/globalHooks.ts",
"scaffolding/rootHooks.ts",
"scaffolding/extrinsicHelpers.ts"
],
"loader": "ts-node/esm",
"spec": [
"./{,!(node_modules|load-tests)/**}/*.test.ts"
],
"timeout": 600000
"exit": true,
"extension": ["ts"],
"parallel": true,
"require": ["scaffolding/globalHooks.ts", "scaffolding/rootHooks.ts", "scaffolding/extrinsicHelpers.ts"],
"loader": "ts-node/esm",
"spec": ["./{,!(node_modules|load-tests)/**}/*.test.ts"],
"timeout": 600000
}
17 changes: 11 additions & 6 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To run an individual test (after starting up a Frequency node):

Note: this is for the "createMsa" tests

`npm run test -- --grep createMsa`
`npm run test:serial -- --grep createMsa`

See below for running load tests.

Expand Down Expand Up @@ -58,18 +58,23 @@ with the following methods:
}
```
7. Extrinsic helper methods & returned events
Many of the extrinsic helper methods pass an event type to the underlying Extrincic object that can be used to parse the targeted event type
from the resulting stream and return it specifically. The `Extrinsic::signAndSend()` method returns an array of `[targetEvent, eventMap]` where
`targetEvent` will be the parsed target event *if present*. The `eventMap` is a map of <string, event> with the keys being `paletteName.eventName`.
Many of the extrinsic helper methods pass an event type to the underlying Extrinsic object that can be used to parse the targeted event type
from the resulting stream and return it specifically. The `Extrinsic::signAndSend()` method returns an object of `{ target, eventMap }` where
`target` will be the parsed target event *if present*. The `eventMap` is a map of <string, event> with the keys being `paletteName.eventName`.
A special key "defaultEvent" is added to also contain the target event, if present.
Events may be used with type guards to access the event-specific structure. Event types are in the `ApiRx.events.<palette>.*` structure, and can be
accessed like so:
```
const extrinsic = ExtrinsicHelper.createMsa(keypair);
const [targetEvent, eventMap] = await extrinsic.fundAndSend();
if (targetEvent && ExtrinsicHelper.api.events.msa.MsaCreated.is(targetEvent)) {
const { target: targetEvent, eventMap } = await extrinsic.fundAndSend();
if (targetEvent) {
const msaId = targetEvent.data.msaId;
}
// OR null coalescing
const maybeMsaId = targetEvent?.data.msaId;

// OR Throw unless defined
const throwIfNotMsaId = targetEvent!.data.msaId;
```

Load Testing
Expand Down
120 changes: 63 additions & 57 deletions e2e/capacity/replenishment.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "@frequency-chain/api-augment";
import { KeyringPair } from "@polkadot/keyring/types";
import { u16, u64 } from "@polkadot/types"
import assert from "assert";
import { Extrinsic, ExtrinsicHelper } from "../scaffolding/extrinsicHelpers";
import '@frequency-chain/api-augment';
import { KeyringPair } from '@polkadot/keyring/types';
import { u16, u64 } from '@polkadot/types';
import assert from 'assert';
import { Extrinsic, ExtrinsicHelper } from '../scaffolding/extrinsicHelpers';
import {
createKeys,
createMsaAndProvider,
Expand All @@ -14,60 +14,60 @@ import {
DOLLARS,
TokenPerCapacity,
assertEvent,
getRemainingCapacity,
getCapacity,
getNonce,
} from "../scaffolding/helpers";
import { getFundingSource } from "../scaffolding/funding";
import { isTestnet } from "../scaffolding/env";
} from '../scaffolding/helpers';
import { getFundingSource } from '../scaffolding/funding';
import { isTestnet } from '../scaffolding/env';

describe("Capacity Replenishment Testing: ", function () {
let schemaId: u16;
const fundingSource = getFundingSource("capacity-replenishment");
const fundingSource = getFundingSource('capacity-replenishment');

describe('Capacity Replenishment Testing: ', function () {
let schemaId: u16;

async function createAndStakeProvider(name: string, stakingAmount: bigint): Promise<[KeyringPair, u64]> {
const stakeKeys = createKeys(name);
const stakeProviderId = await createMsaAndProvider(fundingSource, stakeKeys, "ReplProv", 50n * DOLLARS);
assert.notEqual(stakeProviderId, 0, "stakeProviderId should not be zero");
const stakeProviderId = await createMsaAndProvider(fundingSource, stakeKeys, 'ReplProv', 50n * DOLLARS);
assert.notEqual(stakeProviderId, 0, 'stakeProviderId should not be zero');
await stakeToProvider(fundingSource, stakeKeys, stakeProviderId, stakingAmount);
return [stakeKeys, stakeProviderId];
}


before(async function () {
// Replenishment requires the epoch length to be shorter than testnet (set in globalHooks)
if (isTestnet()) this.skip();

schemaId = await getOrCreateGraphChangeSchema(fundingSource);
});

describe("Capacity is replenished", function () {
it("after new epoch", async function () {
describe('Capacity is replenished', function () {
it('after new epoch', async function () {
const totalStaked = 3n * DOLLARS;
let expectedCapacity = totalStaked / TokenPerCapacity;
const [stakeKeys, stakeProviderId] = await createAndStakeProvider("ReplFirst", totalStaked);
const payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 })
const expectedCapacity = totalStaked / TokenPerCapacity;
const [stakeKeys, stakeProviderId] = await createAndStakeProvider('ReplFirst', totalStaked);
const payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 });
const call = ExtrinsicHelper.addOnChainMessage(stakeKeys, schemaId, payload);
let nonce = await getNonce(stakeKeys);

// confirm that we start with a full tank
await ExtrinsicHelper.runToBlock(await getNextEpochBlock());
let remainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
assert.equal(expectedCapacity, remainingCapacity, "Our expected capacity from staking is wrong");
let remainingCapacity = (await getCapacity(stakeProviderId)).remainingCapacity.toBigInt();
assert.equal(expectedCapacity, remainingCapacity, 'Our expected capacity from staking is wrong');

await call.payWithCapacity(-1);
remainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
assert(expectedCapacity > remainingCapacity, "Our remaining capacity is much higher than expected.");
await call.payWithCapacity(nonce++);
remainingCapacity = (await getCapacity(stakeProviderId)).remainingCapacity.toBigInt();
assert(expectedCapacity > remainingCapacity, 'Our remaining capacity is much higher than expected.');
const capacityPerCall = expectedCapacity - remainingCapacity;
assert(remainingCapacity > capacityPerCall, "We don't have enough to make a second call");

// one more txn to deplete capacity more so this current remaining is different from when
// we submitted the first message.
await call.payWithCapacity(-1);
await call.payWithCapacity(nonce++);
await ExtrinsicHelper.runToBlock(await getNextEpochBlock());

// this should cause capacity to be refilled and then deducted by the cost of one message.
await call.payWithCapacity(-1);
let newRemainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
await call.payWithCapacity(nonce++);
const newRemainingCapacity = (await getCapacity(stakeProviderId)).remainingCapacity.toBigInt();

// this should be the same as after sending the first message, since it is the first message after
// the epoch.
Expand All @@ -76,83 +76,89 @@ describe("Capacity Replenishment Testing: ", function () {
});

function assert_capacity_call_fails_with_balance_too_low(call: Extrinsic) {
return assert.rejects(
call.payWithCapacity(-1), { name: "RpcError", message: /1010.+account balance too low/ });
return assert.rejects(call.payWithCapacity('current'), {
name: 'RpcError',
message: /1010.+account balance too low/,
});
}

describe("Capacity is not replenished", function () {
it("if out of capacity and last_replenished_at is <= current epoch", async function () {
let [stakeKeys, stakeProviderId] = await createAndStakeProvider("NoSend", 150n * CENTS);
let payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 })
let call = ExtrinsicHelper.addOnChainMessage(stakeKeys, schemaId, payload);
describe('Capacity is not replenished', function () {
it('if out of capacity and last_replenished_at is <= current epoch', async function () {
const [stakeKeys, stakeProviderId] = await createAndStakeProvider('NoSend', 150n * CENTS);
const payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 });
const call = ExtrinsicHelper.addOnChainMessage(stakeKeys, schemaId, payload);

// run until we can't afford to send another message.
await drainCapacity(call, stakeProviderId, stakeKeys);
await drainCapacity(call, stakeProviderId);

await assert_capacity_call_fails_with_balance_too_low(call);
});
});

describe("Regression test: when user attempts to stake tiny amounts before provider's first message of an epoch,", function () {
it("provider is still replenished and can send a message", async function () {
it('provider is still replenished and can send a message', async function () {
const providerStakeAmt = 3n * DOLLARS;
const userStakeAmt = 100n * CENTS;
const userIncrementAmt = 1n * CENTS;

const [stakeKeys, stakeProviderId] = await createAndStakeProvider("TinyStake", providerStakeAmt);
const [stakeKeys, stakeProviderId] = await createAndStakeProvider('TinyStake', providerStakeAmt);
// new user/msa stakes to provider
const userKeys = createKeys("userKeys");
const userKeys = createKeys('userKeys');
await fundKeypair(fundingSource, userKeys, 5n * DOLLARS);
const { eventMap } = await ExtrinsicHelper.stake(userKeys, stakeProviderId, userStakeAmt).fundAndSend(fundingSource);
const { eventMap } = await ExtrinsicHelper.stake(userKeys, stakeProviderId, userStakeAmt).signAndSend();
assertEvent(eventMap, 'system.ExtrinsicSuccess');

const payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 })
const payload = JSON.stringify({ changeType: 1, fromId: 1, objectId: 2 });
const call = ExtrinsicHelper.addOnChainMessage(stakeKeys, schemaId, payload);

let expectedCapacity = (providerStakeAmt + userStakeAmt) / TokenPerCapacity;
const totalCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
const expectedCapacity = (providerStakeAmt + userStakeAmt) / TokenPerCapacity;
const totalCapacity = (await getCapacity(stakeProviderId)).totalCapacityIssued.toBigInt();
assert.equal(expectedCapacity, totalCapacity, `expected ${expectedCapacity} capacity, got ${totalCapacity}`);

const callCapacityCost = await drainCapacity(call, stakeProviderId, stakeKeys);
const callCapacityCost = await drainCapacity(call, stakeProviderId);

// ensure provider can't send a message; they are out of capacity
await assert_capacity_call_fails_with_balance_too_low(call);

// go to next epoch
let nextEpochBlock = await getNextEpochBlock();
const nextEpochBlock = await getNextEpochBlock();
await ExtrinsicHelper.runToBlock(nextEpochBlock);

let remainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
let remainingCapacity = (await getCapacity(stakeProviderId)).remainingCapacity.toBigInt();
// double check we still do not have enough to send another message
assert(remainingCapacity < callCapacityCost);

// user stakes tiny additional amount
const { eventMap: hasStaked } = await ExtrinsicHelper.stake(userKeys, stakeProviderId, userIncrementAmt).fundAndSend(fundingSource);
const { eventMap: hasStaked } = await ExtrinsicHelper.stake(
userKeys,
stakeProviderId,
userIncrementAmt
).signAndSend();
assertEvent(hasStaked, 'capacity.Staked');

// provider can now send a message
const { eventMap: hasCapacityWithdrawn } = await call.payWithCapacity(-1);
const { eventMap: hasCapacityWithdrawn } = await call.payWithCapacity();
assertEvent(hasCapacityWithdrawn, 'capacity.CapacityWithdrawn');

remainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
remainingCapacity = (await getCapacity(stakeProviderId)).remainingCapacity.toBigInt();
// show that capacity was replenished and then fee deducted.
let approxExpected = providerStakeAmt + userStakeAmt + userIncrementAmt - callCapacityCost;
const approxExpected = providerStakeAmt + userStakeAmt + userIncrementAmt - callCapacityCost;
assert(remainingCapacity <= approxExpected, `remainingCapacity = ${remainingCapacity.toString()}`);
});
});
});

async function drainCapacity(call, stakeProviderId: u64, stakeKeys: KeyringPair): Promise<bigint> {
const totalCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
async function drainCapacity(call, stakeProviderId: u64): Promise<bigint> {
const totalCapacity = (await getCapacity(stakeProviderId)).totalCapacityIssued.toBigInt();
let nonce = await getNonce(call.keys);
// Figure out the cost per call in Capacity
await call.payWithCapacity(-1);
let remainingCapacity = (await getRemainingCapacity(stakeProviderId)).toBigInt();
const callCapacityCost = totalCapacity - remainingCapacity;
const { eventMap } = await call.payWithCapacity(nonce++);

const callCapacityCost = eventMap['capacity.CapacityWithdrawn'].data.amount.toBigInt();

// Run them out of funds, but don't flake just because it landed near an epoch boundary.
// // Run them out of funds, but don't flake just because it landed near an epoch boundary.
await ExtrinsicHelper.runToBlock(await getNextEpochBlock());
const callsBeforeEmpty = Math.floor(Number(totalCapacity) / Number(callCapacityCost));
const nonce = await getNonce(stakeKeys);
await Promise.all(Array.from({ length: callsBeforeEmpty }, (_, k) => call.payWithCapacity(nonce + k)));
return callCapacityCost;
}
Loading

0 comments on commit 14c0700

Please sign in to comment.