Merge pull request #1266 from nspcc-dev/notifications/filter_by_name

rpc: filter subscriptions' notifications by name
This commit is contained in:
Roman Khimov 2020-08-05 10:00:15 +03:00 committed by GitHub
commit 4bbe863904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 19 deletions

View file

@ -61,7 +61,8 @@ Recognized stream names:
format for one of transaction's `Signers`. format for one of transaction's `Signers`.
* `notification_from_execution` * `notification_from_execution`
Filter: `contract` field containing string with hex-encoded Uint160 (LE Filter: `contract` field containing string with hex-encoded Uint160 (LE
representation). representation) and/or `name` field containing string with execution
notification name.
* `transaction_executed` * `transaction_executed`
Filter: `state` field containing `HALT` or `FAULT` string for successful Filter: `state` field containing `HALT` or `FAULT` string for successful
and failed executions respectively. and failed executions respectively.
@ -276,10 +277,10 @@ Example:
### `notification_from_execution` notification ### `notification_from_execution` notification
Contains three parameters: container hash (hex-encoded LE Uint256 in a Contains three parameters: contract script hash (hex-encoded LE Uint160
string), contract script hash (hex-encoded LE Uint160 in a string) and stack in a string), notification name and stack item (encoded the same way as
item (encoded the same way as `state` field contents for notifications from `state` field contents for notifications from `getapplicationlog`
`getapplicationlog` response). response).
Example: Example:
@ -319,7 +320,8 @@ Example:
], ],
"type" : "Array" "type" : "Array"
}, },
"contract" : "0x1b4357bff5a01bdf2a6581247cf9ed1e24629176" "contract" : "0x1b4357bff5a01bdf2a6581247cf9ed1e24629176",
"name" : "transfer",
} }
] ]
} }

View file

