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`.
* `notification_from_execution`
Filter: `contract` field containing string with hex-encoded Uint160 (LE
representation).
representation) and/or `name` field containing string with execution
notification name.
* `transaction_executed`
Filter: `state` field containing `HALT` or `FAULT` string for successful
and failed executions respectively.
@ -276,10 +277,10 @@ Example:
### `notification_from_execution` notification
Contains three parameters: container hash (hex-encoded LE Uint256 in a
string), contract script hash (hex-encoded LE Uint160 in a string) and stack
item (encoded the same way as `state` field contents for notifications from
`getapplicationlog` response).
Contains three parameters: contract script hash (hex-encoded LE Uint160
in a string), notification name and stack item (encoded the same way as
`state` field contents for notifications from `getapplicationlog`
response).
Example:
@ -319,7 +320,8 @@ Example:
],
"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
// filtered by contract's hash (that emits notifications), nil value puts no such
// 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")
if contract != nil {
params.Values = append(params.Values, request.NotificationFilter{Contract: *contract})
if contract != nil || name != nil {
params.Values = append(params.Values, request.NotificationFilter{Contract: contract, Name: name})
}
return c.performSubscription(params)
}

View file

@ -32,7 +32,7 @@ func TestWSClientSubscription(t *testing.T) {
return wsc.SubscribeForNewTransactions(nil, nil)
},
"notifications": func(wsc *WSClient) (string, error) {
return wsc.SubscribeForExecutionNotifications(nil)
return wsc.SubscribeForExecutionNotifications(nil, nil)
},
"executions": func(wsc *WSClient) (string, error) {
return wsc.SubscribeForTransactionExecutions(nil)
@ -240,10 +240,10 @@ func TestWSFilteredSubscriptions(t *testing.T) {
require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
},
},
{"notifications",
{"notifications contract hash",
func(t *testing.T, wsc *WSClient) {
contract := util.Uint160{1, 2, 3, 4, 5}
_, err := wsc.SubscribeForExecutionNotifications(&contract)
_, err := wsc.SubscribeForExecutionNotifications(&contract, nil)
require.NoError(t, err)
},
func(t *testing.T, p *request.Params) {
@ -252,7 +252,41 @@ func TestWSFilteredSubscriptions(t *testing.T) {
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, 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",

View file

@ -44,9 +44,10 @@ type (
}
// NotificationFilter is a wrapper structure representing filter used for
// notifications generated during transaction execution. Notifications can
// only be filtered by contract hash.
// be filtered by contract hash and by name.
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
// events. It allows to choose failing or successful transactions based

View file

@ -21,11 +21,14 @@ func TestParam_UnmarshalJSON(t *testing.T) {
{"signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "signer": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"name": "my_pretty_notification"},
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "name":"my_pretty_notification"},
{"state": "HALT"},
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"},
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}]]`
contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554")
require.NoError(t, err)
name := "my_pretty_notification"
accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
expected := Params{
@ -86,7 +89,15 @@ func TestParam_UnmarshalJSON(t *testing.T) {
},
{
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,

View file

@ -73,7 +73,9 @@ func (f *feed) Matches(r *response.Notification) bool {
case response.NotificationEventID:
filt := f.filter.(request.NotificationFilter)
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:
filt := f.filter.(request.ExecutionFilter)
applog := r.Payload[0].(result.ApplicationLog)

View file

@ -171,7 +171,7 @@ func TestFilteredSubscriptions(t *testing.T) {
require.Equal(t, "0x"+goodSender.StringLE(), signer0acc)
},
},
"notification matching": {
"notification matching contract hash": {
params: `["notification_from_execution", {"contract":"` + testContractHash + `"}]`,
check: func(t *testing.T, resp *response.Notification) {
rmap := resp.Payload[0].(map[string]interface{})
@ -180,6 +180,26 @@ func TestFilteredSubscriptions(t *testing.T) {
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": {
params: `["transaction_executed", {"state":"HALT"}]`,
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}`,
"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}`,
"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 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", {"state": "STOP"}], "id": 1}`,
}