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

add services payment test #760

Merged
merged 8 commits into from
Dec 2, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import "@tanssi/api-augment";
import { describeSuite, expect, beforeAll, customDevRpcRequest } from "@moonwall/cli";
import { ApiPromise } from "@polkadot/api";
import { generateKeyringPair, KeyringPair } from "@moonwall/util";
import { jumpToSession, jumpSessions } from "util/block";
import { paraIdTank } from "util/payment";

describeSuite({
id: "DTR0902",
title: "Services payment test suite",
foundationMethods: "dev",
testCases: ({ it, context }) => {
let polkadotJs: ApiPromise;
let alice: KeyringPair;
const blocksPerSession = 10n;

beforeAll(async () => {
polkadotJs = context.polkadotJs();
alice = context.keyring.alice;
});
it({
id: "E01",
title: "Genesis container chains have credits and collators",
test: async function () {
await context.createBlock();
await customDevRpcRequest("mock_enableParaInherentCandidate", []);
// Since collators are not assigned until session 2, we need to go till session 2 to actually see heads being injected
await jumpToSession(context, 2);
const parasRegistered = await polkadotJs.query.containerRegistrar.registeredParaIds();

for (const paraId of parasRegistered.toJSON()) {
// Should have credits
const credits = await polkadotJs.query.servicesPayment.blockProductionCredits(paraId);
expect(
credits.toJSON(),
`Container chain ${paraId} does not have enough credits at genesis`
).toBeGreaterThanOrEqual(2n * blocksPerSession);

// Should have assigned collators
const collators = await polkadotJs.query.tanssiCollatorAssignment.collatorContainerChain();

// We are evaluating blockCredits for now, so lets put a lot of collatorAssignmentCredits
const tx = polkadotJs.tx.servicesPayment.setCollatorAssignmentCredits(paraId, 1000n);
await context.createBlock([await polkadotJs.tx.sudo.sudo(tx).signAsync(alice)]);

// Container chain 2001 does not have any collators, this will result in only 1 container chain
// producing blocks at a time. So if both container chains have 1000 credits, container 2000
// will produce blocks 0-999, and container 2001 will produce blocks 1000-1999.
if (paraId == 2000) {
expect(
collators.toJSON().containerChains[paraId].length,
`Container chain ${paraId} has 0 collators`
).toBeGreaterThan(0);
}
}
},
});

it({
id: "E02",
title: "Creating a container chain block costs credits",
test: async function () {
// Read num credits of para 2000, then create that many blocks. Check that authorNoting.blockNum does not increase anymore
// and collatorAssignment does not have collators

// create at least a couple blocks to at least see one block being consumed
// we will be doing this for the whole test, i.e., creating two blocks to ensure the parachain advances
await context.createBlock();
await context.createBlock();

const paraId = 2000n;

// Create a block, the block number should increase, and the number of credits should decrease
const credits1 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
const containerBlockNum1 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;

// create at least a couple blocks to at least see one block being consumed
await context.createBlock();
await context.createBlock();

const credits2 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
const containerBlockNum2 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
expect(containerBlockNum1, "container chain 2000 did not create a block").toBeLessThan(
containerBlockNum2
);
expect(credits1, "container chain 2000 created a block without burning any credits").toBeGreaterThan(
credits2
);
girazoki marked this conversation as resolved.
Show resolved Hide resolved

expect(
credits1 - credits2,
"container chain 2000 created a block without burning any credits"
).to.be.eq(containerBlockNum2 - containerBlockNum1);
},
});

it({
id: "E03",
title: "Collators are unassigned when a container chain does not have enough credits",
test: async function () {
// Create blocks until authorNoting.blockNum does not increase anymore.
// Check that collatorAssignment does not have collators and num credits is less than 2 sessions.

const paraId = 2000n;

// Create blocks until the block number stops increasing
let containerBlockNum3 = -1;
let containerBlockNum4 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;

while (containerBlockNum3 != containerBlockNum4) {
await context.createBlock();
await context.createBlock();
containerBlockNum3 = containerBlockNum4;
containerBlockNum4 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
}

// Now the container chain should have less than 2 sessions worth of credits
const credits = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
expect(
credits,
"Container chain 2000 has stopped producing blocks, so it should not have enough credits"
).toBeLessThan(2n * blocksPerSession);

const collators = await polkadotJs.query.tanssiCollatorAssignment.collatorContainerChain();
expect(
collators.toJSON().containerChains[paraId],
`Container chain ${paraId} should have 0 collators`
).toBeUndefined();
},
});

it({
id: "E04",
title: "Root can remove credits",
test: async function () {
// Remove all the credits of container chain 2001, which should have assigned collators now
// This checks that the node does not panic when we try to subtract credits from 0 (saturating_sub)

const paraId = 2001n;
const credits = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
expect(credits, "Container chain 2001 does not have enough credits").toBeGreaterThanOrEqual(
2n * blocksPerSession
);

// Should have assigned collators
const collators = await polkadotJs.query.tanssiCollatorAssignment.collatorContainerChain();
expect(
collators.toJSON().containerChains[paraId].length,
`Container chain ${paraId} has 0 collators`
).toBeGreaterThan(0);

await context.createBlock();
await context.createBlock();

// Create a block, the block number should increase, and the number of credits should decrease
const credits1 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
const containerBlockNum1 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
await context.createBlock();
await context.createBlock();
const credits2 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
const containerBlockNum2 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
expect(containerBlockNum1, "container chain 2001 did not create a block").toBeLessThan(
containerBlockNum2
);
expect(credits1, "container chain 2001 created a block without burning any credits").toBeGreaterThan(
credits2
);

// Set credits to 0
const tx = polkadotJs.tx.servicesPayment.setBlockProductionCredits(paraId, 0n);
await context.createBlock([await polkadotJs.tx.sudo.sudo(tx).signAsync(alice)]);

const credits3 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON() || 0;
expect(credits3).toBe(0);
// Can still create blocks
const containerBlockNum3 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
await context.createBlock();
await context.createBlock();
const credits4 = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON() || 0;
const containerBlockNum4 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
expect(
containerBlockNum3,
"container chain 2001 did not create a block after root set credits to 0"
).toBeLessThan(containerBlockNum4);
// But credits cannot be lower than 0
expect(credits4, "container chain 2001 has negative credits").toBe(0);
},
});

it({
id: "E05",
title: "Can buy additional credits",
test: async function () {
// As alice, buy credits for para 2000. Check that it is assigned collators again
const paraId = 2000n;

// Create blocks until no collators are assigned to any container chain
for (;;) {
await context.createBlock();
const collators = await polkadotJs.query.tanssiCollatorAssignment.collatorContainerChain();
if (Object.keys(collators.toJSON().containerChains).length == 0) {
break;
}
}

// Use random account instead of alice because alice is getting block rewards
const randomAccount = generateKeyringPair("sr25519");
const value = 100_000_000_000n;
await context.createBlock([
await polkadotJs.tx.balances.transferAllowDeath(randomAccount.address, value).signAsync(alice),
]);

// Now, buy some credits for container chain 2000
const balanceBefore = (
await polkadotJs.query.system.account(randomAccount.address)
).data.free.toBigInt();
const purchasedCredits = 1000n * blocksPerSession;

const requiredBalance = purchasedCredits * 1_000_000n;
const tx = polkadotJs.tx.servicesPayment.purchaseCredits(paraId, requiredBalance);
await context.createBlock([await tx.signAsync(randomAccount)]);

const balanceAfter = (
await polkadotJs.query.system.account(randomAccount.address)
).data.free.toBigInt();
expect(balanceAfter).toBeLessThan(balanceBefore);

const balanceTank = (await polkadotJs.query.system.account(paraIdTank(paraId))).data.free.toBigInt();
expect(balanceTank).toBe(requiredBalance);

// Check that after 2 sessions, container chain 2000 has collators and is producing blocks
await jumpSessions(context, 2);

// spend all credits
let creditsRemaining = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
while (creditsRemaining != 0) {
await context.createBlock();
await context.createBlock();
creditsRemaining = (await polkadotJs.query.servicesPayment.blockProductionCredits(paraId)).toJSON();
}

// create a new block that should trigger para balance to go down
await context.createBlock();
await context.createBlock();

const collators = await polkadotJs.query.tanssiCollatorAssignment.collatorContainerChain();
expect(
collators.toJSON().containerChains[paraId].length,
`Container chain ${paraId} has 0 collators`
).toBeGreaterThan(0);
expect(balanceTank).toBe(requiredBalance);

// Create a block, the block number should increase, and the number of credits should decrease
const containerBlockNum3 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
await context.createBlock();
await context.createBlock();
const containerBlockNum4 = await (await polkadotJs.query.authorNoting.latestAuthor(paraId)).toJSON()
.blockNumber;
expect(containerBlockNum3, "container chain 2000 did not create a block").toBeLessThan(
containerBlockNum4
);
const balanceTankAfter = (
await polkadotJs.query.system.account(paraIdTank(paraId))
).data.free.toBigInt();
expect(balanceTank, "container chain 2000 created a block without burning any credits").toBeGreaterThan(
balanceTankAfter
);
},
});
},
});
Loading
Loading