Browse Source

Merge pull request #274 from gleb-urvanov/feature/upgrade-runtime-test

Feature/upgrade runtime test
Mokhtar Naamani 4 years ago
parent
commit
dc4ab3ae3c

+ 4 - 2
tests/network-tests/.env

@@ -3,7 +3,7 @@ NODE_URL = ws://127.0.0.1:9944
 # Account which is expected to provide sufficient funds to test accounts.
 SUDO_ACCOUNT_URI = //Alice
 # Amount of members able to buy membership in membership creation test.
-MEMBERSHIP_CREATION_N = 1
+MEMBERSHIP_CREATION_N = 2
 # ID of the membership paid terms used in membership creation test.
 MEMBERSHIP_PAID_TERMS = 0
 # Council stake amount for first K accounts in council election test.
@@ -11,4 +11,6 @@ COUNCIL_STAKE_GREATER_AMOUNT = 1500
 # Council stake amount for first the rest participants in council election test.
 COUNCIL_STAKE_LESSER_AMOUNT = 1000
 # Number of members with greater stake in council election test.
-COUNCIL_ELECTION_K = 1
+COUNCIL_ELECTION_K = 2
+# Stake for runtime upgrade proposal test
+RUNTIME_UPGRADE_PROPOSAL_STAKE = 200

+ 9 - 6
tests/network-tests/src/tests/electingCouncilTest.ts

@@ -9,7 +9,7 @@ import { assert } from 'chai';
 import { v4 as uuid } from 'uuid';
 import { Utils } from '../utils/utils';
 
