Modular Transaction Pipeline
Status: Design phase. No implementation dependencies — this is a foundational component. Required by: EIP-8141, Interop Bridge (Phase 3+), Shutter refactor, Lucid integration.
The Modular Transaction Pipeline replaces Erigon's current approach of inserting code at every pipeline stage whenever a new transaction type is added. Today, each new type requires editing 40+ locations across ~15 files — a type switch or if txn.Type == X check at every stage from parsing through serialization. With EIP-8141 frames, Shutter, and Lucid all in scope simultaneously, this model becomes unmanageable.
The pipeline introduces a TypeHandler registry: each transaction type registers its behavior once, and every pipeline stage calls through the registry instead of branching on types. Adding a new transaction type means writing one handler — not touching 40 files.
Key Capabilities
Registry-based dispatch:
Each transaction type implements TypeHandler — a set of interfaces covering parsing, validation, pool policy, execution, and serialization. The pipeline calls through the registry; it never checks types directly. Types register via init() and components.cfg, following the same pattern as other Cocoon components.
Incremental migration:
Existing transaction types (legacy, EIP-1559, EIP-4844, EIP-7702, RIP-7560) are wrapped as TypeHandler implementations with zero behavior change. The scattered type switches are then removed one pipeline stage at a time — each removal is a small, independently-reviewable PR.
Middleware layer: Encrypted mempool integrations (Shutter, Lucid) wrap the registry without touching type-specific code. Shutter decrypts transactions at the slot boundary before the registry handler runs; Lucid reassembles commitment and payload before the handler processes them. Neither needs to know anything about individual transaction types.
Design: TypeHandler Registry
// txtype/registry.go
type TypeHandler interface {
TypeByte() byte
Name() string
Parser // RLP → TxnSlot (txpool) or Transaction (execution)
Validator // mempool acceptance rules
PoolPolicy // replacement, eviction, pool limits
Executor // how this tx type is processed in a block
Serializer // RPC response fields, receipt format
}
Handlers embed DefaultHandler for any interfaces they don't need to override — a new type that is identical to DynamicFee except for one field only needs to implement Parser and Serializer.
Registration follows the components.cfg + init() pattern:
// txtype/types/dynamicfee/register.go
func init() {
txtype.Register(DynamicFeeHandler{})
}
Current touch points per new transaction type
| Layer | Touch points | What changes |
|---|---|---|
| Type system | 5 files | RLP decode switch, JSON unmarshal, receipt encode, hash computation |
| Txpool parsing | 3 functions | ParseTransaction, type-specific body parser, hash/sender recovery |
| Txpool validation | 4 functions | validateTx, replacement rules, pool limits |
| Txpool state | 3 functions | Auth tracking, eviction, fee checks |
| Block builder | 2 functions | block_assembler.go, builderstages/exec.go |
| State processor | 2 functions | applyTransaction, dispatch |
| Parallel execution | 3 functions | txtask.go, trace workers |
| RPC | 2 functions | Field extraction, receipt generation |
Middleware Layer
The middleware layer wraps the registry without touching type-specific code — encrypted mempool integrations don't need to know anything about individual transaction types.
Shutter (threshold decryption): wraps the validation and execution stages. Encrypted transactions enter the mempool as opaque blobs; the Shutter middleware decrypts them at the appropriate slot boundary before the registry handler processes them normally.
Lucid (distributed payload propagation): wraps the pool policy and serialization stages. The commitment (hash) and payload (data) are propagated separately; the Lucid middleware reassembles them before the registry handler runs.
Migration Strategy
Existing transaction types register themselves using the new interface. No pipeline code changes:
LegacyHandler— wraps existing legacy/access-list type codeDynamicFeeHandler— wraps existing EIP-1559 codeBlobHandler— wraps existing EIP-4844 code (KZG, pool limits, replacement rules)SetCodeHandler— wraps existing EIP-7702 code (authorization tracking)AAHandler— wraps existing RIP-7560 code
Once all types are registered, the type switches are removed one pipeline stage at a time. Each removal is a small, independently-reviewable PR.