Skip to content

Commit

Permalink
implements new rpcdata
Browse files Browse the repository at this point in the history
  • Loading branch information
thiagodeev committed Dec 20, 2024
1 parent 4ca79eb commit f9d13d8
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 44 deletions.
2 changes: 1 addition & 1 deletion account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ func (account *Account) WaitForTransactionReceipt(ctx context.Context, transacti
for {
select {
case <-ctx.Done():
return nil, rpc.Err(rpc.InternalError, ctx.Err())
return nil, rpc.Err(rpc.InternalError, &rpc.RPCData{Message: ctx.Err().Error()})
case <-t.C:
receiptWithBlockInfo, err := account.TransactionReceipt(ctx, transactionHash)
if err != nil {
Expand Down
13 changes: 8 additions & 5 deletions account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ func TestWaitForTransactionReceiptMOCK(t *testing.T) {
ShouldCallTransactionReceipt: true,
Hash: new(felt.Felt).SetUint64(1),
ExpectedReceipt: nil,
ExpectedErr: rpc.Err(rpc.InternalError, "UnExpectedErr"),
ExpectedErr: rpc.Err(rpc.InternalError, &rpc.RPCData{Message: "UnExpectedErr"}),
},
{
Timeout: time.Duration(1000),
Expand All @@ -1010,7 +1010,7 @@ func TestWaitForTransactionReceiptMOCK(t *testing.T) {
Hash: new(felt.Felt).SetUint64(3),
ShouldCallTransactionReceipt: false,
ExpectedReceipt: nil,
ExpectedErr: rpc.Err(rpc.InternalError, context.DeadlineExceeded),
ExpectedErr: rpc.Err(rpc.InternalError, &rpc.RPCData{Message: context.DeadlineExceeded.Error()}),
},
},
}[testEnv]
Expand Down Expand Up @@ -1067,7 +1067,7 @@ func TestWaitForTransactionReceipt(t *testing.T) {
type testSetType struct {
Timeout int
Hash *felt.Felt
ExpectedErr error
ExpectedErr *rpc.RPCError
ExpectedReceipt rpc.TransactionReceipt
}
testSet := map[string][]testSetType{
Expand All @@ -1076,7 +1076,7 @@ func TestWaitForTransactionReceipt(t *testing.T) {
Timeout: 3, // Should poll 3 times
Hash: new(felt.Felt).SetUint64(100),
ExpectedReceipt: rpc.TransactionReceipt{},
ExpectedErr: rpc.Err(rpc.InternalError, "Post \"http://0.0.0.0:5050/\": context deadline exceeded"),
ExpectedErr: rpc.Err(rpc.InternalError, &rpc.RPCData{Message: "Post \"http://0.0.0.0:5050/\": context deadline exceeded"}),
},
},
}[testEnv]
Expand All @@ -1087,7 +1087,10 @@ func TestWaitForTransactionReceipt(t *testing.T) {

resp, err := acnt.WaitForTransactionReceipt(ctx, test.Hash, 1*time.Second)
if test.ExpectedErr != nil {
require.Equal(t, test.ExpectedErr.Error(), err.Error())
rpcErr, ok := err.(*rpc.RPCError)
require.True(t, ok)
require.Equal(t, test.ExpectedErr.Code, rpcErr.Code)
require.Equal(t, test.ExpectedErr.Message, rpcErr.Message)
} else {
require.Equal(t, test.ExpectedReceipt.ExecutionStatus, (*resp).ExecutionStatus)
}
Expand Down
6 changes: 3 additions & 3 deletions rpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,20 @@ func (provider *Provider) BlockWithReceipts(ctx context.Context, blockID BlockID

var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
return nil, Err(InternalError, err.Error())
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}

// PendingBlockWithReceipts doesn't contain a "status" field
if _, ok := m["status"]; ok {
var block BlockWithReceipts
if err := json.Unmarshal(result, &block); err != nil {
return nil, Err(InternalError, err.Error())
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}
return &block, nil
} else {
var pendingBlock PendingBlockWithReceipts
if err := json.Unmarshal(result, &pendingBlock); err != nil {
return nil, Err(InternalError, err.Error())
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}
return &pendingBlock, nil
}
Expand Down
7 changes: 5 additions & 2 deletions rpc/call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestCall(t *testing.T) {
FunctionCall FunctionCall
BlockID BlockID
ExpectedPatternResult *felt.Felt
ExpectedError error
ExpectedError *RPCError
}
testSet := map[string][]testSetType{
"devnet": {
Expand Down Expand Up @@ -111,7 +111,10 @@ func TestCall(t *testing.T) {
require := require.New(t)
output, err := testConfig.provider.Call(context.Background(), FunctionCall(test.FunctionCall), test.BlockID)
if test.ExpectedError != nil {
require.EqualError(test.ExpectedError, err.Error())
rpcErr, ok := err.(*RPCError)
require.True(ok)
require.Equal(test.ExpectedError.Code, rpcErr.Code)
require.Equal(test.ExpectedError.Message, rpcErr.Message)
} else {
require.NoError(err)
require.NotEmpty(output, "should return an output")
Expand Down
4 changes: 2 additions & 2 deletions rpc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ func (provider *Provider) Syncing(ctx context.Context) (*SyncStatus, error) {
var result interface{}
// Note: []interface{}{}...force an empty `params[]` in the jsonrpc request
if err := provider.c.CallContext(ctx, &result, "starknet_syncing", []interface{}{}...); err != nil {
return nil, Err(InternalError, err)
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}
switch res := result.(type) {
case bool:
return &SyncStatus{SyncStatus: &res}, nil
case SyncStatus:
return &res, nil
default:
return nil, Err(InternalError, "internal error with starknet_syncing")
return nil, Err(InternalError, &RPCData{Message: "internal error with starknet_syncing"})
}

}
6 changes: 3 additions & 3 deletions rpc/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ func (provider *Provider) ClassAt(ctx context.Context, blockID BlockID, contract
func typecastClassOutput(rawClass map[string]any) (ClassOutput, error) {
rawClassByte, err := json.Marshal(rawClass)
if err != nil {
return nil, Err(InternalError, err)
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}

// if contract_class_version exists, then it's a ContractClass type
if _, exists := (rawClass)["contract_class_version"]; exists {
var contractClass ContractClass
err = json.Unmarshal(rawClassByte, &contractClass)
if err != nil {
return nil, Err(InternalError, err)
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}
return &contractClass, nil
}
var depContractClass DeprecatedContractClass
err = json.Unmarshal(rawClassByte, &depContractClass)
if err != nil {
return nil, Err(InternalError, err)
return nil, Err(InternalError, &RPCData{Message: err.Error()})
}
return &depContractClass, nil
}
Expand Down
8 changes: 6 additions & 2 deletions rpc/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ func TestEstimateMessageFee(t *testing.T) {
MsgFromL1
BlockID
ExpectedFeeEst *FeeEstimation
ExpectedError error
ExpectedError *RPCError
}

// https://sepolia.voyager.online/message/0x273f4e20fc522098a60099e5872ab3deeb7fb8321a03dadbd866ac90b7268361
Expand Down Expand Up @@ -470,7 +470,10 @@ func TestEstimateMessageFee(t *testing.T) {
for _, test := range testSet {
resp, err := testConfig.provider.EstimateMessageFee(context.Background(), test.MsgFromL1, test.BlockID)
if err != nil {
require.EqualError(t, test.ExpectedError, err.Error())
rpcErr, ok := err.(*RPCError)
require.True(t, ok)
require.Equal(t, test.ExpectedError.Code, rpcErr.Code)
require.Equal(t, test.ExpectedError.Message, rpcErr.Message)
} else {
require.Exactly(t, test.ExpectedFeeEst, resp)
}
Expand All @@ -479,6 +482,7 @@ func TestEstimateMessageFee(t *testing.T) {

func TestEstimateFee(t *testing.T) {
//TODO: upgrade the mainnet and testnet test cases before merge
t.Skip("TODO: create a test case for the new 'CONTRACT_EXECUTION_ERROR' type")

testConfig := beforeEach(t)

Expand Down
126 changes: 116 additions & 10 deletions rpc/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package rpc

import (
"encoding/json"
"fmt"

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

const (
Expand All @@ -19,7 +22,7 @@ const (
// - data: any data associated with the error.
// Returns
// - *RPCError: a pointer to an RPCError object.
func Err(code int, data any) *RPCError {
func Err(code int, data *RPCData) *RPCError {
switch code {
case InvalidJSON:
return &RPCError{Code: InvalidJSON, Message: "Parse error", Data: data}
Expand All @@ -46,13 +49,13 @@ func Err(code int, data any) *RPCError {
func tryUnwrapToRPCErr(err error, rpcErrors ...*RPCError) *RPCError {
errBytes, errIn := json.Marshal(err)
if errIn != nil {
return Err(InternalError, errIn.Error())
return Err(InternalError, &RPCData{Message: errIn.Error()})
}

var nodeErr RPCError
errIn = json.Unmarshal(errBytes, &nodeErr)
if errIn != nil {
return Err(InternalError, errIn.Error())
return Err(InternalError, &RPCData{Message: errIn.Error()})
}

for _, rpcErr := range rpcErrors {
Expand All @@ -62,19 +65,74 @@ func tryUnwrapToRPCErr(err error, rpcErrors ...*RPCError) *RPCError {
}

if nodeErr.Code == 0 {
return Err(InternalError, err.Error())
return Err(InternalError, &RPCData{Message: err.Error()})
}
return Err(nodeErr.Code, nodeErr.Data)
}

type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
Code int `json:"code"`
Message string `json:"message"`
Data *RPCData `json:"data,omitempty"`
}

func (e RPCError) Error() string {
return e.Message
if e.Data == nil || e.Data.Message == "" {
return e.Message
}
return e.Message + ": " + e.Data.Message
}

type RPCData struct {
Message string `json:",omitempty"`
ContractErrorData *ContractErrorData `json:",omitempty"`
TransactionExecutionErrorData *TransactionExecutionErrorData `json:",omitempty"`
}

func (rpcData *RPCData) UnmarshalJSON(data []byte) error {
var message string
if err := json.Unmarshal(data, &message); err == nil {
rpcData.Message = message
return nil
}

var contractErrData ContractErrorData
if err := json.Unmarshal(data, &contractErrData); err == nil {
*rpcData = RPCData{
Message: rpcData.Message + contractErrData.RevertError.Message,
ContractErrorData: &contractErrData,
}
return nil
}

var txExErrData TransactionExecutionErrorData
if err := json.Unmarshal(data, &txExErrData); err == nil {
*rpcData = RPCData{
Message: rpcData.Message + txExErrData.ExecutionError.Message,
TransactionExecutionErrorData: &txExErrData,
}
return nil
}

return fmt.Errorf("failed to unmarshal RPCData")
}

func (rpcData *RPCData) MarshalJSON() ([]byte, error) {
var temp any

if rpcData.ContractErrorData != nil {
temp = *rpcData.ContractErrorData
return json.Marshal(temp)
}

if rpcData.TransactionExecutionErrorData != nil {
temp = *rpcData.TransactionExecutionErrorData
return json.Marshal(temp)
}

temp = rpcData.Message

return json.Marshal(temp)
}

var (
Expand Down Expand Up @@ -199,7 +257,55 @@ var (
Message: "An unexpected error occurred",
}
ErrCompilationError = &RPCError{
Code: 9999, //placeholder number as this error has no code so far. TODO: change this with the next updates
Message: "More data about the compilation failure",
Code: 100,
Message: "Failed to compile the contract",
}
)

type ContractErrorData struct {
// the execution trace up to the point of failure
RevertError ContractExecutionError `json:"revert_error,omitempty"`
}

type TransactionExecutionErrorData struct {
// The index of the first transaction failing in a sequence of given transactions
TransactionIndex int `json:"transaction_index,omitempty"`
// the execution trace up to the point of failure
ExecutionError ContractExecutionError `json:"execution_error,omitempty"`
}

type ContractExecutionError struct {
// the error raised during execution
Message string `json:",omitempty"`
ContractExecutionErrorStruct `json:",omitempty"`
}

func (contractEx *ContractExecutionError) UnmarshalJSON(data []byte) error {
var contractErrStruct ContractExecutionErrorStruct
var message string

if err := json.Unmarshal(data, &message); err == nil {
*contractEx = ContractExecutionError{
Message: message,
ContractExecutionErrorStruct: contractErrStruct,
}
return nil
}

if err := json.Unmarshal(data, &contractErrStruct); err == nil {
*contractEx = ContractExecutionError{
Message: "",
ContractExecutionErrorStruct: contractErrStruct,
}
return nil
}

return fmt.Errorf("failed to unmarshal ContractExecutionError")
}

type ContractExecutionErrorStruct struct {
ContractAddress *felt.Felt `json:"contract_address"`
ClassHash *felt.Felt `json:"class_hash"`
Selector *felt.Felt `json:"selector"`
Error *ContractExecutionError `json:"error"`
}
1 change: 1 addition & 0 deletions rpc/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

func TestRPCError(t *testing.T) {
t.Skip("TODO: test the new RPCData field before merge")
if testEnv == "mock" {
testConfig := beforeEach(t)
_, err := testConfig.provider.ChainID(context.Background())
Expand Down
4 changes: 2 additions & 2 deletions rpc/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ func mock_starknet_addInvokeTransaction(result interface{}, args ...interface{})
if invokeTx.SenderAddress != nil {
if invokeTx.SenderAddress.Equal(new(felt.Felt).SetUint64(123)) {
unexpErr := *ErrUnexpectedError
unexpErr.Data = "Something crazy happened"
unexpErr.Data = &RPCData{Message: "Something crazy happened"}
return &unexpErr
}
}
Expand Down Expand Up @@ -1382,7 +1382,7 @@ func mock_starknet_traceTransaction(result interface{}, args ...interface{}) err
return &RPCError{
Code: 10,
Message: "No trace available for transaction",
Data: "REJECTED",
Data: &RPCData{Message: "REJECTED"},
}
default:
return ErrHashNotFound
Expand Down
2 changes: 1 addition & 1 deletion rpc/spy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (s *spy) Compare(o interface{}, debug bool) (string, error) {
}
b, err := json.Marshal(o)
if err != nil {
return "", Err(InternalError, err)
return "", Err(InternalError, &RPCData{Message: err.Error()})
}
diff, _ := jsondiff.Compare(s.s, b, &jsondiff.Options{})
if debug {
Expand Down
Loading

0 comments on commit f9d13d8

Please sign in to comment.