forked from TrueCloudLab/neoneo-go
Merge pull request #1266 from nspcc-dev/notifications/filter_by_name
rpc: filter subscriptions' notifications by name
This commit is contained in:
commit
4bbe863904
7 changed files with 90 additions and 19 deletions
|
@ -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",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}`,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue