Skip to content

Commit

Permalink
Improve gas estimation (0xPolygonHermez#1232)
Browse files Browse the repository at this point in the history
* Improve gas estimation

* Improve gas estimation

* Improve gas estimation
  • Loading branch information
ToniRamirezM authored Oct 7, 2022
1 parent 3c2829e commit 72dfa73
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 34 deletions.
72 changes: 41 additions & 31 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import (

const (
// Size of the memory in bytes reserved by the zkEVM
zkEVMReservedMemorySize int = 128
two uint = 2
cTrue = 1
cFalse = 0
zkEVMReservedMemorySize int = 128
two uint = 2
three uint64 = 3
cTrue = 1
cFalse = 0
)

var (
Expand Down Expand Up @@ -185,7 +186,8 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common

// Run the transaction with the specified gas value.
// Returns a status indicating if the transaction failed, if it was reverted and the accompanying error
testTransaction := func(gas uint64, shouldOmitErr bool) (bool, bool, error) {
testTransaction := func(gas uint64, shouldOmitErr bool) (bool, bool, uint64, error) {
var gasUsed uint64
tx := types.NewTx(&types.LegacyTx{
Nonce: transaction.Nonce(),
To: transaction.To(),
Expand All @@ -198,7 +200,7 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common
batchL2Data, err := EncodeUnsignedTransaction(*tx, s.cfg.ChainID)
if err != nil {
log.Errorf("error encoding unsigned transaction ", err)
return false, false, err
return false, false, gasUsed, err
}

// Create a batch to be sent to the executor
Expand Down Expand Up @@ -228,10 +230,11 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common

txExecutionOnExecutorTime := time.Now()
processBatchResponse, err := s.executorClient.ProcessBatch(ctx, processBatchRequest)
gasUsed = processBatchResponse.Responses[0].GasUsed
log.Debugf("executor time: %vms", time.Since(txExecutionOnExecutorTime).Milliseconds())
if err != nil {
log.Errorf("error processing unsigned transaction ", err)
return false, false, err
return false, false, gasUsed, err
}

// Check if an out of gas error happened during EVM execution
Expand All @@ -242,44 +245,66 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common
// Specifying the transaction failed, but not providing an error
// is an indication that a valid error occurred due to low gas,
// which will increase the lower bound for the search
return true, false, nil
return true, false, gasUsed, nil
}

if isEVMRevertError(err) {
// The EVM reverted during execution, attempt to extract the
// error message and return it
return true, true, constructErrorFromRevert(err, processBatchResponse.Responses[0].ReturnValue)
return true, true, gasUsed, constructErrorFromRevert(err, processBatchResponse.Responses[0].ReturnValue)
}

return true, false, err
return true, false, gasUsed, err
}

return false, false, nil
return false, false, gasUsed, nil
}

txExecutions := []time.Duration{}
var totalExecutionTime time.Duration

// Check if the highEnd is a good value to make the transaction pass
failed, reverted, gasUsed, err := testTransaction(highEnd, false)
log.Debugf("Estimate gas. Trying to execute TX with %v gas", highEnd)
if failed {
if reverted {
return 0, err
}

// The transaction shouldn't fail, for whatever reason, at highEnd
return 0, fmt.Errorf(
"unable to apply transaction even for the highest gas limit %d: %w",
highEnd,
err,
)
}

if lowEnd < gasUsed {
lowEnd = gasUsed
}

highEnd = (gasUsed * three) / uint64(two)

// Start the binary search for the lowest possible gas price
for lowEnd+1 < highEnd {
for (lowEnd < highEnd) && (highEnd-lowEnd) > 4096 {
txExecutionStart := time.Now()
mid := (lowEnd + highEnd) / uint64(two)

log.Debugf("Estimate gas. Trying to execute TX with %v gas", mid)

failed, _, testErr := testTransaction(mid, true)
failed, reverted, _, testErr := testTransaction(mid, true)
executionTime := time.Since(txExecutionStart)
totalExecutionTime += executionTime
txExecutions = append(txExecutions, executionTime)
if testErr != nil &&
!isEVMRevertError(testErr) {
if testErr != nil && !reverted {
// Reverts are ignored in the binary search, but are checked later on
// during the execution for the optimal gas limit found
return 0, testErr
}

if failed {
// If the transaction failed => increase the gas
lowEnd = mid
lowEnd = mid + 1
} else {
// If the transaction didn't fail => make this ok value the high end
highEnd = mid
Expand All @@ -290,21 +315,6 @@ func (s *State) EstimateGas(transaction *types.Transaction, senderAddress common
averageExecutionTime := totalExecutionTime.Milliseconds() / int64(len(txExecutions))
log.Debugf("EstimateGas tx execution average time is %v milliseconds", averageExecutionTime)

// Check if the highEnd is a good value to make the transaction pass
failed, reverted, err := testTransaction(highEnd, false)
log.Debugf("Estimate gas. Trying to execute TX with %v gas", highEnd)
if failed {
if reverted {
return 0, err
}

// The transaction shouldn't fail, for whatever reason, at highEnd
return 0, fmt.Errorf(
"unable to apply transaction even for the highest gas limit %d: %w",
highEnd,
err,
)
}
return highEnd, nil
}

Expand Down
153 changes: 150 additions & 3 deletions state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ func TestMain(m *testing.M) {
}
defer stateDb.Close()

zkProverURI := testutils.GetEnv("ZKPROVER_URI", "localhost")
zkProverURI := testutils.GetEnv("ZKPROVER_URI", "34.245.216.26")

executorServerConfig := executor.Config{URI: fmt.Sprintf("%s:50071", zkProverURI)}
executorServerConfig := executor.Config{URI: fmt.Sprintf("%s:51071", zkProverURI)}
var executorCancel context.CancelFunc
executorClient, executorClientConn, executorCancel = executor.NewExecutorClient(ctx, executorServerConfig)
s := executorClientConn.GetState()
Expand All @@ -77,7 +77,7 @@ func TestMain(m *testing.M) {
executorClientConn.Close()
}()

mtDBServerConfig := merkletree.Config{URI: fmt.Sprintf("%s:50061", zkProverURI)}
mtDBServerConfig := merkletree.Config{URI: fmt.Sprintf("%s:51061", zkProverURI)}
var mtDBCancel context.CancelFunc
mtDBServiceClient, mtDBClientConn, mtDBCancel = merkletree.NewMTDBServiceClient(ctx, mtDBServerConfig)
s = mtDBClientConn.GetState()
Expand Down Expand Up @@ -2021,3 +2021,150 @@ func initOrResetDB() {
panic(err)
}
}

func TestExecutorEstimateGas(t *testing.T) {
var chainIDSequencer = new(big.Int).SetUint64(stateCfg.ChainID)
var sequencerAddress = common.HexToAddress("0x617b3a3528F9cDd6630fd3301B9c8911F7Bf063D")
var sequencerPvtKey = "0x28b2b0318721be8c8339199172cd7cc8f5e273800a35616ec893083a4b32c02e"
var scAddress = common.HexToAddress("0x1275fbb540c8efC58b812ba83B0D0B8b9917AE98")
var sequencerBalance = 4000000
scRevertByteCode, err := testutils.ReadBytecode("Revert2/Revert2.bin")
require.NoError(t, err)

// Set Genesis
block := state.Block{
BlockNumber: 0,
BlockHash: state.ZeroHash,
ParentHash: state.ZeroHash,
ReceivedAt: time.Now(),
}

genesis := state.Genesis{
Actions: []*state.GenesisAction{
{
Address: "0x617b3a3528F9cDd6630fd3301B9c8911F7Bf063D",
Type: int(merkletree.LeafTypeBalance),
Value: "100000000000000000000000",
},
{
Address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
Type: int(merkletree.LeafTypeBalance),
Value: "100000000000000000000000",
},
{
Address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
Type: int(merkletree.LeafTypeBalance),
Value: "100000000000000000000000",
},
},
}

initOrResetDB()

dbTx, err := testState.BeginStateTransaction(ctx)
require.NoError(t, err)
stateRoot, err := testState.SetGenesis(ctx, block, genesis, dbTx)
require.NoError(t, err)
require.NoError(t, dbTx.Commit(ctx))

nonce := uint64(0)

// Deploy revert.sol
tx0 := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: nil,
Value: new(big.Int),
Gas: uint64(sequencerBalance),
GasPrice: new(big.Int).SetUint64(0),
Data: common.Hex2Bytes(scRevertByteCode),
})

privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(sequencerPvtKey, "0x"))
require.NoError(t, err)
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainIDSequencer)
require.NoError(t, err)

signedTx0, err := auth.Signer(auth.From, tx0)
require.NoError(t, err)

// Call SC method
nonce++
tx1 := types.NewTransaction(nonce, scAddress, new(big.Int), 40000, new(big.Int).SetUint64(1), common.Hex2Bytes("4abbb40a"))
signedTx1, err := auth.Signer(auth.From, tx1)
require.NoError(t, err)

batchL2Data, err := state.EncodeTransactions([]types.Transaction{*signedTx0, *signedTx1})
require.NoError(t, err)

// Create Batch
processBatchRequest := &executorclientpb.ProcessBatchRequest{
BatchNum: 1,
Coinbase: sequencerAddress.String(),
BatchL2Data: batchL2Data,
OldStateRoot: stateRoot,
GlobalExitRoot: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
OldLocalExitRoot: common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
EthTimestamp: uint64(time.Now().Unix()),
UpdateMerkleTree: 0,
ChainId: stateCfg.ChainID,
}

processBatchResponse, err := executorClient.ProcessBatch(ctx, processBatchRequest)
require.NoError(t, err)
assert.NotEqual(t, "", processBatchResponse.Responses[0].Error)

convertedResponse, err := state.TestConvertToProcessBatchResponse([]types.Transaction{*signedTx0, *signedTx1}, processBatchResponse)
require.NoError(t, err)
log.Debugf("%v", len(convertedResponse.Responses))

// Store processed txs into the batch
dbTx, err = testState.BeginStateTransaction(ctx)
require.NoError(t, err)

processingContext := state.ProcessingContext{
BatchNumber: processBatchRequest.BatchNum,
Coinbase: common.Address{},
Timestamp: time.Now(),
GlobalExitRoot: common.BytesToHash(processBatchRequest.GlobalExitRoot),
}

err = testState.OpenBatch(ctx, processingContext, dbTx)
require.NoError(t, err)

err = testState.StoreTransactions(ctx, processBatchRequest.BatchNum, convertedResponse.Responses, dbTx)
require.NoError(t, err)

processingReceipt := state.ProcessingReceipt{
BatchNumber: processBatchRequest.BatchNum,
StateRoot: convertedResponse.NewStateRoot,
LocalExitRoot: convertedResponse.NewLocalExitRoot,
}

err = testState.CloseBatch(ctx, processingReceipt, dbTx)
require.NoError(t, err)
require.NoError(t, dbTx.Commit(ctx))

// l2BlockNumber := uint64(2)
nonce++
tx2 := types.NewTx(&types.LegacyTx{
Nonce: nonce,
To: nil,
Value: new(big.Int),
Gas: uint64(sequencerBalance),
GasPrice: new(big.Int).SetUint64(0),
Data: common.Hex2Bytes(scRevertByteCode),
})
signedTx2, err := auth.Signer(auth.From, tx2)
require.NoError(t, err)

estimatedGas, err := testState.EstimateGas(signedTx2, sequencerAddress, nil, nil)
require.NoError(t, err)
log.Debugf("Estimated gas = %v", estimatedGas)

nonce++
tx3 := types.NewTransaction(nonce, scAddress, new(big.Int), 40000, new(big.Int).SetUint64(1), common.Hex2Bytes("4abbb40a"))
signedTx3, err := auth.Signer(auth.From, tx3)
require.NoError(t, err)
_, err = testState.EstimateGas(signedTx3, sequencerAddress, nil, nil)
require.Error(t, err)
}

0 comments on commit 72dfa73

Please sign in to comment.