Skip to content

Commit dda1d23

Browse files
authored
fix(eth): return nil for eth transactions not found (#12999)
1 parent a1102e4 commit dda1d23

File tree

4 files changed

+54
-66
lines changed

4 files changed

+54
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# UNRELEASED
1111

1212
- feat!: actors bundle v16.0.1 & special handling for calibnet ([filecoin-project/lotus#13006](https://github.com/filecoin-project/lotus/pull/13006))
13+
- fix(eth): always return nil for eth transactions not found ([filecoin-project/lotus#12999](https://github.com/filecoin-project/lotus/pull/12999))
1314

1415
# Node and Miner v1.32.1 / 2025-03-28
1516

chain/stmgr/searchwait.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package stmgr
33
import (
44
"context"
55
"errors"
6-
"fmt"
76

87
"github.com/ipfs/go-cid"
98
"golang.org/x/xerrors"
@@ -15,6 +14,8 @@ import (
1514
"github.com/filecoin-project/lotus/chain/types"
1615
)
1716

17+
var ErrFailedToLoadMessage = errors.New("failed to load message")
18+
1819
// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already
1920
// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on
2021
// chain for at least confidence epochs without being reverted before returning.
@@ -25,22 +26,22 @@ func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confid
2526

2627
msg, err := sm.cs.GetCMessage(ctx, mcid)
2728
if err != nil {
28-
return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err)
29+
return nil, nil, cid.Undef, xerrors.Errorf("%w: %w", ErrFailedToLoadMessage, err)
2930
}
3031

3132
tsub := sm.cs.SubHeadChanges(ctx)
3233

3334
head, ok := <-tsub
3435
if !ok {
35-
return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid")
36+
return nil, nil, cid.Undef, xerrors.Errorf("SubHeadChanges stream was invalid")
3637
}
3738

3839
if len(head) != 1 {
39-
return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item")
40+
return nil, nil, cid.Undef, xerrors.Errorf("SubHeadChanges first entry should have been one item")
4041
}
4142

4243
if head[0].Type != store.HCCurrent {
43-
return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type)
44+
return nil, nil, cid.Undef, xerrors.Errorf("expected current head on SHC stream (got %s)", head[0].Type)
4445
}
4546

4647
r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head[0].Val, mcid, msg.VMMessage(), allowReplaced)
@@ -140,7 +141,7 @@ func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confid
140141
func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) {
141142
msg, err := sm.cs.GetCMessage(ctx, mcid)
142143
if err != nil {
143-
return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err)
144+
return nil, nil, cid.Undef, xerrors.Errorf("%w: %w", ErrFailedToLoadMessage, err)
144145
}
145146

146147
r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head, mcid, msg.VMMessage(), allowReplaced)

itests/eth_hash_lookup_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/filecoin-project/lotus/chain/types"
1919
"github.com/filecoin-project/lotus/chain/types/ethtypes"
2020
"github.com/filecoin-project/lotus/itests/kit"
21+
"github.com/filecoin-project/lotus/lib/must"
2122
)
2223

2324
// TestTransactionHashLookup tests to see if lotus correctly stores a mapping from ethereum transaction hash to
@@ -113,6 +114,11 @@ func TestTransactionHashLookup(t *testing.T) {
113114
require.NotEmpty(t, *chainTx.BlockHash)
114115
require.NotNil(t, chainTx.TransactionIndex)
115116
require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction
117+
118+
// test transaction that doesn't exist, should return nil
119+
receipt, err = client.EthGetTransactionReceipt(ctx, must.One(ethtypes.ParseEthHash("0x123456789012345678901234567890123456789012345678901234567890123")))
120+
require.NoError(t, err)
121+
require.Nil(t, receipt)
116122
}
117123

