Skip to content

Add transaction decoding to the state library#1581

Open
chfast wants to merge 4 commits into
masterfrom
state/decode-transaction
Open

Add transaction decoding to the state library#1581
chfast wants to merge 4 commits into
masterfrom
state/decode-transaction

Conversation

@chfast

@chfast chfast commented Jul 7, 2026

Copy link
Copy Markdown
Member

Add state::decode_transaction, the inverse of the existing RLP encoder: it
decodes a legacy or EIP-2718 typed transaction from its network serialization
into a state::Transaction, rejecting malformed input (exception-free, returns
std::nullopt). Covered by state_rlp unit tests.

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.50%. Comparing base (61bfbff) to head (98cdda2).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1581      +/-   ##
==========================================
+ Coverage   97.41%   97.50%   +0.09%     
==========================================
  Files         163      167       +4     
  Lines       14677    15209     +532     
  Branches     3404     3565     +161     
==========================================
+ Hits        14297    14829     +532     
  Misses        280      280              
  Partials      100      100              
Flag Coverage Δ
eest-develop 89.42% <ø> (ø)
eest-develop-gmp 25.16% <0.00%> (-0.92%) ⬇️
eest-legacy 16.90% <0.00%> (-0.62%) ⬇️
eest-libsecp256k1 26.73% <0.00%> (-0.98%) ⬇️
eest-stable 89.40% <ø> (ø)
evmone-unittests 92.94% <100.00%> (+0.31%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
core 96.11% <100.00%> (+0.14%) ⬆️
tooling 90.32% <ø> (ø)
tests 99.81% <100.00%> (+<0.01%) ⬆️
Files with missing lines Coverage Δ
test/state/rlp_decode.cpp 100.00% <100.00%> (ø)
test/state/rlp_decode.hpp 100.00% <100.00%> (ø)
test/state/transaction.cpp 100.00% <100.00%> (ø)
test/state/transaction.hpp 100.00% <ø> (ø)
test/unittests/state_rlp_decode_test.cpp 100.00% <100.00%> (ø)
test/unittests/state_rlp_test.cpp 100.00% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds exception-free transaction decoding to the test/state library by implementing state::decode_transaction() (legacy + EIP-2718 typed envelopes) and introducing supporting RLP decoding primitives, with unit tests validating round-trips and malformed-input rejection.

Changes:

  • Add state::decode_transaction(bytes_view) -> std::optional<Transaction> and its implementation for legacy/EIP-2718 typed transactions.
  • Introduce test/state/rlp_decode.{hpp,cpp} with overflow-safe/canonical RLP header decoding and basic decode helpers.
  • Extend state_rlp unit tests with transaction decode round-trips and regression cases for malformed RLP / invalid transaction encodings.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/unittests/state_rlp_test.cpp Adds unit tests for transaction decoding and RLP regression cases.
test/state/transaction.hpp Declares decode_transaction() API and documents behavior.
test/state/transaction.cpp Implements transaction decoding logic for legacy + typed transactions.
test/state/rlp_decode.hpp Adds header-only RLP decoding primitives (templated decoders).
test/state/rlp_decode.cpp Implements RLP header decoding and byte/address decoding.
test/state/CMakeLists.txt Wires new decoder sources into the test/state target.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/state/transaction.cpp Outdated
Comment on lines +56 to +69
else // Typed: type byte followed by the RLP list.
{
uint8_t t{};
if (!rlp::decode(from, t))
return false;
if (t > static_cast<uint8_t>(Transaction::Type::legacy) &&
t <= static_cast<uint8_t>(Transaction::Type::set_code))
to.type = static_cast<Transaction::Type>(t);
else
return false;

if (!take_list_payload(from, body) || !rlp::decode(body, to.chain_id))
return false;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the current revision: the EIP-2718 type byte is now read as a raw byte (const uint8_t t = from[0]), so the 0x81 0x02 string-wrapped form is rejected (type 129, outside {1..4}). Note it was already rejected before this change as well — decode_header enforces single-byte canonicality (0x81 0x0X with X < 0x80 is non-canonical) — so this is a clarity/robustness change rather than a bug fix.

Comment thread test/state/rlp_decode.hpp
Comment on lines +85 to +102
template <typename T>
[[nodiscard]] bool decode(bytes_view& from, std::vector<T>& to) noexcept
{
Header h;
if (!decode_header(from, h) || !h.is_list)
return false;

auto payload_view = from.substr(0, static_cast<size_t>(h.payload_length));
while (!payload_view.empty())
{
to.emplace_back();
if (!decode(payload_view, to.back()))
return false;
}

from.remove_prefix(static_cast<size_t>(h.payload_length));
return true;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the current revision: elements are decoded into a local vector and std::moved into to only on success, so a failed decode leaves the output vector untouched.

@chfast chfast force-pushed the state/decode-transaction branch 3 times, most recently from 72d44f2 to dcc7adb Compare July 7, 2026 10:23
@chfast chfast requested a review from Copilot July 7, 2026 10:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread test/state/transaction.cpp
Comment thread test/state/transaction.hpp Outdated
Comment on lines +128 to +132
to.v = (v_u256 - 35) % 2 == 0 ? 0 : 1;
const auto chain_id = (v_u256 - 35 - to.v) / 2;
if (chain_id > std::numeric_limits<uint64_t>::max())
return false; // chain id must fit the 64-bit Transaction::chain_id.
to.chain_id = static_cast<uint64_t>(chain_id);
@chfast chfast force-pushed the state/decode-transaction branch from dcc7adb to dd6bed2 Compare July 7, 2026 10:30
@chfast chfast changed the title test: Add transaction decoding to the state library Add transaction decoding to the state library Jul 7, 2026
@chfast chfast force-pushed the state/decode-transaction branch 2 times, most recently from b698a8c to 3060cb0 Compare July 7, 2026 13:59
Add state::decode_transaction, the inverse of the existing RLP encoder: it
decodes a legacy or EIP-2718 typed transaction from its network serialization
into a state::Transaction, rejecting malformed input (exception-free, returns
std::nullopt). Covered by state_rlp unit tests.

Co-authored-by: rodiazet <radek.zagorowicz@gmail.com>
@chfast chfast force-pushed the state/decode-transaction branch from 3060cb0 to aaeddfc Compare July 7, 2026 15:42
@chfast chfast requested a review from Copilot July 7, 2026 16:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

chfast added 3 commits July 8, 2026 09:39
Decode the "to" recipient with the strict rlp::decode(address) overload, which
already requires exactly 20 bytes, rather than decoding a variable-length
string and size-checking by hand; an empty string still selects CREATE. Narrow
rlp::Header::payload_length to uint32_t so it widens to size_t without the
per-call-site casts (it never exceeds the input size, well below 4 GiB). Mark
which decode overloads are fixed-width vs variable-length.
Fold the three copies of the list-framing sequence (decode the header, check
is_list, bound the payload, advance past it) into a single rlp::take_list_payload
used by the vector and pair decoders and the transaction decoder. Decode the
EIP-7702 authorization_list through the generic rlp::decode(std::vector<T>&) by
giving Authorization an ADL-visible decode() overload, matching how access_list
is already decoded, instead of a hand-rolled loop. Rename decode_fields to
decode_multi.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants