rpc: add an ability to filter out NotaryRequestEvents

Add new filter NotaryRequestFilter, support for filtering
NotaryRequestEvents by mempoolevent.Type
(added or removed).

Closes #2425.

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
This commit is contained in:
Ekaterina Pavlova 2023-12-09 11:37:25 +03:00
parent afca64f164
commit 3fd48a743e
8 changed files with 139 additions and 14 deletions

View file

@ -84,7 +84,8 @@ Recognized stream names:
* `notary_request_event` * `notary_request_event`
Filter: `sender` field containing a string with hex-encoded Uint160 (LE Filter: `sender` field containing a string with hex-encoded Uint160 (LE
representation) for notary request's `Sender` and/or `signer` in the same representation) for notary request's `Sender` and/or `signer` in the same
format for one of main transaction's `Signers`. format for one of main transaction's `Signers`. `type` field containing a
string with event type, which could be one of "added" or "removed".
Response: returns subscription ID (string) as a result. This ID can be used to Response: returns subscription ID (string) as a result. This ID can be used to
cancel this subscription and has no meaning other than that. cancel this subscription and has no meaning other than that.

View file

@ -1,6 +1,7 @@
package neorpc package neorpc
import ( import (
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -38,6 +39,14 @@ type (
State *string `json:"state,omitempty"` State *string `json:"state,omitempty"`
Container *util.Uint256 `json:"container,omitempty"` Container *util.Uint256 `json:"container,omitempty"`
} }
// NotaryRequestFilter is a wrapper structure used for notary request events.
// It allows to choose notary request events with the specified request sender,
// main transaction signer and/or type. nil value treated as missing filter.
NotaryRequestFilter struct {
Sender *util.Uint160 `json:"sender,omitempty"`
Signer *util.Uint160 `json:"signer,omitempty"`
Type *mempoolevent.Type `json:"type,omitempty"`
}
) )
// Copy creates a deep copy of the BlockFilter. It handles nil BlockFilter correctly. // Copy creates a deep copy of the BlockFilter. It handles nil BlockFilter correctly.
@ -111,3 +120,24 @@ func (f *ExecutionFilter) Copy() *ExecutionFilter {
} }
return res return res
} }
// Copy creates a deep copy of the NotaryRequestFilter. It handles nil NotaryRequestFilter correctly.
func (f *NotaryRequestFilter) Copy() *NotaryRequestFilter {
if f == nil {
return nil
}
var res = new(NotaryRequestFilter)
if f.Sender != nil {
res.Sender = new(util.Uint160)
*res.Sender = *f.Sender
}
if f.Signer != nil {
res.Signer = new(util.Uint160)
*res.Signer = *f.Signer
}
if f.Type != nil {
res.Type = new(mempoolevent.Type)
*res.Type = *f.Type
}
return res
}

View file

@ -69,8 +69,9 @@ func Matches(f Comparator, r Container) bool {
containerOK := filt.Container == nil || applog.Container.Equals(*filt.Container) containerOK := filt.Container == nil || applog.Container.Equals(*filt.Container)
return stateOK && containerOK return stateOK && containerOK
case neorpc.NotaryRequestEventID: case neorpc.NotaryRequestEventID:
filt := filter.(neorpc.TxFilter) filt := filter.(neorpc.NotaryRequestFilter)
req := r.EventPayload().(*result.NotaryRequestEvent) req := r.EventPayload().(*result.NotaryRequestEvent)
typeOk := filt.Type == nil || req.Type == *filt.Type
senderOk := filt.Sender == nil || req.NotaryRequest.FallbackTransaction.Signers[1].Account == *filt.Sender senderOk := filt.Sender == nil || req.NotaryRequest.FallbackTransaction.Signers[1].Account == *filt.Sender
signerOK := true signerOK := true
if filt.Signer != nil { if filt.Signer != nil {
@ -82,7 +83,7 @@ func Matches(f Comparator, r Container) bool {
} }
} }
} }
return senderOk && signerOK return senderOk && signerOK && typeOk
} }
return false return false
} }

View file

