Skip to content

Commit e33f65f

Browse files
committed
Update ADR for local typed sponsorship tx
1 parent 9eafb5b commit e33f65f

1 file changed

Lines changed: 45 additions & 67 deletions

File tree

docs/adr/ADR-0003-typed-transactions-sponsorship.md

Lines changed: 45 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ This ADR proposes an additional EvNode transaction type that includes gas
1414
sponsorship as a first-class capability, using EIP-2718 typed transactions.
1515
The idea is to define a typed transaction format that separates the gas payer
1616
from the executor so the cost can be covered without altering the normal
17-
execution flow. This reduces complexity for users and integrations.
17+
execution flow. This reduces complexity for users and integrations. The design
18+
defines custom primitives and wrappers locally in this repo and then injects
19+
them into node components, without modifying reth.
1820

1921
## Context
2022

@@ -38,7 +40,8 @@ reth's transaction validation and propagation layers.
3840
This ADR assumes EvNode does not use the transaction pool: 0x76 transactions
3941
are accepted only via Engine API/payload building paths. As a result, there is
4042
no pool-level validation for this type; validation occurs during decode and
41-
execution.
43+
execution. The pool and `eth_sendRawTransaction` paths are explicitly out of
44+
scope for this ADR.
4245

4346
## Decision
4447

@@ -53,12 +56,16 @@ separate executor and sponsor signature domains, so it requires a custom signed
5356
wrapper and signature hashing logic.
5457
The executor is the canonical sender (`from`) and owns the nonce; EVM execution
5558
semantics (CALLER) are always based on the executor. The sponsor only pays fees.
59+
Implementation will define local transaction primitives and envelopes in this
60+
repo and inject them into node builders, without modifying reth crates.
5661

5762
## Implementation Plan
5863

59-
1. Define the consensus transaction envelope and type.
60-
- Define the `EvNodeTransaction` struct and `EvRethTxEnvelope` enum in
61-
`crates/primitives`, using a custom signed wrapper.
64+
1. Define local primitives and transaction envelope.
65+
- Add a new local crate (e.g. `crates/ev-primitives`) to host the transaction
66+
types and wrappers.
67+
- Define the `EvNodeTransaction` struct, `EvNodeSignedTx` wrapper, and
68+
`EvTxEnvelope` enum in that crate, using a custom signed wrapper.
6269
- Register the new typed transaction with `#[envelope(ty = 0x76)]` and keep
6370
the consensus field ordering explicit in the struct.
6471

