From b7cc9abaf71b8d540838e2a3efb960f2f00a885a Mon Sep 17 00:00:00 2001 From: girazoki Date: Wed, 7 Feb 2024 18:16:36 +0100 Subject: [PATCH] new tests --- pallets/collator-assignment/src/lib.rs | 16 +- ...ment_block_credit_buying_free_combined.ts} | 6 +- ...nt_collator_credit_buying_free_combined.ts | 89 ++++++++ ...=> test_services_payment_block_credits.ts} | 0 .../test_services_payment_collator_credits.ts | 198 ++++++++++++++++++ 5 files changed, 304 insertions(+), 5 deletions(-) rename test/suites/common-tanssi/services-payment/{test_services_pament_credit_buying_free_combined.ts => test_services_pament_block_credit_buying_free_combined.ts} (97%) create mode 100644 test/suites/common-tanssi/services-payment/test_services_pament_collator_credit_buying_free_combined.ts rename test/suites/common-tanssi/services-payment/{test_services_payment.ts => test_services_payment_block_credits.ts} (100%) create mode 100644 test/suites/common-tanssi/services-payment/test_services_payment_collator_credits.ts diff --git a/pallets/collator-assignment/src/lib.rs b/pallets/collator-assignment/src/lib.rs index 7feace311..4f0ff0af0 100644 --- a/pallets/collator-assignment/src/lib.rs +++ b/pallets/collator-assignment/src/lib.rs @@ -218,7 +218,6 @@ pub mod pallet { let collators_per_container = T::HostConfiguration::collators_per_container(target_session_index); for para_id in &container_chain_ids { - T::CollatorAssignmentHook::on_collators_assigned(*para_id); chains.push(ChainNumCollators { para_id: *para_id, min_collators: collators_per_container, @@ -228,7 +227,6 @@ pub mod pallet { let collators_per_parathread = T::HostConfiguration::collators_per_parathread(target_session_index); for para_id in ¶threads { - T::CollatorAssignmentHook::on_collators_assigned(*para_id); chains.push(ChainNumCollators { para_id: *para_id, min_collators: collators_per_parathread, @@ -289,6 +287,20 @@ pub mod pallet { } }; + // TODO: this probably is asking for a refactor + // only apply the onCollatorAssignedHook if sufficient collators + for para_id in &container_chain_ids { + if !new_assigned.container_chains.get(para_id).unwrap_or(&vec![]).is_empty() { + T::CollatorAssignmentHook::on_collators_assigned(*para_id); + } + } + + for para_id in ¶threads { + if !new_assigned.container_chains.get(para_id).unwrap_or(&vec![]).is_empty() { + T::CollatorAssignmentHook::on_collators_assigned(*para_id); + } + } + let mut pending = PendingCollatorContainerChain::::get(); let old_assigned_changed = old_assigned != new_assigned; let mut pending_changed = false; diff --git a/test/suites/common-tanssi/services-payment/test_services_pament_credit_buying_free_combined.ts b/test/suites/common-tanssi/services-payment/test_services_pament_block_credit_buying_free_combined.ts similarity index 97% rename from test/suites/common-tanssi/services-payment/test_services_pament_credit_buying_free_combined.ts rename to test/suites/common-tanssi/services-payment/test_services_pament_block_credit_buying_free_combined.ts index 65f8073f3..00695ed49 100644 --- a/test/suites/common-tanssi/services-payment/test_services_pament_credit_buying_free_combined.ts +++ b/test/suites/common-tanssi/services-payment/test_services_pament_block_credit_buying_free_combined.ts @@ -22,7 +22,7 @@ describeSuite({ it({ id: "E01", - title: "Collators are unassigned when a container chain does not have enough credits", + title: "Collators are unassigned when a container chain does not have enough block 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. @@ -45,7 +45,7 @@ describeSuite({ }); it({ id: "E02", - title: "Collators are not assigned when we buy 2 session + ED -1", + title: "Collators are not assigned when we buy 2 session + ED -1 of block credits", test: async function () { const tx2000OneSession = polkadotJs.tx.servicesPayment.setBlockProductionCredits( paraId2000, @@ -71,7 +71,7 @@ describeSuite({ }); it({ id: "E03", - title: "Collators are assigned when we buy at least 2 session + ED", + title: "Collators are assigned when we buy at least 2 session + ED of block credits", test: async function () { // Now, buy the remaining const purchasedCredits = 1n; diff --git a/test/suites/common-tanssi/services-payment/test_services_pament_collator_credit_buying_free_combined.ts b/test/suites/common-tanssi/services-payment/test_services_pament_collator_credit_buying_free_combined.ts new file mode 100644 index 000000000..df2c02457 --- /dev/null +++ b/test/suites/common-tanssi/services-payment/test_services_pament_collator_credit_buying_free_combined.ts @@ -0,0 +1,89 @@ +import "@tanssi/api-augment"; +import { describeSuite, expect, beforeAll } from "@moonwall/cli"; +import { ApiPromise } from "@polkadot/api"; +import { KeyringPair } from "@moonwall/util"; +import { jumpSessions } from "util/block"; + +describeSuite({ + id: "CT0604", + title: "Services payment test suite", + foundationMethods: "dev", + testCases: ({ it, context }) => { + let polkadotJs: ApiPromise; + let alice: KeyringPair; + const paraId2000 = 2000n; + const paraId2001 = 2001n; + const costPerSession = 100_000_000n; + beforeAll(async () => { + polkadotJs = context.polkadotJs(); + alice = context.keyring.alice; + }); + + it({ + id: "E01", + title: "Collators are unassigned when a container chain does not have enough collator assignment 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 tx2000free = polkadotJs.tx.servicesPayment.setCollatorAssignmentCredits(paraId2000, 0n); + const tx2001free = polkadotJs.tx.servicesPayment.setCollatorAssignmentCredits(paraId2001, 0n); + + await context.createBlock([await polkadotJs.tx.sudo.sudo(tx2000free).signAsync(alice)]); + await context.createBlock([await polkadotJs.tx.sudo.sudo(tx2001free).signAsync(alice)]); + + // Check that after 2 sessions, container chain 2000 has collators and is producing blocks + await jumpSessions(context, 2); + + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collators.toJSON().containerChains[paraId2000], + `Container chain ${paraId2000} should have 0 collators` + ).toBeUndefined(); + }, + }); + it({ + id: "E02", + title: "Collators are not assigned when we buy 2 session + ED -1 of collator assignment credits", + test: async function () { + const tx2000OneSession = polkadotJs.tx.servicesPayment.setCollatorAssignmentCredits(paraId2000, 1); + await context.createBlock([await polkadotJs.tx.sudo.sudo(tx2000OneSession).signAsync(alice)]); + const existentialDeposit = await polkadotJs.consts.balances.existentialDeposit.toBigInt(); + // Now, buy some credits for container chain 2000. we only buy ones session -1 + const purchasedCredits = costPerSession + existentialDeposit - 1n; + // Check that after 2 sessions, container chain 2000 has not collators + const tx = polkadotJs.tx.servicesPayment.purchaseCredits(paraId2000, purchasedCredits); + await context.createBlock([await tx.signAsync(alice)]); + + // Check that after 2 sessions, container chain 2000 has 0 collators and is not producing blocks + await jumpSessions(context, 2); + + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collators.toJSON().containerChains[paraId2000], + `Container chain ${paraId2000} should have 0 collators` + ).toBeUndefined(); + }, + }); + it({ + id: "E03", + title: "Collators are assigned when we buy at least 2 session + ED of block credits", + test: async function () { + // Now, buy the remaining + const purchasedCredits = 1n; + // Purchase the remaining 1 + const tx = polkadotJs.tx.servicesPayment.purchaseCredits(paraId2000, purchasedCredits); + await context.createBlock([await tx.signAsync(alice)]); + + // Check that after 2 sessions, container chain 2000 has collators and is producing blocks + await jumpSessions(context, 2); + + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collators.toJSON().containerChains[paraId2000].length, + `Container chain ${paraId2000} has 0 collators` + ).toBeGreaterThan(0); + }, + }); + }, +}); diff --git a/test/suites/common-tanssi/services-payment/test_services_payment.ts b/test/suites/common-tanssi/services-payment/test_services_payment_block_credits.ts similarity index 100% rename from test/suites/common-tanssi/services-payment/test_services_payment.ts rename to test/suites/common-tanssi/services-payment/test_services_payment_block_credits.ts diff --git a/test/suites/common-tanssi/services-payment/test_services_payment_collator_credits.ts b/test/suites/common-tanssi/services-payment/test_services_payment_collator_credits.ts new file mode 100644 index 000000000..0fed52a94 --- /dev/null +++ b/test/suites/common-tanssi/services-payment/test_services_payment_collator_credits.ts @@ -0,0 +1,198 @@ +import "@tanssi/api-augment"; +import { describeSuite, expect, beforeAll } from "@moonwall/cli"; +import { ApiPromise } from "@polkadot/api"; +import { generateKeyringPair, KeyringPair } from "@moonwall/util"; +import { jumpSessions } from "util/block"; +import { paraIdTank } from "util/payment"; + +describeSuite({ + id: "CT0601", + title: "Services payment test suite", + foundationMethods: "dev", + testCases: ({ it, context }) => { + let polkadotJs: ApiPromise; + let alice: KeyringPair; + const startingCredits = 100n; + + beforeAll(async () => { + polkadotJs = context.polkadotJs(); + alice = context.keyring.alice; + }); + it({ + id: "E01", + title: "Genesis container chains have credits and collators and should have one less credit", + test: async function () { + await context.createBlock(); + const parasRegistered = await polkadotJs.query.registrar.registeredParaIds(); + + for (const paraId of parasRegistered) { + // Should have credits + const credits = await polkadotJs.query.servicesPayment.collatorAssignmentCredits(paraId); + + // Should have assigned collators + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + + // 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.toBigInt() === 2000n) { + expect( + credits.unwrap().toBigInt(), + `Container chain ${paraId} should have applied session credits` + ).toBe(startingCredits - 1n); + expect( + collators.toJSON().containerChains[paraId.toString()].length, + `Container chain ${paraId} has 0 collators` + ).toBeGreaterThan(0); + } else { + expect( + credits.unwrap().toBigInt(), + `Container chain ${paraId} should not have substracted credits` + ).toBe(startingCredits); + expect( + collators.toJSON().containerChains[paraId.toString()].length, + `Container chain ${paraId} has 0 collators` + ).toBe(0); + } + } + }, + }); + + it({ + id: "E02", + title: "Getting assignation should consume credits", + test: async function () { + // Moving to the next session should have reduced the credit by one to both parachains + // even if one does not produce blocks + + const paraId = 2000n; + await jumpSessions(context, 1); + const credits = await polkadotJs.query.servicesPayment.collatorAssignmentCredits(paraId); + expect( + credits.unwrap().toBigInt(), + `Container chain ${paraId} does not have enough credits at genesis` + ).toBe(startingCredits - 2n); + }, + }); + + 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(); + 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.collatorAssignmentCredits(paraId)).toJSON(); + expect( + credits, + "Container chain 2000 has stopped producing blocks, so it should not have enough credits" + ).toBeLessThan(2n); + + const collators = await polkadotJs.query.collatorAssignment.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.collatorAssignmentCredits(paraId)).toJSON(); + expect(credits, "Container chain 2001 does not have enough credits").toBeGreaterThanOrEqual(2n); + + // Should have assigned collators + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collators.toJSON().containerChains[paraId].length, + `Container chain ${paraId} has 0 collators` + ).toBeGreaterThan(0); + + // Set credits to 0 + const tx = polkadotJs.tx.servicesPayment.setCollatorAssignmentCredits(paraId, 0n); + await context.createBlock([await polkadotJs.tx.sudo.sudo(tx).signAsync(alice)]); + + // After 2 sessions, the container-chain should not be assigned + await jumpSessions(context, 2); + const collatorsAfter = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collatorsAfter.toJSON().containerChains[paraId], + `Container chain ${paraId} should have 0 collators` + ).toBeUndefined(); + }, + }); + + 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.collatorAssignment.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 = 100n; + + const requiredBalance = purchasedCredits * 100_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); + + const collators = await polkadotJs.query.collatorAssignment.collatorContainerChain(); + expect( + collators.toJSON().containerChains[paraId].length, + `Container chain ${paraId} has 0 collators` + ).toBeGreaterThan(0); + expect(balanceTank).toBe(requiredBalance); + }, + }); + }, +});