Investigating Discrepancies in eth_get_transaction_receipt Response on ICP

When calling eth_get_transaction_receipt from the evm_rpc canister, the JSON-RPC response appears in the console output as expected:

2024-11-07 06:35:02.696446 UTC: [Canister bw4dl-smaaa-aaaaa-qaacq-cai] Direct result: Consistent(Ok(Some(TransactionReceipt { block_hash: 0x4b85fda05ebafb031d778e3d37d804f15fd179921852a23d875bcef53c02275d, block_number: 21133753, effective_gas_price: 9038755583, gas_used: 41309, status: Some(1), transaction_hash: 0x1fcf2b74f5bc7abc56603cb8c491e0dddde76cb620eec8498cdc7b16e6654902, contract_address: None, from: 0xe93ebe6e9c21bec4c091af9eba11f670b9291a4e, logs: [LogEntry { address: 0xdac17f958d2ee523a2206206994597c13d831ec7, topics: [0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000e93ebe6e9c21bec4c091af9eba11f670b9291a4e, 0x000000000000000000000000055d9a4dc18687872d95e2324335aaa4fbd29f05], data: 0x00000000000000000000000000000000000000000000000000000000434b2c40, block_number: Some(21133753), transaction_hash: Some(0x1fcf2b74f5bc7abc56603cb8c491e0dddde76cb620eec8498cdc7b16e6654902), transaction_index: Some(117), block_hash: Some(0x4b85fda05ebafb031d778e3d37d804f15fd179921852a23d875bcef53c02275d), log_index: Some(461), removed: false }], logs_bloom: 0x00000000000000000000000000000000000000000000000000000000001000000000000040000000000000000000010008000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000008000000000000000000000000000, to: Some(0xdac17f958d2ee523a2206206994597c13d831ec7), transaction_index: 117, tx_type: 0x00 })))

However, when invoking the eth_get_transaction_receipt method, the modified code below still results in unexpected output:

#[update(name = "eth_getTransactionReceipt")]
#[candid_method(rename = "eth_getTransactionReceipt")]
pub async fn eth_get_transaction_receipt(
    source: evm_rpc_types::RpcServices,
    config: Option<evm_rpc_types::RpcConfig>,
    tx_hash: Hex32,
) -> MultiRpcResult<Option<evm_rpc_types::TransactionReceipt>> {
    let result = match CandidRpcClient::new(source, config) {
        Ok(source) => source.eth_get_transaction_receipt(tx_hash).await,
        Err(err) => Err(err).into(),
    };
    ic_cdk::println!("Direct result: {:?}", result);
    result
}

Despite the above adjustments, the output remains:

2024-11-07 06:35:02.696446 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] re Full Response: Ok((Consistent(Ok(None)),))
2024-11-07 06:35:02.696446 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] Full Response: Consistent(Ok(None))
2024-11-07 06:35:02.696446 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] Transaction result: Ok(None)
2024-11-07 06:35:02.696446 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] Transaction receipt parsed as None

What might be causing the transaction receipt to still be interpreted as Ok(None) despite the correct direct result appearing in the console?

replenish:
when calling it through another function, I get the following output, showing an Ok(None) instead of the expected Some(TransactionReceipt):

pub async fn get_transaction_receipt(
    tx: String,
    custom_services: Option<Vec<RpcApi>>,
) -> Result<Option<TransactionReceipt>, Box<dyn Error>> {
    let services = get_service(custom_services)?;

    match EVM_RPC.eth_get_transaction_receipt(services, None, tx, CYCLES).await {
        Ok((res, )) => match res {
            MultiGetTransactionReceiptResult::Consistent(transaction_result) => match transaction_result {
                GetTransactionReceiptResult::Ok(transaction_receipt) => return Ok(transaction_receipt),
                GetTransactionReceiptResult::Err(e) => {
                    return Err(format!("Error: {:?}", e).into());
                }
            },
            MultiGetTransactionReceiptResult::Inconsistent(_) => {
                return Err("Block are inconsistent".into());
            }
        },
        Err(e) => return Err(format!("Error: {:?}", e).into()),
    }
}

Hi @yunmin574269

I currently cannot reproduce your problem by calling directly the EVM RPC canister, e.g.

dfx canister call evm_rpc eth_getTransactionReceipt '(variant {EthMainnet = opt vec {variant {PublicNode}; variant {Ankr} }}, null, "0x1fcf2b74f5bc7abc56603cb8c491e0dddde76cb620eec8498cdc7b16e6654902" )' --with-cycles=10000000000 --wallet $(dfx identity get-wallet --ic) --network ic

does return the expected result

(
  variant {
    Consistent = variant {
      Ok = opt record {
        to = opt "0xdac17f958d2ee523a2206206994597c13d831ec7";
        status = opt (1 : nat);
        transactionHash = "0x1fcf2b74f5bc7abc56603cb8c491e0dddde76cb620eec8498cdc7b16e6654902";
        blockNumber = 21_133_753 : nat;
        from = "0xe93ebe6e9c21bec4c091af9eba11f670b9291a4e";
        logs = vec {
          record {
            transactionHash = opt "0x1fcf2b74f5bc7abc56603cb8c491e0dddde76cb620eec8498cdc7b16e6654902";
            blockNumber = opt (21_133_753 : nat);
            data = "0x00000000000000000000000000000000000000000000000000000000434b2c40";
            blockHash = opt "0x4b85fda05ebafb031d778e3d37d804f15fd179921852a23d875bcef53c02275d";
            transactionIndex = opt (117 : nat);
            topics = vec {
              "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
              "0x000000000000000000000000e93ebe6e9c21bec4c091af9eba11f670b9291a4e";
              "0x000000000000000000000000055d9a4dc18687872d95e2324335aaa4fbd29f05";
            };
            address = "0xdac17f958d2ee523a2206206994597c13d831ec7";
            logIndex = opt (461 : nat);
            removed = false;
          };
        };
        blockHash = "0x4b85fda05ebafb031d778e3d37d804f15fd179921852a23d875bcef53c02275d";
        "type" = "0x00";
        transactionIndex = 117 : nat;
        effectiveGasPrice = 9_038_755_583 : nat;
        logsBloom = "0x00000000000000000000000000000000000000000000000000000000001000000000000040000000000000000000010008000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000008000000000000000000000000000";
        contractAddress = null;
        gasUsed = 41_309 : nat;
      }
    }
  },
)
2292346216895

