import { deployContractManager } from "../test/tools/deploy/contractManager" ; import { deployValidatorService } from "../test/tools/deploy/delegation/validatorService" ; import { deploySkaleManager } from "../test/tools/deploy/skaleManager" ; import { ContractManager , Schains , SchainsInternal , SkaleDKGTester , SkaleManager , ValidatorService } from "../typechain" ; import { privateKeys } from "../test/tools/private-keys" ; import { deploySchains } from "../test/tools/deploy/schains" ; import { deploySchainsInternal } from "../test/tools/deploy/schainsInternal" ; import { deploySkaleDKGTester } from "../test/tools/deploy/test/skaleDKGTester" ; import { skipTime } from "../test/tools/time" ; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address" ; import { ethers } from "hardhat" ; import { Event , Wallet } from "ethers" ; import fs from 'fs' ; import { getPublicKey , getValidatorIdSignature } from "../test/tools/signatures" ; import { stringKeccak256 } from "../test/tools/hashes" ; import { fastBeforeEach } from "../test/tools/mocha" ; import { SchainType } from "../test/tools/types" ; import { applySnapshot , makeSnapshot } from "../test/tools/snapshot" ; function findEvent ( events , eventName ) { if ( events ) { const target = events . find ( ( event ) => event . event === eventName ) ; if ( target ) { return target ; } else { throw new Error ( "Event was not emitted" ) ; } } else { throw new Error ( "Event was not emitted" ) ; } } describe ( "nodeRotation" , ( ) => { let owner ; let validator ; let node ; let contractManager ; let validatorService ; let skaleManager ; let schains ; let schainsInternal ; let skaleDKG ; before ( async ( ) => { [ owner , validator ] = await ethers . getSigners ( ) ; node = new Wallet ( String ( privateKeys [ 3 ] ) ) . connect ( ethers . provider ) ; await owner . sendTransaction ( { value : ethers . utils . parseEther ( "1" ) , to : node . address } ) ; contractManager = await deployContractManager ( ) ; skaleDKG = await deploySkaleDKGTester ( contractManager ) ; validatorService = await deployValidatorService ( contractManager ) ; skaleManager = await deploySkaleManager ( contractManager ) ; schainsInternal = await deploySchainsInternal ( contractManager ) ; schains = await deploySchains ( contractManager ) ; await contractManager . setContractsAddress ( "SkaleDKG" , skaleDKG . address ) ; await validatorService . grantRole ( await validatorService . VALIDATOR_MANAGER_ROLE ( ) , owner . address ) ; await validatorService . disableWhitelist ( ) ; } ) ; describe ( "Tests without memory" , ( ) => { fastBeforeEach ( async ( ) => undefined ) ; it ( "64 node rotations on 17 nodes" , async ( ) => { const validatorId = 1 ; await validatorService . connect ( validator ) . registerValidator ( "Validator" , "" , 0 , 0 ) ; const signature = await getValidatorIdSignature ( validatorId , node ) ; await validatorService . connect ( validator ) . linkNodeAddress ( node . address , signature ) ; await schains . grantRole ( await schains . SCHAIN_CREATOR_ROLE ( ) , owner . address ) ; const nodesAmount = 16 ; for ( let nodeId = 0 ; nodeId < nodesAmount ; ++ nodeId ) { await skaleManager . connect ( node ) . createNode ( 1 , 0 , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , getPublicKey ( node ) , "d2-" + nodeId , "some.domain.name" ) ; } const numberOfSchains = 64 ; for ( let schainNumber = 0 ; schainNumber < numberOfSchains ; schainNumber ++ ) { const result = await ( await schains . addSchainByFoundation ( 0 , SchainType . SMALL , 0 , "schain-" + schainNumber , owner . address , ethers . constants . AddressZero ) ) . wait ( ) ; await skaleDKG . setSuccessfulDKGPublic ( stringKeccak256 ( "schain-" + schainNumber ) ) ; console . log ( "create" , schainNumber + 1 , "schain on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; } await skaleManager . connect ( node ) . createNode ( 1 , 0 , "0x7f" + ( "000000" + Number ( 16 ) . toString ( 16 ) ) . slice ( - 6 ) , "0x7f" + ( "000000" + Number ( 16 ) . toString ( 16 ) ) . slice ( - 6 ) , getPublicKey ( node ) , "d2-16" , "some.domain.name" ) ; const gasLimit = 12e6 ; const rotIndex = Math . floor ( Math . random ( ) * nodesAmount ) ; const schainHashes = await schainsInternal . getSchainHashesForNode ( rotIndex ) ; console . log ( "Rotation for node" , rotIndex ) ; console . log ( "Will process" , schainHashes . length , "rotations" ) ; const gas = [ ] ; for ( let i = 0 ; i < schainHashes . length ; i ++ ) { const estimatedGas = await skaleManager . connect ( node ) . estimateGas . nodeExit ( rotIndex ) ; console . log ( "Estimated gas on nodeExit" , estimatedGas . toString ( ) ) ; const overrides = { gasLimit : estimatedGas } ; const result = await ( await skaleManager . connect ( node ) . nodeExit ( rotIndex , overrides ) ) . wait ( ) ; console . log ( "" + ( i + 1 ) + "" , "Rotation on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; gas . push ( result . gasUsed . toNumber ( ) ) ; if ( result . gasUsed . toNumber ( ) > gasLimit ) { break ; } await skaleDKG . setSuccessfulDKGPublic ( schainHashes [ schainHashes . length - i - 1 ] ) ; } } ) ; } ) ; describe ( "max node rotation on 17 nodes" , ( ) => { const validatorId = 1 ; const nodesAmount = 16 ; const numberOfSchains = 128 ; const gasLimit = 12e6 ; const leavingNode = Math . floor ( Math . random ( ) * nodesAmount ) ; const gas = [ ] ; let schainHashes ; let stateBefore ; before ( async ( ) => { stateBefore = await makeSnapshot ( ) ; await validatorService . connect ( validator ) . registerValidator ( "Validator" , "" , 0 , 0 ) ; const signature = await getValidatorIdSignature ( validatorId , node ) ; await validatorService . connect ( validator ) . linkNodeAddress ( node . address , signature ) ; await schains . grantRole ( await schains . SCHAIN_CREATOR_ROLE ( ) , owner . address ) ; for ( let nodeId = 0 ; nodeId < nodesAmount ; ++ nodeId ) { await skaleManager . connect ( node ) . createNode ( 1 , 0 , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , getPublicKey ( node ) , "d2-" + nodeId , "some.domain.name" ) ; } for ( let schainNumber = 0 ; schainNumber < numberOfSchains ; schainNumber ++ ) { const result = await ( await schains . addSchainByFoundation ( 0 , SchainType . SMALL , 0 , "schain-" + schainNumber , owner . address , ethers . constants . AddressZero ) ) . wait ( ) ; const nodeInGroup = findEvent ( result . events , "SchainNodes" ) . args ?. nodesInGroup ; console . log ( "Nodes in Schain:" ) ; const setOfNodes = new Set ( ) ; for ( const nodeOfSchain of nodeInGroup ) { if ( ! setOfNodes . has ( nodeOfSchain . toNumber ( ) ) ) { setOfNodes . add ( nodeOfSchain . toNumber ( ) ) ; } else { console . log ( "Node" , nodeOfSchain . toNumber ( ) , "already exist" ) ; process . exit ( ) ; } console . log ( nodeOfSchain . toNumber ( ) ) ; } await skaleDKG . setSuccessfulDKGPublic ( stringKeccak256 ( "schain-" + schainNumber ) ) ; console . log ( "create" , schainNumber + 1 , "schain on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; } await skaleManager . connect ( node ) . createNode ( 1 , 0 , "0x7f" + ( "000000" + Number ( 16 ) . toString ( 16 ) ) . slice ( - 6 ) , "0x7f" + ( "000000" + Number ( 16 ) . toString ( 16 ) ) . slice ( - 6 ) , getPublicKey ( node ) , "d2-16" , "some.domain.name" ) ; schainHashes = await schainsInternal . getSchainHashesForNode ( leavingNode ) ; } ) ; for ( let test = 1 ; test <= numberOfSchains ; ++ test ) { it ( "should exit schain #" + test , async ( ) => { if ( ( await node . getBalance ( ) ) . lt ( ethers . utils . parseEther ( "0.1" ) ) ) { await owner . sendTransaction ( { value : ethers . utils . parseEther ( "1" ) , to : node . address } ) ; } const estimatedGas = await skaleManager . estimateGas . nodeExit ( leavingNode ) ; const overrides = { gasLimit : Math . ceil ( estimatedGas . toNumber ( ) * 1.1 ) } ; console . log ( "Estimated gas on nodeExit" , overrides . gasLimit ) ; const result = await ( await skaleManager . connect ( node ) . nodeExit ( leavingNode , overrides ) ) . wait ( ) ; console . log ( "" + test + "" , "Rotation on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; gas . push ( result . gasUsed . toNumber ( ) ) ; if ( result . gasUsed . toNumber ( ) > gasLimit ) { return ; } await skaleDKG . setSuccessfulDKGPublic ( schainHashes [ schainHashes . length - test ] ) ; } ) ; } after ( async ( ) => { await applySnapshot ( stateBefore ) ; } ) ; } ) ; describe ( "random rotation on dynamically creating schains" , ( ) => { const validatorId = 1 ; const maxNodesAmount = 200 ; const gasLimit = 12e6 ; const measurementsSchainCreation = [ ] ; const measurementsRotation = [ ] ; const activeNodes = [ ] ; let nodeId = 0 ; let nodesAmount ; let stateBefore ; before ( async ( ) => { stateBefore = await makeSnapshot ( ) ; await validatorService . connect ( validator ) . registerValidator ( "Validator" , "" , 0 , 0 ) ; const signature = await getValidatorIdSignature ( validatorId , node ) ; await validatorService . connect ( validator ) . linkNodeAddress ( node . address , signature ) ; await schains . grantRole ( await schains . SCHAIN_CREATOR_ROLE ( ) , owner . address ) ; } ) ; beforeEach ( async ( ) => { if ( ( await node . getBalance ( ) ) . lt ( ethers . utils . parseEther ( "0.1" ) ) ) { await owner . sendTransaction ( { value : ethers . utils . parseEther ( "1" ) , to : node . address } ) ; } await skaleManager . connect ( node ) . createNode ( 1 , 0 , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , "0x7f" + ( "000000" + nodeId . toString ( 16 ) ) . slice ( - 6 ) , getPublicKey ( node ) , "d2-" + nodeId , "some.domain.name" ) ; activeNodes . push ( nodeId ) ; ++ nodeId ; nodesAmount = nodeId ; if ( nodesAmount >= 16 ) { const result = await ( await schains . addSchainByFoundation ( 0 , SchainType . SMALL , 0 , "schain-" + nodeId , owner . address , ethers . constants . AddressZero ) ) . wait ( ) ; await skaleDKG . setSuccessfulDKGPublic ( stringKeccak256 ( "schain-" + nodeId ) ) ; console . log ( "create schain on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; measurementsSchainCreation . push ( { nodesAmount , gasUsed : result . gasUsed . toNumber ( ) } ) ; if ( result . gasUsed . toNumber ( ) > gasLimit ) { return ; } } } ) ; for ( let test = 1 ; test <= maxNodesAmount ; ++ test ) { it ( "should rotate on " + test + " nodes" , async ( ) => { if ( nodesAmount >= 155 ) { const randomIndex = Math . floor ( Math . random ( ) * activeNodes . length ) ; const leavingNode = activeNodes [ randomIndex ] ; activeNodes [ randomIndex ] = activeNodes [ activeNodes . length - 1 ] ; activeNodes . pop ( ) ; const schainHashes = await schainsInternal . getSchainHashesForNode ( leavingNode ) ; console . log ( "Rotation for node" , leavingNode ) ; console . log ( "Will process" , schainHashes . length , "rotations" ) ; const gas = [ ] ; for ( let i = 0 ; i < schainHashes . length ; i ++ ) { if ( ( await node . getBalance ( ) ) . lt ( ethers . utils . parseEther ( "0.1" ) ) ) { await owner . sendTransaction ( { value : ethers . utils . parseEther ( "1" ) , to : node . address } ) ; } const estimatedGas = await skaleManager . connect ( node ) . estimateGas . nodeExit ( leavingNode ) ; console . log ( "Estimated gas on nodeExit" , estimatedGas . toString ( ) ) ; const overrides = { gasLimit : estimatedGas } ; const result = await ( await skaleManager . connect ( node ) . nodeExit ( leavingNode , overrides ) ) . wait ( ) ; console . log ( "" + ( i + 1 ) + "" , "Rotation on" , nodesAmount , "nodes:\t" , result . gasUsed . toNumber ( ) , "gu" ) ; gas . push ( result . gasUsed . toNumber ( ) ) ; if ( result . gasUsed . toNumber ( ) > gasLimit ) { break ; } await skaleDKG . setSuccessfulDKGPublic ( schainHashes [ schainHashes . length - i - 1 ] ) ; } skipTime ( 43260 ) ; measurementsRotation . push ( { nodesAmount , gasUsedArray : gas } ) ; } } ) ; } after ( async ( ) => { fs . writeFileSync ( "nodeRotation.json" , JSON . stringify ( measurementsSchainCreation , null , 4 ) ) ; await applySnapshot ( stateBefore ) ; } ) ; } ) ; } ) ;