Skip to content

Commit

Permalink
Merge pull request #1544 from hyperledger/docs_v1.3.1
Browse files Browse the repository at this point in the history
docs: ahead of v1.3.1
  • Loading branch information
awrichar authored Aug 5, 2024
2 parents ee5f03e + c5718bb commit aeec387
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 39 deletions.
3 changes: 3 additions & 0 deletions doc-site/docs/reference/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ Once you have configured the blockchain event listener, every event detected
from the blockchain will result in a FireFly event delivered to your application
of type `blockchain_event_received`.

As of 1.3.1 a group of event filters can be established under a single topic when supported by the connector, which has benefits for ordering.
See [Contract Listeners](../reference/types/contractlistener.md) for more detail

Check out the [Custom Contracts Tutorial](../tutorials/custom_contracts/index.md) for
a walk-through of how to set up listeners for the events from your smart contracts.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,144 @@ of the interface for that event.

Check out the [Custom Contracts Tutorial](../../tutorials/custom_contracts/index.md) for
a walk-through of how to set up listeners for the events from your smart contracts.

See below for a deep dive into the format of contract listeners and important concepts to understand when managing them.

### Event filters

#### Multiple filters

From v1.3.1 onwards, a contract listener can be created with multiple filters under a single topic, when supported by the connector. Each filter contains:

- a reference to a specific blockchain event to listen for
- (optional) a specific location/address to listen from
- a connector-specific signature (generated from the event and the location)

In addition to this list of multiple filters, the listener specifies a single `topic` to identify the stream of events.

Creating a single listener that listens for multiple events will allow for the easiest management of listeners, and for strong ordering of the events that they process.

#### Single filter

Before v1.3.1, each contract listener would only support listening to one specific event from a contract interface. Each listener would be comprised of:

- a reference to a specific blockchain event to listen for
- (optional) a specific location/address to listen from
- a connector-specific signature (generated from the event), which allows you to easily identify and search for the contact listener for an event
- a `topic` which determines the ordered stream that these events are part of

For backwards compatibility, this format is still supported by the API.

### Signature strings

#### String format

Each filter is identified by a generated `signature` that matches a single event, and each contract listener is identified by a `signature` computed from its filters.

Ethereum provides a string standard for event signatures, of the form `EventName(uint256,bytes)`. Prior to v1.3.1, the signature of each Ethereum contract listener would exactly follow this Ethereum format.

As of v1.3.1, Ethereum signature strings have been changed, because this format does not fully describe the event - particularly because each top-level parameter can in the ABI definition be marked as `indexed`. For example, while the following two Solidity events have the same signature, they are serialized differently due to the different placement of `indexed` parameters, and thus a listener must define both individually to be able to process them:

- ERC-20 `Transfer`

```solidity
event Transfer(address indexed _from, address indexed _to, uint256 _value)
```

- ERC-721 `Transfer`

```solidity
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
```

The two above are now expressed in the following manner by the Ethereum blockchain connector:

```solidity
Transfer(address,address,uint256) [i=0,1]
Transfer(address,address,uint256) [i=0,1,2]
```

The `[i=]` listing at the end of the signature indicates the position of all parameters that are marked as `indexed`.

Building on the blockchain-specific signature format for each event, FireFly will then compute the final signature for each filter and each contract listener as follows:

- Each filter signature is a combination of the location and the specific connector event signature, such as `0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1:Changed(address,uint256) [i=0]`
- Each contract listener signature is a concatenation of all the filter signatures, separated by `;`

#### Duplicate filters

FireFly restricts the creation of a contract listener containing duplicate filters.

This includes the special case where one filter is a superset of another filter, due to a wildcard location.

For example, if two filters are listening to the same event, but one has specified a location and the other hasn't, then the latter will be a superset, and already be listening to all the events matching the first filter. Creation of duplicate or superset filters within a single listener will be blocked.

#### Duplicate listeners

As noted above, each listener has a generated signature. This signature - containing all the locations and event signatures combined with the listener topic - will guarantee uniqueness of the contract listener. If you tried to create the same listener again, you would receive HTTP 409. This combination can allow a developer to assert that their listener exists, without the risk of creating duplicates.

**Note:** Prior to v1.3.1, FireFly would detect duplicates simply by requiring a unique combination of signature + topic + location for each listener. The updated behavior for the listener signature is intended to preserve similar functionality, even when dealing with listeners that contain many event filters.

### Backwards compatibility

