Skip to content

Commit

Permalink
rpc: handle the entrypoint not found error (NethermindEth#2455)
Browse files Browse the repository at this point in the history
* rpc v8: handle the entrypoint not found error
  • Loading branch information
rianhughes authored Feb 17, 2025
1 parent 2fc5f61 commit 11247e3
Show file tree
Hide file tree
Showing 16 changed files with 217 additions and 68 deletions.
4 changes: 2 additions & 2 deletions mocks/mock_vm.go

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

4 changes: 2 additions & 2 deletions node/throttled_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func NewThrottledVM(res vm.VM, concurrenyBudget uint, maxQueueLen int32) *Thrott

func (tvm *ThrottledVM) Call(callInfo *vm.CallInfo, blockInfo *vm.BlockInfo, state core.StateReader,
network *utils.Network, maxSteps uint64, sierraVersion string,
) ([]*felt.Felt, error) {
var ret []*felt.Felt
) (vm.CallResult, error) {
ret := vm.CallResult{}
return ret, tvm.Do(func(vm *vm.VM) error {
var err error
ret, err = (*vm).Call(callInfo, blockInfo, state, network, maxSteps, sierraVersion)
Expand Down
12 changes: 7 additions & 5 deletions rpc/rpccore/rpccore.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import (
)

const (
MaxEventChunkSize = 10240
MaxEventFilterKeys = 1024
TraceCacheSize = 128
ThrottledVMErr = "VM throughput limit reached"
MaxBlocksBack = 1024
MaxEventChunkSize = 10240
MaxEventFilterKeys = 1024
TraceCacheSize = 128
ThrottledVMErr = "VM throughput limit reached"
MaxBlocksBack = 1024
EntrypointNotFoundFelt string = "0x454e545259504f494e545f4e4f545f464f554e44"
)

//go:generate mockgen -destination=../mocks/mock_gateway_handler.go -package=mocks github.com/NethermindEth/juno/rpc Gateway
Expand All @@ -34,6 +35,7 @@ type TraceCacheKey struct {

var (
ErrContractNotFound = &jsonrpc.Error{Code: 20, Message: "Contract not found"}
ErrEntrypointNotFound = &jsonrpc.Error{Code: 21, Message: "Requested entrypoint does not exist in the contract"}
ErrBlockNotFound = &jsonrpc.Error{Code: 24, Message: "Block not found"}
ErrInvalidTxHash = &jsonrpc.Error{Code: 25, Message: "Invalid transaction hash"}
ErrInvalidBlockHash = &jsonrpc.Error{Code: 26, Message: "Invalid block hash"}
Expand Down
4 changes: 2 additions & 2 deletions rpc/v6/estimate_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan
if rpcErr != nil {
if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code {
data := rpcErr.Data.(TransactionExecutionErrorData)
return nil, makeContractError(errors.New(data.ExecutionError))
return nil, MakeContractError(errors.New(data.ExecutionError))
}
return nil, rpcErr
}
Expand All @@ -99,7 +99,7 @@ type ContractErrorData struct {
RevertError string `json:"revert_error"`
}

func makeContractError(err error) *jsonrpc.Error {
func MakeContractError(err error) *jsonrpc.Error {
return rpccore.ErrContractError.CloneWithData(ContractErrorData{
RevertError: err.Error(),
})
Expand Down
7 changes: 5 additions & 2 deletions rpc/v6/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,10 @@ func (h *Handler) call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp
if errors.Is(err, utils.ErrResourceBusy) {
return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr)
}
return nil, makeContractError(err)
return nil, MakeContractError(err)
}
return res, nil
if res.ExecutionFailed {
return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result)))
}
return res.Result, nil
}
38 changes: 35 additions & 3 deletions rpc/v6/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,10 +440,10 @@ func TestCall(t *testing.T) {
*new(felt.Felt).SetUint64(4),
*new(felt.Felt).SetUint64(5),
}
expectedRes := []*felt.Felt{
expectedRes := vm.CallResult{Result: []*felt.Felt{
new(felt.Felt).SetUint64(6),
new(felt.Felt).SetUint64(7),
}
}}

headsHeader := &core.Header{
Number: 9,
Expand All @@ -466,6 +466,38 @@ func TestCall(t *testing.T) {
Calldata: calldata,
}, rpc.BlockID{Latest: true})
require.Nil(t, rpcErr)
require.Equal(t, expectedRes, res)
require.Equal(t, expectedRes.Result, res)
})

