diff --git a/ethrpc/ethrpc_test.go b/ethrpc/ethrpc_test.go index 866b0c55..1d32e831 100644 --- a/ethrpc/ethrpc_test.go +++ b/ethrpc/ethrpc_test.go @@ -318,6 +318,31 @@ func TestDoRequest_SeqChainHealth(t *testing.T) { assert.NotEmpty(t, expiresAt) } +func TestTransactionByHash_Katana(t *testing.T) { + p, err := ethrpc.NewProvider("https://nodes.sequence.app/katana") + require.NoError(t, err) + + ctx := context.Background() + txHash := common.HexToHash("0x7777fecc4e53f840f07ce615a3f9e491d1a45c003f1e11a7a0183426f08cc79e") + + // This transaction is an OP Stack deposit transaction (type 0x7e) which is + // not supported by the local go-ethereum types. TransactionByHash should + // return ErrTxTypeNotSupported instead of panicking. + _, _, err = p.TransactionByHash(ctx, txHash) + require.Error(t, err) + assert.ErrorIs(t, err, types.ErrTxTypeNotSupported) + + // The transaction receipt should still be fetchable since receipt JSON + // unmarshalling does not validate the transaction type. + receipt, err := p.TransactionReceipt(ctx, txHash) + require.NoError(t, err) + require.NotNil(t, receipt) + assert.Equal(t, txHash, receipt.TxHash) + assert.Equal(t, uint64(1), receipt.Status) // success + assert.Greater(t, receipt.BlockNumber.Uint64(), uint64(0)) + assert.Equal(t, uint8(0x7e), receipt.Type) +} + func TestFetchBlockWithInvalidVRS(t *testing.T) { url := "https://rpc.telos.net" // url := "https://node.mainnet.etherlink.com" diff --git a/ethrpc/unmarshal.go b/ethrpc/unmarshal.go index 0786bdcb..1e042213 100644 --- a/ethrpc/unmarshal.go +++ b/ethrpc/unmarshal.go @@ -20,8 +20,9 @@ type rpcBlock struct { } type rpcTransaction struct { - tx *types.Transaction - txVRSInvalid bool + tx *types.Transaction + txVRSInvalid bool + txTypeNotSupported bool txExtraInfo } @@ -44,6 +45,13 @@ func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { return err } + // flag unsupported txn type (e.g. OP Stack deposit txns, type 0x7e) + // and nil out tx.tx which has nil inner after the failed unmarshal. + if err == types.ErrTxTypeNotSupported { + tx.txTypeNotSupported = true + tx.tx = nil + } + // we set internal flag to check if txn has invalid VRS signature if err == types.ErrInvalidSig { tx.txVRSInvalid = true @@ -110,12 +118,10 @@ func IntoBlock(raw json.RawMessage, ret **types.Block, strictness StrictnessLeve // Fill the sender cache of transactions in the block. txs := make([]*types.Transaction, 0, len(body.Transactions)) for _, tx := range body.Transactions { - if tx.From != nil { - setSenderFromServer(tx.tx, *tx.From, body.Hash) - } - - if strictness >= StrictnessLevel_Semi && tx.txVRSInvalid { - return types.ErrInvalidSig + // Skip transactions with unsupported types (e.g. OP Stack deposit + // txns, type 0x7e) where tx.tx will be nil after unmarshalling. + if tx.tx == nil { + continue } if tx.txExtraInfo.TxType != "" { @@ -130,6 +136,14 @@ func IntoBlock(raw json.RawMessage, ret **types.Block, strictness StrictnessLeve } } + if strictness >= StrictnessLevel_Semi && tx.txVRSInvalid { + return types.ErrInvalidSig + } + + if tx.From != nil { + setSenderFromServer(tx.tx, *tx.From, body.Hash) + } + txs = append(txs, tx.tx) } @@ -170,6 +184,12 @@ func IntoTransactionWithPending(raw json.RawMessage, tx **types.Transaction, pen return ethereum.NotFound } + // tx will be nil when the transaction type is not supported by the + // local go-ethereum types (e.g. OP Stack deposit txns, type 0x7e). + if body.tx == nil { + return types.ErrTxTypeNotSupported + } + if strictness >= StrictnessLevel_Semi { if body.txVRSInvalid { return types.ErrInvalidSig