Skip to content

Commit

Permalink
add transition benchmark test
Browse files Browse the repository at this point in the history
  • Loading branch information
stana-miric committed May 29, 2023
1 parent 38bc3ce commit 70b0df9
Show file tree
Hide file tree
Showing 9 changed files with 493 additions and 101 deletions.
10 changes: 0 additions & 10 deletions benchmark/benchmark_test.go

This file was deleted.

61 changes: 0 additions & 61 deletions benchmark/executors.go

This file was deleted.

96 changes: 96 additions & 0 deletions benchmark/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
21 changes: 21 additions & 0 deletions benchmark/root_child_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
110 changes: 80 additions & 30 deletions benchmark/root_child_send_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,19 @@ package benchmark

import (
"math/big"
"sync"
"testing"
"time"

"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/e2e-polybft/framework"
"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"
)

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.
Expand Down Expand Up @@ -111,14 +87,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{
Expand Down Expand Up @@ -198,3 +174,77 @@ 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++ {
nonce, err := testInput.Relayer.Client().Eth().GetNonce(testInput.Sender.Address(), ethgo.Pending)
require.NoError(b, err)

// 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
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)

// wait for tx to be added in mem pool so that we can create tx with the next nonce
waitForNextNonce(b, nonce, testInput.Sender.Address(), testInput.Relayer)
}
}

wg.Wait()
})
}

func waitForNextNonce(b *testing.B, nonce uint64, address ethgo.Address, txRelayer txrelayer.TxRelayer) {
b.Helper()

startTime := time.Now().UTC()

for {
newNonce, err := txRelayer.Client().Eth().GetNonce(address, ethgo.Pending)
require.NoError(b, err)

if newNonce > nonce {
return
}

elapsedTime := time.Since(startTime)
require.True(b, elapsedTime <= 5*time.Second, "tx not added to the mem poolin 2s")
}
}
Loading

0 comments on commit 70b0df9

Please sign in to comment.