Skip to content
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

Null handling in unwrap library #2795

Closed
roman-khimov opened this issue Nov 15, 2022 · 7 comments
Closed

Null handling in unwrap library #2795

roman-khimov opened this issue Nov 15, 2022 · 7 comments
Labels
enhancement Improving existing functionality I3 Minimal impact rpc RPC server and client S3 Minimally significant U3 Regular
Milestone

Comments

@roman-khimov
Copy link
Member

roman-khimov commented Nov 15, 2022

Another issue I'm thinking about is that array of something can be naturally substituted by Null stackitem and our unwrappers won't accept it. Maybe it's worth to add Null support to unwrap package for arrays?

Originally posted by @AnnaShaleva in #2792 (review)

It's true that we can have Null in many cases. We can have nil in Go at the same time. Maybe it's OK to return it.

@roman-khimov roman-khimov added the rpc RPC server and client label Nov 15, 2022
@roman-khimov roman-khimov added I3 Minimal impact U3 Regular enhancement Improving existing functionality S3 Minimally significant labels Dec 21, 2023
@AnnaShaleva
Copy link
Member

AnnaShaleva commented Jan 23, 2024

Ping to this issue, because NNS wrapper (#3291) uses getRecord NNS method that returns Null stackitem in case of missing record of the specified type. And neither unwrap.UTF8String nor unwrap.Bytes can handle Null stackitem. As a result in the described case we've got an error from the NNS wrapper side on attempt to unwrap Null into Go's string while NNS itself doesn't return any error.

IMO, this behaviour of NNS wrapper is not correct and it will be fixed by this issue. We likely should return an empty string and no error form NNS wrapper in the described case. So something like the following will be useful, but we need to evaluate the usages of unwrap.Bytes and similar methods. Maybe we can directly modify unwrap.Bytes and etc. to properly handle Null.

+// Bytes expects correct execution (HALT state) with a single stack item
+// returned. A slice of bytes is extracted from this item and returned.
+func BytesOrNull(r *result.Invoke, err error) ([]byte, error) {
+       itm, err := Item(r, err)
+       if err != nil {
+               return nil, err
+       }
+       if itm.Equals(stackitem.Null{}) {
+               return nil, nil
+       }
+       return itm.TryBytes()
+}
+
 
+// UTF8String expects correct execution (HALT state) with a single stack item
+// returned. A string is extracted from this item and checked for UTF-8
+// correctness, valid strings are then returned.
+func UTF8StringOrNull(r *result.Invoke, err error) (string, error) {
+       b, err := BytesOrNull(r, err)
+       if err != nil {
+               return "", err
+       }
+       if b == nil {
+               return "", nil
+       }
+       if !utf8.Valid(b) {
+               return "", errors.New("not a UTF-8 string")
+       }
+       return string(b), nil
+}
+

@roman-khimov, what do you think?

@AliceInHunterland, this issue is related to error returned from recordData, err := nnsReader.GetRecord(domainName, recordType)

@AnnaShaleva
Copy link
Member

I'd propose to include this issue in 0.106.0 milestone.

@roman-khimov
Copy link
Member Author

It's pretty straightforward for unwrap.Bytes (or arrays), but UTF8String is different.

empty string and no error

The problem is, it's not nil. And you can't differentiate NULL and real empty string in this case.

Try to check for more examples of how these APIs are used currently.

@AnnaShaleva
Copy link
Member

The problem is, it's not nil. And you can't differentiate NULL and real empty string in this case.

You're right. Then maybe we need to change the NNS wraper itself to return (*string, error)? It doesn't look good to me either, but returning an error also seems not very correct to me.

@roman-khimov
Copy link
Member Author

It's one of:

  • special method with special behavior (additional API to choose from)
  • special error (keeping the API as is)
  • *string (changing the API)

None of them is perfect. But which one to choose depends on typical usage patterns.

@cthulhu-rider
Copy link
Contributor

cthulhu-rider commented Jun 27, 2024

still gives me goosebumps, very waiting for the release!

didn't see this thread right away, so shared some my opinion in nspcc-dev/neofs-contract#415

@roman-khimov
Copy link
Member Author

Voting for a special ErrNull here:

  • compatible with the current behavior
  • works for all types in the same way
  • whether Null is a problem or not is entirely contract-specific and depends on what contract returns
  • in case contract never returns Null it's not a problem already and current behavior is more defensive
  • in case contract returns Null it'd require additional handling, but mostly it'd be if err != nil && !errors.Is(err, unwrap.ErrNull)
  • returning nil, nil is not common in general
  • handling nil, nil is easy for slices of strings and similar things where you iterate over the resulting list, but not so for *string and alike, it'd require about the same amount of code as the snippet above for ErrNull or more

Other opinions and suggestions are welcome.

roman-khimov added a commit that referenced this issue Nov 22, 2024
unwrap: add ErrNull to handle Null returned, fix #2795
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improving existing functionality I3 Minimal impact rpc RPC server and client S3 Minimally significant U3 Regular
Projects
None yet
Development

No branches or pull requests

3 participants