Giter Site home page Giter Site logo

mana-ethereum / ethereumex Goto Github PK

View Code? Open in Web Editor NEW
376.0 376.0 69.0 364 KB

Elixir JSON-RPC client for the Ethereum blockchain 0x063D3d782598744AF1252eBEaf3aA97D990Edf72

License: MIT License

Elixir 99.62% Makefile 0.13% Shell 0.25%
elixir ethereum ethereum-client json-rpc

ethereumex's People

Contributors

akramhussein avatar alisinabh avatar ayrat555 avatar dependabot[bot] avatar ghbutton avatar gregors avatar hayesgm avatar hswick avatar inomurko avatar izelnakri avatar jpjuni0r avatar kianmeng avatar kminevskiy avatar paulperegud avatar pgebal avatar rupurt avatar somoza avatar the-kenny avatar tzumby avatar unnawut avatar zdenal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ethereumex's Issues

just including this in your project breaks things (if its not from hex)

Unchecked dependencies for environment dev:
* exthereumex (https://github.com/exthereum/ethereumex.git)
  could not find an app file at "_build/dev/lib/exthereumex/ebin/exthereumex.app". This may happen if the dependency was not yet compiled, or you specified the wrong application name in your deps, or the dependency indeed has no app file (then you can pass app: false as option)
** (Mix) Can't continue due to errors on dependencies

decode in readme doesnt work

To convert the balance into an integer:

balance_bytes
|> String.slice(2..-1)
|> Base.decode16!(case: :lower)
|> TypeDecoder.decode_raw([{:uint, 256}])
|> List.first

If balance is 0 we get "0x0" as the return and we cant pipe that to Base.decode16!(case: :lower) without an error. We need to pad leading zero in this case.

I think a decode/encode function is needed in the library.

Absolute IPC path

Hi,

How do you feel about removing the user home prefix on the IPC path? With the current approach there is no way to set an absolute path. e.g. /tmp/my.socket

Doesn't work with geth1.9

I don't know what happend, but Ethereumex doesn't work with geth1.9.

When I try to call Ethereumex.HttpClient.web3_client_version I get {:error, :econnrefused}.

But with Parity it works good. I get
{:ok, "Parity-Ethereum//v2.6.0-beta-e38293b-20190708/x86_64-linux-gnu/rustc1.36.0"}

Tests fail on latest version of Parity

When I was messing around with the parity versions for the CircleCI build, I noticed that there are 4 test failures.

Can reproduce this with versions of parity between 2.0.6 and 1.11

watch for new blocks (filter)

Hello, is there a way I can watch for new blocks? Similar to the Javascript web3 API:

const filter = web3.eth.filter('latest');
filter.watch((e,r) => {
  console.log('got new block = ', r);
});

How can I achieve something similar using Ethereumex? I have checked the documentation here, but I think it isn't updated yet.

infinite timeout does not work

I am trying the following configs:

config :ethereumex,
  url: "https://mainnet.infura.io/v3/MY_KEY",
  request_timeout: :infinity

But I get the following response:

{: error, :timeout}

for some requests. Is this response coming from Infura?

In order to get through this, I think I will have to keep making the same call until I get {:ok, response}. Is this the suggested way?

rlp: non-canonical integer (leading zero bytes) for *big.Int, decoding into (types.Transaction)(types.txdata).R

hi there, thanks for your good work.

when i send transaction using Ethereumex.HttpClient.eth_send_raw_transaction(signed_transaction), sometime errors occur just like that:

no match of right hand side value: {:error, %{"code" => -32000, "message" => "rlp: non-canonical integer (leading zero bytes) for *big.Int, decoding into (types.Transaction)(types.txdata).R"}}

if i change any part of transaction, nonce, gas_price, etc., errors disappear.

i searched for a long time, it looks like a problem of RLP, mentioned years before:

https://ethereum.stackexchange.com/questions/71616/ethereum-boardcast-shows-error-non-canonical-integer-leading-zero-bytes-for

MOACChain/moac-core#24

is there something we ignored unintentionally in Ethereumex or ExRLP? and is it possible to resolve it?

thanks in advance.

Configure request timeout for IpcClient

IpcClient requests sometimes fail with a timeout error

22:08:52.499 [error] GenServer Ethereumex.IpcClient terminating
** (stop) exited in: GenServer.call(IpcServer, {:request, "{\"params\":[\"0x5D782\",true],\"method\":\"eth_getBlockByNumber\",\"jsonrpc\":\"2.0\",\"id\":914}"}, 5000)
    ** (EXIT) time out
    (elixir) lib/gen_server.ex:924: GenServer.call/3
    (ethereumex) lib/ethereumex/ipc_client.ex:8: Ethereumex.IpcClient.post_request/2
    (ethereumex) lib/ethereumex/client/server.ex:30: Ethereumex.Client.Server.handle_call/3
    (stdlib) gen_server.erl:636: :gen_server.try_handle_call/4
    (stdlib) gen_server.erl:665: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.74.0>): {:request, %{"jsonrpc" => "2.0", "method" => "eth_getBlockByNumber", "params" => ["0x5D782", true]}, []}