@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc" "github.com/nspcc-dev/neo-go/pkg/neorpc"
@ -47,11 +48,13 @@ func TestMatches(t *testing.T) {
sender := util.Uint160{1, 2, 3} sender := util.Uint160{1, 2, 3}
signer := util.Uint160{4, 5, 6} signer := util.Uint160{4, 5, 6}
contract := util.Uint160{7, 8, 9} contract := util.Uint160{7, 8, 9}
notaryType := mempoolevent.TransactionAdded
badUint160 := util.Uint160{9, 9, 9} badUint160 := util.Uint160{9, 9, 9}
cnt := util.Uint256{1, 2, 3} cnt := util.Uint256{1, 2, 3}
badUint256 := util.Uint256{9, 9, 9} badUint256 := util.Uint256{9, 9, 9}
name := "ntf name" name := "ntf name"
badName := "bad name" badName := "bad name"
badType := mempoolevent.TransactionRemoved
bContainer := testContainer{ bContainer := testContainer{
id: neorpc.BlockEventID, id: neorpc.BlockEventID,
pld: &block.Block{ pld: &block.Block{
@ -76,6 +79,7 @@ func TestMatches(t *testing.T) {
ntrContainer := testContainer{ ntrContainer := testContainer{
id: neorpc.NotaryRequestEventID, id: neorpc.NotaryRequestEventID,
pld: &result.NotaryRequestEvent{ pld: &result.NotaryRequestEvent{
Type: notaryType,
NotaryRequest: &payload.P2PNotaryRequest{ NotaryRequest: &payload.P2PNotaryRequest{
MainTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: signer}}}, MainTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: signer}}},
FallbackTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: util.Uint160{}}, {Account: sender}}}, FallbackTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: util.Uint160{}}, {Account: sender}}},
@ -254,7 +258,7 @@ func TestMatches(t *testing.T) {
name: "notary request, sender mismatch", name: "notary request, sender mismatch",
comparator: testComparator{ comparator: testComparator{
id: neorpc.NotaryRequestEventID, id: neorpc.NotaryRequestEventID,
filter: neorpc.TxFilter{Sender: &badUint160}, filter: neorpc.NotaryRequestFilter{Sender: &badUint160},
}, },
container: ntrContainer, container: ntrContainer,
expected: false, expected: false,
@ -263,7 +267,16 @@ func TestMatches(t *testing.T) {
name: "notary request, signer mismatch", name: "notary request, signer mismatch",
comparator: testComparator{ comparator: testComparator{
id: neorpc.NotaryRequestEventID, id: neorpc.NotaryRequestEventID,
filter: neorpc.TxFilter{Signer: &badUint160}, filter: neorpc.NotaryRequestFilter{Signer: &badUint160},
},
container: ntrContainer,
expected: false,
},
{
name: "notary request, type mismatch",
comparator: testComparator{
id: neorpc.NotaryRequestEventID,
filter: neorpc.NotaryRequestFilter{Type: &badType},
}, },
container: ntrContainer, container: ntrContainer,
expected: false, expected: false,
@ -272,7 +285,7 @@ func TestMatches(t *testing.T) {
name: "notary request, filter match", name: "notary request, filter match",
comparator: testComparator{ comparator: testComparator{
id: neorpc.NotaryRequestEventID, id: neorpc.NotaryRequestEventID,
filter: neorpc.TxFilter{Sender: &sender, Signer: &signer}, filter: neorpc.NotaryRequestFilter{Sender: &sender, Signer: &signer, Type: &notaryType},
}, },
container: ntrContainer, container: ntrContainer,
expected: true, expected: true,

View file

@ -305,7 +305,7 @@ func (r *executionReceiver) Close() {
// notaryRequestReceiver stores information about notary requests subscriber. // notaryRequestReceiver stores information about notary requests subscriber.
type notaryRequestReceiver struct { type notaryRequestReceiver struct {
filter *neorpc.TxFilter filter *neorpc.NotaryRequestFilter
ch chan<- *result.NotaryRequestEvent ch chan<- *result.NotaryRequestEvent
} }
@ -811,11 +811,13 @@ func (c *WSClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr chan<- *s
} }
// ReceiveNotaryRequests registers provided channel as a receiver for notary request // ReceiveNotaryRequests registers provided channel as a receiver for notary request
// payload addition or removal events. Events can be filtered by the given TxFilter // payload addition or removal events. Events can be filtered by the given NotaryRequestFilter
// where sender corresponds to notary request sender (the second fallback transaction // where sender corresponds to notary request sender (the second fallback transaction
// signer) and signer corresponds to main transaction signers. nil value doesn't add // signer), signer corresponds to main transaction signers and type corresponds to the
// any filter. See WSClient comments for generic Receive* behaviour details. // [mempoolevent.Type] and denotes whether notary request was added to or removed from
func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.TxFilter, rcvr chan<- *result.NotaryRequestEvent) (string, error) { // the notary request pool. nil value doesn't add any filter. See WSClient comments
// for generic Receive* behaviour details.
func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.NotaryRequestFilter, rcvr chan<- *result.NotaryRequestEvent) (string, error) {
if rcvr == nil { if rcvr == nil {
return "", ErrNilNotificationReceiver return "", ErrNilNotificationReceiver
} }

View file

@ -17,6 +17,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc" "github.com/nspcc-dev/neo-go/pkg/neorpc"
@ -574,6 +575,71 @@ func TestWSFilteredSubscriptions(t *testing.T) {
require.Equal(t, util.Uint256{1, 2, 3}, *filt.Container) require.Equal(t, util.Uint256{1, 2, 3}, *filt.Container)
}, },
}, },
{
"notary request sender",
func(t *testing.T, wsc *WSClient) {
sender := util.Uint160{1, 2, 3, 4, 5}
_, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Sender: &sender}, make(chan *result.NotaryRequestEvent))
require.NoError(t, err)
},
func(t *testing.T, p *params.Params) {
param := p.Value(1)
filt := new(neorpc.NotaryRequestFilter)
require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender)
require.Nil(t, filt.Signer)
require.Nil(t, filt.Type)
},
},
{
"notary request signer",
func(t *testing.T, wsc *WSClient) {
signer := util.Uint160{0, 42}
_, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Signer: &signer}, make(chan *result.NotaryRequestEvent))
require.NoError(t, err)
},
func(t *testing.T, p *params.Params) {
param := p.Value(1)
filt := new(neorpc.NotaryRequestFilter)
require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Nil(t, filt.Sender)
require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
require.Nil(t, filt.Type)
},
},
{
"notary request type",
func(t *testing.T, wsc *WSClient) {
mempoolType := mempoolevent.TransactionAdded
_, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Type: &mempoolType}, make(chan *result.NotaryRequestEvent))
require.NoError(t, err)
},
func(t *testing.T, p *params.Params) {
param := p.Value(1)
filt := new(neorpc.NotaryRequestFilter)
require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, mempoolevent.TransactionAdded, *filt.Type)
require.Nil(t, filt.Sender)
require.Nil(t, filt.Signer)
},
},
{"notary request sender, signer and type",
func(t *testing.T, wsc *WSClient) {
sender := util.Uint160{1, 2, 3, 4, 5}
signer := util.Uint160{0, 42}
mempoolType := mempoolevent.TransactionAdded
_, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Type: &mempoolType, Signer: &signer, Sender: &sender}, make(chan *result.NotaryRequestEvent))
require.NoError(t, err)
},
func(t *testing.T, p *params.Params) {
param := p.Value(1)
filt := new(neorpc.NotaryRequestFilter)
require.NoError(t, json.Unmarshal(param.RawMessage, filt))
require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender)
require.Equal(t, util.Uint160{0, 42}, *filt.Signer)
require.Equal(t, mempoolevent.TransactionAdded, *filt.Type)
},
},
} }
for _, c := range cases { for _, c := range cases {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {

View file

@ -2734,10 +2734,14 @@ func (s *Server) subscribe(reqParams params.Params, sub *subscriber) (any, *neor
flt := new(neorpc.BlockFilter) flt := new(neorpc.BlockFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
filter = *flt filter = *flt
case neorpc.TransactionEventID, neorpc.NotaryRequestEventID: case neorpc.TransactionEventID:
flt := new(neorpc.TxFilter) flt := new(neorpc.TxFilter)
err = jd.Decode(flt) err = jd.Decode(flt)
filter = *flt filter = *flt
case neorpc.NotaryRequestEventID:
flt := new(neorpc.NotaryRequestFilter)
err = jd.Decode(flt)
filter = *flt
case neorpc.NotificationEventID: case neorpc.NotificationEventID:
flt := new(neorpc.NotificationFilter) flt := new(neorpc.NotificationFilter)
err = jd.Decode(flt) err = jd.Decode(flt)

View file

@ -354,8 +354,16 @@ func TestFilteredNotaryRequestSubscriptions(t *testing.T) {
require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) require.Equal(t, "0x"+goodSender.StringLE(), signer0acc)
}, },
}, },
"matching sender and signer": { "matching type": {
params: `["notary_request_event", {"sender":"` + goodSender.StringLE() + `", "signer":"` + goodSender.StringLE() + `"}]`, params: `["notary_request_event", {"type":"added"}]`,
check: func(t *testing.T, resp *neorpc.Notification) {
require.Equal(t, neorpc.NotaryRequestEventID, resp.Event)
rmap := resp.Payload[0].(map[string]any)
require.Equal(t, "added", rmap["type"].(string))
},
},
"matching sender, signer and type": {
params: `["notary_request_event", {"sender":"` + goodSender.StringLE() + `", "signer":"` + goodSender.StringLE() + `","type":"added"}]`,
check: func(t *testing.T, resp *neorpc.Notification) { check: func(t *testing.T, resp *neorpc.Notification) {
rmap := resp.Payload[0].(map[string]any) rmap := resp.Payload[0].(map[string]any)
require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) require.Equal(t, neorpc.NotaryRequestEventID, resp.Event)