@ -269,10 +269,10 @@ func (c *WSClient) SubscribeForNewTransactions(sender *util.Uint160, signer *uti
// generated during transaction execution to this instance of client. It can be // generated during transaction execution to this instance of client. It can be
// filtered by contract's hash (that emits notifications), nil value puts no such // filtered by contract's hash (that emits notifications), nil value puts no such
// restrictions. // restrictions.
func (c *WSClient) SubscribeForExecutionNotifications(contract *util.Uint160) (string, error) { func (c *WSClient) SubscribeForExecutionNotifications(contract *util.Uint160, name *string) (string, error) {
params := request.NewRawParams("notification_from_execution") params := request.NewRawParams("notification_from_execution")
if contract != nil { if contract != nil || name != nil {
params.Values = append(params.Values, request.NotificationFilter{Contract: *contract}) params.Values = append(params.Values, request.NotificationFilter{Contract: contract, Name: name})
} }
return c.performSubscription(params) return c.performSubscription(params)
} }

View file

@ -32,7 +32,7 @@ func TestWSClientSubscription(t *testing.T) {
return wsc.SubscribeForNewTransactions(nil, nil) return wsc.SubscribeForNewTransactions(nil, nil)
}, },
"notifications": func(wsc *WSClient) (string, error) { "notifications": func(wsc *WSClient) (string, error) {
return wsc.SubscribeForExecutionNotifications(nil) return wsc.SubscribeForExecutionNotifications(nil, nil)
}, },
"executions": func(wsc *WSClient) (string, error) { "executions": func(wsc *WSClient) (string, error) {
return wsc.SubscribeForTransactionExecutions(nil) return wsc.SubscribeForTransactionExecutions(nil)
@ -240,10 +240,10 @@ func TestWSFilteredSubscriptions(t *testing.T) {
require.Equal(t, util.Uint160{0, 42}, *filt.Signer) require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
}, },
}, },
{"notifications", {"notifications contract hash",
func(t *testing.T, wsc *WSClient) { func(t *testing.T, wsc *WSClient) {
contract := util.Uint160{1, 2, 3, 4, 5} contract := util.Uint160{1, 2, 3, 4, 5}
_, err := wsc.SubscribeForExecutionNotifications(&contract) _, err := wsc.SubscribeForExecutionNotifications(&contract, nil)
require.NoError(t, err) require.NoError(t, err)
}, },
func(t *testing.T, p *request.Params) { func(t *testing.T, p *request.Params) {
@ -252,7 +252,41 @@ func TestWSFilteredSubscriptions(t *testing.T) {
require.Equal(t, request.NotificationFilterT, param.Type) require.Equal(t, request.NotificationFilterT, param.Type)
filt, ok := param.Value.(request.NotificationFilter) filt, ok := param.Value.(request.NotificationFilter)
require.Equal(t, true, ok) require.Equal(t, true, ok)
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, filt.Contract) require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract)
require.Nil(t, filt.Name)
},
},
{"notifications name",
func(t *testing.T, wsc *WSClient) {
name := "my_pretty_notification"
_, err := wsc.SubscribeForExecutionNotifications(nil, &name)
require.NoError(t, err)
},
func(t *testing.T, p *request.Params) {
param := p.Value(1)
require.NotNil(t, param)
require.Equal(t, request.NotificationFilterT, param.Type)
filt, ok := param.Value.(request.NotificationFilter)
require.Equal(t, true, ok)
require.Equal(t, "my_pretty_notification", *filt.Name)
require.Nil(t, filt.Contract)
},
},
{"notifications contract hash and name",
func(t *testing.T, wsc *WSClient) {
contract := util.Uint160{1, 2, 3, 4, 5}
name := "my_pretty_notification"
_, err := wsc.SubscribeForExecutionNotifications(&contract, &name)
require.NoError(t, err)
},
func(t *testing.T, p *request.Params) {
param := p.Value(1)
require.NotNil(t, param)
require.Equal(t, request.NotificationFilterT, param.Type)
filt, ok := param.Value.(request.NotificationFilter)
require.Equal(t, true, ok)
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract)
require.Equal(t, "my_pretty_notification", *filt.Name)
}, },
}, },
{"executions", {"executions",

View file

@ -44,9 +44,10 @@ type (
} }
// NotificationFilter is a wrapper structure representing filter used for // NotificationFilter is a wrapper structure representing filter used for
// notifications generated during transaction execution. Notifications can // notifications generated during transaction execution. Notifications can
// only be filtered by contract hash. // be filtered by contract hash and by name.
NotificationFilter struct { NotificationFilter struct {
Contract util.Uint160 `json:"contract"` Contract *util.Uint160 `json:"contract,omitempty"`
Name *string `json:"name,omitempty"`
} }
// ExecutionFilter is a wrapper structure used for transaction execution // ExecutionFilter is a wrapper structure used for transaction execution
// events. It allows to choose failing or successful transactions based // events. It allows to choose failing or successful transactions based

View file

@ -21,11 +21,14 @@ func TestParam_UnmarshalJSON(t *testing.T) {
{"signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"}, {"signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"}, {"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"}, {"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"name": "my_pretty_notification"},
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "name":"my_pretty_notification"},
{"state": "HALT"}, {"state": "HALT"},
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}, {"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"},
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]` [{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]`
contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554") contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554")
require.NoError(t, err) require.NoError(t, err)
name := "my_pretty_notification"
accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569") accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err) require.NoError(t, err)
expected := Params{ expected := Params{
@ -86,7 +89,15 @@ func TestParam_UnmarshalJSON(t *testing.T) {
}, },
{ {
Type: NotificationFilterT, Type: NotificationFilterT,
Value: NotificationFilter{Contract: contr}, Value: NotificationFilter{Contract: &contr},
},
{
Type: NotificationFilterT,
Value: NotificationFilter{Name: &name},
},
{
Type: NotificationFilterT,
Value: NotificationFilter{Contract: &contr, Name: &name},
}, },
{ {
Type: ExecutionFilterT, Type: ExecutionFilterT,

View file

@ -73,7 +73,9 @@ func (f *feed) Matches(r *response.Notification) bool {
case response.NotificationEventID: case response.NotificationEventID:
filt := f.filter.(request.NotificationFilter) filt := f.filter.(request.NotificationFilter)
notification := r.Payload[0].(result.NotificationEvent) notification := r.Payload[0].(result.NotificationEvent)
return notification.Contract.Equals(filt.Contract) hashOk := filt.Contract == nil || notification.Contract.Equals(*filt.Contract)
nameOk := filt.Name == nil || notification.Name == *filt.Name
return hashOk && nameOk
case response.ExecutionEventID: case response.ExecutionEventID:
filt := f.filter.(request.ExecutionFilter) filt := f.filter.(request.ExecutionFilter)
applog := r.Payload[0].(result.ApplicationLog) applog := r.Payload[0].(result.ApplicationLog)

View file

@ -171,7 +171,7 @@ func TestFilteredSubscriptions(t *testing.T) {
require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) require.Equal(t, "0x"+goodSender.StringLE(), signer0acc)
}, },
}, },
"notification matching": { "notification matching contract hash": {
params: `["notification_from_execution", {"contract":"` + testContractHash + `"}]`, params: `["notification_from_execution", {"contract":"` + testContractHash + `"}]`,
check: func(t *testing.T, resp *response.Notification) { check: func(t *testing.T, resp *response.Notification) {
rmap := resp.Payload[0].(map[string]interface{}) rmap := resp.Payload[0].(map[string]interface{})
@ -180,6 +180,26 @@ func TestFilteredSubscriptions(t *testing.T) {
require.Equal(t, "0x"+testContractHash, c) require.Equal(t, "0x"+testContractHash, c)
}, },
}, },
"notification matching name": {
params: `["notification_from_execution", {"name":"my_pretty_notification"}]`,
check: func(t *testing.T, resp *response.Notification) {
rmap := resp.Payload[0].(map[string]interface{})
require.Equal(t, response.NotificationEventID, resp.Event)
n := rmap["name"].(string)
require.Equal(t, "my_pretty_notification", n)
},
},
"notification matching contract hash and name": {
params: `["notification_from_execution", {"contract":"` + testContractHash + `", "name":"my_pretty_notification"}]`,
check: func(t *testing.T, resp *response.Notification) {
rmap := resp.Payload[0].(map[string]interface{})
require.Equal(t, response.NotificationEventID, resp.Event)
c := rmap["contract"].(string)
require.Equal(t, "0x"+testContractHash, c)
n := rmap["name"].(string)
require.Equal(t, "my_pretty_notification", n)
},
},
"execution matching": { "execution matching": {
params: `["transaction_executed", {"state":"HALT"}]`, params: `["transaction_executed", {"state":"HALT"}]`,
check: func(t *testing.T, resp *response.Notification) { check: func(t *testing.T, resp *response.Notification) {
@ -327,7 +347,8 @@ func TestBadSubUnsub(t *testing.T) {
"block invalid filter": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["block_added", 1], "id": 1}`, "block invalid filter": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["block_added", 1], "id": 1}`,
"tx filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", 1], "id": 1}`, "tx filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", 1], "id": 1}`,
"tx filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", {"state": "HALT"}], "id": 1}`, "tx filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", {"state": "HALT"}], "id": 1}`,
"notification filter": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["notification_from_execution", "contract"], "id": 1}`, "notification filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["notification_from_execution", "contract"], "id": 1}`,
"notification filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["notification_from_execution", "name"], "id": 1}`,
"execution filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", "FAULT"], "id": 1}`, "execution filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", "FAULT"], "id": 1}`,
"execution filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", {"state": "STOP"}], "id": 1}`, "execution filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", {"state": "STOP"}], "id": 1}`,
} }