State: {Ethereumex.IpcClient, 914}
Client #PID<0.74.0> is alive

    (kernel) code_server.erl:140: :code_server.call/1
    (kernel) error_handler.erl:41: :error_handler.undefined_function/3
    (elixir) lib/kernel/cli.ex:75: Kernel.CLI.format_error/3
    (elixir) lib/kernel/cli.ex:157: Kernel.CLI.print_error/3
    (elixir) lib/kernel/cli.ex:119: anonymous fn/3 in Kernel.CLI.exec_fun/2

Add a clear example for sending transactions.


    address           = Application.get_env(:ethereumex, :owner) |> String.slice(2..-1) |> Base.decode16!(case: :mixed)
    contract_address  = Application.get_env(:ethereumex, :contract_address)
    abi_encoded_data  = ABI.encode("setTest(uint, string)", [200, "foo"]) |> Base.encode16(case: :lower)
    
    balance_bytes = Ethereumex.HttpClient.eth_send_transaction(%{
      data: "0x" <> abi_encoded_data,
      to: contract_address,
      from: address,
      gas: "0xc350",
      gasPrice: nil
    })

Do I need to do a transaction first?

I got:
** (Jason.EncodeError) invalid byte 0xFC in <<252, 83, 33, 125, 207, 109, 79, 21, 193, 116, 8, 255, 147, 246, 107, 252, 40, 46, 165, 230>>

The README has not a clear example of sending transactions. I would be charmed to add it, but first I should know how to do that :)

eth-call is not working on Moonbeam-Network

Ethereumex.HttpClient.eth_get_transaction_by_hash("0xaee99b5c62a61b2ead425beff718fa69f7b0d44167e6c9adcf66b52cb0d60333", url: "https://rpc.api.moonbeam.network/")

But curl is works:
image

Problem following the documentation...

iex(9)> ABI.encode("balanceOf(address)", [address]) |> Base.encode16(case: :lower)
** (FunctionClauseError) no function clause matching in ABI.TypeEncoder.maybe_encode_unsigned/1

The following arguments were given to ABI.TypeEncoder.maybe_encode_unsigned/1:

    # 1
    {:ok,
     <<91, 29, 177, 3, 188, 236, 87, 218, 123, 135, 200, 13, 34, 181, 46, 75, 64,
       12, 91, 43>>}

Attempted function clauses (showing 2 out of 2):

    defp maybe_encode_unsigned(bin) when is_binary(bin)
    defp maybe_encode_unsigned(int) when is_integer(int)

