From 158de624037add573c5dc6ad8c7a618bebeae6df Mon Sep 17 00:00:00 2001 From: stana-ethernal Date: Mon, 29 May 2023 15:40:46 +0200 Subject: [PATCH] add transition benchmark test --- benchmark/benchmark_test.go | 10 -- benchmark/executors.go | 61 -------- benchmark/helper.go | 96 ++++++++++++ benchmark/root_child_benchmark_test.go | 21 +++ benchmark/root_child_send_tx.go | 85 +++++++---- benchmark/transition_benchmark_test.go | 112 ++++++++++++++ consensus/polybft/contractsapi/init.go | 6 + .../test-contracts/SampleContract.json | 138 ++++++++++++++++++ .../test-contracts/SampleContract.sol | 40 +++++ 9 files changed, 468 insertions(+), 101 deletions(-) delete mode 100644 benchmark/benchmark_test.go delete mode 100644 benchmark/executors.go create mode 100644 benchmark/root_child_benchmark_test.go create mode 100644 benchmark/transition_benchmark_test.go create mode 100644 consensus/polybft/contractsapi/test-contracts/SampleContract.json create mode 100644 consensus/polybft/contractsapi/test-contracts/SampleContract.sol diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go deleted file mode 100644 index 725bafafac..0000000000 --- a/benchmark/benchmark_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package benchmark - -import ( - "testing" -) - -func Benchmark_RunTests(b *testing.B) { - // benchmark tests - rootChildSendTx(b) -} diff --git a/benchmark/executors.go b/benchmark/executors.go deleted file mode 100644 index 0fd55888c0..0000000000 --- a/benchmark/executors.go +++ /dev/null @@ -1,61 +0,0 @@ -package benchmark - -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( - ðgo.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() - }) -} diff --git a/benchmark/helper.go b/benchmark/helper.go index 00ed724a64..9656316de2 100644 --- a/benchmark/helper.go +++ b/benchmark/helper.go @@ -2,16 +2,34 @@ package benchmark import ( "encoding/hex" + "math/big" + "os" + "path/filepath" "testing" + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/state" + itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" + "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/txrelayer" "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" "github.com/umbracle/ethgo" "github.com/umbracle/ethgo/abi" "github.com/umbracle/ethgo/wallet" ) +var ( + singleContCalcFunc = contractsapi.TestBenchmarkSingle.Abi.Methods["compute"] + singleContGetFunc = contractsapi.TestBenchmarkSingle.Abi.Methods["getValue"] + singleContSetFunc = contractsapi.TestBenchmarkSingle.Abi.Methods["addValue"] + multiContSetAAddrFunc = contractsapi.TestBenchmarkA.Abi.Methods["setContractAddr"] + multiContSetBAddrFunc = contractsapi.TestBenchmarkA.Abi.Methods["setContractAddr"] + multiContFnA = contractsapi.TestBenchmarkA.Abi.Methods["fnA"] +) + // deployContractOnRootAndChild deploys contract code on both root and child chain func deployContractOnRootAndChild( b *testing.B, @@ -94,3 +112,81 @@ func getPrivateKey(b *testing.B, privateKeyRaw string) ethgo.Key { return privateKey } + +func transitionDeployContract(b *testing.B, transition *state.Transition, byteCode []byte, + sender types.Address) types.Address { + b.Helper() + + deployResult := transition.Create2(sender, byteCode, big.NewInt(0), 1e9) + require.NoError(b, deployResult.Err) + + return deployResult.Address +} + +func transitionCallContract(b *testing.B, transition *state.Transition, contractAddress types.Address, + sender types.Address, input []byte) *runtime.ExecutionResult { + b.Helper() + + result := transition.Call2(sender, contractAddress, input, big.NewInt(0), 1e9) + require.NoError(b, result.Err) + + return result +} + +func newTestTransition(b *testing.B, alloc map[types.Address]*chain.GenesisAccount, disk bool) *state.Transition { + b.Helper() + + var st *itrie.State + + if disk { + testDir := createTestTempDirectory(b) + stateStorage, err := itrie.NewLevelDBStorage(filepath.Join(testDir, "trie"), hclog.NewNullLogger()) + require.NoError(b, err) + + st = itrie.NewState(stateStorage) + } else { + st = itrie.NewState(itrie.NewMemoryStorage()) + } + + ex := state.NewExecutor(&chain.Params{ + Forks: chain.AllForksEnabled, + BurnContract: map[uint64]string{ + 0: types.ZeroAddress.String(), + }, + }, st, hclog.NewNullLogger()) + + rootHash, err := ex.WriteGenesis(alloc, types.Hash{}) + require.NoError(b, err) + + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { + return rootHash + } + } + + transition, err := ex.BeginTxn( + rootHash, + &types.Header{}, + types.ZeroAddress, + ) + require.NoError(b, err) + + return transition +} + +func createTestTempDirectory(b *testing.B) string { + b.Helper() + + path, err := os.MkdirTemp("", "temp") + if err != nil { + b.Logf("failed to create temp directory, err=%+v", err) + + b.FailNow() + } + + b.Cleanup(func() { + os.RemoveAll(path) + }) + + return path +} diff --git a/benchmark/root_child_benchmark_test.go b/benchmark/root_child_benchmark_test.go new file mode 100644 index 0000000000..3c6d21b57c --- /dev/null +++ b/benchmark/root_child_benchmark_test.go @@ -0,0 +1,21 @@ +package benchmark + +import ( + "testing" +) + +// 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. +func Benchmark_RootChildSendTx(b *testing.B) { + // set up environment, get test cases and clean up fn + testCases, cleanUpFn := RootChildSendTxSetUp(b, "", "", "", true) + defer cleanUpFn() + + // Loop over the test cases and measure the execution time of the transactions + for _, testInput := range testCases { + TxTestCasesExecutor(b, testInput) + } +} diff --git a/benchmark/root_child_send_tx.go b/benchmark/root_child_send_tx.go index ab1fc848a5..81849007e4 100644 --- a/benchmark/root_child_send_tx.go +++ b/benchmark/root_child_send_tx.go @@ -2,6 +2,7 @@ package benchmark import ( "math/big" + "sync" "testing" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" @@ -10,35 +11,9 @@ import ( "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" ) -var ( - singleContCalcFunc = abi.MustNewMethod("function compute(uint256 x, uint256 y) public returns (uint256)") - singleContGetFunc = abi.MustNewMethod("function getValue() public returns (uint256[] memory)") - singleContSetFunc = abi.MustNewMethod("function addValue(uint256 value) public") - multiContSetAddrFunc = abi.MustNewMethod("function setContractAddr(address _contract) public") - multiContFnA = abi.MustNewMethod("function fnA() public returns (uint256)") -) - -// 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. -func rootChildSendTx(b *testing.B) { - b.Helper() - // set up environment, get test cases and clean up fn - testCases, cleanUpFn := RootChildSendTxSetUp(b, "", "", "", true) - defer cleanUpFn() - - // Loop over the test cases and measure the execution time of the transactions - for _, testInput := range testCases { - TxTestCasesExecutor(b, testInput) - } -} - // RootChildSendTxSetUp sets environment for execution of sentTx test cases on both root and child chains and // returns test cases and clean up fn. // The rootJSONRPC, childJSONRPC, privateKey and startCluster params are used to configure the testing environment. @@ -111,14 +86,14 @@ func RootChildSendTxSetUp(b *testing.B, rootNodeAddr, childNodeAddr, // set callee contract addresses for multi call contracts (A->B->C) // set B contract address in A contract setContractDependencyAddress(b, childTxRelayer, multiAContChildAddr, multiBContChildAddr, - multiContSetAddrFunc, sender) + multiContSetAAddrFunc, sender) setContractDependencyAddress(b, rootTxRelayer, multiAContRootAddr, multiBContRootAddr, - multiContSetAddrFunc, sender) + multiContSetAAddrFunc, sender) // set C contract address in B contract setContractDependencyAddress(b, childTxRelayer, multiBContChildAddr, multiCContChildAddr, - multiContSetAddrFunc, sender) + multiContSetBAddrFunc, sender) setContractDependencyAddress(b, rootTxRelayer, multiBContRootAddr, multiCContRootAddr, - multiContSetAddrFunc, sender) + multiContSetBAddrFunc, sender) // create inputs for contract calls singleContInputs := map[string][]byte{ @@ -198,3 +173,53 @@ func RootChildSendTxSetUp(b *testing.B, rootNodeAddr, childNodeAddr, return testCases, cleanUpFn } + +// 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( + ðgo.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 + go 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() + }) +} diff --git a/benchmark/transition_benchmark_test.go b/benchmark/transition_benchmark_test.go new file mode 100644 index 0000000000..c96b603ade --- /dev/null +++ b/benchmark/transition_benchmark_test.go @@ -0,0 +1,112 @@ +package benchmark + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" +) + +func Benchmark_TransitionMultiContractMemDb(b *testing.B) { + multiContractTest(b, false) +} + +func Benchmark_TransitionMultiContractLevelDb(b *testing.B) { + multiContractTest(b, true) +} + +func multiContractTest(b *testing.B, disk bool) { + b.Helper() + + senderAddr := types.Address{1} // account that sends transactions + + alloc := map[types.Address]*chain.GenesisAccount{ + senderAddr: {Balance: ethgo.Ether(100)}, // give some ethers to sender + } + + transition := newTestTransition(b, alloc, disk) + + // deploy contracts + singleContractAddr := transitionDeployContract(b, transition, contractsapi.TestBenchmarkSingle.Bytecode, senderAddr) + contractAAddr := transitionDeployContract(b, transition, contractsapi.TestBenchmarkA.Bytecode, senderAddr) + contractBAddr := transitionDeployContract(b, transition, contractsapi.TestBenchmarkB.Bytecode, senderAddr) + contractCAddr := transitionDeployContract(b, transition, contractsapi.TestBenchmarkC.Bytecode, senderAddr) + + //set multi contracts dependency address + input := getTxInput(b, multiContSetAAddrFunc, []interface{}{contractBAddr}) + transitionCallContract(b, transition, contractAAddr, senderAddr, input) + input = getTxInput(b, multiContSetBAddrFunc, []interface{}{contractCAddr}) + transitionCallContract(b, transition, contractBAddr, senderAddr, input) + + testCases := []struct { + contractAddr types.Address + input []byte + }{ + { + contractAddr: singleContractAddr, + input: getTxInput(b, singleContSetFunc, []interface{}{big.NewInt(10)}), + }, + { + contractAddr: singleContractAddr, + input: getTxInput(b, singleContGetFunc, nil), + }, + { + contractAddr: singleContractAddr, + input: getTxInput(b, singleContCalcFunc, []interface{}{big.NewInt(50), big.NewInt(150)}), + }, + { + contractAddr: contractAAddr, + input: getTxInput(b, multiContFnA, nil), + }, + } + + b.ReportAllocs() + b.ResetTimer() + // execute transactions + for i := 0; i < b.N; i++ { + for j := 0; j < 400; j++ { + for _, tc := range testCases { + transitionCallContract(b, transition, tc.contractAddr, senderAddr, tc.input) + } + } + } +} + +func Benchmark_SampleContractMemDb(b *testing.B) { + sampleContractTest(b, false) +} + +func Benchmark_SampleContractLevelDb(b *testing.B) { + sampleContractTest(b, true) +} + +func sampleContractTest(b *testing.B, disk bool) { + b.Helper() + + senderAddr := types.Address{1} // account that sends transactions + alloc := map[types.Address]*chain.GenesisAccount{ + senderAddr: {Balance: ethgo.Ether(100)}, // give some ethers to sender + } + transition := newTestTransition(b, alloc, disk) + code := contractsapi.SampleContract.Bytecode + cpurchase := getTxInput(b, contractsapi.SampleContract.Abi.Methods["confirmPurchase"], nil) + creceived := getTxInput(b, contractsapi.SampleContract.Abi.Methods["confirmReceived"], nil) + refund := getTxInput(b, contractsapi.SampleContract.Abi.Methods["refund"], nil) + + // deploy contracts + contractAddr := transitionDeployContract(b, transition, code, senderAddr) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for j := 0; j < 400; j++ { + transitionCallContract(b, transition, contractAddr, senderAddr, cpurchase) + transitionCallContract(b, transition, contractAddr, senderAddr, creceived) + transitionCallContract(b, transition, contractAddr, senderAddr, refund) + } + } +} diff --git a/consensus/polybft/contractsapi/init.go b/consensus/polybft/contractsapi/init.go index fb2e967076..f2066edd5c 100644 --- a/consensus/polybft/contractsapi/init.go +++ b/consensus/polybft/contractsapi/init.go @@ -56,6 +56,7 @@ var ( TestBenchmarkB *artifact.Artifact TestBenchmarkC *artifact.Artifact TestBenchmarkSingle *artifact.Artifact + SampleContract *artifact.Artifact ) func init() { @@ -221,6 +222,11 @@ func init() { log.Fatal(err) } + SampleContract, err = artifact.DecodeArtifact(readTestContractContent("SampleContract.json")) + if err != nil { + log.Fatal(err) + } + TestBenchmarkSingle, err = artifact.DecodeArtifact(readTestContractContent("TestBenchmarkSingle.json")) if err != nil { log.Fatal(err) diff --git a/consensus/polybft/contractsapi/test-contracts/SampleContract.json b/consensus/polybft/contractsapi/test-contracts/SampleContract.json new file mode 100644 index 0000000000..31d3fbbd84 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/SampleContract.json @@ -0,0 +1,138 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "SampleContract", + "sourceName": "contracts/test/SampleContract.sol", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [], + "name": "Aborted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "ItemReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "PurchaseConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Refunded", + "type": "event" + }, + { + "inputs": [], + "name": "abort", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "buyer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "confirmPurchase", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "confirmReceived", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "refund", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "seller", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "value", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50600080546001600160a01b0319168155600155600280546001600160a81b0319169055610223806100436000396000f3fe608060405234801561001057600080fd5b50600436106100785760003560e01c806308551a531461007d57806335a063b4146100ad5780633fa4f245146100b7578063590e1ae3146100ce5780637150d8ae146100e657806373fac6f0146100f9578063c19d93fb14610101578063d696069714610127575b600080fd5b600054610090906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100b561012f565b005b6100c060015481565b6040519081526020016100a4565b6100d661015a565b60405190151581526020016100a4565b600254610090906001600160a01b031681565b6100d661018b565b60025461011590600160a01b900460ff1681565b60405160ff90911681526020016100a4565b6100d66101bc565b6040517f72c874aeff0b183a56e2b79c71b46e1aed4dee5e09862134b8821ba2fddbf8bf90600090a1565b6040516000907f8616bbbbad963e4e65b1366f1d75dfb63f9e9704bbbf91fb01bec70849906cf7908290a150600190565b6040516000907fe89152acd703c9d8c7d28829d443260b411454d45394e7995815140c8cbcbcf7908290a150600190565b6040516000907fd5d55c8a68912e9a110618df8d5e2e83b8d83211c57a8ddd1203df92885dc881908290a15060019056fea264697066735822122020dbaeea5fb377656e27e4776ca67b90b97234194ed45764407db48ad4a5e8cb64736f6c63430008130033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100785760003560e01c806308551a531461007d57806335a063b4146100ad5780633fa4f245146100b7578063590e1ae3146100ce5780637150d8ae146100e657806373fac6f0146100f9578063c19d93fb14610101578063d696069714610127575b600080fd5b600054610090906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100b561012f565b005b6100c060015481565b6040519081526020016100a4565b6100d661015a565b60405190151581526020016100a4565b600254610090906001600160a01b031681565b6100d661018b565b60025461011590600160a01b900460ff1681565b60405160ff90911681526020016100a4565b6100d66101bc565b6040517f72c874aeff0b183a56e2b79c71b46e1aed4dee5e09862134b8821ba2fddbf8bf90600090a1565b6040516000907f8616bbbbad963e4e65b1366f1d75dfb63f9e9704bbbf91fb01bec70849906cf7908290a150600190565b6040516000907fe89152acd703c9d8c7d28829d443260b411454d45394e7995815140c8cbcbcf7908290a150600190565b6040516000907fd5d55c8a68912e9a110618df8d5e2e83b8d83211c57a8ddd1203df92885dc881908290a15060019056fea264697066735822122020dbaeea5fb377656e27e4776ca67b90b97234194ed45764407db48ad4a5e8cb64736f6c63430008130033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/consensus/polybft/contractsapi/test-contracts/SampleContract.sol b/consensus/polybft/contractsapi/test-contracts/SampleContract.sol new file mode 100644 index 0000000000..1c8a510dbf --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/SampleContract.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract SampleContract { + address public seller; + uint256 public value; + address public buyer; + uint8 public state; + + event Aborted(); + event PurchaseConfirmed(); + event ItemReceived(); + event Refunded(); + + constructor() public { + seller = address(0); + value = 0; + buyer = address(0); + state = 0; + } + + function abort() external { + emit Aborted(); + } + + function confirmPurchase() external returns (bool) { + emit PurchaseConfirmed(); + return true; + } + + function confirmReceived() external returns (bool) { + emit ItemReceived(); + return true; + } + + function refund() external returns (bool) { + emit Refunded(); + return true; + } +}