118124
// TestTransactionHashLookupBlsFilecoinMessage tests to see if lotus can find a BLS Filecoin Message using the transaction hash
@@ -152,8 +158,8 @@ func TestTransactionHashLookupBlsFilecoinMessage(t *testing.T) {
152158

153159
// Assert that BLS messages cannot be retrieved from the message pool until it lands
154160
// on-chain via the eth API.
155-
_, err = client.EthGetTransactionByHash(ctx, &hash)
156-
require.Error(t, err)
161+
trans, err := client.EthGetTransactionByHash(ctx, &hash)
162+
require.Nil(t, trans)
157163

158164
// Now start mining.
159165
ens.InterconnectAll().BeginMining(blocktime)

node/impl/eth/transaction.go

Lines changed: 38 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/golang-lru/arc/v2"
88
"github.com/ipfs/go-cid"
9+
ipld "github.com/ipfs/go-ipld-format"
910
"golang.org/x/xerrors"
1011

1112
"github.com/filecoin-project/go-state-types/abi"
@@ -14,6 +15,7 @@ import (
1415
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
1516
builtinevm "github.com/filecoin-project/lotus/chain/actors/builtin/evm"
1617
"github.com/filecoin-project/lotus/chain/index"
18+
"github.com/filecoin-project/lotus/chain/stmgr"
1719
"github.com/filecoin-project/lotus/chain/types"
1820
"github.com/filecoin-project/lotus/chain/types/ethtypes"
1921
)
@@ -186,28 +188,36 @@ func (e *ethTransaction) EthGetTransactionByHash(ctx context.Context, txHash *et
186188
return e.EthGetTransactionByHashLimited(ctx, txHash, api.LookbackNoLimit)
187189
}
188190

191+
func (e *ethTransaction) getCidForTransaction(ctx context.Context, txHash *ethtypes.EthHash) (cid.Cid, error) {
192+
if e.chainIndexer == nil {
193+
return cid.Undef, ErrChainIndexerDisabled
194+
}
195+
196+
c, err := e.chainIndexer.GetCidFromHash(ctx, *txHash)
197+
if err != nil {
198+
if errors.Is(err, index.ErrNotFound) {
199+
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
200+
} else {
201+
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
202+
return cid.Undef, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
203+
}
204+
}
205+
if c == cid.Undef {
206+
// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
207+
return txHash.ToCid(), nil
208+
}
209+
return c, nil
210+
}
211+
189212
func (e *ethTransaction) EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error) {
190213
// Ethereum's behavior is to return null when the txHash is invalid, so we use nil to check if txHash is valid
191214
if txHash == nil {
192215
return nil, nil
193216
}
194-
if e.chainIndexer == nil {
195-
return nil, ErrChainIndexerDisabled
196-
}
197217

198-
var c cid.Cid
199-
var err error
200-
c, err = e.chainIndexer.GetCidFromHash(ctx, *txHash)
201-
if err != nil && errors.Is(err, index.ErrNotFound) {
202-
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
203-
} else if err != nil {
204-
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
205-
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
206-
}
207-
208-
// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
209-
if c == cid.Undef {
210-
c = txHash.ToCid()
218+
c, err := e.getCidForTransaction(ctx, txHash)
219+
if err != nil {
220+
return nil, err
211221
}
212222

213223
// first, try to get the cid from mined transactions
@@ -285,29 +295,10 @@ func (e *ethTransaction) EthGetMessageCidByTransactionHash(ctx context.Context,
285295
if txHash == nil {
286296
return nil, nil
287297
}
288-
if e.chainIndexer == nil {
289-
return nil, ErrChainIndexerDisabled
290-
}
291298

292-
var c cid.Cid
293-
var err error
294-
c, err = e.chainIndexer.GetCidFromHash(ctx, *txHash)
295-
if err != nil && errors.Is(err, index.ErrNotFound) {
296-
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
297-
} else if err != nil {
298-
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
299-
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
300-
}
301-
302-
if errors.Is(err, index.ErrNotFound) {
303-
log.Debug("could not find transaction hash %s in lookup table", txHash.String())
304-
} else if e.chainIndexer != nil {
305-
return &c, nil
306-
}
307-
308-
// This isn't an eth transaction we have the mapping for, so let's try looking it up as a filecoin message
309-
if c == cid.Undef {
310-
c = txHash.ToCid()
299+
c, err := e.getCidForTransaction(ctx, txHash)
300+
if err != nil {
301+
return nil, err
311302
}
312303

313304
_, err = e.chainStore.GetSignedMessage(ctx, c)
@@ -391,30 +382,19 @@ func (e *ethTransaction) EthGetTransactionReceipt(ctx context.Context, txHash et
391382
}
392383

393384
func (e *ethTransaction) EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*api.EthTxReceipt, error) {
394-
var c cid.Cid
395-
var err error
396-
if e.chainIndexer == nil {
397-
return nil, ErrChainIndexerDisabled
398-
}
399-
400-
c, err = e.chainIndexer.GetCidFromHash(ctx, txHash)
401-
if err != nil && errors.Is(err, index.ErrNotFound) {
402-
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
403-
} else if err != nil {
404-
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
405-
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
406-
}
407-
408-
// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
409-
if c == cid.Undef {
410-
c = txHash.ToCid()
385+
c, err := e.getCidForTransaction(ctx, &txHash)
386+
if err != nil {
387+
return nil, err
411388
}
412389

413390
msgLookup, err := e.stateApi.StateSearchMsg(ctx, types.EmptyTSK, c, limit, true)
414391
if err != nil {
415-
return nil, xerrors.Errorf("failed to lookup Eth Txn %s as %s: %w", txHash, c, err)
416-
}
417-
if msgLookup == nil {
392+
if ipld.IsNotFound(err) || errors.Is(err, stmgr.ErrFailedToLoadMessage) {
393+
// error came from not being able to turn the cid into something we can find in the chainstore
394+
return nil, nil
395+
}
396+
return nil, xerrors.Errorf("could not find transaction %s: %w", txHash, err)
397+
} else if msgLookup == nil {
418398
// This is the best we can do. In theory, we could have just not indexed this
419399
// transaction, but there's no way to check that here.
420400
return nil, nil

0 commit comments

Comments
 (0)