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

Adding benchmark test - root/child chain measurements #1510

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 17 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Benchmark tests
The benchmark folder contains benchmark tests for the smart contracts.

## Common directory
In the common directory, you'll find:
- the contract_code.go file that contains byte codes for the contracts used in the tests,
- helpers.go that has helper functions needed in tests like deploying contracts and similar,
- executors.go that holds executors which execute the test cases. For example, there is an executor that submits multiple transactions in parallel and measures the execution time.

## Tests
All test scenarios are executed in benchmark_test.go. Decoupling test scenarios from execution enables the usage of different scenarios in the benchmark command, which is written for the purpose of executing the benchmark test on a test environment. The command can be run with in a following way:
polygon-edge benchmark-test --childJSONRPC="http://127.0.0.1:12001" --rootJSONRPC="http://127.0.0.1:8545" --privateKey="aa75e9a7d427efc732f8e4f1a5b7646adcc61fd5bae40f80d13c8419c9f43d6d"

## Testing tx send on root and child chains
The RootChildSendTx function executes test cases that measure transaction execution on both the root and child chains. To do this, it first calls RootChildSendTxSetUp to set up the testing environment, which may include starting the cluster, deploying contracts, and building the test cases. After building the test cases, RootChildSendTx returns them along with a cleanup function that should be called after the test cases have been executed.

The test cases are executed by the TxTestCasesExecutor. The RootJSONRPC, ChildJSONRPC, and PrivateKey flags are used to configure the testing environment. If all of these flags are set, then the local cluster will not be started and the provided addresses will be used as the endpoints to the root and child chains. If any of these flags is not set, the local cluster will be started automatically. If the private key is specified, it will be used as the transaction sender. Otherwise, the local cluster will generate a sender key. If the cluster is not run locally, then the sender must have enough funds for sending transactions.
10 changes: 10 additions & 0 deletions benchmark/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package benchmark

import (
"testing"
)

func Benchmark_RunTests(b *testing.B) {
// benchmark tests
RootChildSendTx(b)
}
81 changes: 81 additions & 0 deletions benchmark/common/contract_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//nolint:lll
package common
goran-ethernal marked this conversation as resolved.
Show resolved Hide resolved

// pragma solidity ^0.5.16;

// contract SingleCallContract {
// uint256[] private val;

// function addValue(uint256 value) public {
// val.push(value);
// }

// function getValue() public view returns (uint256[] memory) {
// return val;
// }

// function compute(uint256 x, uint256 y) public pure returns (uint256) {
// uint256 result = x + y;
// for (uint256 i = 0; i < 10; i++) {
// result = result * 2;
// }
// return result;
// }
// }
const SingleContByteCode = `608060405234801561001057600080fd5b50610210806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806320965255146100465780635b9af12b146100a55780637a85644b146100d3575b600080fd5b61004e61011f565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610091578082015181840152602081019050610076565b505050509050019250505060405180910390f35b6100d1600480360360208110156100bb57600080fd5b8101908080359060200190929190505050610177565b005b610109600480360360408110156100e957600080fd5b8101908080359060200190929190803590602001909291905050506101a6565b6040518082815260200191505060405180910390f35b6060600080548060200260200160405190810160405280929190818152602001828054801561016d57602002820191906000526020600020905b815481526020019060010190808311610159575b5050505050905090565b600081908060018154018082558091505090600182039060005260206000200160009091929091909150555050565b600080828401905060008090505b600a8110156101d05760028202915080806001019150506101b4565b50809150509291505056fea265627a7a72315820ec23cf989c20e0d41d7819001da6dfe6cc129988f15cd8a7b79595a2e61a93d264736f6c63430005100032`

//MULTI CONTRACTS CALL: A->B->C

// pragma solidity ^0.5.16;
// interface IContractB {
// function fnB() external returns (uint256);
// }
// contract ContractA {
// address contractAddr;
//
// function setContractAddr(address _contract) public {
// contractAddr = _contract;
// }