-describe('Council integration tests', () => {
+export function councilTest(m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[]) {
   initConfig();
   const keyring = new Keyring({ type: 'sr25519' });
   const nodeUrl: string = process.env.NODE_URL!;
@@ -20,8 +20,6 @@ describe('Council integration tests', () => {
   const defaultTimeout: number = 120000;
   let sudo: KeyringPair;
   let apiWrapper: ApiWrapper;
-  const m1KeyPairs: KeyringPair[] = new Array();
-  const m2KeyPairs: KeyringPair[] = new Array();
 
   before(async function () {
     this.timeout(defaultTimeout);
@@ -30,9 +28,6 @@ describe('Council integration tests', () => {
     apiWrapper = await ApiWrapper.create(provider);
   });
 
-  membershipTest(m1KeyPairs);
-  membershipTest(m2KeyPairs);
-
   it('Electing a council test', async () => {
     // Setup goes here because M keypairs are generated after before() function
     sudo = keyring.addFromUri(sudoUri);
@@ -121,4 +116,12 @@ describe('Council integration tests', () => {
   after(() => {
     apiWrapper.close();
   });
+}
+
+describe.skip('Council integration tests', () => {
+  const m1KeyPairs: KeyringPair[] = new Array();
+  const m2KeyPairs: KeyringPair[] = new Array();
+  membershipTest(m1KeyPairs);
+  membershipTest(m2KeyPairs);
+  councilTest(m1KeyPairs, m2KeyPairs);
 });

+ 75 - 0
tests/network-tests/src/tests/updateRuntimeTest.ts

@@ -0,0 +1,75 @@
+import { initConfig } from '../utils/config';
+import { Keyring, WsProvider } from '@polkadot/api';
+import { Bytes } from '@polkadot/types';
+import { KeyringPair } from '@polkadot/keyring/types';
+import { membershipTest } from './membershipCreationTest';
+import { councilTest } from './electingCouncilTest';
+import { registerJoystreamTypes } from '@joystream/types';
+import { ApiWrapper } from '../utils/apiWrapper';
+import BN = require('bn.js');
+
+describe('Runtime upgrade integration tests', () => {
+  initConfig();
+  const keyring = new Keyring({ type: 'sr25519' });
+  const nodeUrl: string = process.env.NODE_URL!;
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!;
+  const proposalStake: BN = new BN(+process.env.RUNTIME_UPGRADE_PROPOSAL_STAKE!);
+  const defaultTimeout: number = 120000;
+
+  const m1KeyPairs: KeyringPair[] = new Array();
+  const m2KeyPairs: KeyringPair[] = new Array();
+
+  let apiWrapper: ApiWrapper;
+  let sudo: KeyringPair;
+
+  before(async function () {
+    this.timeout(defaultTimeout);
+    registerJoystreamTypes();
+    const provider = new WsProvider(nodeUrl);
+    apiWrapper = await ApiWrapper.create(provider);
+  });
+
+  membershipTest(m1KeyPairs);
+  membershipTest(m2KeyPairs);
+  councilTest(m1KeyPairs, m2KeyPairs);
+
+  it('Upgrading the runtime test', async () => {
+    // Setup
+    sudo = keyring.addFromUri(sudoUri);
+    const runtime: Bytes = await apiWrapper.getRuntime();
+    const description: string = 'runtime upgrade proposal which is used for API integration testing';
+    const runtimeProposalFee: BN = apiWrapper.estimateProposeRuntimeUpgradeFee(
+      proposalStake,
+      description,
+      description,
+      runtime
+    );
+    const runtimeVoteFee: BN = apiWrapper.estimateVoteForProposalFee();
+
+    // Topping the balances
+    await apiWrapper.transferBalance(sudo, m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake));
+    await apiWrapper.transferBalanceToAccounts(sudo, m2KeyPairs, runtimeVoteFee);
+
+    // Proposal creation
+    const proposalPromise = apiWrapper.expectProposalCreated();
+    await apiWrapper.proposeRuntime(
+      m1KeyPairs[0],
+      proposalStake,
+      'testing runtime',
+      'runtime to test proposal functionality',
+      runtime
+    );
+    const proposalNumber = await proposalPromise;
+
+    // Approving runtime update proposal
+    const runtimePromise = apiWrapper.expectRuntimeUpgraded();
+    await apiWrapper.batchApproveProposal(m2KeyPairs, proposalNumber);
+    await runtimePromise;
+  }).timeout(defaultTimeout);
+
+  membershipTest(new Array<KeyringPair>());
+
+  after(() => {
+    apiWrapper.close();
+  });
+});

+ 69 - 4
tests/network-tests/src/utils/apiWrapper.ts

@@ -1,10 +1,10 @@
 import { ApiPromise, WsProvider } from '@polkadot/api';
-import { Option, Vec, UInt } from '@polkadot/types';
+import { Option, Vec, Bytes } from '@polkadot/types';
 import { Codec } from '@polkadot/types/types';
 import { KeyringPair } from '@polkadot/keyring/types';
 import { UserInfo, PaidMembershipTerms } from '@joystream/types/lib/members';
-import { Seat } from '@joystream/types';
-import { Balance } from '@polkadot/types/interfaces';
+import { Seat, VoteKind } from '@joystream/types';
+import { Balance, EventRecord } from '@polkadot/types/interfaces';
 import BN = require('bn.js');
 import { SubmittableExtrinsic } from '@polkadot/api/types';
 import { Sender } from './sender';
@@ -99,6 +99,14 @@ export class ApiWrapper {
     return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt));
   }
 
+  public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes): BN {
+    return this.estimateTxFee(this.api.tx.proposals.createProposal(stake, name, description, runtime));
+  }
+
+  public estimateVoteForProposalFee(): BN {
+    return this.estimateTxFee(this.api.tx.proposals.voteOnProposal(0, 'Approve'));
+  }
+
   private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
     return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false);
   }
@@ -181,12 +189,69 @@ export class ApiWrapper {
 
   public getCouncil(): Promise<Seat[]> {
     return this.api.query.council.activeCouncil<Vec<Codec>>().then(seats => {
-      console.log('elected council ' + seats.toString());
       return JSON.parse(seats.toString());
     });
   }
 
+  public getRuntime(): Promise<Bytes> {
+    return this.api.query.substrate.code<Bytes>();
+  }
+
+  public proposeRuntime(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    runtime: Bytes
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.proposals.createProposal(stake, name, description, runtime),
+      account,
+      false
+    );
+  }
+
+  public approveProposal(account: KeyringPair, proposal: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.proposals.voteOnProposal(proposal, new VoteKind('Approve')),
+      account,
+      false
+    );
+  }
+
+  public batchApproveProposal(council: KeyringPair[], proposal: BN): Promise<void[]> {
+    return Promise.all(
+      council.map(async keyPair => {
+        await this.approveProposal(keyPair, proposal);
+      })
+    );
+  }
+
   public getBlockDuration(): BN {
     return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime).toBn();
   }
+
+  public expectProposalCreated(): Promise<BN> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method.toString() === 'ProposalCreated') {
+            resolve(new BN(record.event.data[1].toString()));
+          }
+        });
+      });
+    });
+  }
+
+  public expectRuntimeUpgraded(): Promise<void> {
+    return new Promise(async resolve => {
+      await this.api.query.system.events<Vec<EventRecord>>(events => {
+        events.forEach(record => {
+          if (record.event.method.toString() === 'RuntimeUpdated') {
+            resolve();
+          }
+        });
+      });
+    });
+  }
 }

+ 2 - 0
tests/network-tests/src/utils/sender.ts

@@ -37,6 +37,7 @@ export class Sender {
   ): Promise<void> {
     return new Promise(async (resolve, reject) => {
       const nonce: BN = await this.getNonce(account.address);
+      // console.log('sending transaction from ' + account.address + ' with nonce ' + nonce);
       const signedTx = tx.sign(account, { nonce });
       await signedTx
         .send(async result => {
@@ -53,6 +54,7 @@ export class Sender {
             resolve();
           }
           if (result.status.isFuture) {
+            console.log('nonce ' + nonce + ' for account ' + account.address + ' is in future');
             this.clearNonce(account.address);
             reject(new Error('Extrinsic nonce is in future'));
           }