diff --git a/docs/notifications.md b/docs/notifications.md index 41f2ada90..a9f76985e 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -84,7 +84,8 @@ Recognized stream names: * `notary_request_event` Filter: `sender` field containing a string with hex-encoded Uint160 (LE 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 cancel this subscription and has no meaning other than that. diff --git a/pkg/neorpc/filters.go b/pkg/neorpc/filters.go index 2980640ed..255d94e86 100644 --- a/pkg/neorpc/filters.go +++ b/pkg/neorpc/filters.go @@ -1,6 +1,7 @@ package neorpc import ( + "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -38,6 +39,14 @@ type ( State *string `json:"state,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. @@ -111,3 +120,24 @@ func (f *ExecutionFilter) Copy() *ExecutionFilter { } 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 +} diff --git a/pkg/neorpc/rpcevent/filter.go b/pkg/neorpc/rpcevent/filter.go index ecba3e199..26014050b 100644 --- a/pkg/neorpc/rpcevent/filter.go +++ b/pkg/neorpc/rpcevent/filter.go @@ -69,8 +69,9 @@ func Matches(f Comparator, r Container) bool { containerOK := filt.Container == nil || applog.Container.Equals(*filt.Container) return stateOK && containerOK case neorpc.NotaryRequestEventID: - filt := filter.(neorpc.TxFilter) + filt := filter.(neorpc.NotaryRequestFilter) 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 signerOK := true if filt.Signer != nil { @@ -82,7 +83,7 @@ func Matches(f Comparator, r Container) bool { } } } - return senderOk && signerOK + return senderOk && signerOK && typeOk } return false } diff --git a/pkg/neorpc/rpcevent/filter_test.go b/pkg/neorpc/rpcevent/filter_test.go index de56a08fc..d32c696a0 100644 --- a/pkg/neorpc/rpcevent/filter_test.go +++ b/pkg/neorpc/rpcevent/filter_test.go @@ -4,6 +4,7 @@ import ( "testing" "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/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc" @@ -47,11 +48,13 @@ func TestMatches(t *testing.T) { sender := util.Uint160{1, 2, 3} signer := util.Uint160{4, 5, 6} contract := util.Uint160{7, 8, 9} + notaryType := mempoolevent.TransactionAdded badUint160 := util.Uint160{9, 9, 9} cnt := util.Uint256{1, 2, 3} badUint256 := util.Uint256{9, 9, 9} name := "ntf name" badName := "bad name" + badType := mempoolevent.TransactionRemoved bContainer := testContainer{ id: neorpc.BlockEventID, pld: &block.Block{ @@ -76,6 +79,7 @@ func TestMatches(t *testing.T) { ntrContainer := testContainer{ id: neorpc.NotaryRequestEventID, pld: &result.NotaryRequestEvent{ + Type: notaryType, NotaryRequest: &payload.P2PNotaryRequest{ MainTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: signer}}}, 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", comparator: testComparator{ id: neorpc.NotaryRequestEventID, - filter: neorpc.TxFilter{Sender: &badUint160}, + filter: neorpc.NotaryRequestFilter{Sender: &badUint160}, }, container: ntrContainer, expected: false, @@ -263,7 +267,16 @@ func TestMatches(t *testing.T) { name: "notary request, signer mismatch", comparator: testComparator{ 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, expected: false, @@ -272,7 +285,7 @@ func TestMatches(t *testing.T) { name: "notary request, filter match", comparator: testComparator{ id: neorpc.NotaryRequestEventID, - filter: neorpc.TxFilter{Sender: &sender, Signer: &signer}, + filter: neorpc.NotaryRequestFilter{Sender: &sender, Signer: &signer, Type: ¬aryType}, }, container: ntrContainer, expected: true, diff --git a/pkg/rpcclient/wsclient.go b/pkg/rpcclient/wsclient.go index 044214a73..50c68c683 100644 --- a/pkg/rpcclient/wsclient.go +++ b/pkg/rpcclient/wsclient.go @@ -305,7 +305,7 @@ func (r *executionReceiver) Close() { // notaryRequestReceiver stores information about notary requests subscriber. type notaryRequestReceiver struct { - filter *neorpc.TxFilter + filter *neorpc.NotaryRequestFilter 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 -// 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 -// signer) and signer corresponds to main transaction signers. nil value doesn't add -// any filter. See WSClient comments for generic Receive* behaviour details. -func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.TxFilter, rcvr chan<- *result.NotaryRequestEvent) (string, error) { +// signer), signer corresponds to main transaction signers and type corresponds to the +// [mempoolevent.Type] and denotes whether notary request was added to or removed from +// 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 { return "", ErrNilNotificationReceiver } diff --git a/pkg/rpcclient/wsclient_test.go b/pkg/rpcclient/wsclient_test.go index e004659f3..6b14cbfd5 100644 --- a/pkg/rpcclient/wsclient_test.go +++ b/pkg/rpcclient/wsclient_test.go @@ -17,6 +17,7 @@ import ( "github.com/gorilla/websocket" "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/mempoolevent" "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/neorpc" @@ -574,6 +575,71 @@ func TestWSFilteredSubscriptions(t *testing.T) { 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 { t.Run(c.name, func(t *testing.T) { diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index c84306517..d79576bee 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -2734,10 +2734,14 @@ func (s *Server) subscribe(reqParams params.Params, sub *subscriber) (any, *neor flt := new(neorpc.BlockFilter) err = jd.Decode(flt) filter = *flt - case neorpc.TransactionEventID, neorpc.NotaryRequestEventID: + case neorpc.TransactionEventID: flt := new(neorpc.TxFilter) err = jd.Decode(flt) filter = *flt + case neorpc.NotaryRequestEventID: + flt := new(neorpc.NotaryRequestFilter) + err = jd.Decode(flt) + filter = *flt case neorpc.NotificationEventID: flt := new(neorpc.NotificationFilter) err = jd.Decode(flt) diff --git a/pkg/services/rpcsrv/subscription_test.go b/pkg/services/rpcsrv/subscription_test.go index f5b96bcb7..6df8e66df 100644 --- a/pkg/services/rpcsrv/subscription_test.go +++ b/pkg/services/rpcsrv/subscription_test.go @@ -354,8 +354,16 @@ func TestFilteredNotaryRequestSubscriptions(t *testing.T) { require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) }, }, - "matching sender and signer": { - params: `["notary_request_event", {"sender":"` + goodSender.StringLE() + `", "signer":"` + goodSender.StringLE() + `"}]`, + "matching type": { + 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) { rmap := resp.Payload[0].(map[string]any) require.Equal(t, neorpc.NotaryRequestEventID, resp.Event)