@@ -72,7 +79,7 @@ semantics (CALLER) are always based on the executor. The sponsor only pays fees.
7279
)]
7380
#[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact, rlp))]
7481
#[expect(clippy::large_enum_variant)]
75-
pub enum EvRethTxEnvelope {
82+
pub enum EvTxEnvelope {
7683
#[envelope(ty = 0)]
7784
Legacy(Signed<TxLegacy>),
7885
#[envelope(ty = 1)]
@@ -81,7 +88,7 @@ pub enum EvRethTxEnvelope {
8188
Eip1559(Signed<TxEip1559>),
8289
#[envelope(ty = 3)]
8390
Eip4844(Signed<TxEip4844>),
84-
#[envelope(ty = 0x76]
91+
#[envelope(ty = 0x76)]
8592
EvNode(EvNodeSignedTx),
8693
}
8794

@@ -101,37 +108,39 @@ pub struct EvNodeTransaction {
101108
// These mirror EIP-1559 fields to stay compatible with the standard.
102109
pub chain_id: u64,
103110
pub nonce: u64,
104-
pub gas_limit: u64,
105-
pub max_fee_per_gas: u128,
106111
pub max_priority_fee_per_gas: u128,
112+
pub max_fee_per_gas: u128,
113+
pub gas_limit: u64,
107114
pub to: TxKind,
108115
pub value: U256,
109116
pub data: Bytes,
110117
pub access_list: AccessList,
111118
// Sponsorship fields (payer is separate, optional capability)
119+
pub fee_payer: Option<Address>,
112120
pub fee_payer_signature: Option<Signature>,
113121
}
114122
```
115123

116124
2. Specify encoding + signing preimages (keep deterministic signing).
117125
- Define the exact RLP field order for `EvNodeTransaction`:
118126
`chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to,
119-
value, data, access_list, fee_payer_signature`.
127+
value, data, access_list, fee_payer, fee_payer_signature`.
120128
This order is consensus-critical; if encoding is derived from struct field
121129
order, the struct must match this ordering exactly.
122130
- Encode optional fields deterministically:
131+
- `fee_payer`: always encoded; if `None`, encode `0x80`.
123132
- `fee_payer_signature`: always encoded; if `None`, encode `0x80`.
124133
- Executor signature preimage (domain: `0x76`):
125-
- `0x76 || rlp(fields...)` with `fee_payer_signature = 0x80`
126-
regardless of whether a sponsor will sign later.
134+
- `0x76 || rlp(fields...)` with `fee_payer = 0x80` and
135+
`fee_payer_signature = 0x80` regardless of whether a sponsor will sign.
127136
- Sponsor signature preimage (domain: `0x78`):
128-
- `0x78 || rlp(fields...)` where `fee_payer_signature` is replaced by the
129-
executor address.
137+
- `0x78 || rlp(fields...)` where `fee_payer` is set to the executor address
138+
and `fee_payer_signature = 0x80`.
130139
- `tx_hash` uses standard EIP-2718 hashing:
131140
- `keccak256(0x76 || rlp(fields...))` with the *final* `fee_payer_signature`.
132141
- Ensure the custom signed type exposes:
133-
- `executor_signature_hash()` (placeholder sponsor signature)
134-
- `sponsor_signature_hash()` (executor address in sponsor slot)
142+
- `executor_signature_hash()` (fee_payer fields empty)
143+
- `sponsor_signature_hash()` (fee_payer = executor address)
135144
- `recover_executor()` and `recover_sponsor()` as applicable
136145
- trait implementations required by Reth for pool/consensus encoding
137146
(`Encodable`, `Decodable`, `Encodable2718`, `Decodable2718`, `Transaction`,
@@ -143,54 +152,23 @@ pub struct EvNodeTransaction {
143152
- If `fee_payer_signature` is `Some`, the payer is the sponsor and the sponsor
144153
signature must be valid for the sponsor domain and bound to the executor.
145154

146-
4. Add the tx type identifier and compact encoding.
147-
- Register the new type id in the custom `TxType` enum and compact codec
148-
(extended identifier if needed), so storage/network encoding works.
149-
- Ensure `TransactionEnvelope` derives cover both the canonical and pooled
150-
variants without conflicting type ids.
151-
152-
Code-level implications:
153-
- Add a `EvNode`/`EvRethTxType` variant that maps to `0x76`.
154-
- Implement `Compact` for the `TxType` enum so `0x76` round-trips through the
155-
compact codec (use `COMPACT_EXTENDED_IDENTIFIER_FLAG` if required).
156-
- Register `#[envelope(ty = 0x76)]` on both the canonical transaction
157-
envelope and the pooled transaction envelope, so 2718 decoding matches
158-
the compact encoding.
155+
4. Add the tx type identifier and envelope mapping (local).
156+
- Define a local `EvTxType` enum in `crates/ev-primitives` with a `EvNode`
157+
variant mapped to `0x76`.
158+
- Ensure the local `EvTxEnvelope` `#[envelope(ty = 0x76)]` derives cover the
159+
canonical transaction envelope. Pool variants are out of scope.
159160

160161
Example (non-normative):
161162

162163
```rust
163164
pub const EVNODE_TX_TYPE_ID: u8 = 0x76;
164165

165-
impl Compact for EvRethTxType {
166-
fn to_compact<B>(&self, buf: &mut B) -> usize
167-
where
168-
B: BufMut + AsMut<[u8]>,
169-
{
170-
match self {
171-
Self::EvNode => {
172-
buf.put_u8(EVNODE_TX_TYPE_ID);
173-
COMPACT_EXTENDED_IDENTIFIER_FLAG
174-
}
175-
Self::Op(ty) => ty.to_compact(buf),
176-
}
177-
}
178-
179-
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
180-
match identifier {
181-
COMPACT_EXTENDED_IDENTIFIER_FLAG => {
182-
let extended_identifier = buf.get_u8();
183-
match extended_identifier {
184-
EVNODE_TX_TYPE_ID => (Self::EvNode, buf),
185-
_ => panic!("Unsupported TxType identifier: {extended_identifier}"),
186-
}
187-
}
188-
v => {
189-
let (inner, buf) = EvRethTxType::from_compact(buf, v);
190-
(inner, buf)
191-
}
192-
}
193-
}
166+
pub enum EvTxType {
167+
Legacy,
168+
Eip2930,
169+
Eip1559,
170+
Eip4844,
171+
EvNode,
194172
}
195173
```
196174

@@ -235,17 +213,17 @@ match tx.tx() {
235213
}
236214
```
237215

238-
6. Decode in Engine API payloads and validate.
239-
- Update the payload transaction iterator to decode the custom type using
240-
2718 decoding, recover signer, and preserve the encoded bytes.
216+
6. Decode in Engine API payloads and validate (no pool).
217+
- Update the Engine API transaction decoding to use `EvTxEnvelope` 2718
218+
decoding, recover signer, and preserve the encoded bytes.
241219
- Add fast, stateless validation for sponsorship fields during payload
242220
decoding to fail early on malformed or invalid signatures.
243221

244222
Example (non-normative):
245223

246224
```rust
247225
let convert = |encoded: Bytes| {
248-
let tx = EvRethTxEnvelope::decode_2718_exact(encoded.as_ref())
226+
let tx = EvTxEnvelope::decode_2718_exact(encoded.as_ref())
249227
.map_err(Into::into)
250228
.map_err(PayloadError::Decode)?;
251229
let signer = tx.try_recover().map_err(NewPayloadError::other)?;
@@ -257,7 +235,8 @@ let convert = |encoded: Bytes| {
257235

258236
Note: in this repo, the Engine API decode/validation currently happens in
259237
`crates/node/src/attributes.rs` within
260-
`PayloadBuilderAttributes::try_new` (the `attributes.transactions` decoding).
238+
`PayloadBuilderAttributes::try_new` (the `attributes.transactions` decoding),
239+
and currently uses `TransactionSigned::network_decode`.
261240

262241
7. Define sponsorship validation and failure modes.
263242
- Specify the sponsor authorization format, signature verification, and
@@ -290,10 +269,9 @@ builder-level pre-check is optional.
290269

291270
8. RPC and receipts.
292271
- Expose an optional `feePayer` (or `sponsor`) field for 0x76 in
293-
`eth_getTransactionByHash` and transaction objects; `from` remains the
294-
executor.
295-
- If receipts are extended, include the same optional field for
296-
observability; otherwise receipts remain standard.
272+
transaction objects for observability; `from` remains the executor.
273+
- If receipts are extended, include the same optional field; otherwise
274+
receipts remain standard.
297275

298276
## References
299277

0 commit comments

Comments
 (0)