As noted throughout this document, the behavior of listeners is changed in v1.3.1. However, the following behaviors are retained for backwards-compatibility, to ensure that code written prior to v1.3.1 should continue to function.

- The response from all query APIs of `listeners` will continue to populate top-level `event` and `location` fields
- The first entry from the `filters` array is duplicated to these fields
- On input to create a new `listener`, the `event` and `location` fields are still supported
- They function identically to supplying a `filters` array with a single entry
- The `signature` field is preserved at the listener level
- The format has been changed as described above

### Input formats

The two input formats supported when creating a contract listener are shown below.

**Muliple Filters**

```json
{
"filters": [
{
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
},
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"eventPath": "Changed"
},
{
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
},
"location": {
"address": "0xa4ea5d0b6b2eaf194716f0cc73981939dca27da1"
},
"eventPath": "AnotherEvent"
}
],
"options": {
"firstEvent": "newest"
},
"topic": "simple-storage"
}
```

**One filter (old format)**

```json
{
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
},
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"eventPath": "Changed",
"options": {
"firstEvent": "newest"
},
"topic": "simple-storage"
}
```
28 changes: 26 additions & 2 deletions doc-site/docs/reference/types/contractlistener.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,35 @@ title: ContractListener
}
]
},
"signature": "Changed(uint256)",
"signature": "0x596003a91a97757ef1916c8d6c0d42592630d2cf:Changed(uint256)",
"topic": "app1_topic",
"options": {
"firstEvent": "newest"
}
},
"filters": [
{
"event": {
"name": "Changed",
"description": "",
"params": [
{
"name": "x",
"schema": {
"type": "integer",
"details": {
"type": "uint256",
"internalType": "uint256"
}
}
}
]
},
"location": {
"address": "0x596003a91a97757ef1916c8d6c0d42592630d2cf"
},
"signature": "0x596003a91a97757ef1916c8d6c0d42592630d2cf:Changed(uint256)"
}
]
}
```

Expand Down
8 changes: 8 additions & 0 deletions doc-site/docs/releasenotes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ title: Release Notes

[Full release notes](https://github.com/hyperledger/firefly/releases)

## [v1.3.1 - Aug 5, 2024](https://github.com/hyperledger/firefly/releases/tag/v1.3.1)

What's New:

- Enable contract listeners with multiple filters
See [Contract Listeners](../reference/types/contractlistener.md) for details
- New multiparty status API at `/status/multiparty`

## [v1.3.0 - April 25, 2024](https://github.com/hyperledger/firefly/releases/tag/v1.1.0)

[Migration guide](1.3_migration_guide.md)
Expand Down
5 changes: 0 additions & 5 deletions doc-site/docs/tutorials/chains/index.md

This file was deleted.

74 changes: 59 additions & 15 deletions doc-site/docs/tutorials/custom_contracts/ethereum.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,21 +648,25 @@ Here is an example of sending 100 wei with a transaction:

Now that we've seen how to submit transactions and preform read-only queries to the blockchain, let's look at how to receive blockchain events so we know when things are happening in realtime.

If you look at the source code for the smart contract we're working with above, you'll notice that it emits an event when the stored value of the integer is set. In order to receive these events, we first need to instruct FireFly to listen for this specific type of blockchain event. To do this, we create an **Event Listener**. The `/contracts/listeners` endpoint is RESTful so there are `POST`, `GET`, and `DELETE` methods available on it. To create a new listener, we will make a `POST` request. We are going to tell FireFly to listen to events with name `"Changed"` from the FireFly Interface we defined earlier, referenced by its ID. We will also tell FireFly which contract address we expect to emit these events, and the topic to assign these events to. Topics are a way for applications to subscribe to events they are interested in.
If you look at the source code for the smart contract we're working with above, you'll notice that it emits an event when the stored value of the integer is set. In order to receive these events, we first need to instruct FireFly to listen for this specific type of blockchain event. To do this, we create an **Event Listener**. The `/contracts/listeners` endpoint is RESTful so there are `POST`, `GET`, and `DELETE` methods available on it. To create a new listener, we will make a `POST` request. We are going to tell FireFly to listen to events with name `"Changed"` from the FireFly Interface we defined earlier, referenced by its ID. We will also tell FireFly which contract address we expect to emit these events, and the topic to assign these events to. You can specify multiple filters for a listener, in this case we only specify one for our event. Topics are a way for applications to subscribe to events they are interested in.

### Request

`POST` `http://localhost:5000/api/v1/namespaces/default/contracts/listeners`