t.Run("unknown entrypoint blockifier 0.14.0", func(t *testing.T) {
handler = handler.WithCallMaxSteps(1337)

contractAddr := new(felt.Felt).SetUint64(1)
selector := new(felt.Felt).SetUint64(2)
classHash := new(felt.Felt).SetUint64(3)
calldata := []felt.Felt{*new(felt.Felt).SetUint64(4)}
expectedRes := vm.CallResult{
Result: []*felt.Felt{utils.HexToFelt(t, rpccore.EntrypointNotFoundFelt)},
ExecutionFailed: true,
}
expectedErr := rpc.MakeContractError(errors.New(rpccore.EntrypointNotFoundFelt))

headsHeader := &core.Header{
Number: 9,
Timestamp: 101,
}
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockReader.EXPECT().HeadsHeader().Return(headsHeader, nil)
mockState.EXPECT().ContractClassHash(contractAddr).Return(classHash, nil)
mockReader.EXPECT().Network().Return(n)
mockVM.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedRes, nil)

res, rpcErr := handler.Call(rpc.FunctionCall{
ContractAddress: *contractAddr,
EntryPointSelector: *selector,
Calldata: calldata,
}, rpc.BlockID{Latest: true})
require.Nil(t, res)
require.Equal(t, rpcErr, expectedErr)
})
}
4 changes: 2 additions & 2 deletions rpc/v7/estimate_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (h *Handler) estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHan
if rpcErr != nil {
if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code {
data := rpcErr.Data.(TransactionExecutionErrorData)
return nil, httpHeader, makeContractError(errors.New(data.ExecutionError))
return nil, httpHeader, MakeContractError(errors.New(data.ExecutionError))
}
return nil, httpHeader, rpcErr
}
Expand All @@ -123,7 +123,7 @@ type ContractErrorData struct {
RevertError string `json:"revert_error"`
}

func makeContractError(err error) *jsonrpc.Error {
func MakeContractError(err error) *jsonrpc.Error {
return rpccore.ErrContractError.CloneWithData(ContractErrorData{
RevertError: err.Error(),
})
Expand Down
7 changes: 5 additions & 2 deletions rpc/v7/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,10 @@ func (h *Handler) Call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp
if errors.Is(err, utils.ErrResourceBusy) {
return nil, rpccore.ErrInternal.CloneWithData(throttledVMErr)
}
return nil, makeContractError(err)
return nil, MakeContractError(err)
}
return res, nil
if res.ExecutionFailed {
return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result)))
}
return res.Result, nil
}
50 changes: 45 additions & 5 deletions rpc/v7/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,11 +549,13 @@ func TestCall(t *testing.T) {
*new(felt.Felt).SetUint64(4),
*new(felt.Felt).SetUint64(5),
}
expectedRes := []*felt.Felt{
new(felt.Felt).SetUint64(6),
new(felt.Felt).SetUint64(7),
expectedRes := vm.CallResult{
Result: []*felt.Felt{
new(felt.Felt).SetUint64(6),
new(felt.Felt).SetUint64(7),
},
ExecutionFailed: false,
}

headsHeader := &core.Header{
Number: 9,
Timestamp: 101,
Expand All @@ -576,6 +578,44 @@ func TestCall(t *testing.T) {
Calldata: calldata,
}, rpcv7.BlockID{Latest: true})
require.Nil(t, rpcErr)
require.Equal(t, expectedRes, res)
require.Equal(t, expectedRes.Result, res)
})

