Skip to content

Commit

Permalink
Fix gas estimation (0xPolygonHermez#1034)
Browse files Browse the repository at this point in the history
  • Loading branch information
tclemos authored Aug 11, 2022
1 parent 75568f9 commit 9561f07
Show file tree
Hide file tree
Showing 20 changed files with 578 additions and 352 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ test-full-non-e2e: build-docker compile-scs ## Runs non-e2e tests checking race
$(RUNZKPROVERMOCK)
sleep 5
docker logs $(DOCKERCOMPOSEZKPROVER)
trap '$(STOPDB) && $(STOPZKPROVER) && $(STOPZKPROVERMOCK)' EXIT; MallocNanoZone=0 go test -short -race -p 1 -timeout 600s ./...
trap '$(STOPDB) && $(STOPZKPROVER) && $(STOPZKPROVERMOCK)' EXIT; MallocNanoZone=0 go test -short -race -p 1 -timeout 60s ./...

.PHONY: test-e2e-group-1
test-e2e-group-1: build-docker compile-scs ## Runs group 1 e2e tests checking race conditions
Expand Down
2 changes: 1 addition & 1 deletion aggregator/mocks/mock_etherman.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion aggregator/mocks/mock_ethtxmanager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion aggregator/mocks/mock_proverclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion aggregator/mocks/mock_state.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion jsonrpc/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ type RPCError struct {
}

func newRPCError(code int, err string, args ...interface{}) *RPCError {
errMessage := fmt.Sprintf(err, args...)
var errMessage string
if len(args) > 0 {
errMessage = fmt.Sprintf(err, args...)
} else {
errMessage = err
}
return &RPCError{code: code, err: errMessage}
}

Expand Down
54 changes: 45 additions & 9 deletions jsonrpc/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@ func (e *Eth) Call(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) {
arg.Gas = &gas
}

tx := arg.ToTransaction()

blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx)
if rpcErr != nil {
return nil, rpcErr
}

if arg.From == state.ZeroAddress && arg.Nonce == nil {
nonce := argUint64(0)
arg.Nonce = &nonce
} else if arg.From != state.ZeroAddress && arg.Nonce == nil {
n, err := e.state.GetNonce(ctx, arg.From, blockNumber, dbTx)
if err != nil {
return rpcErrorResponse(defaultErrorCode, "failed to get nonce", err)
}
nonce := argUint64(n)
arg.Nonce = &nonce
}

tx := arg.ToTransaction()

