From 3abddc78c0581c832a2754f21acf8215ee6e0f59 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Wed, 26 Jul 2023 17:18:24 +0100 Subject: [PATCH 01/10] core: split ErrAlreadyExists into ErrAlreadyExists and ErrAlreadyInPool ErrAlreadyExists is in blockchain and ErrAlreadyInPool is in mempool. Signed-off-by: Tatiana Nesterenko --- cli/server/server.go | 2 +- pkg/core/blockchain.go | 14 ++++++++------ pkg/core/blockchain_neotest_test.go | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cli/server/server.go b/cli/server/server.go index 8aba05454..3f090cbda 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -423,7 +423,7 @@ func mkP2PNotary(config config.P2PNotary, chain *core.Blockchain, serv *network. } n, err := notary.NewNotary(cfg, serv.Net, serv.GetNotaryPool(), func(tx *transaction.Transaction) error { err := serv.RelayTxn(tx) - if err != nil && !errors.Is(err, core.ErrAlreadyExists) { + if err != nil && !errors.Is(err, core.ErrAlreadyExists) && !errors.Is(err, core.ErrAlreadyInPool) { return fmt.Errorf("can't relay completed notary transaction: hash %s, error: %w", tx.Hash().StringLE(), err) } return nil diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index b5ca036dd..bd28047e4 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -88,10 +88,12 @@ const ( ) var ( - // ErrAlreadyExists is returned when trying to add some already existing - // transaction into the pool (not specifying whether it exists in the - // chain or mempool). - ErrAlreadyExists = errors.New("already exists") + // ErrAlreadyExists is returned when trying to add some transaction + // that already exists on chain. + ErrAlreadyExists = errors.New("already exists in blockchain") + // ErrAlreadyInPool is returned when trying to add some already existing + // transaction into the mempool. + ErrAlreadyInPool = errors.New("already exists in mempool") // ErrOOM is returned when adding transaction to the memory pool because // it reached its full capacity. ErrOOM = errors.New("no space left in the memory pool") @@ -2480,7 +2482,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. if err := bc.dao.HasTransaction(t.Hash(), t.Signers); err != nil { switch { case errors.Is(err, dao.ErrAlreadyExists): - return fmt.Errorf("blockchain: %w", ErrAlreadyExists) + return ErrAlreadyExists case errors.Is(err, dao.ErrHasConflicts): return fmt.Errorf("blockchain: %w", ErrHasConflicts) default: @@ -2500,7 +2502,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. case errors.Is(err, mempool.ErrConflict): return ErrMemPoolConflict case errors.Is(err, mempool.ErrDup): - return fmt.Errorf("mempool: %w", ErrAlreadyExists) + return ErrAlreadyInPool case errors.Is(err, mempool.ErrInsufficientFunds): return ErrInsufficientFunds case errors.Is(err, mempool.ErrOOM): diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index c00f4b0e9..d636ce2b5 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -1358,7 +1358,7 @@ func TestBlockchain_VerifyTx(t *testing.T) { require.NoError(t, bc.PoolTx(tx)) err := bc.PoolTx(tx) - require.ErrorIs(t, err, core.ErrAlreadyExists) + require.ErrorIs(t, err, core.ErrAlreadyInPool) }) t.Run("MemPoolOOM", func(t *testing.T) { mp := mempool.New(1, 0, false, nil) From 31ceb568cb8882cd36a19ff0396f0743f2dbcfc3 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Mon, 14 Aug 2023 17:43:19 +0100 Subject: [PATCH 02/10] neorpc: add error codes and response errors According to proposal: https://github.com/neo-project/proposals/pull/156 Close #2248 Signed-off-by: Tatiana Nesterenko --- pkg/neorpc/errors.go | 258 +++++- pkg/services/rpcsrv/error.go | 2 +- pkg/services/rpcsrv/server.go | 132 +-- pkg/services/rpcsrv/server_test.go | 1241 +++++++++++++++++----------- 4 files changed, 1067 insertions(+), 566 deletions(-) diff --git a/pkg/neorpc/errors.go b/pkg/neorpc/errors.go index 7ed90994c..c7ec5cc00 100644 --- a/pkg/neorpc/errors.go +++ b/pkg/neorpc/errors.go @@ -27,36 +27,232 @@ const ( ) // RPC error codes defined by the Neo JSON-RPC specification extension. +// Codes for missing items. const ( - // RPCErrorCode is returned on RPC request processing error. - RPCErrorCode = -100 + // ErrUnknownBlockCode is returned from a call that accepts as a parameter or searches for a header or a block + // as a part of its job can't find it. + ErrUnknownBlockCode = -101 + // ErrUnknownContractCode is returned from a call that accepts as a parameter or searches for a contract + // as a part of its job can't find it. + ErrUnknownContractCode = -102 + // ErrUnknownTransactionCode is returned from a call that accepts as a parameter or searches for a transaction + // as a part of its job can't find it. + ErrUnknownTransactionCode = -103 + // ErrUnknownStorageItemCode is returned from a call that looks for an item in the contract storage + // as part of its job can't find it. + ErrUnknownStorageItemCode = -104 + // ErrUnknownScriptContainerCode is returned from a call that accepts as a parameter or searches for a script + // container (a block or transaction) as a part of its job can't find it + // (this error generalizes -101 and -103 in cases where it's needed). + ErrUnknownScriptContainerCode = -105 + // ErrUnknownStateRootCode is returned from a call that accepts as a parameter or searches for a state root + // as a part of its job can't find it. + ErrUnknownStateRootCode = -106 + // ErrUnknownSessionCode is returned from a call that accepts as a parameter or searches for an iterator session + // as a part of its job can't find it. + ErrUnknownSessionCode = -107 + // ErrUnknownIteratorCode is returned from a call that accepts as a parameter or searches for a session iterator + // as a part of its job can't find it. + ErrUnknownIteratorCode = -108 + // ErrUnknownHeightCode is returned if block or header height passed as parameter or calculated during call + // execution is not correct (out of the range known to the node). + ErrUnknownHeightCode = -109 +) + +// Codes for calls that use a wallet (-300...-304) can be returned by the C# RPC server only, +// see the https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#unsupported-methods. +const ( + // ErrInsufficientFundsWalletCode is returned if transaction that sends some assets can't be created + // because it fails. Can be returned only by the C# RPC server. + ErrInsufficientFundsWalletCode = -300 + // ErrWalletFeeLimitCode is returned if transaction requires more network fee to be paid + // than is allowed by settings. Can be returned only by the C# RPC server. + ErrWalletFeeLimitCode = -301 + // ErrNoOpenedWalletCode is returned if server doesn't have any opened wallet to operate with. + // Can be returned only by the C# RPC server. + ErrNoOpenedWalletCode = -302 + // ErrWalletNotFoundCode is returned if specified (or configured) wallet file path is invalid. + // Can be returned only by the C# RPC server. + ErrWalletNotFoundCode = -303 + // ErrWalletNotSupportedCode is returned if specified (or configured) file can't be opened as a wallet. + // Can be returned only by the C# RPC server. + ErrWalletNotSupportedCode = -304 +) + +// Inventory verification or verification script errors. +const ( + // ErrVerificationFailedCode is returned on anything that can't be expressed by other codes. + // It is an unclassified inventory verification error. + ErrVerificationFailedCode = -500 + // ErrAlreadyExistsCode is returned if block or transaction is already accepted and processed on chain. + ErrAlreadyExistsCode = -501 + // ErrMempoolCapReachedCode is returned if no more transactions can be accepted into the memory pool + // (unless they have a priority) as its full capacity is reached. + ErrMempoolCapReachedCode = -502 + // ErrAlreadyInPoolCode is returned if transaction is already pooled, but not yet accepted into a block. + ErrAlreadyInPoolCode = -503 + // ErrInsufficientNetworkFeeCode is returned if transaction has incorrect (too small per Policy setting) + // network fee value. + ErrInsufficientNetworkFeeCode = -504 + // ErrPolicyFailedCode is returned from a call denied by the Policy contract (one of signers is blocked) or + // if one of the Policy filters failed. + ErrPolicyFailedCode = -505 + // ErrInvalidScriptCode is returned if transaction contains incorrect executable script. + ErrInvalidScriptCode = -506 + // ErrInvalidAttributeCode is returned if transaction contains an invalid attribute. + ErrInvalidAttributeCode = -507 + // ErrInvalidSignatureCode is returned if one of the verification scripts failed. + ErrInvalidSignatureCode = -508 + // ErrInvalidSizeCode is returned if transaction or its script is too big. + ErrInvalidSizeCode = -509 + // ErrExpiredTransactionCode is returned if transaction's ValidUntilBlock value is already in the past. + ErrExpiredTransactionCode = -510 + // ErrInsufficientFundsCode is returned if sender doesn't have enough GAS to pay for all currently pooled transactions. + ErrInsufficientFundsCode = -511 + // ErrInvalidVerificationFunctionCode is returned if contract doesn't have a verify method or + // this method doesn't return proper value. + ErrInvalidVerificationFunctionCode = -512 +) + +// Errors related to node configuration and various services. +const ( + // ErrSessionsDisabledCode is returned if iterator session support is not enabled on the server. + ErrSessionsDisabledCode = -601 + // ErrOracleDisabledCode is returned if Oracle service is not enabled in the configuration (service is not running). + ErrOracleDisabledCode = -602 + // ErrOracleRequestFinishedCode is returned if Oracle request submitted is already completely processed. + // Can be returned only by the C# RPC server. + ErrOracleRequestFinishedCode = -603 + // ErrOracleRequestNotFoundCode is returned if Oracle request submitted is not known to this node. + // Can be returned only by the C# RPC server. + ErrOracleRequestNotFoundCode = -604 + // ErrOracleNotDesignatedNodeCode is returned if Oracle service is enabled, but this node is not designated + // to provide this functionality. Can be returned only by the C# RPC server. + ErrOracleNotDesignatedNodeCode = -605 + // ErrUnsupportedStateCode is returned if this node can't answer requests for old state because it's configured + // to keep only the latest one. + ErrUnsupportedStateCode = -606 + // ErrInvalidProofCode is returned if state proof verification failed. + ErrInvalidProofCode = -607 + // ErrExecutionFailedCode is returned from a call made a VM execution, but it has failed. + ErrExecutionFailedCode = -608 ) var ( - // ErrInvalidParams represents a generic 'invalid parameters' error. - ErrInvalidParams = NewInvalidParamsError("invalid params") - // ErrUnknownBlock is returned if requested block is not found. - ErrUnknownBlock = NewError(RPCErrorCode, "Unknown block", "") - // ErrUnknownTransaction is returned if requested transaction is not found. - ErrUnknownTransaction = NewError(RPCErrorCode, "Unknown transaction", "") - // ErrUnknownHeader is returned when requested header is not found. - ErrUnknownHeader = NewError(RPCErrorCode, "Unknown header", "") - // ErrUnknownScriptContainer is returned when requested block or transaction is not found. - ErrUnknownScriptContainer = NewError(RPCErrorCode, "Unknown script container", "") - // ErrUnknownStateRoot is returned when requested state root is not found. - ErrUnknownStateRoot = NewError(RPCErrorCode, "Unknown state root", "") - // ErrAlreadyExists represents SubmitError with code -501. - ErrAlreadyExists = NewSubmitError(-501, "Block or transaction already exists and cannot be sent repeatedly.") - // ErrOutOfMemory represents SubmitError with code -502. - ErrOutOfMemory = NewSubmitError(-502, "The memory pool is full and no more transactions can be sent.") - // ErrUnableToVerify represents SubmitError with code -503. - ErrUnableToVerify = NewSubmitError(-503, "The block cannot be validated.") - // ErrValidationFailed represents SubmitError with code -504. - ErrValidationFailed = NewSubmitError(-504, "Block or transaction validation failed.") - // ErrPolicyFail represents SubmitError with code -505. - ErrPolicyFail = NewSubmitError(-505, "One of the Policy filters failed.") - // ErrUnknown represents SubmitError with code -500. - ErrUnknown = NewSubmitError(-500, "Unknown error.") + // ErrInvalidParams represents a generic "Invalid params" error. + ErrInvalidParams = NewInvalidParamsError("Invalid params") + + // ErrUnknownBlock represents an error with code [ErrUnknownBlockCode]. + // Call that accepts as a parameter or searches for a header or a block as a part of its job can't find it. + ErrUnknownBlock = NewErrorWithCode(ErrUnknownBlockCode, "Unknown block") + // ErrUnknownContract represents an error with code [ErrUnknownContractCode]. + // Call that accepts as a parameter or searches for a contract as a part of its job can't find it. + ErrUnknownContract = NewErrorWithCode(ErrUnknownContractCode, "Unknown contract") + // ErrUnknownTransaction represents an error with code [ErrUnknownTransactionCode]. + // Call that accepts as a parameter or searches for a transaction as a part of its job can't find it. + ErrUnknownTransaction = NewErrorWithCode(ErrUnknownTransactionCode, "Unknown transaction") + // ErrUnknownStorageItem represents an error with code [ErrUnknownStorageItemCode]. + // Call that looks for an item in the contract storage as part of its job can't find it. + ErrUnknownStorageItem = NewErrorWithCode(ErrUnknownStorageItemCode, "Unknown storage item") + // ErrUnknownScriptContainer represents an error with code [ErrUnknownScriptContainerCode]. + // Call that accepts as a parameter or searches for a script container (a block or transaction) + // as a part of its job can't find it (this error generalizes [ErrUnknownBlockCode] and [ErrUnknownTransactionCode] + // in cases where it's needed). + ErrUnknownScriptContainer = NewErrorWithCode(ErrUnknownScriptContainerCode, "Unknown script container") + // ErrUnknownStateRoot represents an error with code [ErrUnknownStateRootCode]. + // Call that accepts as a parameter or searches for a state root as a part of its job can't find it. + ErrUnknownStateRoot = NewErrorWithCode(ErrUnknownStateRootCode, "Unknown state root") + // ErrUnknownSession represents an error with code [ErrUnknownSessionCode]. + // Call that accepts as a parameter or searches for an iterator session as a part of its job can't find it. + ErrUnknownSession = NewErrorWithCode(ErrUnknownSessionCode, "Unknown session") + // ErrUnknownIterator represents an error with code [ErrUnknownIteratorCode]. + // Call that accepts as a parameter or searches for a session iterator as a part of its job can't find it. + ErrUnknownIterator = NewErrorWithCode(ErrUnknownIteratorCode, "Unknown iterator") + // ErrUnknownHeight represents an error with code [ErrUnknownHeightCode]. + // Block or header height passed as parameter or calculated during call execution is not correct + // (out of the range known to the node). + ErrUnknownHeight = NewErrorWithCode(ErrUnknownHeightCode, "Unknown height") + + // ErrInsufficientFundsWallet represents an error with code [ErrInsufficientFundsWalletCode]. Can be returned only by the C# RPC server. + // Transaction that sends some assets can't be created because it fails. + ErrInsufficientFundsWallet = NewErrorWithCode(ErrInsufficientFundsWalletCode, "Insufficient funds") + // ErrWalletFeeLimit represents an error with code [ErrWalletFeeLimitCode]. Can be returned only by the C# RPC server. + // Transaction requires more network fee to be paid than is allowed by settings. + ErrWalletFeeLimit = NewErrorWithCode(ErrWalletFeeLimitCode, "Fee limit exceeded") + // ErrNoOpenedWallet represents an error with code [ErrNoOpenedWalletCode]. Can be returned only by the C# RPC server. + // Server doesn't have any opened wallet to operate with. + ErrNoOpenedWallet = NewErrorWithCode(ErrNoOpenedWalletCode, "No opened wallet") + // ErrWalletNotFound represents an error with code [ErrWalletNotFoundCode]. Can be returned only by the C# RPC server. + // Specified (or configured) wallet file path is invalid. + ErrWalletNotFound = NewErrorWithCode(ErrWalletNotFoundCode, "Wallet not found") + // ErrWalletNotSupported represents an error with code [ErrWalletNotSupportedCode]. Can be returned only by the C# RPC server. + // Specified (or configured) file can't be opened as a wallet. + ErrWalletNotSupported = NewErrorWithCode(ErrWalletNotSupportedCode, "Wallet not supported") + + // ErrVerificationFailed represents an error with code [ErrVerificationFailedCode]. + // Any verification error that can't be expressed by other codes. + ErrVerificationFailed = NewErrorWithCode(ErrVerificationFailedCode, "Unclassified inventory verification error") + // ErrAlreadyExists represents an error with code [ErrAlreadyExistsCode]. + // Block or transaction is already accepted and processed on chain. + ErrAlreadyExists = NewErrorWithCode(ErrAlreadyExistsCode, "Inventory already exists on chain") + // ErrMempoolCapReached represents an error with code [ErrMempoolCapReachedCode]. + // No more transactions can be accepted into the memory pool (unless they have a priority) as its full capacity is reached. + ErrMempoolCapReached = NewErrorWithCode(ErrMempoolCapReachedCode, "The memory pool is full and no more transactions can be sent") + // ErrAlreadyInPool represents an error with code [ErrAlreadyInPoolCode]. + // Transaction is already pooled, but not yet accepted into a block. + ErrAlreadyInPool = NewErrorWithCode(ErrAlreadyInPoolCode, "Transaction already exists in the memory pool") + // ErrInsufficientNetworkFee represents an error with code [ErrInsufficientNetworkFeeCode]. + // Transaction has incorrect (too small per Policy setting) network fee value. + ErrInsufficientNetworkFee = NewErrorWithCode(ErrInsufficientNetworkFeeCode, "Insufficient network fee") + // ErrPolicyFailed represents an error with code [ErrPolicyFailedCode]. + // Denied by the Policy contract (one of signers is blocked). + ErrPolicyFailed = NewErrorWithCode(ErrPolicyFailedCode, "One of the Policy filters failed") + // ErrInvalidScript represents an error with code [ErrInvalidScriptCode]. + // Transaction contains incorrect executable script. + ErrInvalidScript = NewErrorWithCode(ErrInvalidScriptCode, "Invalid script") + // ErrInvalidAttribute represents an error with code [ErrInvalidAttributeCode]. + // Transaction contains an invalid attribute. + ErrInvalidAttribute = NewErrorWithCode(ErrInvalidAttributeCode, "Invalid transaction attribute") + // ErrInvalidSignature represents an error with code [ErrInvalidSignatureCode]. + // One of the verification scripts failed. + ErrInvalidSignature = NewErrorWithCode(ErrInvalidSignatureCode, "Invalid signature") + // ErrInvalidSize represents an error with code [ErrInvalidSizeCode]. + // Transaction or its script is too big. + ErrInvalidSize = NewErrorWithCode(ErrInvalidSizeCode, "Invalid inventory size") + // ErrExpiredTransaction represents an error with code [ErrExpiredTransactionCode]. + // Transaction's ValidUntilBlock value is already in the past. + ErrExpiredTransaction = NewErrorWithCode(ErrExpiredTransactionCode, "Expired transaction") + // ErrInsufficientFunds represents an error with code [ErrInsufficientFundsCode]. + // Sender doesn't have enough GAS to pay for all currently pooled transactions. + ErrInsufficientFunds = NewErrorWithCode(ErrInsufficientFundsCode, "Insufficient funds") + // ErrInvalidVerificationFunction represents an error with code [ErrInvalidVerificationFunctionCode]. + // Contract doesn't have a verify method or this method doesn't return proper value. + ErrInvalidVerificationFunction = NewErrorWithCode(ErrInvalidVerificationFunctionCode, "Invalid verification function") + + // ErrSessionsDisabled represents an error with code [ErrSessionsDisabledCode]. + // Iterator session support is not enabled on the server. + ErrSessionsDisabled = NewErrorWithCode(ErrSessionsDisabledCode, "Sessions disabled") + // ErrOracleDisabled represents an error with code [ErrOracleDisabledCode]. + // Service is not enabled in the configuration. + ErrOracleDisabled = NewErrorWithCode(ErrOracleDisabledCode, "Oracle service is not running") + // ErrOracleRequestFinished represents an error with code [ErrOracleRequestFinishedCode]. Can be returned only by the C# RPC server. + // The oracle request submitted is already completely processed. + ErrOracleRequestFinished = NewErrorWithCode(ErrOracleRequestFinishedCode, "Oracle request has already been finished") + // ErrOracleRequestNotFound represents an error with code [ErrOracleRequestNotFoundCode]. Can be returned only by the C# RPC server. + // The oracle request submitted is not known to this node. + ErrOracleRequestNotFound = NewErrorWithCode(ErrOracleRequestNotFoundCode, "Oracle request is not found") + // ErrOracleNotDesignatedNode represents an error with code [ErrOracleNotDesignatedNodeCode]. Can be returned only by the C# RPC server. + // Oracle service is enabled, but this node is not designated to provide this functionality. + ErrOracleNotDesignatedNode = NewErrorWithCode(ErrOracleNotDesignatedNodeCode, "Not a designated oracle node") + // ErrUnsupportedState represents an error with code [ErrUnsupportedStateCode]. + // This node can't answer requests for old state because it's configured to keep only the latest one. + ErrUnsupportedState = NewErrorWithCode(ErrUnsupportedStateCode, "Old state requests are not supported") + // ErrInvalidProof represents an error with code [ErrInvalidProofCode]. + // State proof verification failed. + ErrInvalidProof = NewErrorWithCode(ErrInvalidProofCode, "Invalid proof") + // ErrExecutionFailed represents an error with code [ErrExecutionFailedCode]. + // Call made a VM execution, but it has failed. + ErrExecutionFailed = NewErrorWithCode(ErrExecutionFailedCode, "Execution failed") ) // NewError is an Error constructor that takes Error contents from its parameters. @@ -98,15 +294,9 @@ func NewInternalServerError(data string) *Error { return NewError(InternalServerErrorCode, "Internal error", data) } -// NewRPCError creates a new error with -// code -100. -func NewRPCError(message string, data string) *Error { - return NewError(RPCErrorCode, message, data) -} - -// NewSubmitError creates a new error with +// NewErrorWithCode creates a new error with // specified error code and error message. -func NewSubmitError(code int64, message string) *Error { +func NewErrorWithCode(code int64, message string) *Error { return NewError(code, message, "") } diff --git a/pkg/services/rpcsrv/error.go b/pkg/services/rpcsrv/error.go index 89fb64538..683a3cc43 100644 --- a/pkg/services/rpcsrv/error.go +++ b/pkg/services/rpcsrv/error.go @@ -44,7 +44,7 @@ func getHTTPCodeForError(respErr *neorpc.Error) int { switch respErr.Code { case neorpc.BadRequestCode: httpCode = http.StatusBadRequest - case neorpc.InvalidRequestCode, neorpc.RPCErrorCode, neorpc.InvalidParamsCode: + case neorpc.InvalidRequestCode, neorpc.InvalidParamsCode: httpCode = http.StatusUnprocessableEntity case neorpc.MethodNotFoundCode: httpCode = http.StatusMethodNotAllowed diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index fd1ecf6aa..145b2cb6d 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -250,10 +250,6 @@ var rpcWsHandlers = map[string]func(*Server, params.Params, *subscriber) (any, * "unsubscribe": (*Server).unsubscribe, } -var invalidBlockHeightError = func(index int, height int) *neorpc.Error { - return neorpc.NewRPCError("Invalid block height", fmt.Sprintf("param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height)) -} - // New creates a new Server struct. Pay attention that orc is expected to be either // untyped nil or non-nil structure implementing OracleHandler interface. func New(chain Ledger, conf config.RPC, coreServer *network.Server, @@ -783,7 +779,7 @@ func (s *Server) getBlock(reqParams params.Params) (any, *neorpc.Error) { block, err := s.chain.GetBlock(hash) if err != nil { - return nil, neorpc.NewRPCError("Failed to get block", err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnknownBlock, err.Error()) } if v, _ := reqParams.Value(1).GetBoolean(); v { @@ -801,7 +797,7 @@ func (s *Server) getBlock(reqParams params.Params) (any, *neorpc.Error) { func (s *Server) getBlockHash(reqParams params.Params) (any, *neorpc.Error) { num, err := s.blockHeightFromParam(reqParams.Value(0)) if err != nil { - return nil, neorpc.ErrInvalidParams + return nil, err } return s.chain.GetHeaderHash(num), nil @@ -900,11 +896,11 @@ func (s *Server) calculateNetworkFee(reqParams params.Params) (any, *neorpc.Erro if len(w.VerificationScript) == 0 { // Contract-based verification cs := s.chain.GetContractState(signer.Account) if cs == nil { - return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("signer %d has no verification script and no deployed contract", i)) + return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidVerificationFunction, fmt.Sprintf("signer %d has no verification script and no deployed contract", i)) } md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1) if md == nil || md.ReturnType != smartcontract.BoolType { - return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("signer %d has no verify method in deployed contract", i)) + return 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidVerificationFunction, fmt.Sprintf("signer %d has no verify method in deployed contract", i)) } paramz = md.Parameters // Might as well have none params and it's OK. } else { // Regular signature verification. @@ -1004,7 +1000,7 @@ func (s *Server) getNEP11Balances(ps params.Params) (any, *neorpc.Error) { } lastUpdated, err := s.chain.GetTokenLastUpdated(u) if err != nil { - return nil, neorpc.NewRPCError("Failed to get NEP-11 last updated block", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Failed to get NEP-11 last updated block: %s", err.Error())) } var count int stateSyncPoint := lastUpdated[math.MinInt32] @@ -1092,7 +1088,7 @@ func (s *Server) getNEP11Properties(ps params.Params) (any, *neorpc.Error) { } props, err := s.invokeNEP11Properties(asset, token, nil) if err != nil { - return nil, neorpc.NewRPCError("Failed to get NEP-11 properties", err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrExecutionFailed, fmt.Sprintf("Failed to get NEP-11 properties: %s", err.Error())) } res := make(map[string]any) for _, kv := range props { @@ -1129,7 +1125,7 @@ func (s *Server) getNEP17Balances(ps params.Params) (any, *neorpc.Error) { } lastUpdated, err := s.chain.GetTokenLastUpdated(u) if err != nil { - return nil, neorpc.NewRPCError("Failed to get NEP-17 last updated block", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Failed to get NEP-17 last updated block: %s", err.Error())) } stateSyncPoint := lastUpdated[math.MinInt32] bw := io.NewBufBinWriter() @@ -1426,7 +1422,7 @@ func (s *Server) contractIDFromParam(param *params.Param) (int32, *neorpc.Error) if scriptHash, err := param.GetUint160FromHex(); err == nil { cs := s.chain.GetContractState(scriptHash) if cs == nil { - return 0, neorpc.ErrUnknown + return 0, neorpc.ErrUnknownContract } result = cs.ID } else { @@ -1462,14 +1458,14 @@ func (s *Server) contractScriptHashFromParam(param *params.Param) (util.Uint160, } id, err := strconv.Atoi(nameOrHashOrIndex) if err != nil { - return result, neorpc.NewRPCError("Invalid contract identifier (name/hash/index is expected)", err.Error()) + return result, neorpc.NewInvalidParamsError(fmt.Sprintf("Invalid contract identifier (name/hash/index is expected) : %s", err.Error())) } if err := checkInt32(id); err != nil { return result, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, err.Error()) } result, err = s.chain.GetContractScriptHash(int32(id)) if err != nil { - return result, neorpc.NewRPCError("Unknown contract", "") + return result, neorpc.ErrUnknownContract } return result, nil } @@ -1485,7 +1481,7 @@ var errKeepOnlyLatestState = errors.New("'KeepOnlyLatestState' setting is enable func (s *Server) getProof(ps params.Params) (any, *neorpc.Error) { if s.chain.GetConfig().Ledger.KeepOnlyLatestState { - return nil, neorpc.NewInvalidRequestError(fmt.Sprintf("'getproof' is not supported: %s", errKeepOnlyLatestState)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'getproof' is not supported: %s", errKeepOnlyLatestState)) } root, err := ps.Value(0).GetUint256() if err != nil { @@ -1506,6 +1502,9 @@ func (s *Server) getProof(ps params.Params) (any, *neorpc.Error) { skey := makeStorageKey(cs.ID, key) proof, err := s.chain.GetStateModule().GetStateProof(root, skey) if err != nil { + if errors.Is(err, mpt.ErrNotFound) { + return nil, neorpc.ErrUnknownStorageItem + } return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to get proof: %s", err)) } return &result.ProofWithKey{ @@ -1516,7 +1515,7 @@ func (s *Server) getProof(ps params.Params) (any, *neorpc.Error) { func (s *Server) verifyProof(ps params.Params) (any, *neorpc.Error) { if s.chain.GetConfig().Ledger.KeepOnlyLatestState { - return nil, neorpc.NewInvalidRequestError(fmt.Sprintf("'verifyproof' is not supported: %s", errKeepOnlyLatestState)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'verifyproof' is not supported: %s", errKeepOnlyLatestState)) } root, err := ps.Value(0).GetUint256() if err != nil { @@ -1549,7 +1548,7 @@ func (s *Server) getState(ps params.Params) (any, *neorpc.Error) { return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to get current stateroot: %s", err)) } if !curr.Root.Equals(root) { - return nil, neorpc.NewInvalidRequestError(fmt.Sprintf("'getstate' is not supported for old states: %s", errKeepOnlyLatestState)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'getstate' is not supported for old states: %s", errKeepOnlyLatestState)) } } csHash, err := ps.Value(1).GetUint160FromHex() @@ -1567,7 +1566,10 @@ func (s *Server) getState(ps params.Params) (any, *neorpc.Error) { sKey := makeStorageKey(cs.ID, key) res, err := s.chain.GetStateModule().GetState(root, sKey) if err != nil { - return nil, neorpc.NewRPCError("Failed to get historical item state", err.Error()) + if errors.Is(err, mpt.ErrNotFound) { + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid key: %s", err.Error())) + } + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Failed to get historical item state: %s", err.Error())) } return res, nil } @@ -1583,7 +1585,7 @@ func (s *Server) findStates(ps params.Params) (any, *neorpc.Error) { return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to get current stateroot: %s", err)) } if !curr.Root.Equals(root) { - return nil, neorpc.NewInvalidRequestError(fmt.Sprintf("'findstates' is not supported for old states: %s", errKeepOnlyLatestState)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'findstates' is not supported for old states: %s", errKeepOnlyLatestState)) } } csHash, err := ps.Value(1).GetUint160FromHex() @@ -1670,7 +1672,7 @@ func (s *Server) getHistoricalContractState(root util.Uint256, csHash util.Uint1 csKey := makeStorageKey(native.ManagementContractID, native.MakeContractKey(csHash)) csBytes, err := s.chain.GetStateModule().GetState(root, csKey) if err != nil { - return nil, neorpc.NewRPCError("Failed to get historical contract state", err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnknownContract, fmt.Sprintf("Failed to get historical contract state: %s", err.Error())) } contract := new(state.Contract) err = stackitem.DeserializeConvertible(csBytes, contract) @@ -1720,7 +1722,7 @@ func (s *Server) getStateRoot(ps params.Params) (any, *neorpc.Error) { func (s *Server) getStorage(ps params.Params) (any, *neorpc.Error) { id, rErr := s.contractIDFromParam(ps.Value(0)) - if rErr == neorpc.ErrUnknown { + if rErr == neorpc.ErrUnknownContract { return nil, nil } if rErr != nil { @@ -1759,14 +1761,14 @@ func (s *Server) getrawtransaction(reqParams params.Params) (any, *neorpc.Error) _header := s.chain.GetHeaderHash(height) header, err := s.chain.GetHeader(_header) if err != nil { - return nil, neorpc.NewRPCError("Failed to get header for the transaction", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Failed to get header for the transaction: %s", err.Error())) } aers, err := s.chain.GetAppExecResults(txHash, trigger.Application) if err != nil { - return nil, neorpc.NewRPCError("Failed to get application log for the transaction", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Failed to get application log for the transaction: %s", err.Error())) } if len(aers) == 0 { - return nil, neorpc.NewRPCError("Inconsistent application log", "application log for the transaction is empty") + return nil, neorpc.NewInternalServerError("Inconsistent application log: application log for the transaction is empty") } res.TransactionMetadata = result.TransactionMetadata{ Blockhash: header.Hash(), @@ -1802,7 +1804,7 @@ func (s *Server) getContractState(reqParams params.Params) (any, *neorpc.Error) } cs := s.chain.GetContractState(scriptHash) if cs == nil { - return nil, neorpc.NewRPCError("Unknown contract", "") + return nil, neorpc.ErrUnknownContract } return cs, nil } @@ -1815,7 +1817,7 @@ func (s *Server) getNativeContracts(_ params.Params) (any, *neorpc.Error) { func (s *Server) getBlockSysFee(reqParams params.Params) (any, *neorpc.Error) { num, err := s.blockHeightFromParam(reqParams.Value(0)) if err != nil { - return 0, neorpc.NewRPCError("Invalid height", "invalid block identifier") + return 0, neorpc.WrapErrorWithData(err, fmt.Sprintf("invalid block height: %s", err.Data)) } headerHash := s.chain.GetHeaderHash(num) @@ -1843,7 +1845,7 @@ func (s *Server) getBlockHeader(reqParams params.Params) (any, *neorpc.Error) { verbose, _ := reqParams.Value(1).GetBoolean() h, err := s.chain.GetHeader(hash) if err != nil { - return nil, neorpc.ErrUnknownHeader + return nil, neorpc.ErrUnknownBlock } if verbose { @@ -1877,7 +1879,7 @@ func (s *Server) getUnclaimedGas(ps params.Params) (any, *neorpc.Error) { } gas, err := s.chain.CalculateClaimable(u, s.chain.BlockHeight()+1) // +1 as in C#, for the next block. if err != nil { - return nil, neorpc.NewRPCError("Can't calculate claimable", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Can't calculate claimable: %s", err.Error())) } return result.UnclaimedGas{ Address: u, @@ -1891,11 +1893,11 @@ func (s *Server) getCandidates(_ params.Params) (any, *neorpc.Error) { validators, err := s.chain.GetNextBlockValidators() if err != nil { - return nil, neorpc.NewRPCError("Can't get next block validators", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Can't get next block validators: %s", err.Error())) } enrollments, err := s.chain.GetEnrollments() if err != nil { - return nil, neorpc.NewRPCError("Can't get enrollments", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Can't get enrollments: %s", err.Error())) } var res = make([]result.Candidate, 0) for _, v := range enrollments { @@ -1914,11 +1916,11 @@ func (s *Server) getNextBlockValidators(_ params.Params) (any, *neorpc.Error) { validators, err := s.chain.GetNextBlockValidators() if err != nil { - return nil, neorpc.NewRPCError("Can't get next block validators", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Can't get next block validators: %s", err.Error())) } enrollments, err := s.chain.GetEnrollments() if err != nil { - return nil, neorpc.NewRPCError("Can't get enrollments", err.Error()) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("Can't get enrollments: %s", err.Error())) } var res = make([]result.Validator, 0) for _, v := range enrollments { @@ -2003,7 +2005,7 @@ func (s *Server) getInvokeFunctionParams(reqParams params.Params) (*transaction. } script, err := params.CreateFunctionInvocationScript(scriptHash, method, invparams) if err != nil { - return nil, false, neorpc.NewInternalServerError(fmt.Sprintf("can't create invocation script: %s", err)) + return nil, false, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("can't create invocation script: %s", err)) } tx.Script = script return tx, verbose, nil @@ -2130,7 +2132,7 @@ func (s *Server) getInvokeContractVerifyParams(reqParams params.Params) (util.Ui // handling consistency. func (s *Server) getHistoricParams(reqParams params.Params) (uint32, *neorpc.Error) { if s.chain.GetConfig().Ledger.KeepOnlyLatestState { - return 0, neorpc.NewInvalidRequestError(fmt.Sprintf("only latest state is supported: %s", errKeepOnlyLatestState)) + return 0, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("only latest state is supported: %s", errKeepOnlyLatestState)) } if len(reqParams) < 1 { return 0, neorpc.ErrInvalidParams @@ -2185,7 +2187,14 @@ func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contrac err = s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}}) if err != nil { - return nil, neorpc.NewInternalServerError(fmt.Sprintf("can't prepare verification VM: %s", err)) + switch { + case errors.Is(err, core.ErrUnknownVerificationContract): + return nil, neorpc.WrapErrorWithData(neorpc.ErrUnknownContract, err.Error()) + case errors.Is(err, core.ErrInvalidVerificationContract): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidVerificationFunction, err.Error()) + default: + return nil, neorpc.NewInternalServerError(fmt.Sprintf("can't prepare verification VM: %s", err)) + } } } else { ic.VM.LoadScriptWithFlags(script, callflag.All) @@ -2319,7 +2328,7 @@ func (s *Server) registerOrDumpIterator(item stackitem.Item) (stackitem.Item, uu func (s *Server) traverseIterator(reqParams params.Params) (any, *neorpc.Error) { if !s.config.SessionEnabled { - return nil, neorpc.NewInvalidRequestError("sessions are disabled") + return nil, neorpc.ErrSessionsDisabled } sID, err := reqParams.Value(0).GetUUID() if err != nil { @@ -2376,7 +2385,7 @@ func (s *Server) traverseIterator(reqParams params.Params) (any, *neorpc.Error) func (s *Server) terminateSession(reqParams params.Params) (any, *neorpc.Error) { if !s.config.SessionEnabled { - return nil, neorpc.NewInvalidRequestError("sessions are disabled") + return nil, neorpc.ErrSessionsDisabled } sID, err := reqParams.Value(0).GetUUID() if err != nil { @@ -2412,24 +2421,13 @@ func (s *Server) submitBlock(reqParams params.Params) (any, *neorpc.Error) { if r.Err != nil { return nil, neorpc.NewInvalidParamsError(fmt.Sprintf("can't decode block: %s", r.Err)) } - err = s.chain.AddBlock(b) - if err != nil { - switch { - case errors.Is(err, core.ErrInvalidBlockIndex) || errors.Is(err, core.ErrAlreadyExists): - return nil, neorpc.WrapErrorWithData(neorpc.ErrAlreadyExists, err.Error()) - default: - return nil, neorpc.WrapErrorWithData(neorpc.ErrValidationFailed, err.Error()) - } - } - return &result.RelayResult{ - Hash: b.Hash(), - }, nil + return getRelayResult(s.chain.AddBlock(b), b.Hash()) } // submitNotaryRequest broadcasts P2PNotaryRequest over the Neo network. func (s *Server) submitNotaryRequest(ps params.Params) (any, *neorpc.Error) { if !s.chain.P2PSigExtensionsEnabled() { - return nil, neorpc.NewRPCError("P2PSignatureExtensions are disabled", "") + return nil, neorpc.NewInternalServerError("P2PSignatureExtensions are disabled") } bytePayload, err := ps.Value(0).GetBytesBase64() @@ -2450,21 +2448,37 @@ func getRelayResult(err error, hash util.Uint256) (any, *neorpc.Error) { return result.RelayResult{ Hash: hash, }, nil - case errors.Is(err, core.ErrAlreadyExists): + case errors.Is(err, core.ErrTxExpired): + return nil, neorpc.WrapErrorWithData(neorpc.ErrExpiredTransaction, err.Error()) + case errors.Is(err, core.ErrAlreadyExists) || errors.Is(err, core.ErrInvalidBlockIndex): return nil, neorpc.WrapErrorWithData(neorpc.ErrAlreadyExists, err.Error()) + case errors.Is(err, core.ErrAlreadyInPool): + return nil, neorpc.WrapErrorWithData(neorpc.ErrAlreadyInPool, err.Error()) case errors.Is(err, core.ErrOOM): - return nil, neorpc.WrapErrorWithData(neorpc.ErrOutOfMemory, err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrMempoolCapReached, err.Error()) case errors.Is(err, core.ErrPolicy): - return nil, neorpc.WrapErrorWithData(neorpc.ErrPolicyFail, err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrPolicyFailed, err.Error()) + case errors.Is(err, core.ErrInvalidScript): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidScript, err.Error()) + case errors.Is(err, core.ErrTxTooBig): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidSize, err.Error()) + case errors.Is(err, core.ErrTxSmallNetworkFee): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInsufficientNetworkFee, err.Error()) + case errors.Is(err, core.ErrInvalidAttribute): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidAttribute, err.Error()) + case errors.Is(err, core.ErrInsufficientFunds): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInsufficientFunds, err.Error()) + case errors.Is(err, core.ErrInvalidSignature): + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidSignature, err.Error()) default: - return nil, neorpc.WrapErrorWithData(neorpc.ErrValidationFailed, err.Error()) + return nil, neorpc.WrapErrorWithData(neorpc.ErrVerificationFailed, err.Error()) } } func (s *Server) submitOracleResponse(ps params.Params) (any, *neorpc.Error) { oraclePtr := s.oracle.Load() if oraclePtr == nil { - return nil, neorpc.NewRPCError("Oracle is not enabled", "") + return nil, neorpc.ErrOracleDisabled } oracle := oraclePtr.(OracleHandler) var pub *keys.PublicKey @@ -2481,15 +2495,15 @@ func (s *Server) submitOracleResponse(ps params.Params) (any, *neorpc.Error) { } txSig, err := ps.Value(2).GetBytesBase64() if err != nil { - return nil, neorpc.NewInvalidParamsError(fmt.Sprintf("tx signature is missing: %s", err)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("tx signature is missing: %s", err)) } msgSig, err := ps.Value(3).GetBytesBase64() if err != nil { - return nil, neorpc.NewInvalidParamsError(fmt.Sprintf("msg signature is missing: %s", err)) + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("msg signature is missing: %s", err)) } data := broadcaster.GetMessage(pubBytes, uint64(reqID), txSig) if !pub.Verify(msgSig, hash.Sha256(data).BytesBE()) { - return nil, neorpc.NewRPCError("Invalid request signature", "") + return nil, neorpc.ErrInvalidSignature } oracle.AddResponse(pub, uint64(reqID), txSig) return json.RawMessage([]byte("{}")), nil @@ -2803,7 +2817,7 @@ func (s *Server) blockHeightFromParam(param *params.Param) (uint32, *neorpc.Erro } if num < 0 || int64(num) > int64(s.chain.BlockHeight()) { - return 0, invalidBlockHeightError(0, num) + return 0, neorpc.WrapErrorWithData(neorpc.ErrUnknownHeight, fmt.Sprintf("param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", 0, num)) } return uint32(num), nil } diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index e59055b8a..35e58dbd9 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -19,6 +19,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/config" @@ -60,11 +61,12 @@ type executor struct { } type rpcTestCase struct { - name string - params string - fail bool - result func(e *executor) any - check func(t *testing.T, e *executor, result any) + name string + params string + fail bool + errCode int64 + result func(e *executor) any + check func(t *testing.T, e *executor, result any) } const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4" @@ -93,6 +95,49 @@ var ( nfsoToken1ObjectID = util.Uint256{4, 5, 6} ) +var rpcFunctionsWithUnsupportedStatesTestCases = map[string][]rpcTestCase{ + "getproof": { + { + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.ErrUnsupportedStateCode, + }, + }, + "verifyproof": { + { + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.ErrUnsupportedStateCode, + }, + }, + "getstate": { + { + name: "unknown root/item", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "QQ=="]`, + fail: true, + errCode: neorpc.ErrUnsupportedStateCode, + }, + }, + "findstates": { + { + name: "invalid contract", + params: `["` + block20StateRootLE + `", "0xabcdef"]`, + fail: true, + errCode: neorpc.ErrUnsupportedStateCode, + }, + }, + "invokefunctionhistoric": { + { + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.ErrUnsupportedStateCode, + }, + }, +} + var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { { @@ -151,24 +196,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "invalid trigger (not a string)", - params: `["` + genesisBlockHash + `", 1]`, - fail: true, + name: "invalid trigger (not a string)", + params: `["` + genesisBlockHash + `", 1]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahash"]`, - fail: true, + name: "invalid address", + params: `["notahash"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid tx hash", - params: `["d24cc1d52b5c0216cbf3835bb5bac8ccf32639fa1ab6627ec4e2b9f33f7ec02f"]`, - fail: true, + name: "invalid tx hash", + params: `["d24cc1d52b5c0216cbf3835bb5bac8ccf32639fa1ab6627ec4e2b9f33f7ec02f"]`, + fail: true, + errCode: neorpc.ErrUnknownScriptContainerCode, }, }, "getcontractstate": { @@ -213,41 +262,48 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "negative, bad hash", - params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`, - fail: true, + name: "negative, bad hash", + params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "negative, bad ID", - params: `[-100]`, - fail: true, + name: "negative, bad ID", + params: `[-100]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "negative, bad native name", - params: `["unknown_native"]`, - fail: true, + name: "negative, bad native name", + params: `["unknown_native"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid hash", - params: `["notahex"]`, - fail: true, + name: "invalid hash", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "getnep11balances": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahex"]`, - fail: true, + name: "invalid address", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { name: "positive", @@ -264,24 +320,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getnep11properties": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahex"]`, - fail: true, + name: "invalid address", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no token", - params: `["` + nnsContractHash + `"]`, - fail: true, + name: "no token", + params: `["` + nnsContractHash + `"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad token", - params: `["` + nnsContractHash + `", "abcdef"]`, - fail: true, + name: "bad token", + params: `["` + nnsContractHash + `", "abcdef"]`, + fail: true, + errCode: neorpc.ErrExecutionFailedCode, }, { name: "positive", @@ -297,24 +357,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getnep11transfers": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahex"]`, - fail: true, + name: "invalid address", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid timestamp", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`, - fail: true, + name: "invalid timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid stop timestamp", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`, - fail: true, + name: "invalid stop timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { name: "positive", @@ -325,14 +389,16 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getnep17balances": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahex"]`, - fail: true, + name: "invalid address", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { name: "positive", @@ -349,49 +415,58 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getnep17transfers": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["notahex"]`, - fail: true, + name: "invalid address", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid timestamp", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`, - fail: true, + name: "invalid timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "notanumber"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid stop timestamp", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`, - fail: true, + name: "invalid stop timestamp", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "blah"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid limit", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "0"]`, - fail: true, + name: "invalid limit", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "0"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid limit 2", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "bleh"]`, - fail: true, + name: "invalid limit 2", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "bleh"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid limit 3", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "100500"]`, - fail: true, + name: "invalid limit 3", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "100500"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid page", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "-1"]`, - fail: true, + name: "invalid page", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "-1"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid page 2", - params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "jajaja"]`, - fail: true, + name: "invalid page 2", + params: `["` + testchain.PrivateKeyByID(0).Address() + `", "1", "2", "3", "jajaja"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { name: "positive", @@ -408,88 +483,104 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getproof": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid root", - params: `["0xabcdef"]`, - fail: true, + name: "invalid root", + params: `["0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid contract", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "0xabcdef"]`, - fail: true, + name: "invalid contract", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid key", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "notahex"]`, - fail: true, + name: "invalid key", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "getstate": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid root", - params: `["0xabcdef"]`, - fail: true, + name: "invalid root", + params: `["0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid contract", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "0xabcdef"]`, - fail: true, + name: "invalid contract", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid key", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "notabase64%"]`, - fail: true, + name: "invalid key", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "notabase64%"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "unknown contract", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000", "QQ=="]`, - fail: true, + name: "unknown contract", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000", "QQ=="]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "unknown root/item", - params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "QQ=="]`, - fail: true, + name: "unknown root/item", + params: `["0000000000000000000000000000000000000000000000000000000000000000", "` + testContractHash + `", "QQ=="]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, }, "findstates": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid root", - params: `["0xabcdef"]`, - fail: true, + name: "invalid root", + params: `["0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid contract", - params: `["` + block20StateRootLE + `", "0xabcdef"]`, - fail: true, + name: "invalid contract", + params: `["` + block20StateRootLE + `", "0xabcdef"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid prefix", - params: `["` + block20StateRootLE + `", "` + testContractHash + `", "notabase64%"]`, - fail: true, + name: "invalid prefix", + params: `["` + block20StateRootLE + `", "` + testContractHash + `", "notabase64%"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid key", - params: `["` + block20StateRootLE + `", "` + testContractHash + `", "QQ==", "notabase64%"]`, - fail: true, + name: "invalid key", + params: `["` + block20StateRootLE + `", "` + testContractHash + `", "QQ==", "notabase64%"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "unknown contract/large count", - params: `["` + block20StateRootLE + `", "0000000000000000000000000000000000000000", "QQ==", "QQ==", 101]`, - fail: true, + name: "unknown contract/large count", + params: `["` + block20StateRootLE + `", "0000000000000000000000000000000000000000", "QQ==", "QQ==", 101]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, }, "getstateheight": { @@ -508,14 +599,16 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getstateroot": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid hash", - params: `["0x1234567890"]`, - fail: true, + name: "invalid hash", + params: `["0x1234567890"]`, + fail: true, + errCode: neorpc.ErrUnknownStateRootCode, }, }, "getstorage": { @@ -536,24 +629,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no second parameter", - params: fmt.Sprintf(`["%s"]`, testContractHash), - fail: true, + name: "no second parameter", + params: fmt.Sprintf(`["%s"]`, testContractHash), + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid hash", - params: `["notahex"]`, - fail: true, + name: "invalid hash", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid key", - params: fmt.Sprintf(`["%s", "notabase64$"]`, testContractHash), - fail: true, + name: "invalid key", + params: fmt.Sprintf(`["%s", "notabase64$"]`, testContractHash), + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "getbestblockhash": { @@ -587,29 +684,34 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad params", - params: `[[]]`, - fail: true, + name: "bad params", + params: `[[]]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid height", - params: `[-1]`, - fail: true, + name: "invalid height", + params: `[-1]`, + fail: true, + errCode: neorpc.ErrUnknownHeightCode, }, { - name: "invalid hash", - params: `["notahex"]`, - fail: true, + name: "invalid hash", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "missing hash", - params: `["` + util.Uint256{}.String() + `"]`, - fail: true, + name: "missing hash", + params: `["` + util.Uint256{}.String() + `"]`, + fail: true, + errCode: neorpc.ErrUnknownBlockCode, }, }, "getblockcount": { @@ -633,36 +735,42 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "string height", - params: `["first"]`, - fail: true, + name: "string height", + params: `["first"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid number height", - params: `[-2]`, - fail: true, + name: "invalid number height", + params: `[-2]`, + fail: true, + errCode: neorpc.ErrUnknownHeightCode, }, }, "getblockheader": { { - name: "invalid verbose type", - params: `["9673799c5b5a294427401cb07d6cc615ada3a0d5c5bf7ed6f0f54f24abb2e2ac", true]`, - fail: true, + name: "invalid verbose type", + params: `["9673799c5b5a294427401cb07d6cc615ada3a0d5c5bf7ed6f0f54f24abb2e2ac", true]`, + fail: true, + errCode: neorpc.ErrUnknownBlockCode, }, { - name: "invalid block hash", - params: `["notahash"]`, - fail: true, + name: "invalid block hash", + params: `["notahash"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "unknown block", - params: `["a6e526375a780335112299f2262501e5e9574c3ba61b16bbc1e282b344f6c141"]`, - fail: true, + name: "unknown block", + params: `["a6e526375a780335112299f2262501e5e9574c3ba61b16bbc1e282b344f6c141"]`, + fail: true, + errCode: neorpc.ErrUnknownBlockCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "getblockheadercount": { @@ -689,19 +797,22 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "string height", - params: `["first"]`, - fail: true, + name: "string height", + params: `["first"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid number height", - params: `[-2]`, - fail: true, + name: "invalid number height", + params: `[-2]`, + fail: true, + errCode: neorpc.ErrUnknownHeightCode, }, }, "getcommittee": { @@ -754,19 +865,22 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getrawtransaction": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid hash", - params: `["notahex"]`, - fail: true, + name: "invalid hash", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "missing hash", - params: `["` + util.Uint256{}.String() + `"]`, - fail: true, + name: "missing hash", + params: `["` + util.Uint256{}.String() + `"]`, + fail: true, + errCode: neorpc.ErrUnknownTransactionCode, }, }, "gettransactionheight": { @@ -784,31 +898,36 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid hash", - params: `["notahex"]`, - fail: true, + name: "invalid hash", + params: `["notahex"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "missing hash", - params: `["` + util.Uint256{}.String() + `"]`, - fail: true, + name: "missing hash", + params: `["` + util.Uint256{}.String() + `"]`, + fail: true, + errCode: neorpc.ErrUnknownTransactionCode, }, }, "getunclaimedgas": { { - name: "no params", - params: "[]", - fail: true, + name: "no params", + params: "[]", + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid address", - params: `["invalid"]`, - fail: true, + name: "invalid address", + params: `["invalid"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { name: "positive", @@ -1003,24 +1122,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[42, "test", []]`, - fail: true, + name: "not a string", + params: `[42, "test", []]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "not a scripthash", - params: `["qwerty", "test", []]`, - fail: true, + name: "not a scripthash", + params: `["qwerty", "test", []]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad params", - params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, - fail: true, + name: "bad params", + params: `["50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "invokefunctionhistoric": { @@ -1118,39 +1241,46 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no args", - params: `[20]`, - fail: true, + name: "no args", + params: `[20]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[20, 42, "test", []]`, - fail: true, + name: "not a string", + params: `[20, 42, "test", []]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "not a scripthash", - params: `[20,"qwerty", "test", []]`, - fail: true, + name: "not a scripthash", + params: `[20,"qwerty", "test", []]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad params", - params: `[20,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, - fail: true, + name: "bad params", + params: `[20,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad height", - params: `[100500,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`, - fail: true, + name: "bad height", + params: `[100500,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bad stateroot", - params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`, - fail: true, + name: "bad stateroot", + params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "invokescript": { @@ -1237,19 +1367,22 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[42]`, - fail: true, + name: "not a string", + params: `[42]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bas string", - params: `["qwerty"]`, - fail: true, + name: "bas string", + params: `["qwerty"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "invokescripthistoric": { @@ -1348,34 +1481,40 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no script", - params: `[20]`, - fail: true, + name: "no script", + params: `[20]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[20,42]`, - fail: true, + name: "not a string", + params: `[20,42]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bas string", - params: `[20, "qwerty"]`, - fail: true, + name: "bad string", + params: `[20, "qwerty"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bas height", - params: `[100500,"qwerty"]`, - fail: true, + name: "bad height", + params: `[100500,"qwerty"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "bas stateroot", - params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`, - fail: true, + name: "bad stateroot", + params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "invokecontractverify": { @@ -1468,19 +1607,34 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "unknown contract", - params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()), - fail: true, + name: "invalid call args", + params: fmt.Sprintf(`["%s", [{"type":"Map","value":{"key":"value"}}]]`, verifyWithArgsContractHash), + fail: true, + errCode: neorpc.InternalServerErrorCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "negative, wrong signer", + params: fmt.Sprintf(`["%s", [], [{"account":"aaa"}]]`, verifyContractHash), + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[42, []]`, - fail: true, + name: "unknown contract", + params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()), + fail: true, + errCode: neorpc.ErrUnknownContractCode, + }, + { + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, + }, + { + name: "not a string", + params: `[42, []]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, }, "invokecontractverifyhistoric": { @@ -1586,24 +1740,28 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "unknown contract", - params: fmt.Sprintf(`[20, "%s", []]`, util.Uint160{}.String()), - fail: true, + name: "unknown contract", + params: fmt.Sprintf(`[20, "%s", []]`, util.Uint160{}.String()), + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no args", - params: `[20]`, - fail: true, + name: "no args", + params: `[20]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "not a string", - params: `[20,42, []]`, - fail: true, + name: "not a string", + params: `[20,42, []]`, + fail: true, + errCode: neorpc.ErrUnknownContractCode, }, }, "sendrawtransaction": { @@ -1619,55 +1777,70 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "negative", - params: `["AAoAAAAxboUQOQGdOd/Cw31sP+4Z/VgJhwAAAAAAAAAA8q0FAAAAAACwBAAAAAExboUQOQGdOd/Cw31sP+4Z/VgJhwFdAwDodkgXAAAADBQgcoJ0r6/Db0OgcdMoz6PmKdnLsAwUMW6FEDkBnTnfwsN9bD/uGf1YCYcTwAwIdHJhbnNmZXIMFIl3INjNdvTwCr+jfA7diJwgj96bQWJ9W1I4AUIMQN+VMUEnEWlCHOurXSegFj4pTXx/LQUltEmHRTRIFP09bFxZHJsXI9BdQoVvQJrbCEz2esySHPr8YpEzpeteen4pDCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQQqQav8="]`, - fail: true, + name: "already in pool", + params: `["AB0AAACWP5gAAAAAAEDaEgAAAAAAGAAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsoAAXgsDAOh2SBcAAAAMFBEmW7QXJQBBvgTo+iQOOPV8HlabDBTunqIsJ+NL0BSPxBCOCPdOj1BIshTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1IBQgxAJ6norhWoZxp+Hj1JFhi+Z3qI9DUkLSbfsbaLSaJIqxTfdmPbNFDVK1G+oa+LWmpRp/bj9+QZM7yC+S6HXUI7rigMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwkFW57Mn"]`, + fail: true, + errCode: neorpc.ErrAlreadyInPoolCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "negative", + params: `["AAoAAAAxboUQOQGdOd/Cw31sP+4Z/VgJhwAAAAAAAAAA8q0FAAAAAACwBAAAAAExboUQOQGdOd/Cw31sP+4Z/VgJhwFdAwDodkgXAAAADBQgcoJ0r6/Db0OgcdMoz6PmKdnLsAwUMW6FEDkBnTnfwsN9bD/uGf1YCYcTwAwIdHJhbnNmZXIMFIl3INjNdvTwCr+jfA7diJwgj96bQWJ9W1I4AUIMQN+VMUEnEWlCHOurXSegFj4pTXx/LQUltEmHRTRIFP09bFxZHJsXI9BdQoVvQJrbCEz2esySHPr8YpEzpeteen4pDCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQQqQav8="]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid string", - params: `["notabase64%"]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid tx", - params: `["AnTXkgcmF3IGNvbnRyYWNw=="]`, - fail: true, + name: "invalid string", + params: `["notabase64%"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, + }, + { + name: "invalid tx", + params: `["AnTXkgcmF3IGNvbnRyYWNw=="]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "submitblock": { { - name: "invalid base64", - params: `["%%%"]`, - fail: true, + name: "invalid base64", + params: `["%%%"]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "invalid block bytes", - params: `["AAAAACc="]`, - fail: true, + name: "invalid block bytes", + params: `["AAAAACc="]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "submitoracleresponse": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.ErrOracleDisabledCode, }, }, "submitnotaryrequest": { { - name: "no params", - params: `[]`, - fail: true, + name: "no params", + params: `[]`, + fail: true, + errCode: neorpc.InvalidParamsCode, }, }, "validateaddress": { @@ -1692,6 +1865,12 @@ var rpcTestCases = map[string][]rpcTestCase{ } }, }, + { + name: "no params", + params: "[]", + fail: true, + errCode: neorpc.InvalidParamsCode, + }, }, } @@ -1706,65 +1885,79 @@ func TestRPC(t *testing.T) { } func TestSubmitOracle(t *testing.T) { + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitoracleresponse", "params": %s}` + + t.Run("OracleDisabled", func(t *testing.T) { + chain, rpcSrv, httpSrv := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ApplicationConfiguration.Oracle.Enabled = false + }) + defer chain.Close() + defer rpcSrv.Shutdown() + req := fmt.Sprintf(rpc, "[]") + body := doRPCCallOverHTTP(req, httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrOracleDisabledCode) + }) + chain, rpcSrv, httpSrv := initClearServerWithServices(t, true, false, false) defer chain.Close() defer rpcSrv.Shutdown() - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitoracleresponse", "params": %s}` - runCase := func(t *testing.T, fail bool, params ...string) func(t *testing.T) { + runCase := func(t *testing.T, fail bool, errCode int64, params ...string) func(t *testing.T) { return func(t *testing.T) { ps := `[` + strings.Join(params, ",") + `]` req := fmt.Sprintf(rpc, ps) body := doRPCCallOverHTTP(req, httpSrv.URL, t) - checkErrGetResult(t, body, fail) + checkErrGetResult(t, body, fail, errCode) } } - t.Run("MissingKey", runCase(t, true)) - t.Run("InvalidKey", runCase(t, true, `"1234"`)) + t.Run("MissingKey", runCase(t, true, neorpc.InvalidParamsCode)) + t.Run("InvalidKey", runCase(t, true, neorpc.InvalidParamsCode, `"1234"`)) priv, err := keys.NewPrivateKey() require.NoError(t, err) pubStr := `"` + base64.StdEncoding.EncodeToString(priv.PublicKey().Bytes()) + `"` - t.Run("InvalidReqID", runCase(t, true, pubStr, `"notanumber"`)) - t.Run("InvalidTxSignature", runCase(t, true, pubStr, `1`, `"qwerty"`)) + t.Run("InvalidReqID", runCase(t, true, neorpc.InvalidParamsCode, pubStr, `"notanumber"`)) + t.Run("InvalidTxSignature", runCase(t, true, neorpc.InvalidParamsCode, pubStr, `1`, `"qwerty"`)) txSig := priv.Sign([]byte{1, 2, 3}) txSigStr := `"` + base64.StdEncoding.EncodeToString(txSig) + `"` - t.Run("MissingMsgSignature", runCase(t, true, pubStr, `1`, txSigStr)) - t.Run("InvalidMsgSignature", runCase(t, true, pubStr, `1`, txSigStr, `"0123"`)) + t.Run("MissingMsgSignature", runCase(t, true, neorpc.InvalidParamsCode, pubStr, `1`, txSigStr)) + t.Run("InvalidMsgSignature", runCase(t, true, neorpc.ErrInvalidSignatureCode, pubStr, `1`, txSigStr, `"0123"`)) msg := rpc2.GetMessage(priv.PublicKey().Bytes(), 1, txSig) msgSigStr := `"` + base64.StdEncoding.EncodeToString(priv.Sign(msg)) + `"` - t.Run("Valid", runCase(t, false, pubStr, `1`, txSigStr, msgSigStr)) + t.Run("Valid", runCase(t, false, 0, pubStr, `1`, txSigStr, msgSigStr)) } func TestSubmitNotaryRequest(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitnotaryrequest", "params": %s}` t.Run("disabled P2PSigExtensions", func(t *testing.T) { - chain, rpcSrv, httpSrv := initClearServerWithServices(t, false, false, false) + chain, rpcSrv, httpSrv := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ProtocolConfiguration.P2PSigExtensions = false + }) defer chain.Close() defer rpcSrv.Shutdown() req := fmt.Sprintf(rpc, "[]") body := doRPCCallOverHTTP(req, httpSrv.URL, t) - checkErrGetResult(t, body, true) + checkErrGetResult(t, body, true, neorpc.InternalServerErrorCode) }) chain, rpcSrv, httpSrv := initServerWithInMemoryChainAndServices(t, false, true, false) defer chain.Close() defer rpcSrv.Shutdown() - runCase := func(t *testing.T, fail bool, params ...string) func(t *testing.T) { + runCase := func(t *testing.T, fail bool, errCode int64, params ...string) func(t *testing.T) { return func(t *testing.T) { ps := `[` + strings.Join(params, ",") + `]` req := fmt.Sprintf(rpc, ps) body := doRPCCallOverHTTP(req, httpSrv.URL, t) - checkErrGetResult(t, body, fail) + checkErrGetResult(t, body, fail, errCode) } } - t.Run("missing request", runCase(t, true)) - t.Run("not a base64", runCase(t, true, `"not-a-base64$"`)) - t.Run("invalid request bytes", runCase(t, true, `"not-a-request"`)) + t.Run("missing request", runCase(t, true, neorpc.InvalidParamsCode)) + t.Run("not a base64", runCase(t, true, neorpc.InvalidParamsCode, `"not-a-base64$"`)) + t.Run("invalid request bytes", runCase(t, true, neorpc.InvalidParamsCode, `"not-a-request"`)) t.Run("invalid request", func(t *testing.T) { mainTx := &transaction.Transaction{ Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}}, @@ -1800,7 +1993,7 @@ func TestSubmitNotaryRequest(t *testing.T) { bytes, err := p.Bytes() require.NoError(t, err) str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(bytes)) - runCase(t, true, str)(t) + runCase(t, true, neorpc.ErrVerificationFailedCode, str)(t) }) t.Run("valid request", func(t *testing.T) { sender := testchain.PrivateKeyByID(0) // owner of the deposit in testchain @@ -1808,7 +2001,7 @@ func TestSubmitNotaryRequest(t *testing.T) { bytes, err := p.Bytes() require.NoError(t, err) str := fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(bytes)) - runCase(t, false, str)(t) + runCase(t, false, 0, str)(t) }) } @@ -1856,6 +2049,30 @@ func createValidNotaryRequest(chain *core.Blockchain, sender *keys.PrivateKey, n return p } +func runTestCasesWithExecutor(t *testing.T, e *executor, rpcCall string, method string, testCases []rpcTestCase, doRPCCall func(string, string, *testing.T) []byte, checkErrResult func(t *testing.T, body []byte, expectingFail bool, expectedErrCode int64, expectedErr ...string) json.RawMessage) { + t.Run(method, func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + body := doRPCCall(fmt.Sprintf(rpcCall, method, tc.params), e.httpSrv.URL, t) + result := checkErrResult(t, body, tc.fail, tc.errCode) + if tc.fail { + return + } + + expected, res := tc.getResultPair(e) + err := json.Unmarshal(result, res) + require.NoErrorf(t, err, "could not parse response: %s", result) + + if tc.check == nil { + assert.Equal(t, expected, res) + } else { + tc.check(t, e, res) + } + }) + } + }) +} + // testRPCProtocol runs a full set of tests using given callback to make actual // calls. Some tests change the chain state, thus we reinitialize the chain from // scratch here. @@ -1867,30 +2084,9 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] e := &executor{chain: chain, httpSrv: httpSrv} t.Run("single request", func(t *testing.T) { + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}` for method, cases := range rpcTestCases { - t.Run(method, func(t *testing.T) { - rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}` - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), httpSrv.URL, t) - result := checkErrGetResult(t, body, tc.fail) - if tc.fail { - return - } - - expected, res := tc.getResultPair(e) - err := json.Unmarshal(result, res) - require.NoErrorf(t, err, "could not parse response: %s", result) - - if tc.check == nil { - assert.Equal(t, expected, res) - } else { - tc.check(t, e, res) - } - }) - } - }) + runTestCasesWithExecutor(t, e, rpc, method, cases, doRPCCall, checkErrGetResult) } }) t.Run("batch with single request", func(t *testing.T) { @@ -1898,29 +2094,8 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] if method == "sendrawtransaction" { continue // cannot send the same transaction twice } - t.Run(method, func(t *testing.T) { - rpc := `[{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}]` - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - body := doRPCCall(fmt.Sprintf(rpc, method, tc.params), httpSrv.URL, t) - result := checkErrGetBatchResult(t, body, tc.fail) - if tc.fail { - return - } - - expected, res := tc.getResultPair(e) - err := json.Unmarshal(result, res) - require.NoErrorf(t, err, "could not parse response: %s", result) - - if tc.check == nil { - assert.Equal(t, expected, res) - } else { - tc.check(t, e, res) - } - }) - } - }) + rpc := `[{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}]` + runTestCasesWithExecutor(t, e, rpc, method, cases, doRPCCall, checkErrGetBatchResult) } }) @@ -1975,7 +2150,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("getapplicationlog for block", func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["%s"]}` body := doRPCCall(fmt.Sprintf(rpc, e.chain.GetHeaderHash(1).StringLE()), httpSrv.URL, t) - data := checkErrGetResult(t, body, false) + data := checkErrGetResult(t, body, false, 0) var res result.ApplicationLog require.NoError(t, json.Unmarshal(data, &res)) require.Equal(t, 2, len(res.Executions)) @@ -1984,48 +2159,54 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.Equal(t, trigger.PostPersist, res.Executions[1].Trigger) require.Equal(t, vmstate.Halt, res.Executions[1].VMState) }) - - t.Run("submit", func(t *testing.T) { + t.Run("submitblock", func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "submitblock", "params": ["%s"]}` t.Run("invalid signature", func(t *testing.T) { s := testchain.NewBlock(t, chain, 1, 0) s.Script.VerificationScript[8] ^= 0xff - body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, s)), httpSrv.URL, t) - checkErrGetResult(t, body, true) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, s)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrVerificationFailedCode) }) - priv0 := testchain.PrivateKeyByID(0) - acc0 := wallet.NewAccountFromPrivateKey(priv0) - - addNetworkFee := func(tx *transaction.Transaction) { - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc0.Contract.Script) - tx.NetworkFee += netFee - size += sizeDelta - tx.NetworkFee += int64(size) * chain.FeePerByte() - } - - newTx := func() *transaction.Transaction { - height := chain.BlockHeight() - tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx.Nonce = height + 1 - tx.ValidUntilBlock = height + 10 - tx.Signers = []transaction.Signer{{Account: acc0.PrivateKey().GetScriptHash()}} - addNetworkFee(tx) - require.NoError(t, acc0.SignTx(testchain.Network(), tx)) - return tx - } - t.Run("invalid height", func(t *testing.T) { - b := testchain.NewBlock(t, chain, 2, 0, newTx()) - body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, b)), httpSrv.URL, t) - checkErrGetResult(t, body, true) + b := testchain.NewBlock(t, chain, 2, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 0, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrAlreadyExistsCode) + }) + t.Run("invalid script", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, 0xDD, 10, 0, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInvalidScriptCode) + }) + t.Run("invalid ValidUntilBlock", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 0, 0, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrExpiredTransactionCode) + }) + t.Run("invalid SystemFee", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 999999999999, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrPolicyFailedCode) + }) + t.Run("invalid NetworkFee", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 0, 0, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInsufficientNetworkFeeCode) + }) + t.Run("invalid attribute", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 0, 2, true)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInvalidAttributeCode) + }) + t.Run("insufficient funds", func(t *testing.T) { + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 899999999999, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInsufficientFundsCode) }) - t.Run("positive", func(t *testing.T) { - b := testchain.NewBlock(t, chain, 1, 0, newTx()) - body := doRPCCall(fmt.Sprintf(rpc, encodeBlock(t, b)), httpSrv.URL, t) - data := checkErrGetResult(t, body, false) + b := testchain.NewBlock(t, chain, 1, 0, newTxWithParams(t, chain, opcode.PUSH1, 10, 0, 1, false)) + body := doRPCCall(fmt.Sprintf(rpc, encodeBinaryToString(t, b)), httpSrv.URL, t) + data := checkErrGetResult(t, body, false, 0) var res = new(result.RelayResult) require.NoError(t, json.Unmarshal(data, res)) require.Equal(t, b.Hash(), res.Hash) @@ -2038,7 +2219,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getproof", "params": ["%s", "%s", "%s"]}`, r.Root.StringLE(), testContractHash, base64.StdEncoding.EncodeToString([]byte("testkey"))) body := doRPCCall(rpc, httpSrv.URL, t) - rawRes := checkErrGetResult(t, body, false) + rawRes := checkErrGetResult(t, body, false, 0) res := new(result.ProofWithKey) require.NoError(t, json.Unmarshal(rawRes, res)) h, _ := util.Uint160DecodeStringLE(testContractHash) @@ -2049,7 +2230,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] rpc = fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "verifyproof", "params": ["%s", "%s"]}`, r.Root.StringLE(), res.String()) body = doRPCCall(rpc, httpSrv.URL, t) - rawRes = checkErrGetResult(t, body, false) + rawRes = checkErrGetResult(t, body, false, 0) vp := new(result.VerifyProof) require.NoError(t, json.Unmarshal(rawRes, vp)) require.Equal(t, []byte("testvalue"), vp.Value) @@ -2058,7 +2239,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] testRoot := func(t *testing.T, p string) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [%s]}`, p) body := doRPCCall(rpc, httpSrv.URL, t) - rawRes := checkErrGetResult(t, body, false) + rawRes := checkErrGetResult(t, body, false, 0) res := &state.MPTRoot{} require.NoError(t, json.Unmarshal(rawRes, res)) @@ -2073,7 +2254,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("20", func(t *testing.T) { rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [20]}` body := doRPCCall(rpc, httpSrv.URL, t) - rawRes := checkErrGetResult(t, body, false) + rawRes := checkErrGetResult(t, body, false, 0) res := &state.MPTRoot{} require.NoError(t, json.Unmarshal(rawRes, res)) @@ -2081,10 +2262,10 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) }) t.Run("getstate", func(t *testing.T) { + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getstate", "params": [%s]}` testGetState := func(t *testing.T, p string, expected string) { - rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getstate", "params": [%s]}`, p) - body := doRPCCall(rpc, httpSrv.URL, t) - rawRes := checkErrGetResult(t, body, false) + body := doRPCCall(fmt.Sprintf(rpc, p), httpSrv.URL, t) + rawRes := checkErrGetResult(t, body, false, 0) var actual string require.NoError(t, json.Unmarshal(rawRes, &actual)) @@ -2097,6 +2278,14 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] params := fmt.Sprintf(`"%s", "%s", "%s"`, root.Root.StringLE(), testContractHash, base64.StdEncoding.EncodeToString([]byte("testkey"))) testGetState(t, params, base64.StdEncoding.EncodeToString([]byte("testvalue"))) }) + t.Run("negative: invalid key", func(t *testing.T) { + root, err := e.chain.GetStateModule().GetStateRoot(4) + require.NoError(t, err) + // `testkey`-`testvalue` pair was put to the contract storage at block #3 + params := fmt.Sprintf(`"%s", "%s", "%s"`, root.Root.StringLE(), testContractHash, base64.StdEncoding.EncodeToString([]byte("invalidkey"))) + body := doRPCCall(fmt.Sprintf(rpc, params), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode) + }) t.Run("good: fresh state", func(t *testing.T) { root, err := e.chain.GetStateModule().GetStateRoot(16) require.NoError(t, err) @@ -2109,7 +2298,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] testFindStates := func(t *testing.T, p string, root util.Uint256, expected result.FindStates) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "findstates", "params": [%s]}`, p) body := doRPCCall(rpc, httpSrv.URL, t) - rawRes := checkErrGetResult(t, body, false) + rawRes := checkErrGetResult(t, body, false, 0) var actual result.FindStates require.NoError(t, json.Unmarshal(rawRes, &actual)) @@ -2119,7 +2308,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] rpc = fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "verifyproof", "params": ["%s", "%s"]}`, root.StringLE(), proof.String()) body = doRPCCall(rpc, httpSrv.URL, t) - rawRes = checkErrGetResult(t, body, false) + rawRes = checkErrGetResult(t, body, false, 0) vp := new(result.VerifyProof) require.NoError(t, json.Unmarshal(rawRes, vp)) require.Equal(t, value, vp.Value) @@ -2221,7 +2410,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) - result := checkErrGetResult(t, body, false) + result := checkErrGetResult(t, body, false, 0) var res string err := json.Unmarshal(result, &res) require.NoErrorf(t, err, "could not parse response: %s", result) @@ -2236,7 +2425,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) - result := checkErrGetResult(t, body, false) + result := checkErrGetResult(t, body, false, 0) var res string err := json.Unmarshal(result, &res) require.NoErrorf(t, err, "could not parse response: %s", result) @@ -2252,7 +2441,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] _ = block.Transactions[0].Size() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) - txOut := checkErrGetResult(t, body, false) + txOut := checkErrGetResult(t, body, false, 0) actual := result.TransactionOutputRaw{Transaction: transaction.Transaction{}} err := json.Unmarshal(txOut, &actual) require.NoErrorf(t, err, "could not parse response: %s", txOut) @@ -2269,7 +2458,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] runCase := func(t *testing.T, rpc string, expected, actual any) { body := doRPCCall(rpc, httpSrv.URL, t) - data := checkErrGetResult(t, body, false) + data := checkErrGetResult(t, body, false, 0) require.NoError(t, json.Unmarshal(data, actual)) require.Equal(t, expected, actual) } @@ -2325,7 +2514,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getrawmempool", "params": []}` body := doRPCCall(rpc, httpSrv.URL, t) - res := checkErrGetResult(t, body, false) + res := checkErrGetResult(t, body, false, 0) var actual []util.Uint256 err := json.Unmarshal(res, &actual) @@ -2366,7 +2555,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] p := strings.Join(ps, ", ") rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getnep17transfers", "params": [%s]}`, p) body := doRPCCall(rpc, httpSrv.URL, t) - res := checkErrGetResult(t, body, false) + res := checkErrGetResult(t, body, false, 0) actual := new(result.NEP17Transfers) require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) @@ -2382,7 +2571,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] prepareIteratorSession := func(t *testing.T) (uuid.UUID, uuid.UUID) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "invokefunction", "params": ["%s", "iterateOverValues"]}"`, storageContractHash) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false) + resp := checkErrGetResult(t, body, false, 0) res := new(result.Invoke) err := json.Unmarshal(resp, &res) require.NoErrorf(t, err, "could not parse response: %s", resp) @@ -2400,7 +2589,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] expectedCount := 99 rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, sID.String(), iID.String(), expectedCount) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false) + resp := checkErrGetResult(t, body, false, 0) res := new([]json.RawMessage) require.NoError(t, json.Unmarshal(resp, res)) require.Equal(t, expectedCount, len(*res)) @@ -2409,37 +2598,37 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] _, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["not-a-uuid", "%s", %d]}"`, iID.String(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - checkErrGetResult(t, body, true, "invalid session ID: not a valid UUID") + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "invalid session ID: not a valid UUID") }) t.Run("invalid iterator id", func(t *testing.T) { sID, _ := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "not-a-uuid", %d]}"`, sID.String(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - checkErrGetResult(t, body, true, "invalid iterator ID: not a valid UUID") + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "invalid iterator ID: not a valid UUID") }) t.Run("invalid items count", func(t *testing.T) { sID, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s"]}"`, sID.String(), iID.String()) body := doRPCCall(rpc, httpSrv.URL, t) - checkErrGetResult(t, body, true, "invalid iterator items count") + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "invalid iterator items count") }) t.Run("items count is not an int32", func(t *testing.T) { sID, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, sID.String(), iID.String(), math.MaxInt32+1) body := doRPCCall(rpc, httpSrv.URL, t) - checkErrGetResult(t, body, true, "invalid iterator items count: not an int32") + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "invalid iterator items count: not an int32") }) t.Run("count is out of range", func(t *testing.T) { sID, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, sID.String(), iID.String(), rpcSrv.config.MaxIteratorResultItems+1) body := doRPCCall(rpc, httpSrv.URL, t) - checkErrGetResult(t, body, true, fmt.Sprintf("iterator items count is out of range (%d at max)", rpcSrv.config.MaxIteratorResultItems)) + checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, fmt.Sprintf("iterator items count is out of range (%d at max)", rpcSrv.config.MaxIteratorResultItems)) }) t.Run("unknown session", func(t *testing.T) { _, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, uuid.NewString(), iID.String(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false) + resp := checkErrGetResult(t, body, false, 0) res := new([]json.RawMessage) require.NoError(t, json.Unmarshal(resp, res)) require.Equal(t, 0, len(*res)) // No errors expected, no elements should be returned. @@ -2448,7 +2637,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] sID, _ := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, sID.String(), uuid.NewString(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false) + resp := checkErrGetResult(t, body, false, 0) res := new([]json.RawMessage) require.NoError(t, json.Unmarshal(resp, res)) require.Equal(t, 0, len(*res)) // No errors expected, no elements should be returned. @@ -2458,7 +2647,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] check := func(t *testing.T, id string, expected bool) { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "terminatesession", "params": ["%s"]}"`, id) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false) + resp := checkErrGetResult(t, body, false, 0) res := new(bool) require.NoError(t, json.Unmarshal(resp, res)) require.Equal(t, expected, *res) @@ -2483,15 +2672,15 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("calculatenetworkfee", func(t *testing.T) { t.Run("no parameters", func(t *testing.T) { body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": []}"`, httpSrv.URL, t) - _ = checkErrGetResult(t, body, true, "Invalid Params") + _ = checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "Invalid Params") }) t.Run("non-base64 parameter", func(t *testing.T) { body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["noatbase64"]}"`, httpSrv.URL, t) - _ = checkErrGetResult(t, body, true, "Invalid Params") + _ = checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "Invalid Params") }) t.Run("non-transaction parameter", func(t *testing.T) { body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["bm90IGEgdHJhbnNhY3Rpb24K"]}"`, httpSrv.URL, t) - _ = checkErrGetResult(t, body, true, "Invalid Params") + _ = checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "Invalid Params") }) calcReq := func(t *testing.T, tx *transaction.Transaction) []byte { rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["%s"]}"`, base64.StdEncoding.EncodeToString(tx.Bytes())) @@ -2507,7 +2696,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }}, } body := calcReq(t, tx) - _ = checkErrGetResult(t, body, true, "signer 0 has no verification script and no deployed contract") + _ = checkErrGetResult(t, body, true, neorpc.ErrInvalidVerificationFunctionCode, "signer 0 has no verification script and no deployed contract") }) t.Run("contract with no verify", func(t *testing.T) { tx := &transaction.Transaction{ @@ -2519,10 +2708,10 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }}, } body := calcReq(t, tx) - _ = checkErrGetResult(t, body, true, "signer 0 has no verify method in deployed contract") + _ = checkErrGetResult(t, body, true, neorpc.ErrInvalidVerificationFunctionCode, "signer 0 has no verify method in deployed contract") }) checkCalc := func(t *testing.T, tx *transaction.Transaction, fee int64) { - resp := checkErrGetResult(t, calcReq(t, tx), false) + resp := checkErrGetResult(t, calcReq(t, tx), false, 0) res := new(result.NetworkFee) require.NoError(t, json.Unmarshal(resp, res)) require.Equal(t, fee, res.Value) @@ -2596,6 +2785,87 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] checkContract(t, verAcc, invocScript, 146960) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side. }) }) + t.Run("sendrawtransaction", func(t *testing.T) { + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "sendrawtransaction", "params": ["%s"]}` + t.Run("invalid signature", func(t *testing.T) { + tx := newTxWithParams(t, chain, opcode.PUSH1, 10, 1, 1, false) + tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10] + rawTx := encodeBinaryToString(t, tx) + body := doRPCCall(fmt.Sprintf(rpc, rawTx), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInvalidSignatureCode) + }) + t.Run("too big tx", func(t *testing.T) { + script := make([]byte, transaction.MaxScriptLength) + for i := range script { + script[i] = byte(opcode.PUSH0) + } + groups := make([]*keys.PublicKey, 16) + for i := range groups { + pk, _ := keys.NewPrivateKey() + groups[i] = pk.PublicKey() + } + signers := make([]transaction.Signer, transaction.MaxAttributes) + for i := range signers { + signers[i] = transaction.Signer{ + Account: random.Uint160(), + Scopes: transaction.CustomContracts | transaction.CustomGroups, + AllowedContracts: make([]util.Uint160, 16), + AllowedGroups: groups, + } + } + scripts := make([]transaction.Witness, len(signers)) + for i := range scripts { + scripts[i] = transaction.Witness{ + InvocationScript: random.Bytes(transaction.MaxInvocationScript), + VerificationScript: random.Bytes(transaction.MaxVerificationScript), + } + } + tx := &transaction.Transaction{ + ValidUntilBlock: chain.BlockHeight() + 1, + Script: script, + Attributes: []transaction.Attribute{}, + Signers: signers, + Scripts: scripts, + } + rawTx := encodeBinaryToString(t, tx) + body := doRPCCall(fmt.Sprintf(rpc, rawTx), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrInvalidSizeCode) + }) + t.Run("mempool OOM", func(t *testing.T) { + chain, rpcSrv, httpSrv := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ProtocolConfiguration.MemPoolSize = 1 + }) + + defer chain.Close() + defer rpcSrv.Shutdown() + + // create and push the first (prioritized) transaction with increased networkFee + tx := newTxWithParams(t, chain, opcode.PUSH1, 10, 1, 2, false) + rawTx := encodeBinaryToString(t, tx) + body := doRPCCall(fmt.Sprintf(rpc, rawTx), httpSrv.URL, t) + checkErrGetResult(t, body, false, 0) + + // create and push the second transaction with standard networkFee + tx2 := newTxWithParams(t, chain, opcode.PUSH1, 10, 1, 1, false) + rawTx2 := encodeBinaryToString(t, tx2) + body2 := doRPCCall(fmt.Sprintf(rpc, rawTx2), httpSrv.URL, t) + checkErrGetResult(t, body2, true, neorpc.ErrMempoolCapReachedCode) + }) + }) + t.Run("test functions with unsupported states", func(t *testing.T) { + chain, rpcSrv, httpSrv := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ApplicationConfiguration.Ledger.KeepOnlyLatestState = true + }) + + defer chain.Close() + defer rpcSrv.Shutdown() + + e := &executor{chain: chain, httpSrv: httpSrv} + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "%s", "params": %s}` + for method, cases := range rpcFunctionsWithUnsupportedStatesTestCases { + runTestCasesWithExecutor(t, e, rpc, method, cases, doRPCCall, checkErrGetResult) + } + }) } func (e *executor) getHeader(s string) *block.Header { @@ -2610,11 +2880,37 @@ func (e *executor) getHeader(s string) *block.Header { return &block.Header } -func encodeBlock(t *testing.T, b *block.Block) string { - w := io.NewBufBinWriter() - b.EncodeBinary(w.BinWriter) - require.NoError(t, w.Err) - return base64.StdEncoding.EncodeToString(w.Bytes()) +func encodeBinaryToString(t *testing.T, a io.Serializable) string { + bytes, err := testserdes.EncodeBinary(a) + require.NoError(t, err) + return base64.StdEncoding.EncodeToString(bytes) +} + +func newTxWithParams(t *testing.T, chain *core.Blockchain, code opcode.Opcode, validUntilIncr uint32, systemFee int64, + networkFeeMultiplier int64, addAttrNotValidBeforeT bool) *transaction.Transaction { + priv0 := testchain.PrivateKeyByID(0) + acc0 := wallet.NewAccountFromPrivateKey(priv0) + + height := chain.BlockHeight() + tx := transaction.New([]byte{byte(code)}, 0) + tx.Nonce = height + 1 + tx.ValidUntilBlock = height + validUntilIncr + tx.Signers = []transaction.Signer{{Account: acc0.PrivateKey().GetScriptHash()}} + tx.SystemFee = systemFee + // add network fee + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc0.Contract.Script) + tx.NetworkFee += netFee + size += sizeDelta + tx.NetworkFee += int64(size) * chain.FeePerByte() + tx.NetworkFee = tx.NetworkFee * networkFeeMultiplier + if addAttrNotValidBeforeT { + tx.Attributes = []transaction.Attribute{ + {Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height + 1}}, + } + } + require.NoError(t, acc0.SignTx(testchain.Network(), tx)) + return tx } func (tc rpcTestCase) getResultPair(e *executor) (expected any, res any) { @@ -2624,13 +2920,14 @@ func (tc rpcTestCase) getResultPair(e *executor) (expected any, res any) { return expected, res } -func checkErrGetResult(t *testing.T, body []byte, expectingFail bool, expectedErr ...string) json.RawMessage { +func checkErrGetResult(t *testing.T, body []byte, expectingFail bool, expectedErrCode int64, expectedErr ...string) json.RawMessage { var resp neorpc.Response err := json.Unmarshal(body, &resp) require.Nil(t, err) if expectingFail { require.NotNil(t, resp.Error) assert.NotEqual(t, 0, resp.Error.Code) + assert.Equal(t, expectedErrCode, resp.Error.Code) assert.NotEqual(t, "", resp.Error.Message) if len(expectedErr) != 0 { assert.True(t, strings.Contains(resp.Error.Error(), expectedErr[0]), fmt.Sprintf("expected: %s, got: %s", expectedErr[0], resp.Error.Error())) @@ -2641,7 +2938,7 @@ func checkErrGetResult(t *testing.T, body []byte, expectingFail bool, expectedEr return resp.Result } -func checkErrGetBatchResult(t *testing.T, body []byte, expectingFail bool) json.RawMessage { +func checkErrGetBatchResult(t *testing.T, body []byte, expectingFail bool, expectedErrCode int64, expectedErr ...string) json.RawMessage { var resp []neorpc.Response err := json.Unmarshal(body, &resp) require.Nil(t, err) From 3178c8ff78da1ab39a17dbe69b151a5f08ff512d Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Wed, 16 Aug 2023 09:25:29 +0100 Subject: [PATCH 03/10] docs: add proposal regarding response error codes According to proposal: https://github.com/neo-project/proposals/pull/156 Close #2248 Signed-off-by: Tatiana Nesterenko --- docs/rpc.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/rpc.md b/docs/rpc.md index e3e4fc118..5b3e687d8 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -98,6 +98,13 @@ following data types: Any call that takes any of these types for input in JSON format is affected. +##### Response error codes + +NeoGo currently uses a different set of error codes in comparison to C# implementation, see +[proposal](https://github.com/neo-project/proposals/pull/156). +NeoGo retains certain deprecated error codes, which will be removed once +all nodes adopt the new error standard. + ##### `calculatenetworkfee` NeoGo tries to cover more cases with its calculatenetworkfee implementation, From 3b29720298c7386901f7cdf41f41db7b6c7c4953 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Tue, 15 Aug 2023 21:53:29 +0100 Subject: [PATCH 04/10] neorpc: add deprecated error codes for C#-compatibility While our server no longer uses these codes (-100, -400) they still can come from C# servers and while we consider them deprecated we better at least have some definition of them until C# implements our proposal: https://github.com/neo-project/proposals/pull/156 Also adding description of deprecated RPC error codes in ROADMAP.md. Signed-off-by: Tatiana Nesterenko --- ROADMAP.md | 8 ++++++++ pkg/neorpc/errors.go | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index bfe2aa845..dbc48f246 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -139,3 +139,11 @@ hidden under `version` label and `server_id` contains network server ID hidden under `server_id` label. Removal of `serv_node_version` is scheduled for Sep-Oct 2023 (~0.105.0 release). + +## RPC error codes returned by old versions and C#-nodes + +NeoGo retains certain deprecated error codes: `neorpc.ErrCompatGeneric`, +`neorpc.ErrCompatNoOpenedWallet`. They returned by nodes not compliant with the +neo-project/proposals#156 (NeoGo pre-0.102.0 and all known C# versions). + +Removal of the deprecated RPC error codes is planned once all nodes adopt the new error standard. \ No newline at end of file diff --git a/pkg/neorpc/errors.go b/pkg/neorpc/errors.go index c7ec5cc00..d6488283a 100644 --- a/pkg/neorpc/errors.go +++ b/pkg/neorpc/errors.go @@ -138,6 +138,21 @@ const ( ErrExecutionFailedCode = -608 ) +var ( + // ErrCompatGeneric is an error returned by nodes not compliant with the neo-project/proposals#156 + // (NeoGo pre-0.102.0 and all known C# versions). + // It can be returned for any call and doesn't have any specific meaning. + // + // Deprecated: to be removed after all nodes adopt new error standard. + ErrCompatGeneric = NewErrorWithCode(-100, "RPC error") + + // ErrCompatNoOpenedWallet is an error code returned by nodes not compliant with the neo-project/proposals#156 + // (all known C# versions, NeoGo never used this code). It can be returned for wallet-related operations. + // + // Deprecated: to be removed after all nodes adopt new error standard. + ErrCompatNoOpenedWallet = NewErrorWithCode(-400, "No opened wallet") +) + var ( // ErrInvalidParams represents a generic "Invalid params" error. ErrInvalidParams = NewInvalidParamsError("Invalid params") From d3fe92de14ce93934329e1aaddbf23d2c86a0aef Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Mon, 14 Aug 2023 18:03:07 +0100 Subject: [PATCH 05/10] rpcsrv: remove default request HTTP codes from switch statement No functional changes, just a refactoring. Signed-off-by: Tatiana Nesterenko --- pkg/services/rpcsrv/error.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/services/rpcsrv/error.go b/pkg/services/rpcsrv/error.go index 683a3cc43..9f740f894 100644 --- a/pkg/services/rpcsrv/error.go +++ b/pkg/services/rpcsrv/error.go @@ -44,8 +44,6 @@ func getHTTPCodeForError(respErr *neorpc.Error) int { switch respErr.Code { case neorpc.BadRequestCode: httpCode = http.StatusBadRequest - case neorpc.InvalidRequestCode, neorpc.InvalidParamsCode: - httpCode = http.StatusUnprocessableEntity case neorpc.MethodNotFoundCode: httpCode = http.StatusMethodNotAllowed case neorpc.InternalServerErrorCode: From 1e6372e6d91f4b23b423346a0ac11c0a3fe42a6e Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Fri, 11 Aug 2023 22:56:15 +0100 Subject: [PATCH 06/10] rpcsrv: return error on unknown storage item from `getstorage` Follow the reference implementation, the behaviour was changed in https://github.com/neo-project/neo-modules/commit/237ef7d057265723571efde74e54001f68ce969d. Signed-off-by: Tatiana Nesterenko --- pkg/services/rpcsrv/server.go | 5 +---- pkg/services/rpcsrv/server_test.go | 10 ++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 145b2cb6d..cfc5df532 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -1722,9 +1722,6 @@ func (s *Server) getStateRoot(ps params.Params) (any, *neorpc.Error) { func (s *Server) getStorage(ps params.Params) (any, *neorpc.Error) { id, rErr := s.contractIDFromParam(ps.Value(0)) - if rErr == neorpc.ErrUnknownContract { - return nil, nil - } if rErr != nil { return nil, rErr } @@ -1736,7 +1733,7 @@ func (s *Server) getStorage(ps params.Params) (any, *neorpc.Error) { item := s.chain.GetStorageItem(id, key) if item == nil { - return "", nil + return "", neorpc.ErrUnknownStorageItem } return []byte(item), nil diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index 35e58dbd9..bc098e3ca 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -621,12 +621,10 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "missing key", - params: fmt.Sprintf(`["%s", "dGU="]`, testContractHash), - result: func(e *executor) any { - v := "" - return &v - }, + name: "missing key", + params: fmt.Sprintf(`["%s", "dGU="]`, testContractHash), + fail: true, + errCode: neorpc.ErrUnknownStorageItemCode, }, { name: "no params", From 2598257628070202aeb0f4ebeedb5a55182333ed Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Fri, 11 Aug 2023 23:14:35 +0100 Subject: [PATCH 07/10] core: rename ErrInvalidVerification and ErrInvalidInvocation No functional changes, just a refactoring. Use more specific and meaningful names to be able to use these errors from external packages. Signed-off-by: Tatiana Nesterenko --- pkg/core/blockchain.go | 8 ++++---- pkg/core/blockchain_neotest_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index bd28047e4..2eb6a7698 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -2769,9 +2769,9 @@ var ( ErrWitnessHashMismatch = errors.New("witness hash mismatch") ErrNativeContractWitness = errors.New("native contract witness must have empty verification script") ErrVerificationFailed = errors.New("signature check failed") - ErrInvalidInvocation = errors.New("invalid invocation script") + ErrInvalidInvocationScript = errors.New("invalid invocation script") ErrInvalidSignature = fmt.Errorf("%w: invalid signature", ErrVerificationFailed) - ErrInvalidVerification = errors.New("invalid verification script") + ErrInvalidVerificationScript = errors.New("invalid verification script") ErrUnknownVerificationContract = errors.New("unknown verification contract") ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method") ) @@ -2787,7 +2787,7 @@ func (bc *Blockchain) InitVerificationContext(ic *interop.Context, hash util.Uin } err := vm.IsScriptCorrect(witness.VerificationScript, nil) if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidVerification, err) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors + return fmt.Errorf("%w: %v", ErrInvalidVerificationScript, err) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors } ic.VM.LoadScriptWithHash(witness.VerificationScript, hash, callflag.ReadOnly) } else { @@ -2812,7 +2812,7 @@ func (bc *Blockchain) InitVerificationContext(ic *interop.Context, hash util.Uin if len(witness.InvocationScript) != 0 { err := vm.IsScriptCorrect(witness.InvocationScript, nil) if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidInvocation, err) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors + return fmt.Errorf("%w: %v", ErrInvalidInvocationScript, err) //nolint:errorlint // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors } ic.VM.LoadScript(witness.InvocationScript) } diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index d636ce2b5..53480424a 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -1293,7 +1293,7 @@ func TestBlockchain_VerifyTx(t *testing.T) { InvocationScript: []byte{}, VerificationScript: verif, }) - checkErr(t, core.ErrInvalidVerification, tx) + checkErr(t, core.ErrInvalidVerificationScript, tx) }) t.Run("InvalidInvocationScript", func(t *testing.T) { tx := newTestTx(t, h, testScript) @@ -1308,7 +1308,7 @@ func TestBlockchain_VerifyTx(t *testing.T) { InvocationScript: []byte{byte(opcode.JMP), 3, 0xff}, VerificationScript: verif, }) - checkErr(t, core.ErrInvalidInvocation, tx) + checkErr(t, core.ErrInvalidInvocationScript, tx) }) t.Run("Conflict", func(t *testing.T) { balance := bc.GetUtilityTokenBalance(h).Int64() From f557959c2412374f7f042d2b6220c8e2817636f6 Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Sun, 13 Aug 2023 20:15:31 +0100 Subject: [PATCH 08/10] rpcsrv: return error on invalid proof from `verifyProof` This change makes code incompatible with C# node, because currently no error is returned on invalid proof. According to proposal: https://github.com/neo-project/proposals/pull/156 Also adding `verifyProof` descpiption in docs/rpc.md. Signed-off-by: Tatiana Nesterenko --- docs/rpc.md | 5 +++++ pkg/services/rpcsrv/server.go | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/rpc.md b/docs/rpc.md index 5b3e687d8..73b5b0a0b 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -196,6 +196,11 @@ enabled in the server's protocol configuration. ##### `getnep11transfers` and `getnep17transfers` `transfernotifyindex` is not tracked by NeoGo, thus this field is always zero. +##### `verifyProof` + +NeoGo can generate an error in response to an invalid proof, unlike +the error-free C# implementation. + ### Unsupported methods Methods listed below are not going to be supported for various reasons diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index cfc5df532..86c4f3dc3 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -1531,9 +1531,10 @@ func (s *Server) verifyProof(ps params.Params) (any, *neorpc.Error) { } vp := new(result.VerifyProof) val, ok := mpt.VerifyProof(root, p.Key, p.Proof) - if ok { - vp.Value = val + if !ok { + return nil, neorpc.ErrInvalidProof } + vp.Value = val return vp, nil } From f3760c1a98810e3b521617f67cd019ecaaa3498a Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Sun, 13 Aug 2023 21:33:49 +0100 Subject: [PATCH 09/10] rpcsrv: return ErrUnknownSession and ErrUnknownIterator Behaviour change. `terminatesession` returns ErrUnknownSession in case of impossibility of finding session, previously there was no-error response with `false` result. `traverseIterator`returns ErrUnknownSession in case of impossibility of finding session, previously there was no-error response with default result; `traverseIterator`returns ErrUnknownIterator, there were no such errors before. Accordingly to proposal: https://github.com/neo-project/proposals/pull/156 Also adding description of `traverseIterator` in docs/rpc.md. Signed-off-by: Tatiana Nesterenko --- docs/rpc.md | 5 ++++ pkg/services/rpcsrv/client_test.go | 6 +++-- pkg/services/rpcsrv/server.go | 28 +++++++++++++-------- pkg/services/rpcsrv/server_test.go | 40 +++++++++++++++++------------- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/docs/rpc.md b/docs/rpc.md index 73b5b0a0b..13083e8e1 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -196,6 +196,11 @@ enabled in the server's protocol configuration. ##### `getnep11transfers` and `getnep17transfers` `transfernotifyindex` is not tracked by NeoGo, thus this field is always zero. +##### `traverseiterator` and `terminatesession` + +NeoGo returns an error when it is unable to find a session or iterator, unlike +the error-free C# response that provides a default result. + ##### `verifyProof` NeoGo can generate an error in response to an invalid proof, unlike diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 73f36154f..a7e2b3784 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1723,7 +1723,8 @@ func TestClient_IteratorSessions(t *testing.T) { require.True(t, ok) ok, err = c.TerminateSession(sID) - require.NoError(t, err) + require.Error(t, err) + require.ErrorIs(t, err, neorpc.ErrUnknownSession) require.False(t, ok) // session has already been terminated. }) t.Run("automatically", func(t *testing.T) { @@ -1746,7 +1747,8 @@ func TestClient_IteratorSessions(t *testing.T) { time.Duration(rpcSrv.config.SessionExpirationTime)*time.Second/4) ok, err := c.TerminateSession(sID) - require.NoError(t, err) + require.Error(t, err) + require.ErrorIs(t, err, neorpc.ErrUnknownSession) require.False(t, ok) // session has already been terminated. }) }) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 86c4f3dc3..45d629278 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -2351,7 +2351,7 @@ func (s *Server) traverseIterator(reqParams params.Params) (any, *neorpc.Error) session, ok := s.sessions[sID.String()] if !ok { s.sessionsLock.Unlock() - return []json.RawMessage{}, nil + return nil, neorpc.ErrUnknownSession } session.iteratorsLock.Lock() // Perform `till` update only after session.iteratorsLock is taken in order to have more @@ -2362,14 +2362,19 @@ func (s *Server) traverseIterator(reqParams params.Params) (any, *neorpc.Error) var ( iIDStr = iID.String() iVals []stackitem.Item + found bool ) for _, it := range session.iteratorIdentifiers { if iIDStr == it.ID { iVals = iterator.Values(it.Item, count) + found = true break } } session.iteratorsLock.Unlock() + if !found { + return nil, neorpc.ErrUnknownIterator + } result := make([]json.RawMessage, len(iVals)) for j := range iVals { @@ -2393,17 +2398,18 @@ func (s *Server) terminateSession(reqParams params.Params) (any, *neorpc.Error) s.sessionsLock.Lock() defer s.sessionsLock.Unlock() session, ok := s.sessions[strSID] - if ok { - // Iterators access Seek channel under the hood; finalizer closes this channel, thus, - // we need to perform finalisation under iteratorsLock. - session.iteratorsLock.Lock() - session.finalize() - if !session.timer.Stop() { - <-session.timer.C - } - delete(s.sessions, strSID) - session.iteratorsLock.Unlock() + if !ok { + return nil, neorpc.ErrUnknownSession } + // Iterators access Seek channel under the hood; finalizer closes this channel, thus, + // we need to perform finalisation under iteratorsLock. + session.iteratorsLock.Lock() + session.finalize() + if !session.timer.Stop() { + <-session.timer.C + } + delete(s.sessions, strSID) + session.iteratorsLock.Unlock() return ok, nil } diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index bc098e3ca..b50a4be1f 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -2582,6 +2582,13 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] return res.Session, *iterator.ID } t.Run("traverseiterator", func(t *testing.T) { + t.Run("sessions disabled", func(t *testing.T) { + _, _, httpSrv2 := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ApplicationConfiguration.RPC.SessionEnabled = false + }) + body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": []}"`, httpSrv2.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrSessionsDisabledCode) + }) t.Run("good", func(t *testing.T) { sID, iID := prepareIteratorSession(t) expectedCount := 99 @@ -2626,36 +2633,35 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] _, iID := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, uuid.NewString(), iID.String(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false, 0) - res := new([]json.RawMessage) - require.NoError(t, json.Unmarshal(resp, res)) - require.Equal(t, 0, len(*res)) // No errors expected, no elements should be returned. + checkErrGetResult(t, body, true, neorpc.ErrUnknownSessionCode) }) t.Run("unknown iterator", func(t *testing.T) { sID, _ := prepareIteratorSession(t) rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "traverseiterator", "params": ["%s", "%s", %d]}"`, sID.String(), uuid.NewString(), 1) body := doRPCCall(rpc, httpSrv.URL, t) - resp := checkErrGetResult(t, body, false, 0) - res := new([]json.RawMessage) - require.NoError(t, json.Unmarshal(resp, res)) - require.Equal(t, 0, len(*res)) // No errors expected, no elements should be returned. + checkErrGetResult(t, body, true, neorpc.ErrUnknownIteratorCode) }) }) t.Run("terminatesession", func(t *testing.T) { - check := func(t *testing.T, id string, expected bool) { - rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "terminatesession", "params": ["%s"]}"`, id) - body := doRPCCall(rpc, httpSrv.URL, t) + rpc := `{"jsonrpc": "2.0", "id": 1, "method": "terminatesession", "params": ["%s"]}"` + t.Run("sessions disabled", func(t *testing.T) { + _, _, httpSrv2 := initClearServerWithCustomConfig(t, func(c *config.Config) { + c.ApplicationConfiguration.RPC.SessionEnabled = false + }) + body := doRPCCall(fmt.Sprintf(rpc, uuid.NewString()), httpSrv2.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrSessionsDisabledCode) + }) + t.Run("true", func(t *testing.T) { + sID, _ := prepareIteratorSession(t) + body := doRPCCall(fmt.Sprintf(rpc, sID.String()), httpSrv.URL, t) resp := checkErrGetResult(t, body, false, 0) res := new(bool) require.NoError(t, json.Unmarshal(resp, res)) - require.Equal(t, expected, *res) - } - t.Run("true", func(t *testing.T) { - sID, _ := prepareIteratorSession(t) - check(t, sID.String(), true) + require.Equal(t, true, *res) }) t.Run("false", func(t *testing.T) { - check(t, uuid.NewString(), false) + body := doRPCCall(fmt.Sprintf(rpc, uuid.NewString()), httpSrv.URL, t) + checkErrGetResult(t, body, true, neorpc.ErrUnknownSessionCode) }) t.Run("expired", func(t *testing.T) { _, _ = prepareIteratorSession(t) From 90f1b0fd17b208310ac29da98937fb1cd7bf73ad Mon Sep 17 00:00:00 2001 From: Tatiana Nesterenko Date: Tue, 15 Aug 2023 13:00:54 +0100 Subject: [PATCH 10/10] core: change text in ErrInvalidVerificationContract No functional changes, just a refactoring. Change error text to be able to use this error from external packages. Signed-off-by: Tatiana Nesterenko --- pkg/core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 2eb6a7698..e2327de60 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -2773,7 +2773,7 @@ var ( ErrInvalidSignature = fmt.Errorf("%w: invalid signature", ErrVerificationFailed) ErrInvalidVerificationScript = errors.New("invalid verification script") ErrUnknownVerificationContract = errors.New("unknown verification contract") - ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method") + ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method or `verify` method has unexpected return value") ) // InitVerificationContext initializes context for witness check.