-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ws: allow filtering notification by parameters #3689
base: master
Are you sure you want to change the base?
Conversation
Some questions:
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3689 +/- ##
==========================================
- Coverage 83.04% 83.01% -0.04%
==========================================
Files 335 335
Lines 46719 46782 +63
==========================================
+ Hits 38800 38834 +34
- Misses 6327 6350 +23
- Partials 1592 1598 +6 ☔ View full report in Codecov by Sentry. |
d4643f2
to
fd5ad7b
Compare
Use and extend the following tests:
neo-go/pkg/services/rpcsrv/client_test.go Line 2131 in 990634a
That's the way how we test subscriptions. If it's not enough, then create your own test based on neo-go/pkg/services/rpcsrv/client_test.go Line 1895 in 990634a
Will be answered in review.
Let's limit the number of parameters to 16 for now, it's pretty enough for notifications used by NeoFS and at the same time it won't allow to DoS the node with useless filtering process for large notifications/filters. Also, parameter types should be limited by non-compound types (simple Integer, String, Hash160 and etc.; excluding Arrays, Structs and Maps), we don't need compounds for now and NeoFS contracts don't use them in notifications; in future the set of supported types may be extended.
Avoid filters misuse and unwanted load for RPC server. This extension will be available on public RPC nodes.
Deploy contract that emits thousands of notifications (it's possible, hi, #3490), then subscribe to RPC server with matching filters. |
pkg/neorpc/filters.go
Outdated
if len(f.Parameters) != 0 { // todo: limit max size? what number? | ||
for i, parameter := range f.Parameters { | ||
if parameter.Type < smartcontract.AnyType || parameter.Type > smartcontract.SignatureType { | ||
return fmt.Errorf("%w: NotificationFilter unsupported %d parameter type: %s", ErrInvalidSubscriptionFilter, i, parameter.Type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NotificationFilter unsupported %d parameter type: %s
It's supposed to be a sentence, like other logs. Let's rephrase to NotificationFilter type parameter %d is unsupported: %s
pkg/neorpc/rpcevent/filter.go
Outdated
parametersOk = false | ||
break | ||
} | ||
converted, err := p.ToStackItem() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's too expensive to convert every filter's parameter every time you need to access it. Consider contract that emits 100500 notifications that match filter's requirements, every block. To improve it define an additional method (some (*neorpc.NotificationFilter) ParametersSI() []stackitem.Item
), this method should convert filter parameters to stckitems and cache the resulting value inside filter's structure. Cache should be reused for subsequent invocations of this method. Cache should be cleaned on filter's Copy.
Also, prior to parameter's value comparison use parameter types comparison:
neo-go/pkg/smartcontract/param_type.go
Line 193 in 990634a
func (pt ParamType) Match(v stackitem.Item) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please add a separate unit-test for various parameter types matching comparison.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what is more scary: caching bugs and more complex logic or converting vars on stack, but don't mind. no mutexes since it should not be used concurrently, ping me if you don't agree
Also, prior to parameter's value comparison use parameter types comparison:
isn't stackitem.Item.Equal
enough?
Also please add a separate unit-test for various parameter types matching comparison.
can, please, explain, why it is needed? there is a single smartcontract.Parameter
-> stackitem.Item
conversion rule and i use it (was not written by me). if it is not fixed, how does this work at all then? at least that is how i understand it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't stackitem.Item.Equal enough?
It's enough but type comparison allows to fail fast. Parameter to stackitem conversion is not cheap.
can, please, explain, why it is needed?
Fail fast in case of types mismatch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail fast
got you
pkg/neorpc/filters.go
Outdated
@@ -134,6 +152,16 @@ func (f NotificationFilter) IsValid() error { | |||
if f.Name != nil && len(*f.Name) > runtime.MaxEventNameLen { | |||
return fmt.Errorf("%w: NotificationFilter name parameter must be less than %d", ErrInvalidSubscriptionFilter, runtime.MaxEventNameLen) | |||
} | |||
if len(f.Parameters) != 0 { // todo: limit max size? what number? | |||
for i, parameter := range f.Parameters { | |||
if parameter.Type < smartcontract.AnyType || parameter.Type > smartcontract.SignatureType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The case when all parameter types are Any is also a no-op.
9191c15
to
b4fc362
Compare
@AnnaShaleva thanks for the review! It was kinda draft with the main questions but tried to answer and fix all the threads you left, check one more time, please. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are not yet checked, will review them after PR finalisation.
b4fc362
to
a87d7cb
Compare
pkg/neorpc/filters.go
Outdated
if len(f.Parameters) != 0 { | ||
res.Parameters = slices.Clone(f.Parameters) | ||
} | ||
f.parametersCache = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why? It's res.Parameters
cache that should be cleaned (but it's not set anyway). So just remove f.parametersCache = nil
and adjust method documentation a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i tried to follow your suggestion, i believe:
Cache should be cleaned on filter's Copy.
i treat it like a new life for the struct (otherwise i do not know why somebody needs to copy something). if you copy smth, you can try to reuse the original struct and then it will be unexpected when you have copied the struct, changed f.Parameters
, and then called ParametersSI
with the old return values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache should be cleaned on filter's Copy.
This sentence was about the copy itself. Cache of the copy should be cleaned. It's the way how it works in NeoGo for cachable fields of transaction/block, we need to follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can try to reuse the original struct
The original struct should be kept unchanged (including cache). The copy may be reused by the user and modified, hence it requires clean cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i do not mind, if this approach has become established in neo-go. dropped cache resetting
a87d7cb
to
b7d0172
Compare
b7d0172
to
29d2a57
Compare
@carpawell tests are failing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of previous conversations are unresolved.
pkg/neorpc/filters.go
Outdated
if f.parametersCache != nil { | ||
return f.parametersCache, nil | ||
} | ||
f.parametersCache = make([]stackitem.Item, 0, len(f.Parameters)) | ||
for i, p := range f.Parameters { | ||
si, err := p.ToStackItem() | ||
if err != nil { | ||
f.parametersCache = nil | ||
return nil, fmt.Errorf("converting %d parameter to stack item: %w", i, err) | ||
} | ||
f.parametersCache = append(f.parametersCache, si) | ||
} | ||
return f.parametersCache, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And BTW, usually in NeoGo it's vice-versa:
if f.parametersCache == nil {
// fill parametersCache in
}
return f.parametersCache, nil
neo-go/pkg/core/transaction/transaction.go
Lines 331 to 334 in ff15e39
if t.size == 0 { | |
t.size = io.GetVarSize(t) | |
} | |
return t.size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it adds additional nesting, usually, i find it a more complex code, but ok
docs/notifications.md
Outdated
32 bytes and/or `parameters` field containing an ordered array of structs | ||
with `type` and `value` fields. Parameter's `type` must be not-a-complex | ||
type from the list: `Any`, `Boolean`, `Integer`, `ByteArray`, `String`, | ||
`Hash160`, `Hash256`, `PublicKey` or `Signature`. Filter that allows any | ||
parameter must be omitted or must be `Any` typed with zero value. It is | ||
prohibited to have `parameters` be filled with `Any` types only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, mention 16
constraint here.
pkg/neorpc/filters.go
Outdated
// parameters. Notification parameter filters will be applied in the order | ||
// corresponding to a produced notification's parameters. `Any`-typed | ||
// parameter with zero value allows any notification parameter. Supported | ||
// parameter types: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please mention MaxNotificationFilterParametersCount
in the doc.
29d2a57
to
0919d65
Compare
`Any` type with nil/null value is treated as a parameter filter that allows any notification value. Not more than 16 filter parameters are allowed. Closes #3624. Signed-off-by: Pavel Karpy <[email protected]>
Signed-off-by: Pavel Karpy <[email protected]>
0919d65
to
97bd73b
Compare
Can you point me, please? As I see it: it is either fixed or I asked some questions that have not been answered or something is not relevant already. |
Closes #3624.