-
Notifications
You must be signed in to change notification settings - Fork 102
Change Label to be a Union of strings and bytes #1580
Conversation
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.
Thanks, very helpful.
actors/builtin/market/deal.go
Outdated
|
||
var EmptyDealLabel = DealLabel{} | ||
|
||
func NewDealLabelFromString(s string) (DealLabel, error) { |
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.
Nit: deal.NewLabelFromString
, don't repeat the word "deal".
Codecov Report
@@ Coverage Diff @@
## master #1580 +/- ##
======================================
Coverage 69.0% 69.0%
======================================
Files 73 74 +1
Lines 8798 8887 +89
======================================
+ Hits 6077 6140 +63
- Misses 1859 1873 +14
- Partials 862 874 +12 |
Can't tag you for review since you opened this @arajasek but please review this too |
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.
👌 very nice, I'm excited about this
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.
(Disclaimer: I'm not sure how much of this came from @ZenGround0, I may be disagreeing with past-Aayush)
I have some concerns about the variance in what can and cannot be constructed. For example:
- You can unmarshal labels whose lengths are longer than
DealMaxLabelSize
. I'm not sure what the impact of this might be, but it feels undesirable -- we could mitigate by having theUnmarshal
method use the sameNewLabelFromString
andNewLabelFromBytes
constructors? - Am I misunderstanding, or would
NewLabelFromString("").IsEmpty()
returnfalse
? Is that intentional? We could mitigate by makingIsEmpty()
returnlen(label.s) == 0 && len(label.bs) == 0
? - Is the
ToString
/ToBytes
method purely for testing? It feels a little weird to have them -- in my head the point of this struct is to say "your label can be 1 of 2 (potentially very different) types, and everything will work seamlessly". Converting between them is odd to me.
Yes bullets 1 and 2 were inherited from your draft of the PR. For the first point, this is just the current state of the chain, after unmarshalling proposals we then further check we're not exceeding max label length. Seems like the right breakdown. Let cbor worry about cbor limits let application code worry about application limits.
Yeah its intentional. Its a type thing not a value thing. Empty means contains no string or bytes and the empty string is a string. EmptyLabel serializes to a string but its not a string. This is all just shorthand to be a bit clearer than comparing both fields to nil in a few places. I'll add a comment but don't think this needs to change. Your suggestion is outdated with the pointer representation I changed to on steb's suggestion. b and s are pointers so never have length. For the final point you added these when you made this PR and they turned out useful for testing. I made ToBytes convert a string on steb's suggestion. But I think your right that converting here doesn't make sense so I'll change that back to erroring if its not bytes. |
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 would have done a few things a bit differently, but this looks correct.
actors/builtin/market/deal.go
Outdated
type DealLabel struct { | ||
s *string | ||
bs *[]byte | ||
} |
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.
personal opinion: I'd write this as
type DealLabel struct {
bs []byte
notString bool
}
Your version is more "go-like", but also requires extra allocations and may be a bit annoying to deal with...
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.
Ironically I was following up on your comment here is this not the "put them behind pointers" approach?
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'm going to move it to the above approach
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 I think we want nvm notString is the way it is to make this niceisString
to make the 0 value serialize to cbor text not cbor bytes
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.
Ironically I was following up on your comment #1580 (comment) is this not the "put them behind pointers" approach?
Your version is the "put them behind pointers approach". My version here is the "enum thing" approach (which, after looking at this, seems simpler.
nvm notString is the way it is to make this nice
Welcome to go! zero values are over there 👉 .
actors/builtin/market/deal.go
Outdated
func (label DealLabel) IsEmpty() bool { | ||
return label.s == nil && label.bs == 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.
Is this accurate? I'd assume that IsEmpty
would mean "is empty". I.e., ""
would also be empty.
We shouldn't distinguish between the two kinds of empty. That is, |
Sorry, I'm still confused about the emptiness thing. Some qs:
I might be wrong about some of these claims. |
actors/builtin/market/deal.go
Outdated
return err | ||
} | ||
|
||
if _, err := w.Write((label.bs)[:]); err != 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.
if _, err := w.Write((label.bs)[:]); err != nil { | |
if _, err := w.Write(label.bs); err != nil { |
actors/builtin/market/deal.go
Outdated
return err | ||
} | ||
} else { // label.IsString() | ||
if len(label.bs) > cbg.MaxLength { |
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.
Se use MaxByteArrayLen
in both cases (on further inspection). I'd just lift this case to the top.
return bytes.Equal(l.bs, o.bs) && l.notString == o.notString | ||
} | ||
|
||
func (label *DealLabel) MarshalCBOR(w io.Writer) error { |
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 think we can vastly simplify this now:
- If null, write empty string.
- If too large, return an error.
- Set a
majorType
variable depending on the type. - Write the value (no need for a conditional).
actors/builtin/market/deal.go
Outdated
if maj == cbg.MajTextString { | ||
|
||
if length > cbg.MaxLength { | ||
return fmt.Errorf("label string was too long (%d), max allowed (%d)", length, cbg.MaxLength) | ||
} | ||
|
||
buf := make([]byte, length) | ||
_, err = io.ReadAtLeast(br, buf, int(length)) | ||
if err != nil { | ||
return err | ||
} | ||
s := string(buf) | ||
if !utf8.ValidString(s) { | ||
return fmt.Errorf("label string not valid utf8") | ||
} | ||
label.bs = buf | ||
label.notString = false | ||
} else if maj == cbg.MajByteString { | ||
|
||
if length > cbg.ByteArrayMaxLen { | ||
return fmt.Errorf("label bytes was too long (%d), max allowed (%d)", length, cbg.ByteArrayMaxLen) | ||
} | ||
|
||
bs := make([]uint8, length) | ||
label.bs = bs | ||
label.notString = true | ||
if _, err := io.ReadFull(br, bs[:]); err != nil { | ||
return err |
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.
We should be able to unify these codepaths.
- Make sure we have either a text string or a byte string.
- Set "not string" if it's a byte string.
- Check the length.
- Read the value.
- If a string, check if it's utf8.
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.
code nits but otherwise LGTM
actors/builtin/market/deal.go
Outdated
if _, err := io.WriteString(w, string("")); err != nil { | ||
return err | ||
} | ||
return 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.
Can just return.
actors/builtin/market/deal.go
Outdated
if _, err := w.Write(label.bs); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return 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.
Can just return.
actors/builtin/market/deal.go
Outdated
if !label.notString { | ||
s := string(buf) | ||
if !utf8.ValidString(s) { |
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.
if !label.notString { | |
s := string(buf) | |
if !utf8.ValidString(s) { | |
if !label.notString && !utf8.ValidString(string(buf)) { |
Implements FIP-0027. Will need a migration.