// function fnA() public returns (uint256) {
// uint256 valB = IContractB(contractAddr).fnB();
// return valB;
// }
// }
const MultiContAByteCode = `608060405234801561001057600080fd5b506101c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063286d2e3a1461003b57806368685ad31461007f575b600080fd5b61007d6004803603602081101561005157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061009d565b005b6100876100e0565b6040518082815260200191505060405180910390f35b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636cde00cd6040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561014c57600080fd5b505af1158015610160573d6000803e3d6000fd5b505050506040513d602081101561017657600080fd5b81019080805190602001909291905050509050809150509056fea265627a7a7231582082d7a079b4ea6bcf371ef0665da89a56bd53bdc82ae90daa9dd21b61fc6c115864736f6c63430005100032`

// pragma solidity ^0.5.16;
// interface IContractC {
// function fnC1() external returns (uint256);
// }
// contract ContractB {
// uint256 public valB;
// address contractAddr;

// function setContractAddr(address _contract) public {
// contractAddr = _contract;
// }

// function fnB() external returns (uint256) {
// uint256 valC = IContractC(contractAddr).fnC1();
// valB += valC;
// return valC;
// }

// }
const MultiContBByteCode = `608060405234801561001057600080fd5b50610205806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063286d2e3a146100465780636cde00cd1461008a578063735b7e6f146100a8575b600080fd5b6100886004803603602081101561005c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c6565b005b61009261010a565b6040518082815260200191505060405180910390f35b6100b06101ca565b6040518082815260200191505060405180910390f35b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166349ec07186040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561017757600080fd5b505af115801561018b573d6000803e3d6000fd5b505050506040513d60208110156101a157600080fd5b810190808051906020019092919050505090508060008082825401925050819055508091505090565b6000548156fea265627a7a7231582082a5dbbf184a5c59907837a73f0ea2083719218b0bd60ef31a3ef2b209aad00764736f6c63430005100032`

// pragma solidity ^0.5.16;
// contract ContractC {
// uint256 public valC;
// function fnC1() external returns (uint256) {
// uint256 valC2 = fnC2();
// valC++;
// return valC2;
// }

// function fnC2() public view returns (uint256) {
// return uint256(keccak256(abi.encode(block.timestamp, block.difficulty))) % 100;
// }
// }
const MultiContCByteCode = `608060405234801561001057600080fd5b50610143806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631990ceb9146100465780633b3cf4e31461006457806349ec071814610082575b600080fd5b61004e6100a0565b6040518082815260200191505060405180910390f35b61006c6100e3565b6040518082815260200191505060405180910390f35b61008a6100e9565b6040518082815260200191505060405180910390f35b60006064424460405160200180838152602001828152602001925050506040516020818303038152906040528051906020012060001c816100dd57fe5b06905090565b60005481565b6000806100f46100a0565b90506000808154809291906001019190505550809150509056fea265627a7a72315820834484e13fa60ebe10a9d7102df12bafa8db4d9cdad5a38d2af6d360adc7ff4064736f6c63430005100032`
61 changes: 61 additions & 0 deletions benchmark/common/executors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package common

import (
"sync"
"testing"

"github.com/0xPolygon/polygon-edge/txrelayer"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
)

// TxTestCase represents a test case data to be run with txTestCasesExecutor
type TxTestCase struct {
Name string
Relayer txrelayer.TxRelayer
ContractAddr ethgo.Address
Input [][]byte
Sender ethgo.Key
TxNumber int
}

// TxTestCasesExecutor executes transactions from testInput and waits in separate
// go routins for each tx receipt
func TxTestCasesExecutor(b *testing.B, testInput TxTestCase) {
b.Helper()
b.Run(testInput.Name, func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
var wg sync.WaitGroup

// submit all tx 'repeatCall' times
for i := 0; i < testInput.TxNumber; i++ {
// call contract for the all inputs
for j := 0; j < len(testInput.Input); j++ {
// the tx is submitted to the blockchain without waiting for the receipt,
// since we want to have multiple tx in one block
txHash, err := testInput.Relayer.SumbitTransaction(
&ethgo.Transaction{
To: &testInput.ContractAddr,
Input: testInput.Input[j],
}, testInput.Sender)
require.NoError(b, err)
require.NotEqual(b, ethgo.ZeroHash, txHash)

wg.Add(1)

// wait for receipt of submitted tx in a separate routine, and continue with the next tx
func(hash ethgo.Hash) {
defer wg.Done()

receipt, err := testInput.Relayer.WaitForReceipt(hash)
require.NoError(b, err)
require.Equal(b, uint64(types.ReceiptSuccess), receipt.Status)
}(txHash)
}
}

wg.Wait()
})
}
99 changes: 99 additions & 0 deletions benchmark/common/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package common

import (
"encoding/hex"
"testing"

"github.com/0xPolygon/polygon-edge/txrelayer"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/abi"
"github.com/umbracle/ethgo/wallet"
)

// DeployContractOnRootAndChild deploys contract code on both root and child chain
func DeployContractOnRootAndChild(
goran-ethernal marked this conversation as resolved.
Show resolved Hide resolved
b *testing.B,
childTxRelayer txrelayer.TxRelayer,
rootTxRelayer txrelayer.TxRelayer,
sender ethgo.Key,
byteCodeString string) (ethgo.Address, ethgo.Address) {
b.Helper()

// bytecode from string
byteCode, err := hex.DecodeString(byteCodeString)
require.NoError(b, err)

// deploy contract on the child chain
contractChildAddr := DeployContract(b, childTxRelayer, sender, byteCode)

// deploy contract on the root chain
contractRootAddr := DeployContract(b, rootTxRelayer, sender, byteCode)

return contractChildAddr, contractRootAddr
}

// DeployContract deploys contract code for the given relayer
func DeployContract(b *testing.B, txRelayer txrelayer.TxRelayer, sender ethgo.Key, byteCode []byte) ethgo.Address {
b.Helper()

txn := &ethgo.Transaction{
To: nil, // contract deployment
Input: byteCode,
}

receipt, err := txRelayer.SendTransaction(txn, sender)
require.NoError(b, err)
require.Equal(b, uint64(types.ReceiptSuccess), receipt.Status)
require.NotEqual(b, ethgo.ZeroAddress, receipt.ContractAddress)

return receipt.ContractAddress
}

// GetTxInput returns input for sending tx, given the abi encoded method and call parameters
func GetTxInput(b *testing.B, method *abi.Method, args interface{}) []byte {
b.Helper()

var input []byte
goran-ethernal marked this conversation as resolved.
Show resolved Hide resolved

var err error

if args != nil {
input, err = method.Encode(args)
} else {
input = method.ID()
}

require.NoError(b, err)

return input
}

// SetContractDependencyAddress calls setContract function on caller contract, to set address of the callee contract
func SetContractDependencyAddress(b *testing.B, txRelayer txrelayer.TxRelayer, callerContractAddr ethgo.Address,
calleeContractAddr ethgo.Address, setContractAbiMethod *abi.Method, sender ethgo.Key) {
b.Helper()

input := GetTxInput(b, setContractAbiMethod, []interface{}{calleeContractAddr})
receipt, err := txRelayer.SendTransaction(
&ethgo.Transaction{
To: &callerContractAddr,
Input: input,
}, sender)
require.NoError(b, err)
require.Equal(b, uint64(types.ReceiptSuccess), receipt.Status)
}

// GetPrivateKey initializes a private key from provided raw private key
func GetPrivateKey(b *testing.B, privateKeyRaw string) ethgo.Key {
b.Helper()

dec, err := hex.DecodeString(privateKeyRaw)
require.NoError(b, err)

privateKey, err := wallet.NewWalletFromPrivKey(dec)
require.NoError(b, err)

return privateKey
}
Loading