Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/client/compatibility_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func toFilterArg(q ethereum.FilterQuery) (interface{}, error) {
if q.BlockHash != nil {
arg["blockHash"] = *q.BlockHash
if q.FromBlock != nil || q.ToBlock != nil {
return nil, errors.New("cannot specify both BlockHash and FromBlock/ToBlock")
return nil, errors.New("invalid filter query: cannot specify both BlockHash and FromBlock/ToBlock parameters simultaneously. Use either BlockHash for a single block or FromBlock/ToBlock for a block range")
}
} else {
if q.FromBlock == nil {
Expand Down
10 changes: 5 additions & 5 deletions pkg/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ var monad = ClientErrors{
InsufficientEth: regexp.MustCompile("Signer had insufficient balance"),
}

const TerminallyStuckMsg = "transaction terminally stuck"
const TerminallyStuckMsg = "transaction is terminally stuck in the mempool and cannot be included in a block: this transaction will not be retried and must be investigated manually"

// Tx.Error messages that are set internally so they are not chain or client specific
var internal = ClientErrors{
Expand Down Expand Up @@ -567,20 +567,20 @@ func ExtractRPCErrorOrNil(err error) *JsonError {
// { "error": { "code": 3, "data": "0xABC123...", "message": "execution reverted: hello world" } } // revert reason automatically parsed if a simple require and included in message.
func ExtractRPCError(baseErr error) (*JsonError, error) {
if baseErr == nil {
return nil, pkgerrors.New("no error present")
return nil, pkgerrors.New("failed to extract RPC error: no error was provided to ExtractRPCError")
}
cause := pkgerrors.Cause(baseErr)
jsonBytes, err := json.Marshal(cause)
if err != nil {
return nil, pkgerrors.Wrap(err, "unable to marshal err to json")
return nil, pkgerrors.Wrap(err, "failed to extract RPC error: unable to marshal the underlying error to JSON for inspection")
}
jErr := JsonError{}
err = json.Unmarshal(jsonBytes, &jErr)
if err != nil {
return nil, pkgerrors.Wrapf(err, "unable to unmarshal json into jsonError struct (got: %v)", baseErr)
return nil, pkgerrors.Wrapf(err, "failed to extract RPC error: unable to unmarshal JSON into JsonError struct, the error may not be a standard JSON-RPC error (got: %v)", baseErr)
}
if jErr.Code == 0 {
return nil, pkgerrors.Errorf("not a RPCError because it does not have a code (got: %v)", baseErr)
return nil, pkgerrors.Errorf("failed to extract RPC error: error does not contain a JSON-RPC error code, so it is not a standard RPC error (got: %v)", baseErr)
}
return &jErr, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/limited_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func GetResponseSizeLimit(ctx context.Context) uint32 {
return limit
}

var errResponseTooLarge = errors.New("response is too large")
var errResponseTooLarge = errors.New("response is too large: the RPC response exceeded the configured maximum size limit. Consider increasing the response size limit in node configuration or narrowing the query scope")

// limitReader returns a Reader that reads from r
// but stops with EOF after n bytes.
Expand Down
24 changes: 12 additions & 12 deletions pkg/client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (r *RPCClient) Dial(callerCtx context.Context) error {
ws := r.ws.Load()
httpClient := r.http.Load()
if ws == nil && httpClient == nil {
return errors.New("cannot dial rpc client when both ws and http info are missing")
return errors.New("failed to dial RPC client: both WebSocket and HTTP URLs are missing. At least one connection URL must be configured")
}

promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc()
Expand Down Expand Up @@ -356,8 +356,8 @@ func (r *RPCClient) BatchCallContext(rootCtx context.Context, b []rpc.BatchElem)
if r.chainType == chaintype.ChainAstar {
for _, el := range b {
if el.Method == "eth_getLogs" {
r.rpcLog.Critical("evmclient.BatchCallContext: eth_getLogs is not supported")
return errors.New("evmclient.BatchCallContext: eth_getLogs is not supported")
r.rpcLog.Critical("evmclient.BatchCallContext failed: eth_getLogs is not supported for Astar chain type in batch calls")
return errors.New("evmclient.BatchCallContext failed: eth_getLogs is not supported for Astar chain type in batch calls. Use individual log queries instead")
}
if !isRequestingFinalizedBlock(el) {
continue
Expand Down Expand Up @@ -441,7 +441,7 @@ func (r *RPCClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H
}

if ws == nil {
return nil, nil, errors.New("SubscribeNewHead is not allowed without ws url")
return nil, nil, errors.New("failed to subscribe to new heads: WebSocket URL is required for head subscriptions but none was configured. Enable HTTP polling or configure a WebSocket URL")
}

if multinode.CtxIsHealthCheckRequest(ctx) {
Expand Down Expand Up @@ -639,7 +639,7 @@ func (r *RPCClient) astarLatestFinalizedBlock(ctx context.Context, result interf
}

if astarHead.Number == nil {
return r.wrapRPCClientError(errors.New("expected non empty head number of finalized block"))
return r.wrapRPCClientError(errors.New("failed to get Astar finalized block: the finalized block header returned a nil block number. The RPC node may be syncing or unhealthy"))
}

err = r.ethGetBlockByNumber(ctx, astarHead.Number.String(), result)
Expand Down Expand Up @@ -790,7 +790,7 @@ func (r *RPCClient) SendTransaction(ctx context.Context, tx *types.Transaction)
lggr.Debug("RPC call: evmclient.Client#SendTransaction")
start := time.Now()
if r.isChainType(chaintype.ChainTron) {
err := errors.New("SendTransaction not implemented for Tron, this should never be called")
err := errors.New("SendTransaction is not supported for Tron chain type: Tron uses a different transaction submission mechanism. This method should never be called for Tron nodes")
return struct{}{}, multinode.Fatal, err
}

Expand All @@ -804,7 +804,7 @@ func (r *RPCClient) SendTransaction(ctx context.Context, tx *types.Transaction)

func (r *RPCClient) SimulateTransaction(ctx context.Context, tx *types.Transaction) error {
// Not Implemented
return pkgerrors.New("SimulateTransaction not implemented")
return pkgerrors.New("SimulateTransaction is not implemented for this RPC client")
}

func (r *RPCClient) SendEmptyTransaction(
Expand All @@ -816,7 +816,7 @@ func (r *RPCClient) SendEmptyTransaction(
fromAddress common.Address,
) (txhash string, err error) {
// Not Implemented
return "", pkgerrors.New("SendEmptyTransaction not implemented")
return "", pkgerrors.New("SendEmptyTransaction is not implemented for this RPC client")
}

// PendingSequenceAt returns one higher than the highest nonce from both mempool and mined transactions
Expand All @@ -831,7 +831,7 @@ func (r *RPCClient) PendingSequenceAt(ctx context.Context, account common.Addres

// Tron doesn't have the concept of nonces, this shouldn't be called but just in case we'll return an error
if r.isChainType(chaintype.ChainTron) {
err = errors.New("tron does not support eth_getTransactionCount")
err = errors.New("Tron chain type does not support eth_getTransactionCount: Tron does not use the nonce-based transaction model")
return
}

Expand Down Expand Up @@ -861,7 +861,7 @@ func (r *RPCClient) NonceAt(ctx context.Context, account common.Address, blockNu

// Tron doesn't have the concept of nonces, this shouldn't be called but just in case we'll return an error
if r.isChainType(chaintype.ChainTron) {
err = errors.New("tron does not support eth_getTransactionCount")
err = errors.New("Tron chain type does not support eth_getTransactionCount: Tron does not use the nonce-based transaction model")
return
}

Expand Down Expand Up @@ -1225,7 +1225,7 @@ func (r *RPCClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu
ctx, cancel, chStopInFlight, ws, _ := r.acquireQueryCtx(ctx, r.rpcTimeout)
defer cancel()
if ws == nil {
return nil, errors.New("SubscribeFilterLogs is not allowed without ws url")
return nil, errors.New("failed to subscribe to filter logs: WebSocket URL is required for log subscriptions but none was configured. Configure a WebSocket URL to enable log subscriptions")
}
lggr := r.newRqLggr().With("q", q)

Expand Down Expand Up @@ -1486,7 +1486,7 @@ func (r *RPCClient) doWithConfidence(ctx context.Context, request rpc.BatchElem,
}

if referencedHead == nil {
return errors.New("referenced block request returned nil. RPC is unhealthy or chain does not support specified tag")
return errors.New("referenced block request returned nil block header: the RPC node may be unhealthy, still syncing, or the chain does not support the specified block tag. Verify the RPC node status and chain compatibility")
}

maxAvailableHeight, err := r.referenceHeadToMaxAvailableHeight(confidence, referencedHead.Number)
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/rpc_client_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func TestRPCClient_doWithConfidence(t *testing.T) {
EthCallResult: "0x00",
ExpectedTag: "safe",
BlockByNumberResult: "null",
ExpectedError: "referenced block request returned nil. RPC is unhealthy or chain does not support specified tag",
ExpectedError: "referenced block request returned nil block header: the RPC node may be unhealthy, still syncing, or the chain does not support the specified block tag. Verify the RPC node status and chain compatibility",
},
{
Name: "Happy path",
Expand Down
6 changes: 3 additions & 3 deletions pkg/client/rpc_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestRPCClient_SubscribeToHeads(t *testing.T) {
t.Run("WS and HTTP URL cannot be both empty", func(t *testing.T) {
// ws is optional when LogBroadcaster is disabled, however SubscribeFilterLogs will return error if ws is missing
rpcClient := client.NewTestRPCClient(t, client.RPCClientOpts{})
require.Equal(t, errors.New("cannot dial rpc client when both ws and http info are missing"), rpcClient.Dial(ctx))
require.Equal(t, errors.New("failed to dial RPC client: both WebSocket and HTTP URLs are missing. At least one connection URL must be configured"), rpcClient.Dial(ctx))
})

t.Run("Updates chain info on new blocks", func(t *testing.T) {
Expand Down Expand Up @@ -391,7 +391,7 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) {
require.NoError(t, rpcClient.Dial(ctx))

_, err = rpcClient.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log))
require.Equal(t, errors.New("SubscribeFilterLogs is not allowed without ws url"), err)
require.Equal(t, errors.New("failed to subscribe to filter logs: WebSocket URL is required for log subscriptions but none was configured. Configure a WebSocket URL to enable log subscriptions"), err)
})
t.Run("Failed SubscribeFilterLogs logs and returns proper error", func(t *testing.T) {
server := testutils.NewWSServer(t, chainID, func(reqMethod string, reqParams gjson.Result) (resp testutils.JSONRPCResponse) {
Expand Down Expand Up @@ -901,7 +901,7 @@ func TestRPCClient_Tron(t *testing.T) {

// Verify it returns the expected error for Tron
require.Error(t, err)
assert.Equal(t, "SendTransaction not implemented for Tron, this should never be called", err.Error())
assert.Equal(t, "SendTransaction is not supported for Tron chain type: Tron uses a different transaction submission mechanism. This method should never be called for Tron nodes", err.Error())
})

t.Run("NonceAt", func(t *testing.T) {
Expand Down
10 changes: 5 additions & 5 deletions pkg/client/simulated_backend_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func (c *SimulatedBackendClient) TokenBalance(ctx context.Context, address commo
}
err = balanceOfABI.UnpackIntoInterface(balance, "balanceOf", b)
if err != nil {
return nil, errors.New("unable to unpack balance")
return nil, errors.New("failed to unpack ERC20 balanceOf response: the returned data could not be decoded. The contract may not implement the standard ERC20 balanceOf interface")
}
return balance, nil
}
Expand Down Expand Up @@ -243,7 +243,7 @@ func (c *SimulatedBackendClient) blockNumber(ctx context.Context, number interfa
return nil, nil
}
if n.Sign() < 0 {
return nil, errors.New("block number must be non-negative")
return nil, errors.New("invalid block number: block number must be non-negative. Use nil for the latest block or provide a valid block number >= 0")
}
return n, nil
default:
Expand Down Expand Up @@ -959,13 +959,13 @@ func interfaceToAddress(value interface{}) (common.Address, error) {
return *v, nil
case string:
if ok := common.IsHexAddress(v); !ok {
return common.Address{}, errors.New("string not formatted as a hex encoded evm address")
return common.Address{}, errors.New("invalid address format: string is not a valid hex-encoded EVM address. Expected format: 0x followed by 40 hex characters (e.g., 0x1234...abcd)")
}

return common.HexToAddress(v), nil
case *big.Int:
if v.Uint64() > 0 || len(v.Bytes()) > 20 {
return common.Address{}, errors.New("invalid *big.Int; value must be larger than 0 with a byte length <= 20")
return common.Address{}, errors.New("invalid *big.Int for address conversion: value must be greater than 0 with a byte length of 20 or fewer bytes to represent a valid EVM address")
}
Comment on lines 966 to 969
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the *big.Int address conversion branch, the validation condition v.Uint64() > 0 || len(v.Bytes()) > 20 rejects every positive value, making virtually all non-zero addresses invalid. This looks inverted; it should reject negative values (and/or values whose byte length exceeds 20), not values greater than 0.

Copilot uses AI. Check for mistakes.

return common.BigToAddress(v), nil
Expand All @@ -983,7 +983,7 @@ func interfaceToHash(value interface{}) (*common.Hash, error) {
case string:
b, err := hex.DecodeString(v)
if err != nil || len(b) != 32 {
return nil, errors.New("string does not represent a 32-byte hexadecimal number")
return nil, errors.New("invalid hash format: string does not represent a valid 32-byte (64 hex character) hash. Ensure the input is a valid hex-encoded 256-bit hash")
}
h := common.Hash(b)
return &h, nil
Expand Down
20 changes: 10 additions & 10 deletions pkg/read/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ func newErrorFromCall(err error, call Call, block string, tp readType) Error {
func (e Error) Error() string {
var builder strings.Builder

builder.WriteString("[read error]")
builder.WriteString("[contract read error]")
builder.WriteString(fmt.Sprintf(" err: %s;", e.Err.Error()))
builder.WriteString(fmt.Sprintf(" type: %s;", e.Type))
builder.WriteString(fmt.Sprintf(" read type: %s;", e.Type))

if e.Detail != nil {
builder.WriteString(fmt.Sprintf(" block: %s;", e.Detail.Block))
Expand Down Expand Up @@ -101,9 +101,9 @@ func newErrorFromCalls(err error, calls []Call, block string, tp readType) Multi
func (e MultiCallError) Error() string {
var builder strings.Builder

builder.WriteString("[read error]")
builder.WriteString("[batch contract read error]")
builder.WriteString(fmt.Sprintf(" err: %s;", e.Err.Error()))
builder.WriteString(fmt.Sprintf(" type: %s;", e.Type))
builder.WriteString(fmt.Sprintf(" read type: %s;", e.Type))

if e.Detail != nil {
builder.WriteString(fmt.Sprintf(" block: %s;", e.Detail.Block))
Expand Down Expand Up @@ -133,25 +133,25 @@ type ConfigError struct {

func newMissingReadIdentifierErr(readIdentifier string) ConfigError {
return ConfigError{
Msg: fmt.Sprintf("[no configured reader] read-identifier: '%s'", readIdentifier),
Msg: fmt.Sprintf("[contract reader configuration error] no configured reader found for read-identifier: '%s'. Ensure the contract and read name are registered in the chain reader configuration", readIdentifier),
}
}

func newMissingContractErr(readIdentifier, contract string) ConfigError {
return ConfigError{
Msg: fmt.Sprintf("[no configured reader] read-identifier: %s; contract: %s;", readIdentifier, contract),
Msg: fmt.Sprintf("[contract reader configuration error] no configured reader found for contract '%s' (read-identifier: %s). Ensure the contract is registered in the chain reader configuration", contract, readIdentifier),
}
}

func newMissingReadNameErr(readIdentifier, contract, readName string) ConfigError {
return ConfigError{
Msg: fmt.Sprintf("[no configured reader] read-identifier: %s; contract: %s; read-name: %s;", readIdentifier, contract, readName),
Msg: fmt.Sprintf("[contract reader configuration error] no configured reader found for read-name '%s' on contract '%s' (read-identifier: %s). Ensure the method or event is registered in the chain reader configuration", readName, contract, readIdentifier),
}
}

func newUnboundAddressErr(address, contract, readName string) ConfigError {
return ConfigError{
Msg: fmt.Sprintf("[address not bound] address: %s; contract: %s; read-name: %s;", address, contract, readName),
Msg: fmt.Sprintf("[contract reader configuration error] address '%s' is not bound to contract '%s' for read-name '%s'. Bind the address to the contract before attempting reads", address, contract, readName),
}
}

Expand All @@ -166,7 +166,7 @@ type FilterError struct {
}

func (e FilterError) Error() string {
return fmt.Sprintf("[logpoller filter error] action: %s; err: %s; filter: %+v;", e.Action, e.Err.Error(), e.Filter)
return fmt.Sprintf("[log poller filter error] failed during '%s' action: %s; filter details: %+v. Check that the filter configuration matches the expected contract events and addresses", e.Action, e.Err.Error(), e.Filter)
}

func (e FilterError) Unwrap() error {
Expand All @@ -179,7 +179,7 @@ type NoContractExistsError struct {
}

func (e NoContractExistsError) Error() string {
return fmt.Sprintf("%s: contract does not exist at address: %s", e.Err.Error(), e.Address)
return fmt.Sprintf("%s: no contract exists at address %s. Verify that the contract has been deployed to this address on the correct chain and that the address is not an externally-owned account (EOA)", e.Err.Error(), e.Address)
}

func (e NoContractExistsError) Unwrap() error {
Expand Down
Loading