(ex_abi 0.5.9) lib/abi/type_encoder.ex:272: ABI.TypeEncoder.maybe_encode_unsigned/1
(ex_abi 0.5.9) lib/abi/type_encoder.ex:224: ABI.TypeEncoder.encode_uint/2
(ex_abi 0.5.9) lib/abi/type_encoder.ex:100: ABI.TypeEncoder.do_encode_type/4
(ex_abi 0.5.9) lib/abi/type_encoder.ex:83: ABI.TypeEncoder.do_encode/4
(ex_abi 0.5.9) lib/abi/type_encoder.ex:21: ABI.TypeEncoder.encode/3

How to send ERC20 tokens from one ethereum address to another?

Could you please provide an example of sending ERC20 tokens using Ethereumex or ExW3? Do I need to use some ABI like this one?

// transfer
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function"
  }

I would like to send USDT from one Ethereum address to another.

Move rpc url from config to parameter

It makes more sense to configure RPC client using parameters (not in config)
Otherwise it's impossible to use it as real RPC client.

For example right now it's not possible to connect to 2 different EVMs at same time or reconfigure it without pain.

What do you think about something like this:

iex> config = %Ethereumex.Config{http_url: "https://localhost:8545"}
iex> Ethereumex.HttpClient.web3_client_version(config)
{:ok, "Parity//v1.7.2-beta-9f47909-20170918/x86_64-macos/rustc1.19.0"}

Weird http pooling related error after v0.6.patch upgrade

Hi there,

After upgrading ethereumex from v0.6.0 to v0.6.1 my test suite fails/gets stuck with some odd error. After some debugging I find error to be a very implicit/fundamental change in the way http pool is handled due to internal httpoison upgrade:

https://github.com/izelnakri/eth/blob/master/test/support/eth_client.exs#L13
https://github.com/izelnakri/eth/blob/master/test/support/eth_client.exs#L42 -> following code first returns hundreds/thousands of {:error, :econnrefused} while waiting for the spawned processes/ganache-cli http server to start and then it subsequent requests dont seem to get sent, and I assume the previous ones on the pool now returns {:error, :checkout_timeout} after ganache-cli starts serving the requests.

I'm still digging to see what has changed, the test suite doesnt work on the latest ethereumex as well. Most likely, our http pooling logic has changed and needs adjustments. I'll share more information once I have better understanding.

More info on the {:error, :checkout_timeout}: edgurgel/httpoison#359

error without optional telemetry dependency installed

Looks like telemetry was added recently as an optional dependency - but it doesn't look optional

