fix(rpc): handle 3-byte RLP list headers in decode_method_call_data - #1746
Open
memosr wants to merge 1 commit into
Open
fix(rpc): handle 3-byte RLP list headers in decode_method_call_data#1746memosr wants to merge 1 commit into
memosr wants to merge 1 commit into
Conversation
`decode_method_call_data` unwraps the rlp([calldata, leader_only]) payload
that genlayer-js sends for `gen_call` (type "read") and `eth_call`. It
parsed the outer list header by hand and assumed a long list header is
always 2 bytes.
An RLP long list header is 0xF7 + the number of length bytes, so it is
3 bytes once the payload reaches 256 bytes. Any read whose calldata pushes
the payload past that point hit the wrong offset:
rlp.exceptions.DecodingError: RLP string ends with 255 superfluous bytes
The boundary is exact: 252 bytes of calldata decode, 253 bytes fail. In
practice that is roughly 235 characters of string argument.
Two further defects shared the same root cause and are fixed here:
- `leader_only=True` encodes as a trailing 0x01, so the `raw_bytes[-1] == 0`
guard never fired and the entire RLP payload was returned as if it were
calldata. This failed silently rather than raising, and `leaderOnly` is a
public option on genlayer-js `readContract`.
- Empty or single-byte input raised IndexError.
The header length is now computed from the prefix. Every existing test of
this method mocks it, so it had no real coverage; adds tests covering the
252/253 boundary, payloads up to 70000 bytes, the leader_only path, and
degenerate inputs.
Follow-up to genlayerlabs#1184, which introduced the 2-byte assumption.
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1745
What
Compute the outer RLP list header length from its prefix instead of assuming 2 bytes
Accept a trailing
0x01, soleader_only=Truepayloads are unwrapped likeleader_only=FalseonesGuard against inputs too short to be a wrapped payload
Add unit tests for
decode_method_call_data, which previously had noneWhy
decode_method_call_dataunwraps therlp([calldata, leader_only])payload that genlayer-js sends forgen_call(type"read") andeth_call. It parsed the outer list header by hand:An RLP long list header is
0xF7 + <number of length bytes>, so it is 3 bytes once the payload reaches 256 bytes, not always 2. Past that point the slice lands at the wrong offset:rlp.exceptions.DecodingError: RLP string ends with 255 superfluous bytes
The boundary is exact, 252 bytes of calldata decode and 253 bytes fail, which is roughly 235 characters of string argument. Both callers in
endpoints.pyare affected, so this breaks ordinary contract reads with moderately sized arguments.Two further defects shared the same root cause and are fixed in the same change:
leader_only=Trueencodes as a trailing0x01, so theraw_bytes[-1] == 0guard never fired and the entire RLP payload was returned as if it were calldata. This corrupted silently rather than raising, andleaderOnlyis a public option on genlayer-jsreadContract.Empty or single-byte input raised
IndexError.Testing done
Behaviour of the current code on
v0.123-devversus this change:| case | before | after |
|---|---|---|
| calldata 252 bytes,
leader_only=False| passes | passes || calldata 253 bytes,
leader_only=False|DecodingError| passes || calldata 1000 bytes,
leader_only=False|DecodingError| passes || any size,
leader_only=True| wrong value, no error | passes || bare unwrapped calldata | passes | passes |
|
"0x"|IndexError| passes |Added tests to
tests/unit/test_transactions_parser.pycovering the 252/253 boundary, payloads up to 70000 bytes, theleader_onlypath, and degenerate inputs. All fail onv0.123-devbefore the change and pass after.Full unit suite: 1218 passed, 7 skipped. Baseline before the change was 1203 passed, 7 skipped, so no regressions.
black --checkclean on both files.Decisions made
Kept the existing structure and fixed the header arithmetic rather than replacing the hand-rolled unwrapping with a plain
rlp.decodeand a shape check. The latter is cleaner but changes behaviour for unwrapped legacy payloads that happen to decode as a 2-element list, and this PR is meant to be a surgical fix. Happy to do that refactor separately if you prefer it.Widening the trigger to a trailing
0x01means an unwrapped legacy payload ending in0x01would now be treated as wrapped. The same exposure already existed for0x00. The length guard limits it, but flagging it explicitly since it is the one behavioural widening here.Targeting
v0.123-devsince that is where recent PRs land. The same defects are present onv0.121andmain; happy to open companion PRs.Checks
I have tested this code
I have reviewed my own PR
I have created an issue for this PR
I have set a descriptive PR title compliant with conventional commits
Reviewing tips
The functional change is 6 lines in
transactions_parser.py. The key line is the header length,1 + (prefix - 0xF7), which follows directly from the RLP spec: for a long list the prefix encodes how many length bytes follow.The quickest way to confirm the bug independently is to check out
v0.123-devand run the new tests, which fail there.User facing release notes
Fixes contract read calls (
gen_callandeth_call) failing with an RLP decoding error when arguments were large enough to push the request payload past 255 bytes, roughly 235 characters of string argument. Also fixes reads made withleaderOnly: truebeing decoded incorrectly.