Frame Transactions (EIP-8141) - #3047
Conversation
362b750 to
1402c53
Compare
1402c53 to
2f8aa48
Compare
|
Note to reviewers: The first commit of this PR I have built the frame transaction implementation on top of the re-factor since the changes introduced in the refactor make it much nicer to implement frames. Once PR#3192 gets approved and merged, I will rebase this PR branch. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## eips/amsterdam/eip-8141 #3047 +/- ##
===========================================================
- Coverage 93.53% 93.17% -0.37%
===========================================================
Files 624 576 -48
Lines 37070 33246 -3824
Branches 3394 2975 -419
===========================================================
- Hits 34675 30976 -3699
+ Misses 1645 1578 -67
+ Partials 750 692 -58
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
2f8aa48 to
aef48b6
Compare
fb05113 to
7f4ee85
Compare
| VERSIONED_HASH_VERSION_KZG, | ||
| ) | ||
|
|
||
| if U256(tx.nonce) >= U256(U64.MAX_VALUE): |
|
Hey I made a couple of fixes, opened PRs: |
cb6d6cf to
94b7234
Compare
Thanks @Marchhill |
Implement the type-6 frame transaction flow on the amsterdam fork, separated from the regular flow from admission onwards: - static validation, signature verification, intrinsic gas, and the two gas anchors (standard_gas_limit, max_gas) in transactions/frame_transaction.py - consensus receipt types (FrameReceipt, FrameTransactionReceipt) and the 0x06 receipt codec arms in blocks.py - TransactionEnvironment split into top_level_context/frame_context in vm/__init__.py - admission through a dedicated check_frame_transaction in frame_processing.py; process_transaction dispatches frame transactions after the chain-id check - frames execute as independent top-level calls in vm/frame_interpreter.py: per-frame gas meters, atomic batches, cross-frame warm journal, default verify code, approval rollback - the six frame opcodes (TXPARAM, FRAMEDATALOAD, FRAMEDATACOPY, FRAMEPARAM, SIGPARAM, APPROVE) in vm/instructions/frame.py - settlement anchored on the standard gas limit with the calldata floor binding through max_gas; the payer refund is max_cost - charged_fee; the frame transaction receipt reports status, gas, and logs per frame Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
EIP-8141 aligned the SIGPARAM (0xb4) copy operation's stack order with CALLDATACOPY (ethereum/EIPs 4a9ad32c): after signatureIndex and param the operands are memOffset, dataOffset, length. The implementation still popped them in the old order (length, dataOffset, memOffset) — the reverse of its own FRAMEDATACOPY. Swap to match the live spec.
Frame transaction processing initializes the shared, once-per-tx warm address set with the sender so its first EVM access is charged warm (100) rather than cold (2600). The sender is loaded unconditionally for the nonce check and, like a regular transaction's origin, must start warm per EIP-2929 and EIP-3651.
test: cover the EIP-8141 introspection instructions
Upstream ethereum#3121 wrapped amsterdam gas values in ExecutionGas/StateGas NewTypes; wrap the frame transaction call sites accordingly.
81f335d to
6712806
Compare
|
Added some test cases: SamWilsn#12 |
* feat(tests): frame-transaction block access list cases Add EIP-8141 frame-transaction cases that pin the consensus-visible EIP-7928 block access list across frame rollback paths: an atomic-batch unroll and a frame revert drop the discarded storage write and re-file the slot as a bare access; a skipped atomic-batch frame stays absent from the BAL; and a sponsored transaction attributes the payer's fee and the sender's nonce to distinct accounts. These paths are exercised only as state tests today, so the BAL is unpinned for them. * refactor(tests): tighten frame BAL cases and reuse module helpers Build the frames with the module's `verify_frame`/`sender_frame` helpers instead of hand-rolled `Frame` literals, matching the rest of the suite. Tighten the assertions so each case pins what it claims: the sponsored transaction now asserts the sender has no balance change and the payer no nonce bump, so a spurious BAL entry cannot pass on a correct post state, and the atomic-batch cases assert the sender's nonce bump is recorded whether or not the batch unrolls. Derive the payer's post balance from the charged gas and fee per gas rather than hardcoding it, and pin both on the transaction, so a gas change surfaces as a gas mismatch instead of an opaque balance. --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
@Marchhill Merged. I have also added the test cases to the list here |
* feat(tests): frame target resolution cases Cover the three ways a frame's resolved target carries the empty code hash: a precompile under each non-VERIFY mode and under VERIFY, and an EIP-7702 authority whose designation is resolved at frame entry or points at a precompile. * feat(tests): discriminate the VERIFY target resolution split Move the approvals of the precompile-target `VERIFY` case onto the first frame and leave the frame under test approving nothing. With the approvals on the frame under test, dispatching the precompile leaves the transaction with no payment approver, so it is rejected either way and the case rests on matching the exact rejection reason; with them moved, dispatching makes the transaction valid. Add a `VERIFY` frame whose target designates a precompile: its resolved code is empty, as for the codeless target that does route to the default code, but the target holds a designation and so must follow it instead. * refactor(tests): pair the warming frame with its receipt The warming frame, its expected receipt and the designated address's access charge each branched on `warm_delegate` separately, the receipt through a comprehension over the frame list. Decide all three in one place so the frame and its receipt cannot drift apart. * feat(tests): frame gas that cannot afford the designation access The designated account is read only once its access is paid for, so a frame that cannot afford it leaves the account out of the block access list as well as unexecuted. * refactor(tests): use the frame entry gas calculator Account the frame entry charges with `fork.frame_entry_gas_calculator()` rather than summing `gas_costs` members, so the target and designation access charges come from one place. Pin the sender's nonce as unchanged in the rejected precompile-target case, and move the unaffordable-designation case to the block access list suite: the receipts cannot tell a resolution failure from a failure inside the resolved code, since either forfeits the whole frame gas limit, so assert the designated account absent from the BAL and the target present as a bare access. Its frame gas is the largest that still cannot afford the designation's access. --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com>
spencer-tb
left a comment
There was a problem hiding this comment.
Small catch! Feel free to merge, will review in more detail during the week!
| with: | ||
| python-version: "pypy3.11" | ||
| - uses: ./.github/actions/setup-env-pypy | ||
| # Stop short of Amsterdam: this branch's amsterdam spec module |
There was a problem hiding this comment.
Can we make these "TODO:"'s?
| if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: | ||
| raise PriorityFeeGreaterThanMaxFeeError( | ||
| "priority fee greater than max fee" | ||
| ) |
There was a problem hiding this comment.
| if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: | |
| raise PriorityFeeGreaterThanMaxFeeError( | |
| "priority fee greater than max fee" | |
| ) | |
| if tx.max_fee_per_gas > Uint(U256.MAX_VALUE): | |
| raise InvalidFrameError("max fee per gas exceeds 2**256 - 1") | |
| if tx.max_priority_fee_per_gas > Uint(U256.MAX_VALUE): | |
| raise InvalidFrameError("max priority fee per gas exceeds 2**256 - 1") | |
| if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: | |
| raise PriorityFeeGreaterThanMaxFeeError( | |
| "priority fee greater than max fee" | |
| ) |
I think we should be adding this validation as per the EIPs constraints: max_fee_per_gas < 2**256 / max_priority_fee_per_gas < 2**256.
Without this, as frame transactions have no upfront balance >= gas * max_fee check to make oversized values unincludable, these txs will execute and raise an uncaught OverflowError from U256(tx.max_fee_per_gas) instead of the transaction being rejected, I think
We should add basic tests for this too!
🗒️ Description
My work so far on frame transactions (EIP-8141)
Cute Animal Picture