-
Notifications
You must be signed in to change notification settings - Fork 209
[Breaking Change] Normalize nil payload value encoding to empty slice #8307
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
Changes from all commits
929887b
7db1e3e
ff72b16
b015b20
0a7dad1
d57c6a6
d49ab59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,13 +266,32 @@ type serializablePayload struct { | |
| Value Value | ||
| } | ||
|
|
||
| func (p Payload) seralizable() (serializablePayload, error) { | ||
| k, err := p.Key() | ||
| if err != nil { | ||
| return serializablePayload{}, err | ||
| } | ||
|
|
||
| v := p.value | ||
| if v == nil { | ||
| // Normalize nil payload values to empty slice (Value{}) to ensure consistency | ||
| // across all serialization formats (checkpoint files, WAL files, and execution data). | ||
| // This eliminates the distinction between nil and empty slice values, as both represent | ||
| // the removal of a register from the execution state. When an execution node loads a trie, | ||
| // it first reads checkpoint files and then WAL files, during which nil values are normalized | ||
| // to []byte{}. By normalizing at payload encoding time, we ensure all code paths produce | ||
| // consistent encoded data regardless of whether the original value was nil or []byte{}. | ||
| v = Value{} | ||
| } | ||
| return serializablePayload{Key: k, Value: v}, nil | ||
| } | ||
|
|
||
| // MarshalJSON returns JSON encoding of p. | ||
| func (p Payload) MarshalJSON() ([]byte, error) { | ||
| k, err := p.Key() | ||
| sp, err := p.seralizable() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| sp := serializablePayload{Key: k, Value: p.value} | ||
| return json.Marshal(sp) | ||
| } | ||
|
|
||
|
|
@@ -292,11 +311,10 @@ func (p *Payload) UnmarshalJSON(b []byte) error { | |
|
|
||
| // MarshalCBOR returns CBOR encoding of p. | ||
| func (p Payload) MarshalCBOR() ([]byte, error) { | ||
| k, err := p.Key() | ||
| sp, err := p.seralizable() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| sp := serializablePayload{Key: k, Value: p.value} | ||
| return cbor.Marshal(sp) | ||
| } | ||
|
|
||
|
|
@@ -363,6 +381,8 @@ func (p *Payload) Address() (flow.Address, error) { | |
|
|
||
| // Value returns payload value. | ||
| // CAUTION: do not modify returned value because it shares underlying data with payload value. | ||
| // CAUTION: to check wheather the payload value is empty, use len(payload.Value()) == 0, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have checked there is no incompatible usage of checking with |
||
| // due to normalization of nil and empty slice values in payloads during serialization. | ||
| func (p *Payload) Value() Value { | ||
| if p == nil { | ||
| return Value{} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -616,3 +616,48 @@ func TestTrieUpdateSerialization(t *testing.T) { | |
| require.True(t, decodedtu.Equals(tu)) | ||
| }) | ||
| } | ||
|
|
||
| // TestTrieUpdateNilVsEmptySlice verifies that EncodeTrieUpdate/DecodeTrieUpdate | ||
| // for payloads created with nil vs empty []byte values results in both being treated | ||
| // as empty []byte{} after decoding, due to normalization in NewPayload. | ||
| func TestTrieUpdateNilVsEmptySlice(t *testing.T) { | ||
| p1 := testutils.PathByUint16(1) | ||
| kp1 := ledger.NewKeyPart(uint16(1), []byte("key 1")) | ||
| k1 := ledger.NewKey([]ledger.KeyPart{kp1}) | ||
| // Original value is nil, but will be normalized to []byte{} | ||
| pl1 := ledger.NewPayload(k1, nil) | ||
|
|
||
| p2 := testutils.PathByUint16(2) | ||
| kp2 := ledger.NewKeyPart(uint16(1), []byte("key 2")) | ||
| k2 := ledger.NewKey([]ledger.KeyPart{kp2}) | ||
| // Original value is []byte{} | ||
| pl2 := ledger.NewPayload(k2, []byte{}) | ||
|
|
||
| tu := &ledger.TrieUpdate{ | ||
| RootHash: testutils.RootHashFixture(), | ||
| Paths: []ledger.Path{p1, p2}, | ||
| Payloads: []*ledger.Payload{pl1, pl2}, | ||
| } | ||
|
|
||
| // Step 1: Verify original distinction | ||
| require.Nil(t, tu.Payloads[0].Value(), "Payload 0 should have nil value (we don't normalize at creation time, only encoding time)") | ||
| require.NotNil(t, tu.Payloads[1].Value(), "Payload 1 should have non-nil value") | ||
| require.Equal(t, 0, len(tu.Payloads[0].Value()), "Payload 0 should have 0 length") | ||
| require.Equal(t, 0, len(tu.Payloads[1].Value()), "Payload 1 should have 0 length") | ||
|
|
||
| // Step 2: Encode and Decode | ||
| encoded := ledger.EncodeTrieUpdate(tu) | ||
| decoded, err := ledger.DecodeTrieUpdate(encoded) | ||
| require.NoError(t, err) | ||
|
|
||
| // Both will be []byte{} after decode due to normalization in NewPayload. | ||
| t.Logf("Decoded Payload 0 value: %v (isNil=%v)", decoded.Payloads[0].Value(), decoded.Payloads[0].Value() == nil) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the normalization was not added, this would log as |
||
| t.Logf("Decoded Payload 1 value: %v (isNil=%v)", decoded.Payloads[1].Value(), decoded.Payloads[1].Value() == nil) | ||
|
|
||
| require.Equal(t, 0, len(decoded.Payloads[0].Value()), "Decoded Payload 0 should have 0 length") | ||
| require.Equal(t, 0, len(decoded.Payloads[1].Value()), "Decoded Payload 1 should have 0 length") | ||
|
|
||
| // The key assertion: they are now identical despite starting differently | ||
| require.Equal(t, decoded.Payloads[0].Value(), decoded.Payloads[1].Value(), | ||
| "Decoded nil and []byte{} are identical") | ||
| } | ||
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 moved the normalization from payload creation time to encoding time, keeping the surface small.
Uh oh!
There was an error while loading. Please reload this page.
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.
After moving the normalization to encoding time, there should be no performance penalty. @janezpodhostnik