t.Run("unknown entrypoint blockifier 0.14.0", func(t *testing.T) {
handler = handler.WithCallMaxSteps(1337)

contractAddr := new(felt.Felt).SetUint64(1)
selector := new(felt.Felt).SetUint64(2)
classHash := new(felt.Felt).SetUint64(3)
calldata := []felt.Felt{*new(felt.Felt).SetUint64(4)}
expectedRes := vm.CallResult{
Result: []*felt.Felt{utils.HexToFelt(t, rpccore.EntrypointNotFoundFelt)},
ExecutionFailed: true,
}
expectedErr := rpcv7.MakeContractError(errors.New(rpccore.EntrypointNotFoundFelt))

headsHeader := &core.Header{
Number: 9,
Timestamp: 101,
}
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockReader.EXPECT().HeadsHeader().Return(headsHeader, nil)
mockState.EXPECT().ContractClassHash(contractAddr).Return(classHash, nil)
mockState.EXPECT().Class(classHash).Return(&core.DeclaredClass{Class: &core.Cairo1Class{}}, nil)
mockReader.EXPECT().Network().Return(n)
mockVM.EXPECT().Call(&vm.CallInfo{
ContractAddress: contractAddr,
ClassHash: classHash,
Selector: selector,
Calldata: calldata,
}, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil)

res, rpcErr := handler.Call(rpcv7.FunctionCall{
ContractAddress: *contractAddr,
EntryPointSelector: *selector,
Calldata: calldata,
}, rpcv7.BlockID{Latest: true})
require.Nil(t, res)
require.Equal(t, rpcErr, expectedErr)
})
}
4 changes: 2 additions & 2 deletions rpc/v8/estimate_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func estimateMessageFee(msg MsgFromL1, id BlockID, f estimateFeeHandler) (*FeeEs
if rpcErr != nil {
if rpcErr.Code == rpccore.ErrTransactionExecutionError.Code {
data := rpcErr.Data.(TransactionExecutionErrorData)
return nil, httpHeader, makeContractError(errors.New(data.ExecutionError))
return nil, httpHeader, MakeContractError(errors.New(data.ExecutionError))
}
return nil, httpHeader, rpcErr
}
Expand All @@ -102,7 +102,7 @@ type ContractErrorData struct {
RevertError string `json:"revert_error"`
}

func makeContractError(err error) *jsonrpc.Error {
func MakeContractError(err error) *jsonrpc.Error {
return rpccore.ErrContractError.CloneWithData(ContractErrorData{
RevertError: err.Error(),
})
Expand Down
14 changes: 12 additions & 2 deletions rpc/v8/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,17 @@ func (h *Handler) Call(funcCall FunctionCall, id BlockID) ([]*felt.Felt, *jsonrp
if errors.Is(err, utils.ErrResourceBusy) {
return nil, rpccore.ErrInternal.CloneWithData(rpccore.ThrottledVMErr)
}
return nil, makeContractError(err)
return nil, MakeContractError(err)
}
if res.ExecutionFailed {
// the blockifier 0.13.4 update requires us to check if the execution failed,
// and if so, return ErrEntrypointNotFound if res.Result[0]==EntrypointNotFoundFelt,
// otherwise we should wrap the result in ErrContractError
if len(res.Result) != 0 && res.Result[0].String() == rpccore.EntrypointNotFoundFelt {
return nil, rpccore.ErrEntrypointNotFound
}
// Todo: There is currently no standardised way to format these error messages
return nil, MakeContractError(errors.New(utils.FeltArrToString(res.Result)))
}
return res, nil
return res.Result, nil
}
44 changes: 41 additions & 3 deletions rpc/v8/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,10 +562,10 @@ func TestCall(t *testing.T) {
*new(felt.Felt).SetUint64(4),
*new(felt.Felt).SetUint64(5),
}
expectedRes := []*felt.Felt{
expectedRes := vm.CallResult{Result: []*felt.Felt{
new(felt.Felt).SetUint64(6),
new(felt.Felt).SetUint64(7),
}
}}

headsHeader := &core.Header{
Number: 9,
Expand All @@ -589,6 +589,44 @@ func TestCall(t *testing.T) {
Calldata: calldata,
}, rpc.BlockID{Latest: true})
require.Nil(t, rpcErr)
require.Equal(t, expectedRes, res)
require.Equal(t, expectedRes.Result, res)
})

t.Run("entrypoint not found error", func(t *testing.T) {
handler = handler.WithCallMaxSteps(1337)

contractAddr := new(felt.Felt).SetUint64(1)
selector := new(felt.Felt).SetUint64(2)
classHash := new(felt.Felt).SetUint64(3)
calldata := []felt.Felt{*new(felt.Felt).SetUint64(4)}
expectedRes := vm.CallResult{
Result: []*felt.Felt{utils.HexToFelt(t, rpccore.EntrypointNotFoundFelt)},
ExecutionFailed: true,
}
expectedErr := rpccore.ErrEntrypointNotFound

headsHeader := &core.Header{
Number: 9,
Timestamp: 101,
}
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockReader.EXPECT().HeadsHeader().Return(headsHeader, nil)
mockState.EXPECT().ContractClassHash(contractAddr).Return(classHash, nil)
mockState.EXPECT().Class(classHash).Return(&core.DeclaredClass{Class: &core.Cairo1Class{}}, nil)
mockReader.EXPECT().Network().Return(n)
mockVM.EXPECT().Call(&vm.CallInfo{
ContractAddress: contractAddr,
ClassHash: classHash,
Selector: selector,
Calldata: calldata,
}, &vm.BlockInfo{Header: headsHeader}, gomock.Any(), &utils.Mainnet, uint64(1337), "").Return(expectedRes, nil)

res, rpcErr := handler.Call(rpc.FunctionCall{
ContractAddress: *contractAddr,
EntryPointSelector: *selector,
Calldata: calldata,
}, rpc.BlockID{Latest: true})
require.Nil(t, res)
require.Equal(t, rpcErr, expectedErr)
})
}
11 changes: 11 additions & 0 deletions utils/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package utils
import (
"reflect"
"slices"
"strings"

"github.com/NethermindEth/juno/core/felt"
)

func Map[T1, T2 any](slice []T1, f func(T1) T2) []T2 {
Expand Down Expand Up @@ -60,3 +63,11 @@ func Set[T comparable](slice []T) []T {

return result
}

func FeltArrToString(arr []*felt.Felt) string {
res := make([]string, len(arr))
for i, felt := range arr {
res[i] = felt.String()
}
return strings.Join(res, ", ")
}
Loading

0 comments on commit 11247e3

Please sign in to comment.