```json
{
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
},
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"eventPath": "Changed",
"filters": [
{
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
},
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"eventPath": "Changed"
}
],
"options": {
"firstEvent": "newest"
},
Expand All @@ -674,17 +678,17 @@ If you look at the source code for the smart contract we're working with above,

```json
{
"id": "1bfa3b0f-3d90-403e-94a4-af978d8c5b14",
"id": "e7c8457f-4ffd-42eb-ac11-4ad8aed30de1",
"interface": {
"id": "8bdd27a5-67c1-4960-8d1e-7aa31b9084d3"
"id": "55fdb62a-fefc-4313-99e4-e3f95fcca5f0"
},
"namespace": "default",
"name": "sb-66209ffc-d355-4ac0-7151-bc82490ca9df",
"protocolId": "sb-66209ffc-d355-4ac0-7151-bc82490ca9df",
"name": "019104d7-bb0a-c008-76a9-8cb923d91b37",
"backendId": "019104d7-bb0a-c008-76a9-8cb923d91b37",
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"created": "2022-02-17T22:02:36.34549538Z",
"created": "2024-07-30T18:12:12.704964Z",
"event": {
"name": "Changed",
"description": "",
Expand Down Expand Up @@ -712,9 +716,49 @@ If you look at the source code for the smart contract we're working with above,
}
]
},
"signature": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1:Changed(address,uint256) [i=0]",
"topic": "simple-storage",
"options": {
"firstEvent": "oldest"
}
"firstEvent": "newest"
},
"filters": [
{
"event": {
"name": "Changed",
"description": "",
"params": [
{
"name": "from",
"schema": {
"type": "string",
"details": {
"type": "address",
"internalType": "address",
"indexed": true
}
}
},
{
"name": "value",
"schema": {
"type": "integer",
"details": {
"type": "uint256",
"internalType": "uint256"
}
}
}
]
},
"location": {
"address": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1"
},
"interface": {
"id": "55fdb62a-fefc-4313-99e4-e3f95fcca5f0"
},
"signature": "0xa5ea5d0a6b2eaf194716f0cc73981939dca26da1:Changed(address,uint256) [i=0]"
}
]
}
```

Expand Down
50 changes: 34 additions & 16 deletions doc-site/docs/tutorials/custom_contracts/fabric.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,16 +576,20 @@ The `/contracts/listeners` endpoint is RESTful so there are `POST`, `GET`, and `

```json
{
"interface": {
"id": "f1e5522c-59a5-4787-bbfd-89975e5b0954"
},
"location": {
"channel": "firefly",
"chaincode": "asset_transfer"
},
"event": {
"name": "AssetCreated"
},
"filters": [
{
"interface": {
"id": "f1e5522c-59a5-4787-bbfd-89975e5b0954"
},
"location": {
"channel": "firefly",
"chaincode": "asset_transfer"
},
"event": {
"name": "AssetCreated"
}
}
],
"options": {
"firstEvent": "oldest"
},
Expand All @@ -597,25 +601,39 @@ The `/contracts/listeners` endpoint is RESTful so there are `POST`, `GET`, and `

```json
{
"id": "6e7f5dd8-5a57-4163-a1d2-5654e784dc31",
"id": "d6b5e774-c9e5-474c-9495-ec07fa47a907",
"namespace": "default",
"name": "sb-2cac2bfa-38af-4408-4ff3-973421410e5d",
"backendId": "sb-2cac2bfa-38af-4408-4ff3-973421410e5d",
"name": "sb-44aa348a-bafb-4243-594e-dcad689f1032",
"backendId": "sb-44aa348a-bafb-4243-594e-dcad689f1032",
"location": {
"channel": "firefly",
"chaincode": "asset_transfer"
},
"created": "2022-05-02T17:19:13.144561086Z",
"created": "2024-07-22T15:36:58.514085959Z",
"event": {
"name": "AssetCreated",
"description": "",
"params": null
},
"signature": "AssetCreated",
"signature": "firefly-asset_transfer:AssetCreated",
"topic": "assets",
"options": {
"firstEvent": "oldest"
}
},
"filters": [
{
"event": {
"name": "AssetCreated",
"description": "",
"params": null
},
"location": {
"channel": "firefly",
"chaincode": "asset_transfer"
},
"signature": "firefly-asset_transfer:AssetCreated"
}
]
}
```

Expand Down
Loading

0 comments on commit aeec387

Please sign in to comment.