Skip to content

Commit

Permalink
Merge pull request #23 from hswick/new-events
Browse files Browse the repository at this point in the history
get_filter_changes (0.3.0)
  • Loading branch information
hswick authored Sep 29, 2018
2 parents e9498bc + b2caaa0 commit ad595b5
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 290 deletions.
113 changes: 48 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

```elixir
def deps do
[{:exw3, "~> 0.2.0"}]
[{:exw3, "~> 0.3.0"}]
end
```
## Overview

ExW3 is a wrapper around ethereumex to provide a high level, user friendly json rpc api. It currently only supports Http. The primary feature it provides is a handy abstraction for working with smart contracts.
ExW3 is a wrapper around ethereumex to provide a high level, user friendly json rpc api. It currently ONLY supports http. The primary feature of this library is a handy abstraction for working with smart contracts.

## Usage

Expand Down Expand Up @@ -91,96 +91,79 @@ iex(11)> ExW3.Contract.call(:SimpleStorage, :get)

## Asynchronous

ExW3 now provides async versions of `call` and `send`. They both return a `Task` that can be awaited on.
ExW3 provides async versions of `call` and `send`. They both return a `Task` that can be awaited on.

```elixir
t = ExW3.Contract.call_async(:SimpleStorage, :get)
{:ok, data} = Task.await(t)
t = ExW3.Contract.call_async(:SimpleStorage, :get)
{:ok, data} = Task.await(t)
```

## Listening for Events
## Events

Elixir doesn't have event listeners like say JS. However, we can simulate that behavior with message passing.
The way ExW3 handles event filters is with a background process that calls eth_getFilterChanges every cycle.
Whenever a change is detected it will send a message to whichever process is listening.
ExW3 allows the retrieval of event logs using filters or transaction receipts. In this example we will demonstrate a filter. Assume we have already deployed and registered a contract called EventTester.

```elixir
# Start the background listener
ExW3.EventListener.start_link
# We can optionally specify extra parameters like `:fromBlock`, and `:toBlock`
{:ok, filter_id} = ExW3.Contract.filter(:EventTester, "Simple", %{fromBlock: 42, toBlock: "latest"})

# Assuming we have already registered our contract called :EventTester
# We can then add a filter for the event listener to look out for by passing in the event name, and the process we want to receive the messages when an event is triggered.
# For now we are going to use the main process, however, we could pass in a pid of a different process.
# We can also optionally specify extra parameters like `:fromBlock`, and `:toBlock`

filter_id = ExW3.Contract.filter(:EventTester, "Simple", self(), %{fromBlock: 42, toBlock: "latest"})

# We can then wait for the event. Using the typical receive keyword we wait for the first instance of the event, and then continue with the rest of the code. This is useful for testing.
receive do
{:event, {filter_id, data}} -> IO.inspect data
end
# After some point that we think there are some new changes
{:ok, changes} = ExW3.Contract.get_filter_changes(filter_id)

# We can then uninstall the filter after we are done using it
ExW3.uninstall_filter(filter_id)

# ExW3 also provides a helper method to continuously listen for events, with the `listen` method.
# One use is to combine all of our filters with pattern matching
ExW3.EventListener.listen(fn result ->
case result do
{filter_id, data} -> IO.inspect data
{filter_id2, data} -> IO.inspect data
end
end

# The listen method is a simple receive loop waiting for `{:event, _}` messages.
# It looks like this:
def listen(callback) do
receive do
{:event, result} -> apply callback, [result]
end
listen(callback)
end

# You could do something similar with your own process, whether it is a simple Task or a more involved GenServer.
ExW3.Contract.uninstall_filter(filter_id)
```

## Listening for Indexed Events
## Indexed Events

Ethereum allows for filtering events specific to its parameters using indexing. For all of the options see [here](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter)
Ethereum allows a user to add topics to filters. This means the filter will only return events with the specific index parameters. For all of the extra options see [here](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter)

If you have written your event in Solidity like this:
```
event SimpleIndex(uint256 indexed num, bytes32 indexed data, uint256 otherNum);
event SimpleIndex(uint256 indexed num, bytes32 indexed data, uint256 otherNum);
```

You can add filter on which logs will be returned back to the RPC client, based on the indexed fields. ExW3 allows for 2 ways of specifying these parameters or `topics` in two ways. The first, and probably more preferred way, is with a map:
You can add a filter on which logs will be returned back to the RPC client based on the indexed fields.

ExW3 allows for 2 ways of specifying these parameters (`:topics`) in two ways. The first, and probably more preferred way, is with a map:

```elixir
indexed_filter_id = ExW3.Contract.filter(
:EventTester,
"SimpleIndex",
self(),
%{
topics: %{num: 46, data: "Hello, World!"},
}
)
indexed_filter_id = ExW3.Contract.filter(
:EventTester,
"SimpleIndex",
%{
topics: %{num: 46, data: "Hello, World!"},
}
)
```

The other option is with a list, but this is order dependent, and any values you don't want to specify must be represented with a `nil`.
The other option is a list (mapped version is an abstraction over this). The downside here is this is order dependent. Any values you don't want to specify must be represented with a `nil`. This approach has been included because it is the implementation of the JSON RPC spec.

```elixir
indexed_filter_id = ExW3.Contract.filter(
:EventTester,
"SimpleIndex",
self(),
%{
topics: [nil, "Hello, World!"]
}
)
indexed_filter_id = ExW3.Contract.filter(
:EventTester,
"SimpleIndex",
%{
topics: [nil, "Hello, World!"]
}
)
```

In this case we are skipping the `num` topic, and only filtering on the `data` parameter.
Here we are skipping the `num` topic, and only filtering on the `data` parameter.

NOTE!!! These two approaches are mutually exclusive, and for almost all cases you should prefer the map.

## Continuous Event Handling

In many cases, you will want some process to continuously listen for events. We can implement this functionality using a recursive function. Since Elixir uses tail call optimization, we won't have to worry about blowing up the stack.

```elixir
def listen_for_event do
{:ok, changes} = ExW3.Contract.get_filter_changes(filter_id) # Get our changes from the blockchain
handle_changes(changes) # Some function to deal with the data. Good place to use pattern matching.
:timer.sleep(1000) # Some delay in milliseconds. Recommended to save bandwidth, and not spam.
listen_for_event() # Recurse
end
```

# Compiling Solidity

Expand Down
Loading

0 comments on commit ad595b5

Please sign in to comment.