result := e.state.ProcessUnsignedTransaction(ctx, tx, arg.From, blockNumber, dbTx)
if result.Failed() {
return rpcErrorResponse(defaultErrorCode, "failed to execute call", result.Err)
Expand All @@ -81,14 +93,38 @@ func (e *Eth) ChainId() (interface{}, rpcError) { //nolint:revive
// Note that the estimate may be significantly more than the amount of gas actually
// used by the transaction, for a variety of reasons including EVM mechanics and
// node performance.
func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, rpcError) {
tx := arg.ToTransaction()
func (e *Eth) EstimateGas(arg *txnArgs, number *BlockNumber) (interface{}, rpcError) {
return e.txMan.NewDbTxScope(e.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, rpcError) {
if number == nil {
lbn := LatestBlockNumber
number = &lbn
}

gasEstimation, err := e.state.EstimateGas(tx, arg.From)
if err != nil {
return rpcErrorResponse(defaultErrorCode, "failed to estimate gas", err)
}
return hex.EncodeUint64(gasEstimation), nil
blockNumber, rpcErr := number.getNumericBlockNumber(ctx, e.state, dbTx)
if rpcErr != nil {
return nil, rpcErr
}

if arg.From == state.ZeroAddress && arg.Nonce == nil {
nonce := argUint64(0)
arg.Nonce = &nonce
} else if arg.From != state.ZeroAddress && arg.Nonce == nil {
n, err := e.state.GetNonce(ctx, arg.From, blockNumber, dbTx)
if err != nil {
return rpcErrorResponse(defaultErrorCode, "failed to get nonce", err)
}
nonce := argUint64(n)
arg.Nonce = &nonce
}

tx := arg.ToTransaction()

gasEstimation, err := e.state.EstimateGas(tx, arg.From, blockNumber, dbTx)
if err != nil {
return rpcErrorResponse(defaultErrorCode, err.Error(), nil)
}
return hex.EncodeUint64(gasEstimation), nil
})
}

// GasPrice returns the average gas price based on the last x blocks
Expand Down
114 changes: 86 additions & 28 deletions jsonrpc/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func TestCall(t *testing.T) {
expectedError: nil,
setupMocks: func(m *mocks, testCase *testCase) {
blockNumber := uint64(1)
nonce := uint64(7)
m.DbTx.On("Commit", context.Background()).Return(nil).Once()
m.State.On("BeginStateTransaction", context.Background()).Return(m.DbTx, nil).Once()
m.State.On("GetLastL2BlockNumber", context.Background(), m.DbTx).Return(blockNumber, nil).Once()
Expand All @@ -141,8 +142,10 @@ func TestCall(t *testing.T) {
tx.To().Hex() == testCase.to.Hex() &&
tx.GasPrice().Uint64() == testCase.gasPrice.Uint64() &&
tx.Value().Uint64() == testCase.value.Uint64() &&
hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data)
hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data) &&
tx.Nonce() == nonce
})
m.State.On("GetNonce", context.Background(), testCase.from, blockNumber, m.DbTx).Return(nonce, nil).Once()
m.State.On("ProcessUnsignedTransaction", context.Background(), txMatchBy, testCase.from, blockNumber, m.DbTx).Return(&runtime.ExecutionResult{ReturnValue: testCase.expectedResult}).Once()
},
},
Expand Down Expand Up @@ -257,6 +260,7 @@ func TestCall(t *testing.T) {
expectedError: newRPCError(defaultErrorCode, "failed to execute call"),
setupMocks: func(m *mocks, testCase *testCase) {
blockNumber := uint64(1)
nonce := uint64(7)
m.DbTx.On("Rollback", context.Background()).Return(nil).Once()
m.State.On("BeginStateTransaction", context.Background()).Return(m.DbTx, nil).Once()
m.State.On("GetLastL2BlockNumber", context.Background(), m.DbTx).Return(blockNumber, nil).Once()
Expand All @@ -266,8 +270,10 @@ func TestCall(t *testing.T) {
tx.To().Hex() == testCase.to.Hex() &&
tx.GasPrice().Uint64() == testCase.gasPrice.Uint64() &&
tx.Value().Uint64() == testCase.value.Uint64() &&
hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data)
hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data) &&
tx.Nonce() == nonce
})
m.State.On("GetNonce", context.Background(), testCase.from, blockNumber, m.DbTx).Return(nonce, nil).Once()
m.State.On("ProcessUnsignedTransaction", context.Background(), txMatchBy, testCase.from, blockNumber, m.DbTx).Return(&runtime.ExecutionResult{Err: errors.New("failed to process unsigned transaction")}).Once()
},
},
Expand Down Expand Up @@ -308,17 +314,20 @@ func TestEstimateGas(t *testing.T) {
s, m, c := newSequencerMockedServer(t)
defer s.Stop()

testCases := []struct {
name string
from common.Address
to *common.Address
gas uint64
gasPrice *big.Int
value *big.Int
data []byte
type testCase struct {
name string
from common.Address
to *common.Address
gas uint64
gasPrice *big.Int
value *big.Int
data []byte
setupMocks func(*mocks, *testCase)

expectedResult uint64
}{
}

testCases := []testCase{
{
name: "Transaction with all information",
from: common.HexToAddress("0x1"),
Expand All @@ -328,6 +337,40 @@ func TestEstimateGas(t *testing.T) {
value: big.NewInt(2),
data: []byte("data"),
expectedResult: 100,
setupMocks: func(m *mocks, testCase *testCase) {
blockNumber := uint64(10)
nonce := uint64(7)
txMatchBy := mock.MatchedBy(func(tx *types.Transaction) bool {
if tx == nil {
return false
}

matchTo := tx.To().Hex() == testCase.to.Hex()
matchGasPrice := tx.GasPrice().Uint64() == testCase.gasPrice.Uint64()
matchValue := tx.Value().Uint64() == testCase.value.Uint64()
matchData := hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data)
matchNonce := tx.Nonce() == nonce
return matchTo && matchGasPrice && matchValue && matchData && matchNonce
})

m.DbTx.On("Commit", context.Background()).Return(nil).Once()
m.State.On("BeginStateTransaction", context.Background()).Return(m.DbTx, nil).Once()

m.State.
On("GetLastL2BlockNumber", context.Background(), m.DbTx).
Return(blockNumber, nil).
Once()

m.State.
On("GetNonce", context.Background(), testCase.from, blockNumber, m.DbTx).
Return(nonce, nil).
Once()

m.State.
On("EstimateGas", txMatchBy, testCase.from, blockNumber, m.DbTx).
Return(testCase.expectedResult, nil).
Once()
},
},
{
name: "Transaction without from and gas",
Expand All @@ -336,29 +379,44 @@ func TestEstimateGas(t *testing.T) {
value: big.NewInt(2),
data: []byte("data"),
expectedResult: 100,
setupMocks: func(m *mocks, testCase *testCase) {
blockNumber := uint64(9)
nonce := uint64(0)
txMatchBy := mock.MatchedBy(func(tx *types.Transaction) bool {
if tx == nil {
return false
}

matchTo := tx.To().Hex() == testCase.to.Hex()
matchGasPrice := tx.GasPrice().Uint64() == testCase.gasPrice.Uint64()
matchValue := tx.Value().Uint64() == testCase.value.Uint64()
matchData := hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data)
matchNonce := tx.Nonce() == nonce
return matchTo && matchGasPrice && matchValue && matchData && matchNonce
})

m.DbTx.On("Commit", context.Background()).Return(nil).Once()
m.State.On("BeginStateTransaction", context.Background()).Return(m.DbTx, nil).Once()

m.State.
On("GetLastL2BlockNumber", context.Background(), m.DbTx).
Return(blockNumber, nil).
Once()

m.State.
On("EstimateGas", txMatchBy, testCase.from, blockNumber, m.DbTx).
Return(testCase.expectedResult, nil).
Once()
},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
msg := ethereum.CallMsg{From: testCase.from, To: testCase.to, Gas: testCase.gas, GasPrice: testCase.gasPrice, Value: testCase.value, Data: testCase.data}

txMatchBy := mock.MatchedBy(func(tx *types.Transaction) bool {
if tx == nil {
return false
}

return tx.To().Hex() == testCase.to.Hex() &&
tx.GasPrice().Uint64() == testCase.gasPrice.Uint64() &&
tx.Value().Uint64() == testCase.value.Uint64() &&
hex.EncodeToHex(tx.Data()) == hex.EncodeToHex(testCase.data)
})

m.State.
On("EstimateGas", txMatchBy, testCase.from).
Return(testCase.expectedResult, nil).
Once()
tc := testCase
tc.setupMocks(m, &tc)

msg := ethereum.CallMsg{From: testCase.from, To: testCase.to, Gas: testCase.gas, GasPrice: testCase.gasPrice, Value: testCase.value, Data: testCase.data}
result, err := c.EstimateGas(context.Background(), msg)
require.NoError(t, err)

Expand Down
2 changes: 1 addition & 1 deletion jsonrpc/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type stateInterface interface {
GetLastL2BlockNumber(ctx context.Context, dbTx pgx.Tx) (uint64, error)
GetLastL2Block(ctx context.Context, dbTx pgx.Tx) (*types.Block, error)
GetLastL2BlockHeader(ctx context.Context, dbTx pgx.Tx) (*types.Header, error)
EstimateGas(transaction *types.Transaction, senderAddress common.Address) (uint64, error)
EstimateGas(transaction *types.Transaction, senderAddress common.Address, l2BlockNumber uint64, dbTx pgx.Tx) (uint64, error)
GetBalance(ctx context.Context, address common.Address, blockNumber uint64, dbTx pgx.Tx) (*big.Int, error)
GetL2BlockByHash(ctx context.Context, hash common.Hash, dbTx pgx.Tx) (*types.Block, error)
GetL2BlockByNumber(ctx context.Context, blockNumber uint64, dbTx pgx.Tx) (*types.Block, error)
Expand Down
16 changes: 8 additions & 8 deletions jsonrpc/mock_state_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions jsonrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ func handleError(w http.ResponseWriter, err error) {

func rpcErrorResponse(code int, errorMessage string, err error) (interface{}, rpcError) {
if err != nil {
log.Errorf("%v:%v", errorMessage, err)
log.Errorf("%v:%v", errorMessage, err.Error())
} else {
log.Errorf("%v", errorMessage)
log.Error(errorMessage)
}
return nil, newRPCError(code, errorMessage)
}
2 changes: 1 addition & 1 deletion sequencer/txmanager-mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 15 additions & 15 deletions state/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,23 @@ func convertToProcessTransactionResponse(txs []types.Transaction, responses []*p
return results, nil
}

func convertToLog(responses []*pb.Log) []*types.Log {
results := make([]*types.Log, 0, len(responses))

for _, response := range responses {
result := new(types.Log)
result.Address = common.HexToAddress(response.Address)
result.Topics = convertToTopics(response.Topics)
result.Data = response.Data
result.BlockNumber = response.BatchNumber
result.TxHash = common.BytesToHash(response.TxHash)
result.TxIndex = uint(response.TxIndex)
result.BlockHash = common.BytesToHash(response.BatchHash)
result.Index = uint(response.Index)
results = append(results, result)
func convertToLog(protoLogs []*pb.Log) []*types.Log {
logs := make([]*types.Log, 0, len(protoLogs))

for _, protoLog := range protoLogs {
log := new(types.Log)
log.Address = common.HexToAddress(protoLog.Address)
log.Topics = convertToTopics(protoLog.Topics)
log.Data = protoLog.Data
log.BlockNumber = protoLog.BatchNumber
log.TxHash = common.BytesToHash(protoLog.TxHash)
log.TxIndex = uint(protoLog.TxIndex)
log.BlockHash = common.BytesToHash(protoLog.BatchHash)
log.Index = uint(protoLog.Index)
logs = append(logs, log)
}

return results
return logs
}

func convertToTopics(responses [][]byte) []common.Hash {
Expand Down
Loading

0 comments on commit 9561f07

Please sign in to comment.