Summary
morph_getTransactionHashesByReference returns a different order (and a different empty result) on morph-reth than on morph-geth. Both differences come from the index key layout.
1. Result order differs when consecutive blocks share a timestamp
morph-geth keys the reference index by reference || blockTimestamp || txIndex || txHash (core/rawdb/schema.go referenceIndexKey) and pages by iterating that key space, so results are ordered by (blockTimestamp, txIndex, txHash).
morph-reth keys ReferenceIndex by reference || block_number || transaction_index || transaction_hash (crates/reference-index/src/tables.rs), so results are ordered by (blockNumber, txIndex, txHash).
Since Emerald a block may carry the same timestamp as its parent, and with the default 300 ms fast block interval this is routine: in a 200-block window ending at mainnet block 26701490, 12 consecutive pairs shared a timestamp (e.g. 26701309/26701310 at 1789278633, 26701318/26701319 at 1789278648).
For a reference that appears in two such blocks, say block N at index 5 and block N+1 at index 2, morph-geth returns the block N+1 entry before the block N entry, while morph-reth returns them in chain order. Because pagination is by key order, offset/limit windows also cut at different places on the two clients, so a client paging across nodes of both kinds can see an entry twice or miss one.
The documentation (about-morph/11-morphtx.md) describes the morph-geth order ("sorted by block timestamp + tx index, ascending"), but that order is not chronological within an equal-timestamp run; the morph-reth order is. Fixing this on the morph-geth side means changing the key layout (block number instead of, or in front of, the timestamp) and re-indexing existing databases; fixing it on the morph-reth side means replicating the interleaving. Either way the two clients should agree, so I wanted to raise it here before touching either.
2. Empty result: null vs []
morph-geth returns nil, nil when the page is empty (internal/ethapi/api.go, PublicMorphAPI.GetTransactionHashesByReference), which serializes as "result": null. morph-reth returns [] (both from query_at and from the explicit pre-Jade short-circuit in crates/rpc/src/morph/handler.rs).
morph-geth is not fully consistent with itself either: if index entries exist but every ReadTxLookupEntry misses, it returns a non-nil empty slice, i.e. [].
Notes
Happy to implement whichever direction you prefer for either point (a block-number-first key on the geth side with a reindex, or matching null on the reth side).
Summary
morph_getTransactionHashesByReferencereturns a different order (and a different empty result) on morph-reth than on morph-geth. Both differences come from the index key layout.1. Result order differs when consecutive blocks share a timestamp
morph-geth keys the reference index by
reference || blockTimestamp || txIndex || txHash(core/rawdb/schema.goreferenceIndexKey) and pages by iterating that key space, so results are ordered by(blockTimestamp, txIndex, txHash).morph-reth keys
ReferenceIndexbyreference || block_number || transaction_index || transaction_hash(crates/reference-index/src/tables.rs), so results are ordered by(blockNumber, txIndex, txHash).Since Emerald a block may carry the same timestamp as its parent, and with the default 300 ms fast block interval this is routine: in a 200-block window ending at mainnet block 26701490, 12 consecutive pairs shared a timestamp (e.g. 26701309/26701310 at 1789278633, 26701318/26701319 at 1789278648).
For a reference that appears in two such blocks, say block
Nat index 5 and blockN+1at index 2, morph-geth returns the blockN+1entry before the blockNentry, while morph-reth returns them in chain order. Because pagination is by key order,offset/limitwindows also cut at different places on the two clients, so a client paging across nodes of both kinds can see an entry twice or miss one.The documentation (
about-morph/11-morphtx.md) describes the morph-geth order ("sorted by block timestamp + tx index, ascending"), but that order is not chronological within an equal-timestamp run; the morph-reth order is. Fixing this on the morph-geth side means changing the key layout (block number instead of, or in front of, the timestamp) and re-indexing existing databases; fixing it on the morph-reth side means replicating the interleaving. Either way the two clients should agree, so I wanted to raise it here before touching either.2. Empty result:
nullvs[]morph-geth returns
nil, nilwhen the page is empty (internal/ethapi/api.go,PublicMorphAPI.GetTransactionHashesByReference), which serializes as"result": null. morph-reth returns[](both fromquery_atand from the explicit pre-Jade short-circuit incrates/rpc/src/morph/handler.rs).morph-geth is not fully consistent with itself either: if index entries exist but every
ReadTxLookupEntrymisses, it returns a non-nil empty slice, i.e.[].Notes
Happy to implement whichever direction you prefer for either point (a block-number-first key on the geth side with a reindex, or matching
nullon the reth side).