diff --git a/db/migrations/postgres/000116_update_contractlisteners_add_events_column.down.sql b/db/migrations/postgres/000116_update_contractlisteners_add_events_column.down.sql new file mode 100644 index 0000000000..5340286b5c --- /dev/null +++ b/db/migrations/postgres/000116_update_contractlisteners_add_events_column.down.sql @@ -0,0 +1,3 @@ +BEGIN; +ALTER TABLE contractlisteners DROP COLUMN filters; +COMMIT; \ No newline at end of file diff --git a/db/migrations/postgres/000116_update_contractlisteners_add_events_column.up.sql b/db/migrations/postgres/000116_update_contractlisteners_add_events_column.up.sql new file mode 100644 index 0000000000..6ff35a34fe --- /dev/null +++ b/db/migrations/postgres/000116_update_contractlisteners_add_events_column.up.sql @@ -0,0 +1,5 @@ +BEGIN; +ALTER TABLE contractlisteners ADD COLUMN filters TEXT; +ALTER TABLE contractlisteners ALTER COLUMN event DROP NOT NULL; +ALTER TABLE contractlisteners ALTER COLUMN location DROP NOT NULL; +COMMIT: \ No newline at end of file diff --git a/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.down.sql b/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.down.sql new file mode 100644 index 0000000000..234055a4f0 --- /dev/null +++ b/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.down.sql @@ -0,0 +1 @@ +ALTER TABLE contractlisteners DROP COLUMN filters; \ No newline at end of file diff --git a/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.up.sql b/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.up.sql new file mode 100644 index 0000000000..3a7875e267 --- /dev/null +++ b/db/migrations/sqlite/000116_update_contractlisteners_add_events_column.up.sql @@ -0,0 +1,12 @@ +ALTER TABLE contractlisteners ADD COLUMN filters TEXT; + +-- SQLite doesn't support dropping NOT NULL constraints so we have to move +-- everything to a temp colum, create a new column, then copy the data back +ALTER TABLE contractlisteners RENAME COLUMN event TO event_tmp; +ALTER TABLE contractlisteners RENAME COLUMN location TO location_tmp; +ALTER TABLE contractlisteners ADD COLUMN event TEXT; +ALTER TABLE contractlisteners ADD LOCATION event TEXT; +UPDATE contractlisteners SET event = event_tmp; +UPDATE contractlisteners SET location = location_tmp; +ALTER TABLE contractlisteners DROP COLUMN event_tmp; +ALTER TABLE contractlisteners DROP COLUMN location_tmp; \ No newline at end of file diff --git a/docs/reference/types/contractlistener.md b/docs/reference/types/contractlistener.md index b152677122..9b8b97c79d 100644 --- a/docs/reference/types/contractlistener.md +++ b/docs/reference/types/contractlistener.md @@ -64,13 +64,14 @@ nav_order: 10 | Field Name | Description | Type | |------------|-------------|------| | `id` | The UUID of the smart contract listener | [`UUID`](simpletypes#uuid) | -| `interface` | A reference to an existing FFI, containing pre-registered type information for the event | [`FFIReference`](#ffireference) | +| `interface` | Deprecated: Please use 'interface' in the array of 'filters' instead | [`FFIReference`](#ffireference) | | `namespace` | The namespace of the listener, which defines the namespace of all blockchain events detected by this listener | `string` | | `name` | A descriptive name for the listener | `string` | | `backendId` | An ID assigned by the blockchain connector to this listener | `string` | -| `location` | A blockchain specific contract identifier. For example an Ethereum contract address, or a Fabric chaincode name and channel | [`JSONAny`](simpletypes#jsonany) | +| `location` | Deprecated: Please use 'location' in the array of 'filters' instead | [`JSONAny`](simpletypes#jsonany) | | `created` | The creation time of the listener | [`FFTime`](simpletypes#fftime) | -| `event` | The definition of the event, either provided in-line when creating the listener, or extracted from the referenced FFI | [`FFISerializedEvent`](#ffiserializedevent) | +| `event` | Deprecated: Please use 'event' in the array of 'filters' instead | [`FFISerializedEvent`](#ffiserializedevent) | +| `filters` | A list of filters for the contract listener. Each filter is made up of an Event and an optional Location. Events matching these filters will always be emitted in the order determined by the blockchain. | [`ListenerFilter[]`](#listenerfilter) | | `signature` | The stringified signature of the event, as computed by the blockchain plugin | `string` | | `topic` | A topic to set on the FireFly event that is emitted each time a blockchain event is detected from the blockchain. Setting this topic on a number of listeners allows applications to easily subscribe to all events they need | `string` | | `options` | Options that control how the listener subscribes to events from the underlying blockchain | [`ContractListenerOptions`](#contractlisteneroptions) | @@ -102,6 +103,16 @@ nav_order: 10 +## ListenerFilter + +| Field Name | Description | Type | +|------------|-------------|------| +| `event` | The definition of the event, either provided in-line when creating the listener, or extracted from the referenced FFI | [`FFISerializedEvent`](#ffiserializedevent) | +| `location` | A blockchain specific contract identifier. For example an Ethereum contract address, or a Fabric chaincode name and channel | [`JSONAny`](simpletypes#jsonany) | +| `interface` | A reference to an existing FFI, containing pre-registered type information for the event | [`FFIReference`](#ffireference) | +| `signature` | The stringified signature of the event, as computed by the blockchain plugin | `string` | + + ## ContractListenerOptions | Field Name | Description | Type | diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 1aa9e191b3..cecfa30184 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1183,9 +1183,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array + of ''filters'' instead' properties: description: description: A description of the smart contract event @@ -1221,13 +1220,94 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + items: + description: A list of filters for the contract listener. + Each filter is made up of an Event and an optional Location. + Events matching these filters will always be emitted in + the order determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument + definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note + that parameters must be ordered correctly + on the FFI, according to the order in the + blockchain smart contract + type: string + schema: + description: FireFly uses an extended subset + of JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. + See the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -1241,9 +1321,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For - example an Ethereum contract address, or a Fabric chaincode - name and channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -1309,9 +1388,6 @@ paths: application/json: schema: properties: - location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and channel name: description: A descriptive name for the listener type: string @@ -1348,9 +1424,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -1386,13 +1461,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -1406,9 +1560,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -5582,9 +5735,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array + of ''filters'' instead' properties: description: description: A description of the smart contract event @@ -5620,13 +5772,94 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + items: + description: A list of filters for the contract listener. + Each filter is made up of an Event and an optional Location. + Events matching these filters will always be emitted in + the order determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument + definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note + that parameters must be ordered correctly + on the FFI, according to the order in the + blockchain smart contract + type: string + schema: + description: FireFly uses an extended subset + of JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. + See the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -5640,9 +5873,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For - example an Ethereum contract address, or a Fabric chaincode - name and channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -5695,52 +5927,82 @@ paths: application/json: schema: properties: - event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced FFI - properties: - description: - description: A description of the smart contract event - type: string - details: - additionalProperties: - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - type: object - name: - description: The name of the event - type: string - params: - description: An array of event parameter/argument definitions - items: - description: An array of event parameter/argument definitions + filters: + description: A list of filters for the contract listener. Each filter + is made up of an Event and an optional Location. Events matching + these filters will always be emitted in the order determined by + the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from the + referenced FFI properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object name: - description: The name of the parameter. Note that parameters - must be ordered correctly on the FFI, according to the - order in the blockchain smart contract + description: The name of the event type: string - schema: - description: FireFly uses an extended subset of JSON Schema - to describe parameters, similar to OpenAPI/Swagger. - Converters are available for native blockchain interface - definitions / type systems - such as an Ethereum ABI. - See the documentation for more detail + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the FFI, + according to the order in the blockchain smart + contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar to + OpenAPI/Swagger. Converters are available for + native blockchain interface definitions / type + systems - such as an Ethereum ABI. See the documentation + for more detail + type: object + type: array type: object - type: array - type: object - eventPath: - description: When creating a listener from an existing FFI, this - is the pathname of the event on that FFI to be detected by this - listener - type: string + interface: + description: A reference to an existing FFI, containing pre-registered + type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. For + example an Ethereum contract address, or a Fabric chaincode + name and channel + type: object + type: array interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -5753,9 +6015,6 @@ paths: description: The version of the FireFly interface type: string type: object - location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and channel name: description: A descriptive name for the listener type: string @@ -5792,9 +6051,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -5830,13 +6088,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -5850,9 +6187,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -5953,9 +6289,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -5991,13 +6326,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -6011,9 +6425,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -13229,9 +13642,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array + of ''filters'' instead' properties: description: description: A description of the smart contract event @@ -13267,13 +13679,94 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + items: + description: A list of filters for the contract listener. + Each filter is made up of an Event and an optional Location. + Events matching these filters will always be emitted in + the order determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument + definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note + that parameters must be ordered correctly + on the FFI, according to the order in the + blockchain smart contract + type: string + schema: + description: FireFly uses an extended subset + of JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. + See the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -13287,9 +13780,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For - example an Ethereum contract address, or a Fabric chaincode - name and channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -13362,47 +13854,82 @@ paths: application/json: schema: properties: - event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced FFI - properties: - description: - description: A description of the smart contract event - type: string - details: - additionalProperties: - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - type: object - name: - description: The name of the event - type: string - params: - description: An array of event parameter/argument definitions - items: - description: An array of event parameter/argument definitions + filters: + description: A list of filters for the contract listener. Each filter + is made up of an Event and an optional Location. Events matching + these filters will always be emitted in the order determined by + the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from the + referenced FFI properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object name: - description: The name of the parameter. Note that parameters - must be ordered correctly on the FFI, according to the - order in the blockchain smart contract + description: The name of the event type: string - schema: - description: FireFly uses an extended subset of JSON Schema - to describe parameters, similar to OpenAPI/Swagger. - Converters are available for native blockchain interface - definitions / type systems - such as an Ethereum ABI. - See the documentation for more detail + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the FFI, + according to the order in the blockchain smart + contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar to + OpenAPI/Swagger. Converters are available for + native blockchain interface definitions / type + systems - such as an Ethereum ABI. See the documentation + for more detail + type: object + type: array type: object - type: array - type: object + interface: + description: A reference to an existing FFI, containing pre-registered + type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. For + example an Ethereum contract address, or a Fabric chaincode + name and channel + type: object + type: array interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -13415,9 +13942,6 @@ paths: description: The version of the FireFly interface type: string type: object - location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and channel name: description: A descriptive name for the listener type: string @@ -13454,9 +13978,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -13492,13 +14015,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -13512,9 +14114,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -18060,9 +18661,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array + of ''filters'' instead' properties: description: description: A description of the smart contract event @@ -18098,13 +18698,94 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + items: + description: A list of filters for the contract listener. + Each filter is made up of an Event and an optional Location. + Events matching these filters will always be emitted in + the order determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument + definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note + that parameters must be ordered correctly + on the FFI, according to the order in the + blockchain smart contract + type: string + schema: + description: FireFly uses an extended subset + of JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. + See the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -18118,9 +18799,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For - example an Ethereum contract address, or a Fabric chaincode - name and channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -18180,52 +18860,82 @@ paths: application/json: schema: properties: - event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced FFI - properties: - description: - description: A description of the smart contract event - type: string - details: - additionalProperties: - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - description: Additional blockchain specific fields about this - event from the original smart contract. Used by the blockchain - plugin and for documentation generation. - type: object - name: - description: The name of the event - type: string - params: - description: An array of event parameter/argument definitions - items: - description: An array of event parameter/argument definitions + filters: + description: A list of filters for the contract listener. Each filter + is made up of an Event and an optional Location. Events matching + these filters will always be emitted in the order determined by + the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from the + referenced FFI properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object name: - description: The name of the parameter. Note that parameters - must be ordered correctly on the FFI, according to the - order in the blockchain smart contract + description: The name of the event type: string - schema: - description: FireFly uses an extended subset of JSON Schema - to describe parameters, similar to OpenAPI/Swagger. - Converters are available for native blockchain interface - definitions / type systems - such as an Ethereum ABI. - See the documentation for more detail + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the FFI, + according to the order in the blockchain smart + contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar to + OpenAPI/Swagger. Converters are available for + native blockchain interface definitions / type + systems - such as an Ethereum ABI. See the documentation + for more detail + type: object + type: array type: object - type: array - type: object - eventPath: - description: When creating a listener from an existing FFI, this - is the pathname of the event on that FFI to be detected by this - listener - type: string + interface: + description: A reference to an existing FFI, containing pre-registered + type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. For + example an Ethereum contract address, or a Fabric chaincode + name and channel + type: object + type: array interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -18238,9 +18948,6 @@ paths: description: The version of the FireFly interface type: string type: object - location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and channel name: description: A descriptive name for the listener type: string @@ -18277,9 +18984,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -18315,13 +19021,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -18335,9 +19120,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string @@ -18452,9 +19236,8 @@ paths: format: date-time type: string event: - description: The definition of the event, either provided in-line - when creating the listener, or extracted from the referenced - FFI + description: 'Deprecated: Please use ''event'' in the array of + ''filters'' instead' properties: description: description: A description of the smart contract event @@ -18490,13 +19273,92 @@ paths: type: object type: array type: object + filters: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order determined + by the blockchain. + items: + description: A list of filters for the contract listener. Each + filter is made up of an Event and an optional Location. Events + matching these filters will always be emitted in the order + determined by the blockchain. + properties: + event: + description: The definition of the event, either provided + in-line when creating the listener, or extracted from + the referenced FFI + properties: + description: + description: A description of the smart contract event + type: string + details: + additionalProperties: + description: Additional blockchain specific fields + about this event from the original smart contract. + Used by the blockchain plugin and for documentation + generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + type: object + name: + description: The name of the event + type: string + params: + description: An array of event parameter/argument definitions + items: + description: An array of event parameter/argument + definitions + properties: + name: + description: The name of the parameter. Note that + parameters must be ordered correctly on the + FFI, according to the order in the blockchain + smart contract + type: string + schema: + description: FireFly uses an extended subset of + JSON Schema to describe parameters, similar + to OpenAPI/Swagger. Converters are available + for native blockchain interface definitions + / type systems - such as an Ethereum ABI. See + the documentation for more detail + type: object + type: array + type: object + interface: + description: A reference to an existing FFI, containing + pre-registered type information for the event + properties: + id: + description: The UUID of the FireFly interface + format: uuid + type: string + name: + description: The name of the FireFly interface + type: string + version: + description: The version of the FireFly interface + type: string + type: object + location: + description: A blockchain specific contract identifier. + For example an Ethereum contract address, or a Fabric + chaincode name and channel + signature: + description: The stringified signature of the event, as + computed by the blockchain plugin + type: string + type: object + type: array id: description: The UUID of the smart contract listener format: uuid type: string interface: - description: A reference to an existing FFI, containing pre-registered - type information for the event + description: 'Deprecated: Please use ''interface'' in the array + of ''filters'' instead' properties: id: description: The UUID of the FireFly interface @@ -18510,9 +19372,8 @@ paths: type: string type: object location: - description: A blockchain specific contract identifier. For example - an Ethereum contract address, or a Fabric chaincode name and - channel + description: 'Deprecated: Please use ''location'' in the array + of ''filters'' instead' name: description: A descriptive name for the listener type: string diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go index 2f6ac6654c..1216ba4e70 100644 --- a/internal/blockchain/ethereum/ethereum.go +++ b/internal/blockchain/ethereum/ethereum.go @@ -881,9 +881,38 @@ func (e *Ethereum) AddContractListener(ctx context.Context, listener *core.Contr return err } } - abi, err := ffi2abi.ConvertFFIEventDefinitionToABI(ctx, &listener.Event.FFIEventDefinition) - if err != nil { - return i18n.WrapError(ctx, err, coremsgs.MsgContractParamInvalid) + + filters := make([]*filter, 0) + if listener.Event != nil { + abi, err := ffi2abi.ConvertFFIEventDefinitionToABI(ctx, &listener.Event.FFIEventDefinition) + if err != nil { + return i18n.WrapError(ctx, err, coremsgs.MsgContractParamInvalid) + } + evmFilter := &filter{ + Event: abi, + } + if location != nil { + evmFilter.Address = location.Address + } + filters = append(filters, evmFilter) + } else { + for _, f := range listener.Filters { + abi, err := ffi2abi.ConvertFFIEventDefinitionToABI(ctx, &f.Event.FFIEventDefinition) + if err != nil { + return i18n.WrapError(ctx, err, coremsgs.MsgContractParamInvalid) + } + evmFilter := &filter{ + Event: abi, + } + if f.Location != nil { + location, err = e.parseContractLocation(ctx, f.Location) + if err != nil { + return err + } + evmFilter.Address = location.Address + } + filters = append(filters, evmFilter) + } } subName := fmt.Sprintf("ff-sub-%s-%s", listener.Namespace, listener.ID) @@ -891,7 +920,7 @@ func (e *Ethereum) AddContractListener(ctx context.Context, listener *core.Contr if listener.Options != nil { firstEvent = listener.Options.FirstEvent } - result, err := e.streams.createSubscription(ctx, location, e.streamID, subName, firstEvent, abi) + result, err := e.streams.createSubscription(ctx, e.streamID, subName, firstEvent, filters) if err != nil { return err } diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go index 93e7fed1d5..becab4cf92 100644 --- a/internal/blockchain/ethereum/ethereum_test.go +++ b/internal/blockchain/ethereum/ethereum_test.go @@ -1985,6 +1985,98 @@ func TestAddSubscriptionWithoutLocation(t *testing.T) { assert.NoError(t, err) } +func TestAddSubscriptionMultipleFilters(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + e.streamID = "es-1" + e.streams = &streamManager{ + client: e.client, + } + + sub := &core.ContractListener{ + Filters: core.ListenerFilters{ + { + Event: &core.FFISerializedEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "Changed", + Params: fftypes.FFIParams{ + { + Name: "value", + Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": "string"}}`), + }, + }, + }, + }, + }, + { + Event: &core.FFISerializedEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "Changed2", + Params: fftypes.FFIParams{ + { + Name: "value2", + Schema: fftypes.JSONAnyPtr(`{"type": "string", "details": {"type": "string"}}`), + }, + }, + }, + }, + Location: fftypes.JSONAnyPtr(`{"address":"0x1234"}`), + }, + }, + Options: &core.ContractListenerOptions{ + FirstEvent: string(core.SubOptsFirstEventOldest), + }, + } + + httpmock.RegisterResponder("POST", `http://localhost:12345/subscriptions`, + httpmock.NewJsonResponderOrPanic(200, &subscription{})) + + err := e.AddContractListener(context.Background(), sub) + + assert.NoError(t, err) +} + +func TestAddSubscriptionInvalidAbi(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + e.streamID = "es-1" + e.streams = &streamManager{ + client: e.client, + } + + sub := &core.ContractListener{ + Filters: core.ListenerFilters{ + { + Location: fftypes.JSONAnyPtr(fftypes.JSONObject{ + "address": "0x123", + }.String()), + Event: &core.FFISerializedEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "Changed", + Params: fftypes.FFIParams{ + { + Name: "value", + Schema: fftypes.JSONAnyPtr(`"not an abi"`), + }, + }, + }, + }, + }, + }, + } + + httpmock.RegisterResponder("POST", `http://localhost:12345/subscriptions`, + httpmock.NewJsonResponderOrPanic(200, &subscription{})) + + err := e.AddContractListener(context.Background(), sub) + + assert.Regexp(t, "FF10311", err) +} + func TestAddSubscriptionBadParamDetails(t *testing.T) { e, cancel := newTestEthereum() defer cancel() @@ -2020,7 +2112,7 @@ func TestAddSubscriptionBadParamDetails(t *testing.T) { assert.Regexp(t, "FF10311", err) } -func TestAddSubscriptionBadLocation(t *testing.T) { +func TestAddLegacySubscriptionBadLocation(t *testing.T) { e, cancel := newTestEthereum() defer cancel() httpmock.ActivateNonDefault(e.client.GetClient()) @@ -2041,6 +2133,31 @@ func TestAddSubscriptionBadLocation(t *testing.T) { assert.Regexp(t, "FF10310", err) } +func TestAddSubscriptionBadLocation(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + + e.streamID = "es-1" + e.streams = &streamManager{ + client: e.client, + } + + sub := &core.ContractListener{ + Filters: core.ListenerFilters{ + { + Location: fftypes.JSONAnyPtr(""), + Event: &core.FFISerializedEvent{}, + }, + }, + } + + err := e.AddContractListener(context.Background(), sub) + + assert.Regexp(t, "FF10310", err) +} + func TestAddSubscriptionFail(t *testing.T) { e, cancel := newTestEthereum() defer cancel() diff --git a/internal/blockchain/ethereum/eventstream.go b/internal/blockchain/ethereum/eventstream.go index 8b1d505abc..8d8b5aac56 100644 --- a/internal/blockchain/ethereum/eventstream.go +++ b/internal/blockchain/ethereum/eventstream.go @@ -24,7 +24,6 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/ffresty" - "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-signer/pkg/abi" @@ -52,16 +51,21 @@ type eventStream struct { } type subscription struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Stream string `json:"stream"` - FromBlock string `json:"fromBlock"` - EthCompatAddress string `json:"address,omitempty"` - EthCompatEvent *abi.Entry `json:"event,omitempty"` - Filters []fftypes.JSONAny `json:"filters"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Stream string `json:"stream"` + FromBlock string `json:"fromBlock"` + EthCompatAddress string `json:"address,omitempty"` + EthCompatEvent *abi.Entry `json:"event,omitempty"` + Filters []*filter `json:"filters"` subscriptionCheckpoint } +type filter struct { + Event *abi.Entry `json:"event"` + Address string `json:"address"` +} + type subscriptionCheckpoint struct { Checkpoint ListenerCheckpoint `json:"checkpoint,omitempty"` Catchup bool `json:"catchup,omitempty"` @@ -182,7 +186,7 @@ func (s *streamManager) getSubscriptionName(ctx context.Context, subID string) ( return sub.Name, nil } -func (s *streamManager) createSubscription(ctx context.Context, location *Location, stream, subName, firstEvent string, abi *abi.Entry) (*subscription, error) { +func (s *streamManager) createSubscription(ctx context.Context, stream, subName, firstEvent string, filters []*filter) (*subscription, error) { // Map FireFly "firstEvent" values to Ethereum "fromBlock" values switch firstEvent { case string(core.SubOptsFirstEventOldest): @@ -191,14 +195,10 @@ func (s *streamManager) createSubscription(ctx context.Context, location *Locati firstEvent = "latest" } sub := subscription{ - Name: subName, - Stream: stream, - FromBlock: firstEvent, - EthCompatEvent: abi, - } - - if location != nil { - sub.EthCompatAddress = location.Address + Name: subName, + Stream: stream, + FromBlock: firstEvent, + Filters: filters, } res, err := s.client.R(). @@ -267,7 +267,13 @@ func (s *streamManager) ensureFireFlySubscription(ctx context.Context, namespace name = v1Name } location := &Location{Address: instancePath} - if sub, err = s.createSubscription(ctx, location, stream, name, firstEvent, abi); err != nil { + filters := []*filter{ + { + Event: abi, + Address: location.Address, + }, + } + if sub, err = s.createSubscription(ctx, stream, name, firstEvent, filters); err != nil { return nil, err } log.L(ctx).Infof("%s subscription: %s", abi.Name, sub.ID) diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index 5dc6a556e3..1b068254db 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -834,48 +834,53 @@ func (cm *contractManager) AddContractListener(ctx context.Context, listener *co listener.Options.FirstEvent = cm.getDefaultContractListenerOptions().FirstEvent } - err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { - // Namespace + Name must be unique - if listener.Name != "" { - if existing, err := cm.database.GetContractListener(ctx, cm.namespace, listener.Name); err != nil { - return err - } else if existing != nil { - return i18n.NewError(ctx, coremsgs.MsgContractListenerNameExists, cm.namespace, listener.Name) - } + // Normalize Event/EventPath + Location to list of Listeners + if len(listener.Filters) == 0 { + listener.Filters = append(listener.Filters, &core.ListenerFilterInput{ + ListenerFilter: core.ListenerFilter{ + Event: listener.Event, + Location: listener.Location, + Interface: listener.Interface, + }, + EventPath: listener.EventPath, + }) + } + + // err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { + + // Namespace + Name must be unique + if listener.Name != "" { + if existing, err := cm.database.GetContractListener(ctx, cm.namespace, listener.Name); err != nil { + return nil, err + } else if existing != nil { + return nil, i18n.NewError(ctx, coremsgs.MsgContractListenerNameExists, cm.namespace, listener.Name) } + } - if listener.Event == nil { - if listener.EventPath == "" || listener.Interface == nil { - return i18n.NewError(ctx, coremsgs.MsgListenerNoEvent) + for _, filter := range listener.Filters { + if filter.Event == nil { + if filter.EventPath == "" || filter.Interface == nil { + return nil, i18n.NewError(ctx, coremsgs.MsgListenerNoEvent) } - // Copy the event definition into the listener - if listener.Event, err = cm.resolveEvent(ctx, listener.Interface, listener.EventPath); err != nil { - return err + // Copy the event definition into the filter + if filter.Event, err = cm.resolveEvent(ctx, filter.Interface, filter.EventPath); err != nil { + return nil, err } } else { - listener.Interface = nil + filter.Interface = nil } - // Namespace + Topic + Location + Signature must be unique - listener.Signature = cm.blockchain.GenerateEventSignature(ctx, &listener.Event.FFIEventDefinition) - fb := database.ContractListenerQueryFactory.NewFilter(ctx) - if existing, _, err := cm.database.GetContractListeners(ctx, cm.namespace, fb.And( - fb.Eq("topic", listener.Topic), - fb.Eq("location", listener.Location.String()), - fb.Eq("signature", listener.Signature), - )); err != nil { - return err - } else if len(existing) > 0 { - return i18n.NewError(ctx, coremsgs.MsgContractListenerExists) + filter.Signature = cm.blockchain.GenerateEventSignature(ctx, &filter.Event.FFIEventDefinition) + if err := cm.validateFFIEvent(ctx, &filter.Event.FFIEventDefinition); err != nil { + return nil, err } - return nil - }) - if err != nil { - return nil, err - } - if err := cm.validateFFIEvent(ctx, &listener.Event.FFIEventDefinition); err != nil { - return nil, err + listener.ContractListener.Filters = append(listener.ContractListener.Filters, &core.ListenerFilter{ + Event: filter.Event, + Location: filter.Location, + Interface: filter.Interface, + Signature: filter.Signature, + }) } if err = cm.blockchain.AddContractListener(ctx, &listener.ContractListener); err != nil { return nil, err diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index 133d849474..b0a0845297 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -870,7 +870,7 @@ func TestAddContractListenerByEventPath(t *testing.T) { result, err := cm.AddContractListener(context.Background(), sub) assert.NoError(t, err) assert.NotNil(t, result.ID) - assert.NotNil(t, result.Event) + assert.NotNil(t, result.Filters[0].Event) mbi.AssertExpectations(t) mdi.AssertExpectations(t) @@ -1230,58 +1230,6 @@ func TestAddContractListenerNameError(t *testing.T) { mdi.AssertExpectations(t) } -func TestAddContractListenerTopicConflict(t *testing.T) { - cm := newTestContractManager() - mbi := cm.blockchain.(*blockchainmocks.Plugin) - mdi := cm.database.(*databasemocks.Plugin) - - sub := &core.ContractListenerInput{ - ContractListener: core.ContractListener{ - Location: fftypes.JSONAnyPtr(fftypes.JSONObject{ - "address": "0x123", - }.String()), - Event: &core.FFISerializedEvent{}, - Topic: "test-topic", - }, - } - - mbi.On("NormalizeContractLocation", context.Background(), blockchain.NormalizeListener, sub.Location).Return(sub.Location, nil) - mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed") - mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return([]*core.ContractListener{{}}, nil, nil) - - _, err := cm.AddContractListener(context.Background(), sub) - assert.Regexp(t, "FF10383", err) - - mbi.AssertExpectations(t) - mdi.AssertExpectations(t) -} - -func TestAddContractListenerTopicError(t *testing.T) { - cm := newTestContractManager() - mbi := cm.blockchain.(*blockchainmocks.Plugin) - mdi := cm.database.(*databasemocks.Plugin) - - sub := &core.ContractListenerInput{ - ContractListener: core.ContractListener{ - Location: fftypes.JSONAnyPtr(fftypes.JSONObject{ - "address": "0x123", - }.String()), - Event: &core.FFISerializedEvent{}, - Topic: "test-topic", - }, - } - - mbi.On("NormalizeContractLocation", context.Background(), blockchain.NormalizeListener, sub.Location).Return(sub.Location, nil) - mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed") - mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop")) - - _, err := cm.AddContractListener(context.Background(), sub) - assert.EqualError(t, err, "pop") - - mbi.AssertExpectations(t) - mdi.AssertExpectations(t) -} - func TestAddContractListenerValidateFail(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) @@ -1426,7 +1374,7 @@ func TestAddContractAPIListener(t *testing.T) { return *l.Interface.ID == *interfaceID && l.Topic == "test-topic" })).Return(nil) mdi.On("InsertContractListener", context.Background(), mock.MatchedBy(func(l *core.ContractListener) bool { - return *l.Interface.ID == *interfaceID && l.Event.Name == "changed" && l.Topic == "test-topic" + return *l.Filters[0].Interface.ID == *interfaceID && l.Filters[0].Event.Name == "changed" && l.Topic == "test-topic" })).Return(nil) _, err := cm.AddContractAPIListener(context.Background(), "simple", "changed", listener) diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go index d7b86913a4..9b88777d89 100644 --- a/internal/coremsgs/en_struct_descriptions.go +++ b/internal/coremsgs/en_struct_descriptions.go @@ -301,22 +301,29 @@ var ( // ContractListener field descriptions ContractListenerID = ffm("ContractListener.id", "The UUID of the smart contract listener") - ContractListenerInterface = ffm("ContractListener.interface", "A reference to an existing FFI, containing pre-registered type information for the event") + ContractListenerInterface = ffm("ContractListener.interface", "Deprecated: Please use 'interface' in the array of 'filters' instead") ContractListenerNamespace = ffm("ContractListener.namespace", "The namespace of the listener, which defines the namespace of all blockchain events detected by this listener") ContractListenerName = ffm("ContractListener.name", "A descriptive name for the listener") ContractListenerBackendID = ffm("ContractListener.backendId", "An ID assigned by the blockchain connector to this listener") - ContractListenerLocation = ffm("ContractListener.location", "A blockchain specific contract identifier. For example an Ethereum contract address, or a Fabric chaincode name and channel") + ContractListenerLocation = ffm("ContractListener.location", "Deprecated: Please use 'location' in the array of 'filters' instead") ContractListenerCreated = ffm("ContractListener.created", "The creation time of the listener") - ContractListenerEvent = ffm("ContractListener.event", "The definition of the event, either provided in-line when creating the listener, or extracted from the referenced FFI") + ContractListenerEvent = ffm("ContractListener.event", "Deprecated: Please use 'event' in the array of 'filters' instead") + ContractListenerFilters = ffm("ContractListener.filters", "A list of filters for the contract listener. Each filter is made up of an Event and an optional Location. Events matching these filters will always be emitted in the order determined by the blockchain.") ContractListenerTopic = ffm("ContractListener.topic", "A topic to set on the FireFly event that is emitted each time a blockchain event is detected from the blockchain. Setting this topic on a number of listeners allows applications to easily subscribe to all events they need") ContractListenerOptions = ffm("ContractListener.options", "Options that control how the listener subscribes to events from the underlying blockchain") - ContractListenerEventPath = ffm("ContractListener.eventPath", "When creating a listener from an existing FFI, this is the pathname of the event on that FFI to be detected by this listener") + ContractListenerEventPath = ffm("ContractListener.eventPath", "Deprecated: Please use 'eventPath' in the array of 'filters' instead") ContractListenerSignature = ffm("ContractListener.signature", "The stringified signature of the event, as computed by the blockchain plugin") ContractListenerState = ffm("ContractListener.state", "This field is provided for the event listener implementation of the blockchain provider to record state, such as checkpoint information") // ContractListenerOptions field descriptions ContractListenerOptionsFirstEvent = ffm("ContractListenerOptions.firstEvent", "A blockchain specific string, such as a block number, to start listening from. The special strings 'oldest' and 'newest' are supported by all blockchain connectors. Default is 'newest'") + ListenerFilterInterface = ffm("ListenerFilter.interface", "A reference to an existing FFI, containing pre-registered type information for the event") + ListenerFilterEvent = ffm("ListenerFilter.event", "The definition of the event, either provided in-line when creating the listener, or extracted from the referenced FFI") + ListenerFilterEventPath = ffm("ListenerFilter.eventPath", "When creating a listener from an existing FFI, this is the pathname of the event on that FFI to be detected by this listener") + ListenerFilterLocation = ffm("ListenerFilter.location", "A blockchain specific contract identifier. For example an Ethereum contract address, or a Fabric chaincode name and channel") + ListenerFilterSignature = ffm("ListenerFilter.signature", "The stringified signature of the event, as computed by the blockchain plugin") + // DIDDocument field descriptions DIDDocumentContext = ffm("DIDDocument.@context", "See https://www.w3.org/TR/did-core/#json-ld") DIDDocumentID = ffm("DIDDocument.id", "See https://www.w3.org/TR/did-core/#did-document-properties") diff --git a/internal/database/sqlcommon/contractlisteners_sql.go b/internal/database/sqlcommon/contractlisteners_sql.go index a4e081dac5..b0e2d43889 100644 --- a/internal/database/sqlcommon/contractlisteners_sql.go +++ b/internal/database/sqlcommon/contractlisteners_sql.go @@ -44,6 +44,7 @@ var ( "topic", "options", "created", + "filters", } contractListenerFilterFieldMap = map[string]string{ "interface": "interface_id", @@ -81,6 +82,7 @@ func (s *SQLCommon) InsertContractListener(ctx context.Context, listener *core.C listener.Topic, listener.Options, listener.Created, + listener.Filters, ), func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionContractListeners, core.ChangeEventTypeCreated, listener.Namespace, listener.ID) @@ -108,10 +110,28 @@ func (s *SQLCommon) contractListenerResult(ctx context.Context, row *sql.Rows) ( &listener.Topic, &listener.Options, &listener.Created, + &listener.Filters, ) if err != nil { return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, contractlistenersTable) } + + // If we have a legacy "event" and "address" stored in the DB, return them as a single item in the "filters" array + // Address is optional + if len(listener.Filters) == 0 && (listener.Event != nil) { + filter := &core.ListenerFilter{ + Event: listener.Event, + Location: listener.Location, + Interface: listener.Interface, + Signature: listener.Signature, + } + listener.Filters = []*core.ListenerFilter{filter} + listener.Event = nil + listener.Location = nil + listener.Interface = nil + listener.Signature = "" + } + return &listener, nil } diff --git a/internal/database/sqlcommon/contractlisteners_sql_test.go b/internal/database/sqlcommon/contractlisteners_sql_test.go index b321a685a7..6902148b13 100644 --- a/internal/database/sqlcommon/contractlisteners_sql_test.go +++ b/internal/database/sqlcommon/contractlisteners_sql_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestContractListenerE2EWithDB(t *testing.T) { +func TestContractListenerLegacyE2EWithDB(t *testing.T) { s, cleanup := newSQLiteTestProvider(t) defer cleanup() ctx := context.Background() @@ -58,6 +58,30 @@ func TestContractListenerE2EWithDB(t *testing.T) { }, } + newSub := &core.ContractListener{ + ID: sub.ID, + Filters: core.ListenerFilters{ + { + Event: &core.FFISerializedEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "event1", + }, + }, + Location: fftypes.JSONAnyPtrBytes(locationJson), + Interface: &fftypes.FFIReference{ + ID: sub.Interface.ID, + }, + }, + }, + Namespace: "ns", + Name: "sub1", + BackendID: "sb-123", + Topic: "topic1", + Options: &core.ContractListenerOptions{ + FirstEvent: "0", + }, + } + s.callbacks.On("UUIDCollectionNSEvent", database.CollectionContractListeners, core.ChangeEventTypeCreated, "ns", sub.ID).Return() s.callbacks.On("UUIDCollectionNSEvent", database.CollectionContractListeners, core.ChangeEventTypeUpdated, "ns", sub.ID).Return() s.callbacks.On("UUIDCollectionNSEvent", database.CollectionContractListeners, core.ChangeEventTypeDeleted, "ns", sub.ID).Return() @@ -65,7 +89,8 @@ func TestContractListenerE2EWithDB(t *testing.T) { err := s.InsertContractListener(ctx, sub) assert.NotNil(t, sub.Created) assert.NoError(t, err) - subJson, _ := json.Marshal(&sub) + newSub.Created = sub.Created + newSubJson, _ := json.Marshal(&newSub) // Query back the listener (by query filter) fb := database.ContractListenerQueryFactory.NewFilter(ctx) @@ -77,7 +102,7 @@ func TestContractListenerE2EWithDB(t *testing.T) { assert.Equal(t, 1, len(subs)) assert.Equal(t, int64(1), *res.TotalCount) subReadJson, _ := json.Marshal(subs[0]) - assert.Equal(t, string(subJson), string(subReadJson)) + assert.Equal(t, string(newSubJson), string(subReadJson)) // Update by backend ID err = s.UpdateContractListener(ctx, "ns", sub.ID, database.ContractListenerQueryFactory.NewUpdate(ctx).Set("backendid", "sb-234")) @@ -87,21 +112,22 @@ func TestContractListenerE2EWithDB(t *testing.T) { subRead, err := s.GetContractListener(ctx, "ns", "sub1") assert.NoError(t, err) sub.BackendID = "sb-234" - subJson, _ = json.Marshal(&sub) + newSub.BackendID = "sb-234" + newSubJson, _ = json.Marshal(&newSub) subReadJson, _ = json.Marshal(subRead) - assert.Equal(t, string(subJson), string(subReadJson)) + assert.Equal(t, string(newSubJson), string(subReadJson)) // Query back the listener (by ID) subRead, err = s.GetContractListenerByID(ctx, "ns", sub.ID) assert.NoError(t, err) subReadJson, _ = json.Marshal(subRead) - assert.Equal(t, string(subJson), string(subReadJson)) + assert.Equal(t, string(newSubJson), string(subReadJson)) // Query back the listener (by protocol ID) subRead, err = s.GetContractListenerByBackendID(ctx, "ns", sub.BackendID) assert.NoError(t, err) subReadJson, _ = json.Marshal(subRead) - assert.Equal(t, string(subJson), string(subReadJson)) + assert.Equal(t, string(newSubJson), string(subReadJson)) // Query back the listener (by query filter) filter = fb.And( @@ -112,7 +138,7 @@ func TestContractListenerE2EWithDB(t *testing.T) { assert.Equal(t, 1, len(subs)) assert.Equal(t, int64(1), *res.TotalCount) subReadJson, _ = json.Marshal(subs[0]) - assert.Equal(t, string(subJson), string(subReadJson)) + assert.Equal(t, string(newSubJson), string(subReadJson)) // Test delete, and refind no return err = s.DeleteContractListenerByID(ctx, "ns", sub.ID) @@ -211,7 +237,7 @@ func TestContractListenerDeleteFail(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin() mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows(contractListenerColumns).AddRow( - fftypes.NewUUID(), nil, []byte("{}"), "ns1", "sub1", "123", "{}", "sig", "topic1", nil, fftypes.Now()), + fftypes.NewUUID(), nil, []byte("{}"), "ns1", "sub1", "123", "{}", "sig", "topic1", nil, fftypes.Now(), "[]"), ) mock.ExpectExec("DELETE .*").WillReturnError(fmt.Errorf("pop")) err := s.DeleteContractListenerByID(context.Background(), "ns", fftypes.NewUUID()) diff --git a/pkg/core/contract_listener.go b/pkg/core/contract_listener.go index fb3fb1241b..6db9d29317 100644 --- a/pkg/core/contract_listener.go +++ b/pkg/core/contract_listener.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2023 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -31,10 +31,11 @@ type ContractListener struct { Namespace string `ffstruct:"ContractListener" json:"namespace,omitempty" ffexcludeinput:"true"` Name string `ffstruct:"ContractListener" json:"name,omitempty"` BackendID string `ffstruct:"ContractListener" json:"backendId,omitempty" ffexcludeinput:"true"` - Location *fftypes.JSONAny `ffstruct:"ContractListener" json:"location,omitempty"` + Location *fftypes.JSONAny `ffstruct:"ContractListener" json:"location,omitempty" ffexcludeinput:"true"` Created *fftypes.FFTime `ffstruct:"ContractListener" json:"created,omitempty" ffexcludeinput:"true"` - Event *FFISerializedEvent `ffstruct:"ContractListener" json:"event,omitempty" ffexcludeinput:"postContractAPIListeners"` - Signature string `ffstruct:"ContractListener" json:"signature" ffexcludeinput:"true"` + Event *FFISerializedEvent `ffstruct:"ContractListener" json:"event,omitempty" ffexcludeinput:"true"` + Filters ListenerFilters `ffstruct:"ContractListener" json:"filters,omitempty" ffexcludeinput:"postContractAPIListeners"` + Signature string `ffstruct:"ContractListener" json:"signature,omitempty" ffexcludeinput:"true"` Topic string `ffstruct:"ContractListener" json:"topic,omitempty"` Options *ContractListenerOptions `ffstruct:"ContractListener" json:"options,omitempty"` } @@ -53,9 +54,25 @@ type ListenerStatusError struct { type ContractListenerInput struct { ContractListener - EventPath string `ffstruct:"ContractListener" json:"eventPath,omitempty"` + Filters ListenerFiltersInput `ffstruct:"ContractListener" json:"filters,omitempty" ffexcludeinput:"postContractAPIListeners"` + EventPath string `ffstruct:"ContractListener" json:"eventPath,omitempty" ffexcludeinput:"true"` } +type ListenerFilter struct { + Event *FFISerializedEvent `ffstruct:"ListenerFilter" json:"event,omitempty"` + Location *fftypes.JSONAny `ffstruct:"ListenerFilter" json:"location,omitempty"` + Interface *fftypes.FFIReference `ffstruct:"ListenerFilter" json:"interface,omitempty" ffexcludeinput:"postContractAPIListeners"` + Signature string `ffstruct:"ListenerFilter" json:"signature" ffexcludeinput:"true"` +} + +type ListenerFilterInput struct { + ListenerFilter + EventPath string `ffstruct:"ListenerFilter" json:"eventPath,omitempty"` +} + +type ListenerFilters []*ListenerFilter +type ListenerFiltersInput []*ListenerFilterInput + type FFISerializedEvent struct { fftypes.FFIEventDefinition } @@ -99,3 +116,23 @@ func (o ContractListenerOptions) Value() (driver.Value, error) { bytes, _ := json.Marshal(o) return bytes, nil } + +// Scan implements sql.Scanner +func (lf *ListenerFilters) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + lf = nil + return nil + case string: + return json.Unmarshal([]byte(src), &lf) + case []byte: + return json.Unmarshal(src, &lf) + default: + return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, lf) + } +} + +func (lf ListenerFilters) Value() (driver.Value, error) { + bytes, _ := json.Marshal(lf) + return bytes, nil +} diff --git a/pkg/core/contract_listener_test.go b/pkg/core/contract_listener_test.go index fbbaf88ca7..cb5566b25e 100644 --- a/pkg/core/contract_listener_test.go +++ b/pkg/core/contract_listener_test.go @@ -96,3 +96,37 @@ func TestContractListenerOptionsValue(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"firstEvent":"newest"}`, string(val.([]byte))) } + +func TestListenerFiltersScan(t *testing.T) { + filters := ListenerFilters{} + err := filters.Scan([]byte(`[{"event":{"name":"event1","description":"asuperevent","params":[{"name":"value","schema":{"type":"integer","details":{"type":"uint256","internalType":"uint256"}}}]},"location":{"address":"0x1234"}}]`)) + assert.NoError(t, err) +} + +func TestListenerFiltersScanNil(t *testing.T) { + params := &ListenerFilters{} + err := params.Scan(nil) + assert.Nil(t, err) +} + +func TestListenerFiltersScanString(t *testing.T) { + params := &ListenerFilters{} + err := params.Scan(`[{"event":{"name":"event1","description":"asuperevent","params":[{"name":"value","schema":{"type":"integer","details":{"type":"uint256","internalType":"uint256"}}}]},"location":{"address":"0x1234"},"signature":"changed"}]`) + assert.NoError(t, err) +} + +func TestListenerFiltersScanError(t *testing.T) { + params := &ListenerFilters{} + err := params.Scan(map[string]interface{}{"this is": "not a supported serialization of a FFISerializedEvent"}) + assert.Regexp(t, "FF00105", err) +} + +func TestListenerFiltersValue(t *testing.T) { + filtersStr := `[{"event":{"name":"event1","description":"asuperevent","params":[{"name":"value","schema":{"type":"integer","details":{"type":"uint256","internalType":"uint256"}}}]},"location":{"address":"0x1234"},"signature":"changed"}]` + filters := ListenerFilters{} + err := filters.Scan(filtersStr) + assert.NoError(t, err) + value, err := filters.Value() + assert.NoError(t, err) + assert.Equal(t, filtersStr, string(value.([]byte))) +}