Skip to content

MorphTx enhance & implement reference key#282

Open
SegueII wants to merge 17 commits intomainfrom
ref_key
Open

MorphTx enhance & implement reference key#282
SegueII wants to merge 17 commits intomainfrom
ref_key

Conversation

@SegueII
Copy link
Contributor

@SegueII SegueII commented Jan 26, 2026

Summary by CodeRabbit

  • New Features

    • Added MorphTx (versioned transactions with optional Reference and Memo), receipt JSON/storage and RPC support, a public "morph" API, and paginated GetTransactionHashesByReference.
    • Introduced a 32-byte Reference type and a background reference index with read/write/query support.
  • Enhancements

    • Transaction, validation, pool, state, tracing and receipt flows updated for MorphTx version/memo rules and cost handling; receipts now include version/reference/memo.
  • Tests

    • Extensive MorphTx and reference-indexing tests for encoding, signing, storage, indexing, pool behavior and tracing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

Adds a versioned MorphTx type (v0/v1) with Reference and Memo, replaces AltFeeTx paths, persists and indexes MorphTx references with a background indexer, and propagates Version/Reference/Memo through transactions, receipts, RPC, pool, state, and tests.

Changes

Cohort / File(s) Summary
Reference type & encoding
common/types.go
Add 32‑byte Reference type, encodings, DB/GraphQL/JSON helpers, and MaxMemoLength.
New MorphTx type & compat tests
core/types/morph_tx.go, core/types/morph_tx_compat_test.go
Introduce MorphTx (v0/v1), RLP encode/decode, sigHash variants, accessors, validation and compatibility tests.
Transaction system migration
core/types/transaction.go, core/types/transaction_marshalling.go, core/types/transaction_signing.go, core/types/transaction_test.go
Replace AltFeeTx with MorphTx across decoding, accessors, errors, signer registration, JSON marshal/unmarshal, and tests; add Version/Reference/Memo accessors and validators.
Receipt, tracing & storage
core/types/receipt.go, core/types/gen_receipt_json.go, core/types/l2trace.go, core/types/receipt_test.go
Extend receipts/traces and storage formats to include Version, Reference, Memo and morph fee fields; add v8 storage handling and JSON updates; update tests.
Reference index schema & helpers
core/rawdb/schema.go, core/rawdb/accessors_reference_index.go
Add reference index key format, tail key, Read/Write/Delete helpers and ReferenceIndexEntry struct.
Reference index iterator & batcher
core/rawdb/reference_index_iterator.go
Add concurrent streaming iterator and IndexReferences/UnindexReferences with batching, interruption, tail updates and progress logging.
Blockchain integration & tests
core/blockchain.go, core/blockchain_test.go, core/block_validator.go
Persist/delete reference index entries during block insertion, add maintainReferenceIndex background routine, and extensive tests validating reference indexing.
Message / state changes
core/state_processor.go, core/state_transition.go, interfaces.go, accounts/abi/bind/base.go
Populate receipt Version/Reference/Memo; extend Message, CallMsg and TransactOpts with Version/Reference/Memo; add createMorphTx and version validation.
Tx-pool, listing & filtering
core/tx_pool.go, core/tx_list.go, core/tx_pool_test.go
Add MorphTx handling in pool validation/selection, cost computation, memo/version validation, and add pool tests.
Bindings, RPC & client
accounts/abi/bind/backends/simulated.go, accounts/external/backend.go, internal/ethapi/api.go, internal/ethapi/transaction_args.go, ethclient/ethclient.go, internal/ethapi/backend.go
Propagate Version/Reference/Memo through simulated backend, SignTx, RPCTransaction, TransactionArgs; add Public Morph API and GetTransactionHashesByReference.
Raw DB & receipt storage
core/rawdb/accessors_chain.go, core/types/receipt.go, core/types/gen_receipt_json.go
Extend stored receipt RLP/v8 encoding to include Version, Reference, Memo and update encode/decode and index encoding to use MorphTxType.
Rollup & tracing
rollup/fees/rollup_fee.go, rollup/tracing/tracing.go
Add Message accessors, rename asUnsignedAltFeeTxasUnsignedMorphTx, include new fields in constructed txs and propagate into traces.
Signer & CLI tooling
signer/core/apitypes/types.go, cmd/evm/internal/t8ntool/execution.go
Add SendTxArgs fields for Version/Reference/Memo, determineMorphTxVersion(), and include receipt metadata in execution tooling.
Tests & minor callsite updates
core/*_test.go, tests/state_test_util.go, graphql/graphql.go, light/txpool.go, les/odr_test.go, light/odr_test.go
Extensive tests for MorphTx and reference indexing; update GraphQL and light client callsites to use MorphTx labels and expanded call message literals.
Small accessors/backends
accounts/abi/bind/backends/simulated.go, accounts/external/backend.go
Added callMsg accessors and SignTx branch to include Version/Reference/Memo and fee fields.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant RPC as RPC
    participant TxPool as TxPool
    participant Blockchain as Blockchain
    participant State as State
    participant RefIdx as RefIdx
    participant DB as DB

    User->>RPC: SendTransaction(MorphTx w/ Reference)
    RPC->>TxPool: SubmitTx
    TxPool->>TxPool: ValidateMorphTxVersion(), ValidateMemo()
    TxPool-->>RPC: Accepted

    Blockchain->>State: ApplyTransaction(MorphTx)
    State->>State: Create Receipt (Version, Reference, Memo)
    State-->>Blockchain: Receipt

    Blockchain->>RefIdx: WriteReferenceIndexEntriesForBlock(block)
    RefIdx->>DB: WriteReferenceIndexEntry(...)
    RefIdx->>DB: WriteReferenceIndexTail(...)

    User->>RPC: GetTransactionHashesByReference(reference, offset, limit)
    RPC->>DB: ReadReferenceIndexEntries(reference)
    DB-->>RPC: ReferenceIndexEntry[]
    RPC-->>User: ReferenceTransactionResult[]
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • twcctop
  • FletcherMan
  • r3aker86

Poem

🐇 I hopped through lines both old and new,

Found references thirty-two and memos too,
MorphTxs now march with versions bright,
Indexed, traced, and signed into the night,
A carrot-coded sprint—fast, clever, and true!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'MorphTx enhance & implement reference key' accurately summarizes the main changes: enhancing the MorphTx transaction type and implementing reference-based indexing functionality across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ref_key

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SegueII SegueII marked this pull request as ready for review February 3, 2026 07:12
@SegueII SegueII requested a review from a team as a code owner February 3, 2026 07:12
@SegueII SegueII requested review from twcctop and removed request for a team February 3, 2026 07:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
core/rawdb/accessors_chain.go (1)

631-645: ⚠️ Potential issue | 🔴 Critical

Fix type mismatches to align with core/types/receipt.go canonical definition.

The struct has type inconsistencies with the canonical storedReceiptRLP definition in core/types/receipt.go:

  • Memo should be *[]byte (pointer), not []byte (direct slice) — the canonical definition and all RLP encoding/decoding logic use the pointer type
  • Version should be uint8 for consistency with the canonical definition (byte is an alias, but uint8 is the standard notation used throughout core/types)

These mismatches will cause encoding/decoding issues since the canonical definition in core/types/receipt.go uses *[]byte for Memo in both storedReceiptRLP and v8StoredReceiptRLP.

core/tx_list.go (1)

401-419: ⚠️ Potential issue | 🟠 Major

altCostLimit isn’t enforced for MorphTxWithAltFee.
allLower is computed but never used in the IsMorphTxWithAltFee branch, so alt-token balance limits won’t trigger eviction. If that’s unintentional, incorporate the limit into the predicate (or compare per‑tx alt cost).

🧭 Minimal fix to apply alt-cost caps
 		if tx.IsMorphTxWithAltFee() {
 			for id, limit := range altCostLimit {
 				lower := l.costcap.Alt(id).Cmp(limit) <= 0
 				if !lower {
 					l.costcap.SetAltAmount(id, limit)
 				}
 				allLower = allLower && lower
 			}
-			return tx.Gas() > gasLimit || tx.Value().Cmp(costLimit) > 0
+			return !allLower || tx.Gas() > gasLimit || tx.Value().Cmp(costLimit) > 0
 		}
core/blockchain.go (1)

1978-1994: ⚠️ Potential issue | 🟠 Major

Reorgs don’t remove reference‑index entries.
Tx lookup entries are deleted, but reference entries for removed blocks remain, leaving stale reference lookups after reorgs. Consider deleting reference entries for oldChain blocks in the same batch.

🧩 Suggested fix
 indexesBatch := bc.db.NewBatch()
+for _, block := range oldChain {
+	rawdb.DeleteReferenceIndexEntriesForBlock(indexesBatch, block)
+}
 for _, tx := range types.TxDifference(deletedTxs, addedTxs) {
 	rawdb.DeleteTxLookupEntry(indexesBatch, tx.Hash())
 }
core/types/transaction_marshalling.go (1)

576-611: ⚠️ Potential issue | 🔴 Critical

Guard dec.Version before dereferencing to prevent nil pointer panic.

The decoder receives Version as a *uint8 pointer. If JSON omits the "version" field, dec.Version is nil, causing a panic at line 608 when dereferenced without a guard. All other required fields (ChainID, Nonce, Gas, etc.) are validated before use; Version must follow the same pattern.

Default to MorphTxVersion0 if the field is absent:

Suggested fix
-		itx.Version = *dec.Version
+		if dec.Version == nil {
+			itx.Version = MorphTxVersion0
+		} else {
+			itx.Version = *dec.Version
+		}
core/types/transaction.go (1)

450-459: ⚠️ Potential issue | 🟠 Major

Cost() panics for MorphTx transactions.

Calling Cost() on a MorphTx will cause a panic. While there's a TODO comment indicating this is intentional for now, this is a significant correctness risk. Any code path that calls Cost() on a MorphTx without first checking IsMorphTx() will crash.

Consider one of:

  1. Return an error instead of panicking
  2. Document this clearly in the method's godoc
  3. Implement proper cost calculation for MorphTx without alt-fee
🐛 Consider returning error or calculating cost

Option 1 - Return error (requires signature change):

func (tx *Transaction) Cost() (*big.Int, error) {
    if tx.IsMorphTx() {
        return nil, ErrCostNotSupported
    }
    total := tx.GasFee()
    total.Add(total, tx.Value())
    return total, nil
}

Option 2 - Calculate cost for MorphTx without alt-fee:

 func (tx *Transaction) Cost() *big.Int {
-	// TODO: morph tx without fee token
-	if tx.IsMorphTx() {
+	if tx.IsMorphTxWithAltFee() {
 		panic(ErrCostNotSupported)
 	}
 	total := tx.GasFee()
 	total.Add(total, tx.Value())
 	return total
 }
🤖 Fix all issues with AI agents
In `@accounts/abi/bind/base.go`:
- Around line 340-358: The current version-selection logic (where version :=
opts.Version; if version == 0 && opts.FeeTokenID == 0 { version =
types.MorphTxVersion1 }) can leave Version 0 when FeeTokenID > 0 and
accidentally drop Reference/Memo; change it so that when opts.Version == 0 you
also force types.MorphTxVersion1 if either opts.Reference or opts.Memo is
non-nil (treat non-nil pointers as explicit intent even if empty), then build
the types.MorphTx (baseTx) with that version; apply the same fix to the second
occurrence of the selection logic (the other block around the later lines) so
both code paths respect Reference/Memo presence.

In `@accounts/external/backend.go`:
- Around line 224-234: The code dereferences tx.Memo() when handling
types.MorphTxType which can be nil; update the MorphTxType branch in the switch
to check if tx.Memo() != nil before converting/dereferencing it and assigning to
args.Memo (e.g., only create hexutil.Bytes(*tx.Memo()) and set args.Memo when
tx.Memo() is non-nil). Keep other assignments unchanged; mirror the guard
pattern used in ValidateMemo()/AsMorphTx() and other callers to avoid a panic.

In `@common/types.go`:
- Around line 124-131: Reference.SetBytes currently left-aligns the input which
breaks numeric/hex round-trips and differs from Hash.SetBytes; change it to
right-align and zero any leading bytes: if len(b) > len(r) crop b from the left
as before, otherwise clear r (set all bytes to 0) and copy b into the trailing
bytes of r so the least-significant bytes are right-aligned; update the
implementation in the Reference.SetBytes method to perform the zeroing and
tail-copy (use the existing Reference type and ReferenceLength/len(r) to locate
the copy destination).

In `@core/rawdb/reference_index_iterator.go`:
- Around line 104-113: The indexing stalls because when reading from rlpCh in
the loop (the block with header/rlp decode), the code uses continue on errors
and never sends a result for that block to resultCh, leaving gaps that make
indexReferences' priority check (the priority vs lastNum-1 logic) wait forever;
fix by emitting a placeholder/empty result into resultCh whenever data.header ==
nil or rlp.DecodeBytes fails (preserving data.number), so the priority queue
still receives a contiguous entry for that block (include any error metadata in
the placeholder), i.e., in the loop that decodes into types.Body and handles
errors, create and send an empty/failed BlockReference result for the given
block number to resultCh instead of continuing.

In `@core/types/gen_receipt_json.go`:
- Around line 172-174: The generated truncation occurs because receiptMarshaling
uses hexutil.Uint64 for the Type and Version fields which are later cast to
uint8 in gen_receipt_json.go; update the source marshaling struct in
receiptMarshaling (in core/types/receipt.go) to use a narrower type (e.g., add
and use hexutil.Uint8) or add validation at the top-level unmarshaling
entrypoint (e.g., the receipt UnmarshalJSON/Unmarshal function) to reject values
>255 before the generated code runs, and apply the same change for both Type and
Version to prevent silent truncation.

In `@core/types/l2trace.go`:
- Around line 204-217: Guard against nil dereference of tx.Memo() in the MorphTx
handling block: before converting memo to hexutil.Bytes and assigning to
result.Memo, check if tx.Memo() != nil; only then do the conversion (memo :=
hexutil.Bytes(*tx.Memo())) and set result.Memo = &memo, otherwise leave
result.Memo nil. Update the block that checks tx.Type() == MorphTxType (and uses
tx.Version(), tx.Reference(), FeeTokenID, FeeLimit) to include this nil-check
for tx.Memo().

In `@core/types/morph_tx.go`:
- Around line 181-227: The V0 encode path in MorphTx.encode lacks the same
FeeTokenID != 0 validation present in decodeV0MorphTxRLP, causing asymmetry; add
a check at the start of the MorphTxVersion0 branch in encode (inside
MorphTx.encode) to return an error if tx.FeeTokenID == 0 (with a clear message
like "invalid FeeTokenID for v0 morph tx") before calling rlp.Encode, ensuring
behavior matches decodeV0MorphTxRLP and prevents encoding invalid v0 payloads.

In `@core/types/transaction_test.go`:
- Around line 732-738: The test currently compares Sender errors with direct
equality (in the table-driven loop using tc.wantSenderErr) which fails for
wrapped errors; update the assertion to use errors.Is(err, tc.wantSenderErr)
instead of err == tc.wantSenderErr and adjust the test import to include the
"errors" package if not present; locate the comparison inside the t.Run block
where Sender(signer, tc.tx) is called and replace the equality check and error
message accordingly.

In `@core/types/transaction.go`:
- Around line 974-981: The comment for copyReferencePtr is wrong—replace
"copyAddressPtr copies an address" with a proper Go doc comment that starts with
the function name and describes what it does, e.g. "copyReferencePtr copies a
common.Reference and returns a pointer to the copy"; update the comment to
reference common.Reference and ensure it follows Go comment conventions for
exported identifiers.

In `@ethclient/ethclient.go`:
- Around line 375-384: GetTransactionHashesByReference is a public method but
returns internal type ethapi.ReferenceTransactionResult (from internal/ethapi),
which external callers cannot use; define a public result type in the ethclient
package (e.g. type ReferenceTransactionResult struct { ... } matching the fields
of ethapi.ReferenceTransactionResult), then change
GetTransactionHashesByReference to return
[]ethclient.ReferenceTransactionResult; inside the method, call the RPC into a
local slice of ethapi.ReferenceTransactionResult as currently done, map/convert
each ethapi.ReferenceTransactionResult to the new
ethclient.ReferenceTransactionResult and return that slice (keep function name
GetTransactionHashesByReference and RPC call
"morph_getTransactionHashesByReference" unchanged).

In `@internal/ethapi/transaction_args.go`:
- Around line 373-387: The code unconditionally sets MorphTx metadata (version,
reference, memo) before calling types.NewMessage, causing non‑Morph calls to be
treated as MorphTx; change the logic in the ToMessage (or the containing
function) to only initialize and pass version, reference, and memo when
args.isMorphTxArgs() returns true — otherwise leave them nil/defaults — and then
call types.NewMessage with those conditional values so non‑Morph calls keep
their original encoding/behavior.

In `@signer/core/apitypes/types.go`:
- Around line 124-153: The code casts *args.Version to uint8 for
types.MorphTx.Version which can silently truncate large hexutil.Uint64 values;
before assigning do an explicit range check on args.Version (e.g., ensure
*args.Version <= 255) and return/propagate an error if out of range, or change
the source type to a uint8-typed value; update the block that sets version
(checking args.Version != nil) to validate the numeric range and only cast when
safe so types.MorphTx.Version cannot receive a truncated value.
🧹 Nitpick comments (9)
core/blockchain_test.go (1)

3590-4063: Avoid fixed sleeps for index initialization to reduce test flakiness.
time.Sleep(50ms) can be brittle on slow CI. Consider polling for expected entry counts or reference-index tail with a timeout helper instead.

core/types/transaction_marshalling.go (1)

65-70: Consider omitting MorphTx fields when unused.
Without omitempty, non‑MorphTx JSON includes "version": null, "reference": null, "memo": null. If that’s not intended, add omitempty to keep output stable.

🔧 Suggested tweak
-	Version    *uint8            `json:"version"`
-	Reference  *common.Reference `json:"reference"`
-	Memo       *hexutil.Bytes    `json:"memo"`
+	Version    *uint8            `json:"version,omitempty"`
+	Reference  *common.Reference `json:"reference,omitempty"`
+	Memo       *hexutil.Bytes    `json:"memo,omitempty"`
core/types/receipt.go (1)

121-151: storedReceiptRLP and v8StoredReceiptRLP are identical structures.

Both structs have the exact same fields. If they're meant to be identical for v8, consider removing the duplication. If v8 is supposed to differ from the current format, one of them likely has the wrong fields.

Additionally, in DecodeRLP (lines 397-402), decodeStoredReceiptRLP is tried first, and if it fails, decodeV8StoredReceiptRLP is tried. Since both decode to identical structures, the v8 decoder will likely never succeed if the first one failed (and vice versa).

♻️ Consider consolidating or differentiating the structs

If v8 is the current format:

-// storedReceiptRLP is the storage encoding of a receipt.
-type storedReceiptRLP struct {
-	PostStateOrStatus []byte
-	CumulativeGasUsed uint64
-	Logs              []*LogForStorage
-	L1Fee             *big.Int
-	FeeTokenID        *uint16
-	FeeRate           *big.Int
-	TokenScale        *big.Int
-	FeeLimit          *big.Int
-	Version           uint8
-	Reference         *common.Reference
-	Memo              *[]byte
-}
-
-// v8StoredReceiptRLP is the storage encoding of a receipt used in database version 8.
-// This version was introduced when MorphTx feature was added.
-// It includes L1Fee and all MorphTx fields (FeeTokenID, FeeRate, TokenScale, FeeLimit, Version, Reference, Memo).
-type v8StoredReceiptRLP struct {
+// storedReceiptRLP is the storage encoding of a receipt used in database version 8.
+// This version was introduced when MorphTx feature was added.
+// It includes L1Fee and all MorphTx fields (FeeTokenID, FeeRate, TokenScale, FeeLimit, Version, Reference, Memo).
+type storedReceiptRLP struct {
 	PostStateOrStatus []byte
 	CumulativeGasUsed uint64
 	Logs              []*LogForStorage
 	L1Fee             *big.Int
 	FeeTokenID        *uint16
 	FeeRate           *big.Int
 	TokenScale        *big.Int
 	FeeLimit          *big.Int
 	Version           uint8
 	Reference         *common.Reference
 	Memo              *[]byte
 }

Then remove decodeV8StoredReceiptRLP and the corresponding call in DecodeRLP.

core/types/morph_tx.go (1)

1-28: Copyright header is placed after the package declaration and imports.

The copyright notice should appear at the very top of the file, before the package statement. This is both a Go convention and ensures proper license attribution.

♻️ Move copyright to top of file
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
 package types

 import (
 	"bytes"
 	"errors"
 	"math/big"
 	"strconv"

 	"github.com/morph-l2/go-ethereum/common"
 	"github.com/morph-l2/go-ethereum/rlp"
 )
-
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
internal/ethapi/api.go (2)

1846-1876: Inconsistent error handling for pagination edge cases.

When len(entries) == 0, the method returns nil, nil (line 1847-1849). However, when offsetVal >= len(entries), it returns an error (line 1852-1854). This inconsistency may confuse API consumers:

  • Empty reference → nil result
  • Valid reference but offset too high → error

Consider returning an empty slice for both cases, or documenting this behavior clearly.

Additionally, when ReadTxLookupEntry returns nil (line 1866-1869), entries are silently skipped. This could result in fewer results than expected without any indication to the caller.

♻️ Consider more consistent error handling
 	entries := rawdb.ReadReferenceIndexEntries(s.b.ChainDb(), reference)
 	if len(entries) == 0 {
-		return nil, nil
+		return []ReferenceTransactionResult{}, nil
 	}

 	// Validate offset
 	if offsetVal >= uint64(len(entries)) {
-		return nil, fmt.Errorf("offset %d exceeds total results %d", offsetVal, len(entries))
+		return []ReferenceTransactionResult{}, nil
 	}

1974-1979: MorphTx receipt fields are always included regardless of transaction type.

The version, reference, and memo fields are included in the receipt response for all transaction types. For non-MorphTx transactions, these will be 0/nil. Consider conditionally including these fields only for MorphTxType to reduce response payload size and avoid confusion.

core/rawdb/reference_index_iterator.go (3)

50-53: Edge case: from == to returns nil channel, preventing single-block indexing.

If someone wants to index exactly one block (e.g., IndexReferences(db, 100, 101, nil)), this works. But from == to returns nil, which could cause nil pointer issues in callers if not handled. The comment says "short circuit for invalid range" but from == to meaning "index zero blocks" is valid behavior.

Consider returning a closed channel instead of nil for safer handling by callers.

♻️ Return closed channel for empty range
 func iterateReferences(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockReferenceInfo {
 	if to == from {
-		return nil
+		ch := make(chan *blockReferenceInfo)
+		close(ch)
+		return ch
 	}

128-141: Inconsistent indentation in the process function.

Lines 128-141 have inconsistent indentation compared to lines 115-127 within the same function scope. This affects readability.

📝 Fix indentation
 			}
 		}

-	// Always send result for every block (even if no references) to maintain
-	// contiguous block numbers for gap-filling logic in indexReferences
-	result := &blockReferenceInfo{
-		number:         data.number,
-		blockTimestamp: data.header.Time,
-		references:     refs,
-	}
-	// Feed the block to the aggregator, or abort on interrupt
-	select {
-	case resultCh <- result:
-	case <-interrupt:
-		return
-	}
+		// Always send result for every block (even if no references) to maintain
+		// contiguous block numbers for gap-filling logic in indexReferences
+		result := &blockReferenceInfo{
+			number:         data.number,
+			blockTimestamp: data.header.Time,
+			references:     refs,
+		}
+		// Feed the block to the aggregator, or abort on interrupt
+		select {
+		case resultCh <- result:
+		case <-interrupt:
+			return
+		}
 		}
 	}

285-295: Hardcoded flush frequency differs from indexing path.

The indexing path uses ethdb.IdealBatchSize (line 200) for batch flushing, but unindexing uses a hardcoded blocks%1000 == 0 (line 288). The comment explains deletions count as size 1, but consider making this configurable or using a named constant for consistency and maintainability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@internal/ethapi/transaction_args.go`:
- Around line 87-95: The isMorphTxArgs method on TransactionArgs currently omits
FeeLimit, so add a check for the FeeLimit field (e.g., args.FeeLimit != nil) to
the boolean expression in TransactionArgs.isMorphTxArgs so that transactions
with only FeeLimit set are detected as MorphTxs; update the return expression to
include the FeeLimit check alongside FeeTokenID, Version, Reference, and Memo.
- Around line 107-111: The code reads args.Version (a *hexutil.Uint16) and
directly casts it to uint8 into variable version (defaulting to
types.MorphTxVersion1), which can silently truncate values >255; add explicit
validation where args.Version is handled: check that args.Version != nil and
that uint16(*args.Version) is <= 1 (allowed values 0 or 1), otherwise return a
descriptive error (e.g., "invalid Version: must be 0 or 1"); only after this
validation assign to version (cast to uint8). Ensure you reference args.Version,
the local variable version, and types.MorphTxVersion1 when applying the change.
🧹 Nitpick comments (1)
internal/ethapi/api.go (1)

1822-1877: Avoid loading full reference index before pagination.
ReadReferenceIndexEntries loads all entries then slices, which can be heavy for hot references. Consider an iterator that skips/limits in‑stream; apply the same approach to the PublicMorphAPI method.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@internal/ethapi/transaction_args.go`:
- Around line 137-139: The current check only rejects positive FeeLimit when
feeTokenID == 0; change the condition to reject any non-zero FeeLimit (i.e.,
check args.FeeLimit.ToInt().Sign() != 0) so negative values are also rejected
and return types.ErrMorphTxV1FeeLimitWithoutFeeToken; apply the same fix to the
analogous check around the other occurrence (the second check referenced in the
comment).
🧹 Nitpick comments (1)
internal/ethapi/transaction_args.go (1)

464-468: Extract duplicated MorphTx detection logic.

Lines 464-467 duplicate the condition from isMorphTxArgs(). If detection criteria change, both locations must be updated.

♻️ Proposed refactor
 	switch {
 	//	must take precedence over MaxFeePerGas.
-	case (args.FeeTokenID != nil && *args.FeeTokenID > 0) ||
-		(args.Version != nil) || // Any explicit version setting indicates MorphTx intent
-		(args.Reference != nil && *args.Reference != (common.Reference{})) ||
-		(args.Memo != nil && len(*args.Memo) > 0):
+	case args.isMorphTxArgs():
 		usedType = types.MorphTxType

Comment on lines +137 to +139
if feeTokenID == 0 && args.FeeLimit != nil && args.FeeLimit.ToInt().Sign() > 0 {
return types.ErrMorphTxV1FeeLimitWithoutFeeToken
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Negative FeeLimit values are not rejected.

The check args.FeeLimit.ToInt().Sign() > 0 only rejects positive values when FeeTokenID == 0. Negative FeeLimit values slip through since Sign() returns -1 for negatives.

🛠️ Proposed fix
-			if feeTokenID == 0 && args.FeeLimit != nil && args.FeeLimit.ToInt().Sign() > 0 {
+			if feeTokenID == 0 && args.FeeLimit != nil && args.FeeLimit.ToInt().Sign() != 0 {
 				return types.ErrMorphTxV1FeeLimitWithoutFeeToken
 			}

Similarly at line 159:

-		if !hasFeeToken && args.FeeLimit != nil && args.FeeLimit.ToInt().Sign() > 0 {
+		if !hasFeeToken && args.FeeLimit != nil && args.FeeLimit.ToInt().Sign() != 0 {
 			return types.ErrMorphTxV1FeeLimitWithoutFeeToken
 		}
🤖 Prompt for AI Agents
In `@internal/ethapi/transaction_args.go` around lines 137 - 139, The current
check only rejects positive FeeLimit when feeTokenID == 0; change the condition
to reject any non-zero FeeLimit (i.e., check args.FeeLimit.ToInt().Sign() != 0)
so negative values are also rejected and return
types.ErrMorphTxV1FeeLimitWithoutFeeToken; apply the same fix to the analogous
check around the other occurrence (the second check referenced in the comment).

ethCost = new(big.Int).Add(cost, l1DataFee)
} else {
ethCost = new(big.Int).Add(tx.Cost(), l1DataFee)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to merge else if tx.IsMorphTx() with the final else if we roll back the tx.Cost() function?

// - args.reference: the reference key to query
// - args.offset: pagination offset (default: 0)
// - args.limit: pagination limit (default: 100, max: 100)
func (s *PublicMorphAPI) GetTransactionHashesByReference(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it better that the two GetTransactionHashesByReference() methods share a common code base?

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.

4 participants