Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# UNRELEASED

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

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

Expand Down
13 changes: 7 additions & 6 deletions chain/stmgr/searchwait.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package stmgr
import (
"context"
"errors"
"fmt"

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

var ErrFailedToLoadMessage = errors.New("failed to load message")

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

msg, err := sm.cs.GetCMessage(ctx, mcid)
if err != nil {
return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err)
return nil, nil, cid.Undef, xerrors.Errorf("%w: %w", ErrFailedToLoadMessage, err)
Comment thread
rvagg marked this conversation as resolved.
}

tsub := sm.cs.SubHeadChanges(ctx)

head, ok := <-tsub
if !ok {
return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid")
return nil, nil, cid.Undef, xerrors.Errorf("SubHeadChanges stream was invalid")
}

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

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

r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head[0].Val, mcid, msg.VMMessage(), allowReplaced)
Expand Down Expand Up @@ -140,7 +141,7 @@ func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confid
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) {
msg, err := sm.cs.GetCMessage(ctx, mcid)
if err != nil {
return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err)
return nil, nil, cid.Undef, xerrors.Errorf("%w: %w", ErrFailedToLoadMessage, err)
}

r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head, mcid, msg.VMMessage(), allowReplaced)
Expand Down
10 changes: 8 additions & 2 deletions itests/eth_hash_lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
"github.com/filecoin-project/lotus/lib/must"
)

// TestTransactionHashLookup tests to see if lotus correctly stores a mapping from ethereum transaction hash to
Expand Down Expand Up @@ -113,6 +114,11 @@ func TestTransactionHashLookup(t *testing.T) {
require.NotEmpty(t, *chainTx.BlockHash)
require.NotNil(t, chainTx.TransactionIndex)
require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction

// test transaction that doesn't exist, should return nil
receipt, err = client.EthGetTransactionReceipt(ctx, must.One(ethtypes.ParseEthHash("0x123456789012345678901234567890123456789012345678901234567890123")))
require.NoError(t, err)
require.Nil(t, receipt)
}

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

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

// Now start mining.
ens.InterconnectAll().BeginMining(blocktime)
Expand Down
96 changes: 38 additions & 58 deletions node/impl/eth/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/hashicorp/golang-lru/arc/v2"
"github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-state-types/abi"
Expand All @@ -14,6 +15,7 @@ import (
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
builtinevm "github.com/filecoin-project/lotus/chain/actors/builtin/evm"
"github.com/filecoin-project/lotus/chain/index"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
Expand Down Expand Up @@ -186,28 +188,36 @@ func (e *ethTransaction) EthGetTransactionByHash(ctx context.Context, txHash *et
return e.EthGetTransactionByHashLimited(ctx, txHash, api.LookbackNoLimit)
}

func (e *ethTransaction) getCidForTransaction(ctx context.Context, txHash *ethtypes.EthHash) (cid.Cid, error) {
if e.chainIndexer == nil {
return cid.Undef, ErrChainIndexerDisabled
}

c, err := e.chainIndexer.GetCidFromHash(ctx, *txHash)
if err != nil {
if errors.Is(err, index.ErrNotFound) {
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
} else {
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
return cid.Undef, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
}
}
if c == cid.Undef {
// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
return txHash.ToCid(), nil
}
return c, nil
}

func (e *ethTransaction) EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error) {
// Ethereum's behavior is to return null when the txHash is invalid, so we use nil to check if txHash is valid
if txHash == nil {
return nil, nil
}
if e.chainIndexer == nil {
return nil, ErrChainIndexerDisabled
}

var c cid.Cid
var err error
c, err = e.chainIndexer.GetCidFromHash(ctx, *txHash)
if err != nil && errors.Is(err, index.ErrNotFound) {
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
} else if err != nil {
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
}

// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
if c == cid.Undef {
c = txHash.ToCid()
c, err := e.getCidForTransaction(ctx, txHash)
if err != nil {
return nil, err
}

// first, try to get the cid from mined transactions
Expand Down Expand Up @@ -285,29 +295,10 @@ func (e *ethTransaction) EthGetMessageCidByTransactionHash(ctx context.Context,
if txHash == nil {
return nil, nil
}
if e.chainIndexer == nil {
return nil, ErrChainIndexerDisabled
}

var c cid.Cid
var err error
c, err = e.chainIndexer.GetCidFromHash(ctx, *txHash)
if err != nil && errors.Is(err, index.ErrNotFound) {
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
} else if err != nil {
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
}

if errors.Is(err, index.ErrNotFound) {
log.Debug("could not find transaction hash %s in lookup table", txHash.String())
} else if e.chainIndexer != nil {
return &c, nil
}

// This isn't an eth transaction we have the mapping for, so let's try looking it up as a filecoin message
if c == cid.Undef {
c = txHash.ToCid()
c, err := e.getCidForTransaction(ctx, txHash)
if err != nil {
return nil, err
}

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

func (e *ethTransaction) EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*api.EthTxReceipt, error) {
var c cid.Cid
var err error
if e.chainIndexer == nil {
return nil, ErrChainIndexerDisabled
}

c, err = e.chainIndexer.GetCidFromHash(ctx, txHash)
if err != nil && errors.Is(err, index.ErrNotFound) {
log.Debug("could not find transaction hash %s in chain indexer", txHash.String())
} else if err != nil {
log.Errorf("failed to lookup transaction hash %s in chain indexer: %s", txHash.String(), err)
return nil, xerrors.Errorf("failed to lookup transaction hash %s in chain indexer: %w", txHash.String(), err)
}

// This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message
if c == cid.Undef {
c = txHash.ToCid()
c, err := e.getCidForTransaction(ctx, &txHash)
if err != nil {
return nil, err
}

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