Could you maybe share more information:

  1. What is the exact request being queried?
  2. What is CandidRpcClient or EVM_RPC, is that your own client or does it come from some library?

When I call the eth_getTransactionReceipt method directly on my local evm_rpc canister, I see the expected JSON-RPC response in the console, as shown below:

 dfx canister call evm_rpc eth_getTransactionReceipt \
  '(variant { Custom = record { chainId = 56; services = vec { record { url = "https://rpc.ankr.com/bsc/7162116b886f7ce81c81a1a9d6980643b2a829ad3b90f6b8cb2a3c419f2beb6e"; headers = null } } } }, null, "0xfcf2c4f3a7a9db7e4e8e2ec193460f95ecb8e246d88280c57d898fa4aa818531")'  --with-cycles=10000000000 --wallet $(dfx identity get-wallet)

Decryption complete.
(
  variant {
    Consistent = variant {
      Ok = opt record {
        to = opt "0xd3ca58f60e6a1c42947bfc9702dfc48ceb7cfbe3";
        status = opt (1 : nat);
        transactionHash = "0xfcf2c4f3a7a9db7e4e8e2ec193460f95ecb8e246d88280c57d898fa4aa818531";
        blockNumber = 43_792_397 : nat;
        from = "0x425ba48f408623789d2803a6af6fc5a71a678e77";
        logs = vec {};
        blockHash = "0xbc01dac93a661950239a98951e7944f6a9c4f97048989c095862ec6bb8ccb733";
        "type" = "0x00";
        transactionIndex = 64 : nat;
        effectiveGasPrice = 1_000_000_000 : nat;
        logsBloom = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
        contractAddress = null;
        gasUsed = 21_000 : nat;
      }
    }
  },
)

However, when I call the same function get_transaction_receipt from within my Rust code, it consistently returns Ok(None), as shown in the output below:

2024-11-07 09:56:56.008124 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] result: Ok(None)

This suggests that the evm_rpc canister itself is functioning correctly and returns data directly when called. However, my Rust function get_transaction_receipt doesn’t capture this result correctly and returns None. I suspect that the issue lies in my Rust code setup, but I am unable to identify the exact problem.

Code for get_transaction_receipt in Rust:

lazy_static! {
    // static ref EVM_RPC: EvmRpcCanister = EvmRpcCanister(Principal::from_text("be2us-64aaa-aaaaa-qaabq-cai").unwrap());
    static ref EVM_RPC: EvmRpcCanister = EvmRpcCanister(Principal::from_text("bkyz2-fmaaa-aaaaa-qaaaq-cai").unwrap());
}

fn get_service(
    custom_services: Option<Vec<RpcApi>>
) -> Result<RpcServices, Box<dyn Error>> {
    if let Some(services) = custom_services {
        return Ok(RpcServices::Custom { chainId: CHAIN_ID, services });
    } else {
        return Err("Custom network requires chain_id and services".into());
    }
}

pub async fn get_transaction_receipt(
    tx: String,
    custom_services: Option<Vec<RpcApi>>,
) -> Result<Option<TransactionReceipt>, Box<dyn Error>> {
    let services = get_service(custom_services)?;

    ic_cdk::println!("services: {:?}", services);
    match EVM_RPC.eth_get_transaction_receipt(services, None, tx, CYCLES).await {
        Ok((res, )) => match res {
            MultiGetTransactionReceiptResult::Consistent(transaction_result) => match transaction_result {
                GetTransactionReceiptResult::Ok(transaction_receipt) => return Ok(transaction_receipt),
                GetTransactionReceiptResult::Err(e) => {
                    return Err(format!("Error: {:?}", e).into());
                }
            },
            MultiGetTransactionReceiptResult::Inconsistent(_) => {
                return Err("Block are inconsistent".into());
            }
        },
        Err(e) => return Err(format!("Error: {:?}", e).into()),
    }
}

Could anyone provide insights into why the get_transaction_receipt function returns None in this setup?

The console output for services is as follows:

services: Custom { chainId: 56, services: [RpcApi { url: "https://rpc.ankr.com/bsc/7162116b886f7ce81c81a1a9d6980643b2a829ad3b90f6b8cb2a3c419f2beb6e", headers: None }] }

I suspect a problem with the EVM_RPC client. I’m assuming you’re using the crate evm-rpc-canister-types, correct? If that’s the case it seems the definition of TransactionReceipt is no longer actual since this was changed as part of a bug fix (see issue 262, the fields to and status must be optional) and should match that of this one for example.

Yes, the version used is evm-rpc-canister-types = “1.0.0”

I also updated the version, 3.0.0, and tried it again, returning None