iex(1)> Ethereumex.HttpClient.web3_client_version
** (UndefinedFunctionError) function :telemetry.execute/3 is undefined (module :telemetry is not available)
    :telemetry.execute([:ethereumex], %{counter: 1}, %{method_name: "web3_clientVersion"})
    (ethereumex 0.6.1) lib/ethereumex/counter.ex:59: Ethereumex.Counter.inc/2
    (ethereumex 0.6.1) lib/ethereumex/client/base_client.ex:505: Ethereumex.HttpClient.prepare_request/1
    (ethereumex 0.6.1) lib/ethereumex/client/base_client.ex:485: Ethereumex.HttpClient.server_request/2```

Step by step example to send rawTransaction

I've tried to follow the document but got this error. So need in detail example for this.

iex(7)> abi_encoded_data = ABI.encode("transferFrom(address,address,uint)", ["0x7e5f4552091a69125d5dfcb7b8c2659029395bdf", "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf", 123])
** (RuntimeError) Data overflow encoding uint, data `0x7e5f4552091a69125d5dfcb7b8c2659029395bdf` cannot fit in 160 bits

Figure out why request ids are being incremented by the parameter count

It looks like request ids are being incremented by the number of parameters in the request. It's possible this is happening for batch requests. Should this happen for single requests as well? It's possible this is set up correctly. If so we can close this. If not we should update it to increment the request id by 1.

websocket support

Is there any plan to include support for websocket?
I saw there is PR for this, but unfortunately the owner has removed it.
If necessary I have time to contribute to it. Please give me a guidance

Interpret empty-body responses from JSON rpc as errors

geth returns an empty-body HTTP response, when it happens to be brought down when a request is being handled (so it seems at least). Such response is coded 200 and as such is passed onto Jason.decode which gives a Jason.DecodeError for "".

Firstly: maybe you are aware of why would geth be doing this to me or perhaps have run into the same issue? Asking in case this is purely a geth issue (or myself erring somewhere).

Secondly: in case geth's behavior makes at least some sense, maybe ethereumex's HttpClient could interpret "" response body as something that it shouldn't pass onto Jason for a more sensible error message?

Steps to reproduce

1/ IO.inspect here
2/ run geth, connect to it and run a lot of JSON rpc queries against it (I'm giving one which should actually work, when connected to Rinkeby, in the console dump below)
3/ Ctrl+C the geth
4/ get this (I'm pattern matching an {:ok... response from HttpClient), mind the empty body and error:

{:ok,
 %HTTPoison.Response{
   body: "",
   headers: [
     {"Content-Type", "application/json"},
     {"Date", "Wed, 06 Feb 2019 11:10:42 GMT"},
     {"Content-Length", "0"}
   ],
   request: %HTTPoison.Request{
     body: "{\"id\":32523,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x058af4226c60a5fd54158d7e36d7614d445d071ecc595206abc44b0938272491\"]}",
     headers: [{"Content-Type", "application/json"}],
     method: :post,
     options: [hackney: [pool: :default], recv_timeout: 60000],
     params: %{},
     url: "http://localhost:8546"
   },
   request_url: "http://localhost:8546",
   status_code: 200
 }}

<snip>

    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:invalid_json, %Jason.DecodeError{data: "", position: 0, token: nil}}}

<snip>

I see this is slightly along the lines of #19, just my issue here is that "" could perhaps be treated as a special case?

Calling smart contracts and executing functions

I'm curious how you guys are calling smart contracts and executing functions, I'm bit struggling with compiling and functions from smart contracts. I'm working on an oracle service which should use eth_call function and I need to set some params on data params. Any tutorial or advice would be awesome.

Multiple endpoints

Currently, the only way send requests to a node is to set the configuration variable as such: Application.put_env(:ethereumex, :url, node)

Would it be possible to have an option to pass the node on the function call instead of having it at the application level. Something like this: Ethereumex.HttpClient.eth_gas_price(node: "http://localhost:8545")

I can help with a PR if you are ok with this option.

Multiple endpoints

Hello friends, we have a requirement for a project to integrate with multiple networks/endpoints (we need ethereum and polygon). I understand that this is not supported. If you think it would be useful for the project I could try to implement that functionality as a PR with a care to be backwards compatible with the single provider version (i.e people that use a single provider won't need to change anything to their code). I'd be happy to see your opinions.

Kind regards,
Serafeim

Issue with web3_client_version & default behaviour targeting Anvil and more?

Sorry if I am missing some page on issue guidelines or something. I looked around a bit but couldn't find one. Here is the TL;DR

Description

When running Ethereumex.HttpClient.web3_client_version() against an Anvil node, Anvil will respond with missing Content-Type header. It seems that somewhere along the way the library drops that header from this request at least. I know this because you can make this call Ethereumex.HttpClient.web3_client_version(http_headers: [{"Content-Type", "application/json"}]) and Anvil accepts the request. Since this request is a POST, and you are POSTing JSON, it seems appropriate to add the Content-Type header to the HTTP Request. Other RPCs like Alchemy happen to be a bit more lenient.

Reference:

Generic Client Protocol

It would be great if there was a generic client protocol that could be implemented by specific providers, like http, websocket, and ipc. It seems that a lot of the code for this to happen already exists, but a found a few difficulties when trying to extend this library with a websocket client.

  1. The supervisor tree has the HttpClient hardcoded into it.
  2. When implementing the WebSocketClient module, I wasn't sure where I should put WebSockex.start_link("ws://*url*", __MODULE__, state) since the GenServer details are abstracted away by Ethereumex.Client.Server

This issue probably relates to #8 as well, in that a generic client would allow for a client to simply implement the protocol. Also, I have Ethereumex.HttpClient hardcoded in a bunch of places in ExW3, which isn't ideal.

Ideally the API would be similar to web3.js where the user can manually set the provider url and type.

Implement Ipc Client

Implement Ipc Client like HttpClient is implemented (with Ethereumex.Client.Macro)

defmodule Ethereumex.IpcClient do
  use Ethereumex.Client.Macro

  def request(payload) do
    // request with Unix socket
  end
end

Currently there is no decent adapter for Unix sockets in Elixir ecosystem. I think we should try to use procket but its api is too ugly and low level.

After finishing ipcClient HttpClient in SmartContract modules should be replaced with the new client.

MatchError for IpcClient

I'm trying to connect to geth and receiving following error:

23:29:41.836 [info]  Application ethereumex exited: Ethereumex.start(:normal, []) returned an error: shutdown: failed to start child: :worker
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:bad_return_value, {:error, :enoent}}}
            (poolboy) src/poolboy.erl:275: :poolboy.new_worker/1
            (poolboy) src/poolboy.erl:296: :poolboy.prepopulate/3
            (poolboy) src/poolboy.erl:145: :poolboy.init/3
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
 
23:29:41.836 [error] GenServer #PID<0.242.0> terminating
** (MatchError) no match of right hand side value: {:error, {:bad_return_value, {:error, :enoent}}}
    (poolboy) src/poolboy.erl:275: :poolboy.new_worker/1
    (poolboy) src/poolboy.erl:296: :poolboy.prepopulate/3
    (poolboy) src/poolboy.erl:145: :poolboy.init/3
    (stdlib) gen_server.erl:374: :gen_server.init_it/2
    (stdlib) gen_server.erl:342: :gen_server.init_it/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:EXIT, #PID<0.241.0>, {{:badmatch, {:error, {:bad_return_value, {:error, :enoent}}}}, [{:poolboy, :new_worker, 1, [file: 'src/poolboy.erl', line: 275]}, {:poolboy, :prepopulate, 3, [file: 'src/poolboy.erl', line: 296]}, {:poolboy, :init, 3, [file: 'src/poolboy.erl', line: 145]}, {:gen_server, :init_it, 2, [file: 'gen_server.erl', line: 374]}, {:gen_server, :init_it, 6, [file: 'gen_server.erl', line: 342]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}}
State: {:state, {#PID<0.242.0>, :poolboy_sup}, :simple_one_for_one, {[Ethereumex.IpcServer], %{Ethereumex.IpcServer => {:child, :undefined, Ethereumex.IpcServer, {Ethereumex.IpcServer, :start_link, [[path: "/home/den/home/den/.ethereum/geth.ipc", ipc_request_timeout: 60000]]}, :temporary, 5000, :worker, [Ethereumex.IpcServer]}}}, {:sets, {:set, 0, 16, 16, 8, 80, 48, {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}, {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}}}}, 0, 1, [], 0, :poolboy_sup, {Ethereumex.IpcServer, [path: "/home/den/home/den/.ethereum/geth.ipc", ipc_request_timeout: 60000]}}
** (Mix) Could not start application ethereumex: Ethereumex.start(:normal, []) returned an error: shutdown: failed to start child: :worker
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:bad_return_value, {:error, :enoent}}}
            (poolboy) src/poolboy.erl:275: :poolboy.new_worker/1
            (poolboy) src/poolboy.erl:296: :poolboy.prepopulate/3
            (poolboy) src/poolboy.erl:145: :poolboy.init/3
            (stdlib) gen_server.erl:374: :gen_server.init_it/2
            (stdlib) gen_server.erl:342: :gen_server.init_it/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Config file:

config :ethereumex,
  client_type: :ipc,
  ipc_path: "/home/den/.ethereum/geth.ipc",
  ipc_worker_size: 5,
  ipc_max_worker_overflow: 2,
  ipc_request_timeout: 60_000

Geth:

den@nb:~$ .config/Ethereum\ Wallet/binaries/Geth/unpacked/geth --syncmode light
...
INFO [12-21|23:36:21.485] IPC endpoint opened                      url=/home/den/.ethereum/geth.ipc
...

Dialyzer complains about RPC wrappers taking booleans as arguments

I'm trying to call one of such functions:

Ethereumex.HttpClient.eth_get_block_by_number("0x0", false)

(boolean at 2nd argument) and all works fine but dialyzer complains:

apps/omg_eth/lib/eth.ex:95: The call 'Elixir.Ethereumex.HttpClient':eth_get_block_by_number(<<_:16,_:_*8>>,'false') will never return since it differs in the 2nd argument from the success typing arguments: (binary() | [binary()] | map(),binary() | [binary()] | map())

looking at

@callback eth_get_block_by_number(binary(), binary(), keyword()) :: {:ok, map()} | error
it looks like the specs have binary() in that spot.

The function doesn't work with "false":

iex(37)> Ethereumex.HttpClient.eth_get_block_by_number("0x0", "false")
{:error,
 %{
   "code" => -32602,
   "message" => "invalid argument 1: json: cannot unmarshal string into Go value of type bool"
 }}

I'm not entirely sure if I'm missing something or the specs should be updated for all boolean arguments like this

Decoding examples

Not sure if this belongs in ABI or here, but it would be helpful if there was an example for encoding arguments for a transaction.

You're ABI library outputs binaries, but http poison does not have a protocol for bit strings. I am relatively new to Elixir so perhaps there is something I'm missing.

Ethereumex.HttpClient.eth_send_transaction doesnt work for testrpc

Thanks for writing an API that one-to-one matches the Ethereum JSON RPC. The send transaction doesnt seem to work though:

error code:

{:error,
 %{"code" => -32000,
   "message" => "TypeError: callback is not a function\n    at StateManager.queueTransaction (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:83065:5)\n    at GethApiDouble.eth_sendTransaction (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:82631:14)\n    at GethApiDouble.handleRequest (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:82434:10)\n    at next (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:52153:18)\n    at VmSubprovider.handleRequest (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:59291:12)\n    at next (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:52153:18)\n    at GethDefaults.handleRequest (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:82339:12)\n    at next (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:52153:18)\n    at FilterSubprovider.handleRequest (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:58850:7)\n    at next (/usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js:52153:18)"}}

Handle non-JSON responses

I'm having some issues using a pretty loaded geth-node as the rpc server. It seems like sometimes it doesn't return a json body, causing https://github.com/exthereum/ethereumex/blob/master/lib/ethereumex/http_client.ex#L24 to error out with an Undefined token on position 0 error in Poison.decode!, crashing the server. If ethereumex is under load and crashes more than 5 times in a quick succession, it brings down the whole ethereumex application (and the whole VM if it's running in MIX_ENV=prod).

One solution would be using the with macro in decode_body and post_request, gracefully propagating http- as well as parsing errors. This would make the whole system much more robust.

Getting Jason.DecodeError, 400 in all requests

Hi! Thanks for the great work.

Running ethereum 0.7.1, httppoison 1.8.0

I keep getting this error

{:error,
 {:invalid_json,
  %Jason.DecodeError{
    data: "<html>\r\n<head><title>400 Bad Request</title></head>\r\n<body>\r\n<center><h1>400 Bad Request</h1></center>\r\n<hr><center>openresty</center>\r\n</body>\r\n</html>\r\n",
    position: 0,
    token: nil
  }}}

for whatever request I make, using the HTTPClient provided. I'm using a blockdaemon node.

I cloned the repo and added a IO.inspect after the httppoison request:

{:ok,
 %HTTPoison.Response{
   body: "<html>\r\n<head><title>400 Bad Request</title></head>\r\n<body>\r\n<center><h1>400 Bad Request</h1></center>\r\n<hr><center>openresty</center>\r\n</body>\r\n</html>\r\n",
   headers: [
     {"Date", "Tue, 09 Nov 2021 00:10:54 GMT"},
     {"Content-Type", "text/html"},
     {"Content-Length", "154"},
     {"Connection", "close"},
     {"Server", "nginx"}
   ],
   request: %HTTPoison.Request{
     body: "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\",\"params\":[\"0x0\",false]}",
     headers: [{"Content-Type", "application/json"}],
     method: :post,
     options: [],
     params: %{},
     url: "https://ethshared.bdnodes.net?auth={{KEY}}"
   },
   request_url: "https://ethshared.bdnodes.net?auth={{KEY}}",
   status_code: 400
 }}

I suspect this may be a problem with HTTPoison but couldn't figure out why. I wrote a BaseClient implementation that's almost the same as HttpClient, but uses Tesla instead, and it works, so the problem is probably not my node.

defmodule MyEthereumexClient do
  @moduledoc false

  use Ethereumex.Client.BaseClient
  alias Ethereumex.Config

  @type opt :: {:url, String.t()}
  @type empty_response :: :empty_response
  @type invalid_json :: {:invalid_json, any()}
  @type http_client_error :: {:error, empty_response() | invalid_json() | any()}

  @spec post_request(binary(), [opt()]) :: {:ok, any()} | http_client_error()
  def post_request(payload, opts) do
    url = Keyword.get(opts, :url) || Config.rpc_url()
    [base_url, query] = String.split(url, "?")
    %{"auth" => auth} = URI.decode_query(query)

    client =
      Tesla.client([
        {Tesla.Middleware.BaseUrl, base_url},
        {Tesla.Middleware.Headers, [{"content-type", "application/json"}]}
      ])

    case Tesla.post(client, "", payload, query: [auth: auth]) do
      {:ok, response} ->
        %Tesla.Env{body: body, status: code} = response
        decode_body(body, code)

      error ->
        error
    end
  end

  @spec decode_body(binary(), integer()) :: {:ok, any()} | http_client_error()
  defp decode_body(body, code) do
    case Jason.decode(body) do
      {:ok, decoded_body} ->
        case {code, decoded_body} do
          {200, %{"error" => error}} -> {:error, error}
          {200, result = [%{} | _]} -> {:ok, format_batch(result)}
          {200, %{"result" => result}} -> {:ok, result}
          _ -> {:error, decoded_body}
        end

      {:error, %Jason.DecodeError{data: ""}} ->
        {:error, :empty_response}

      {:error, error} ->
        {:error, {:invalid_json, error}}
    end
  end
end

I'm quite happy with the custom impl, but perhaps other people are having the same problems as me, and we could try to solve it.

dialyzer issues when using opts argument

if you have a eth_call like this in the code:

Ethereumex.HttpClient.eth_call(%{to: contract, data: data}, "latest", opts)

where opts = [url: "http://localhost:8545"]
dialyzer will yield:

The function call will not succeed.

will never return since it differs in arguments with
positions 3rd from the success typing arguments:

(binary(), any(), [{:batch, true}])

#todo need to include a small example project

multiple nodes - reconnection

Many thanks for the amazing library!
I was thinkiing a way to have a really robust way to be in synch with Ethereum events, so let me hear if it makes sense:

  1. create a reconnect procedure [we can get some inspiration from this ws client.
  2. connect to 2 or more nodes, and subscribe to receive from them at the same time as a redundancy connection, and simple filter to ignore one of them
  3. create a Live View monitoring dashboard
  4. create a cache plugin, where we check if every event is concatenated, with an alarm if we lose any of them [let's say for a timeframe of 5 days]

Batched requests don't handle errors

If a single request fails in a batched http request, it will throw the following error

** (FunctionClauseError) no function clause matching in anonymous fn/1 in Ethereumex.HttpClient.format_batch/1
    (ethereumex 0.8.0) lib/ethereumex/client/base_client.ex:472: anonymous fn(%{"error" => %{"code" => -32000, "message" => "execution reverted"}, "id" => 86, "jsonrpc" => "2.0"}) in Ethereumex.HttpClient.format_batch/1
    (elixir 1.12.3) lib/enum.ex:1582: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.12.3) lib/enum.ex:1582: Enum."-map/2-lists^map/1-0-"/2
    (ethereumex 0.8.0) lib/ethereumex/http_client.ex:34: Ethereumex.HttpClient.decode_body/2

This is because the batch is implicitly assumed to be completely successful and the results will be returned in a tuple {:ok, results = []}.

Unfortunately handling failing requests is going to require an api change, as currently the only way to map the response back to the request from application code is via the index of the response within the result list - i.e. it's not possible to return anything other than a single list and keep the order intact.

Currently I handle this by including the error map in the response list, but this is pretty dirty and requires custom parsing.

Add a module for making contract calls

Currently we make contract calls by encoding ABI manually.

abi_encoded_data = ABI.encode("transferFrom(address,address,uint)",[from_address, to_address, token_id])
contract_address = "0x123" |> String.slice(2..-1) |> Base.decode16(case: :mixed)

transaction_data = %Blockchain.Transaction{
    data: abi_encoded_data,
    gas_limit: 100_000,
    gas_price: 16_000_000_000,
    init: <<>>,
    nonce: 5,
    to: contract_address,
    value: 0
}
|> Blockchain.Transaction.Signature.sign_transaction(private_key)
|> Blockchain.Transaction.serialize()
|> ExRLP.encode()
|> Base.encode16(case: :lower)

Ethereumex.HttpClient.eth_send_raw_transaction("0x" <> transaction_data)

I think we could make a cleaner interface.

Here’s a sketch of the API I propose:

abi = File.read!(“ERC20.abi”)
contract_address = "0x123" |> String.slice(2..-1) |> Base.decode16(case: :mixed)
contract = Ethereumex.Contract.new(%{abi: abi, address: contract_address})
client = Ethereumex.Client.new(%{gas_limit: 100_000, gas_price: 16_000_000_000})
account =  Ethereumex.Client.new(%{private_key: private_key, nonce: 5})

{:ok, account, transaction_hash} = contract
  |> Ethereumex.call(:transfer_to, from_address, to_address, amount)
  |> client.send(account)

This would take some refactoring but I think it would make calling contracts in client code much cleaner.

It’s possible that this is a higher level abstraction than this library is meant to provide. If that’s the case we/I can pull it into a new package. web3ex?

To recap:

  1. Would anyone else benefit from the API I suggested?
  2. Should this live in this repo or a new one?

If anyone has any thoughts feel free to comment here or join the mana gitter.

Default block parameter for eth_estimate_gas (and some others)

Hi,

I was trying to use the eth_estimate_gas function, but it looks like it has not been tested and all calls to it were failing.

Here's the error:

{:error, %{"code" => -32602, "message" => "too many arguments, want at most 1"}}

So after looking at the Ethereum JSON RPC specs, I figured that default block parameters can be specified only for certain calls. However, when scrolling through the actual examples, some of them still specify the quantity/tag param.

But back to the issue at hand.

I removed the default block parameter from the spec, from the function signature and body params and it works as expected. Am I doing something wrong (with your initial implementation) or is it an actual bug and I should submit a PR?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.