Skip to content

Commit 4f0d355

Browse files
authored
fix(rpcv6): Add check for pending block in TransactionReceiptByHash (#2642)
* fix: Add check for pending block * fix: Add tests
1 parent e746325 commit 4f0d355

File tree

2 files changed

+283
-8
lines changed

2 files changed

+283
-8
lines changed

rpc/v6/transaction.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -501,14 +501,48 @@ func (h *Handler) TransactionByBlockIDAndIndex(id BlockID, txIndex int) (*Transa
501501
// It follows the specification defined here:
502502
// https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json#L222
503503
func (h *Handler) TransactionReceiptByHash(hash felt.Felt) (*TransactionReceipt, *jsonrpc.Error) {
504+
var (
505+
pendingB *core.Block
506+
pendingBIndex int
507+
)
508+
504509
txn, err := h.bcReader.TransactionByHash(&hash)
505510
if err != nil {
506-
return nil, rpccore.ErrTxnHashNotFound
511+
if !errors.Is(err, db.ErrKeyNotFound) {
512+
return nil, rpccore.ErrInternal.CloneWithData(err)
513+
}
514+
515+
pendingB = h.syncReader.PendingBlock()
516+
if pendingB == nil {
517+
return nil, rpccore.ErrTxnHashNotFound
518+
}
519+
520+
for i, t := range pendingB.Transactions {
521+
if hash.Equal(t.Hash()) {
522+
pendingBIndex = i
523+
txn = t
524+
break
525+
}
526+
}
527+
528+
if txn == nil {
529+
return nil, rpccore.ErrTxnHashNotFound
530+
}
507531
}
508532

509-
receipt, blockHash, blockNumber, err := h.bcReader.Receipt(&hash)
510-
if err != nil {
511-
return nil, rpccore.ErrTxnHashNotFound
533+
var (
534+
receipt *core.TransactionReceipt
535+
blockHash *felt.Felt
536+
blockNumber uint64
537+
)
538+
539+
if pendingB != nil {
540+
receipt = pendingB.Receipts[pendingBIndex]
541+
} else {
542+
receipt, blockHash, blockNumber, err = h.bcReader.Receipt(&hash)
543+
if err != nil {
544+
return nil, rpccore.ErrTxnHashNotFound
545+
}
512546
}
513547

514548
status := TxnAcceptedOnL2

rpc/v6/transaction_test.go

+245-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/NethermindEth/juno/core"
1212
"github.com/NethermindEth/juno/core/felt"
1313
"github.com/NethermindEth/juno/db"
14+
"github.com/NethermindEth/juno/jsonrpc"
1415
"github.com/NethermindEth/juno/mocks"
1516
rpccore "github.com/NethermindEth/juno/rpc/rpccore"
1617
rpc "github.com/NethermindEth/juno/rpc/v6"
@@ -587,6 +588,243 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) {
587588
})
588589
}
589590

591+
func TestTransactionReceiptByHash(t *testing.T) {
592+
t.Run("internal error when returning tx by hash from DB", func(t *testing.T) {
593+
mockCtrl := gomock.NewController(t)
594+
t.Cleanup(mockCtrl.Finish)
595+
596+
mockReader := mocks.NewMockReader(mockCtrl)
597+
handler := rpc.New(mockReader, nil, nil, "", &utils.Sepolia, nil)
598+
599+
mockReader.EXPECT().TransactionByHash(gomock.Any()).Return(nil, errors.New("some internal error"))
600+
601+
txHash := new(felt.Felt).SetBytes([]byte("some tx hash"))
602+
tx, rpcErr := handler.TransactionReceiptByHash(*txHash)
603+
604+
assert.Nil(t, tx)
605+
assert.Equal(t, rpccore.ErrInternal.CloneWithData(errors.New("some internal error")), rpcErr)
606+
})
607+
608+
t.Run("not found if pending block nil", func(t *testing.T) {
609+
mockCtrl := gomock.NewController(t)
610+
t.Cleanup(mockCtrl.Finish)
611+
612+
mockReader := mocks.NewMockReader(mockCtrl)
613+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
614+
handler := rpc.New(mockReader, mockSyncReader, nil, "", &utils.Sepolia, nil)
615+
616+
mockReader.EXPECT().TransactionByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound)
617+
mockSyncReader.EXPECT().PendingBlock().Return(nil)
618+
619+
txHash := new(felt.Felt).SetBytes([]byte("some tx hash"))
620+
tx, rpcErr := handler.TransactionReceiptByHash(*txHash)
621+
622+
assert.Nil(t, tx)
623+
assert.Equal(t, rpccore.ErrTxnHashNotFound, rpcErr)
624+
})
625+
626+
t.Run("not found in non-nil pending block", func(t *testing.T) {
627+
mockCtrl := gomock.NewController(t)
628+
t.Cleanup(mockCtrl.Finish)
629+
630+
mockReader := mocks.NewMockReader(mockCtrl)
631+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
632+
n := &utils.Sepolia
633+
handler := rpc.New(mockReader, mockSyncReader, nil, "", n, nil)
634+
635+
mockReader.EXPECT().TransactionByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound)
636+
637+
client := feeder.NewTestClient(t, n)
638+
gateway := adaptfeeder.New(client)
639+
mockSyncReader.EXPECT().PendingBlock().DoAndReturn(func() *core.Block {
640+
block, err := gateway.BlockByNumber(context.Background(), 4850)
641+
require.NoError(t, err)
642+
643+
return block
644+
})
645+
646+
unexistingTxHashInBlock := utils.HexToFelt(t, "0x123")
647+
tx, rpcErr := handler.TransactionReceiptByHash(*unexistingTxHashInBlock)
648+
649+
assert.Nil(t, tx)
650+
assert.Equal(t, rpccore.ErrTxnHashNotFound, rpcErr)
651+
})
652+
653+
t.Run("receipt not found (even though tx is found)", func(t *testing.T) {
654+
mockCtrl := gomock.NewController(t)
655+
t.Cleanup(mockCtrl.Finish)
656+
657+
mockReader := mocks.NewMockReader(mockCtrl)
658+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
659+
n := &utils.Sepolia
660+
handler := rpc.New(mockReader, mockSyncReader, nil, "", n, nil)
661+
662+
client := feeder.NewTestClient(t, n)
663+
gateway := adaptfeeder.New(client)
664+
block, err := gateway.BlockByNumber(context.Background(), 4850)
665+
require.NoError(t, err)
666+
667+
tx0HashInBlock4850 := utils.HexToFelt(t, "0x236102aee88702cfa0546d84e54967e3de1ec6b784bc27364bbbdd25931140c")
668+
669+
mockReader.EXPECT().TransactionByHash(tx0HashInBlock4850).Return(block.Transactions[0], nil)
670+
mockReader.EXPECT().Receipt(tx0HashInBlock4850).Return(nil, nil, uint64(0), errors.New("some error"))
671+
672+
txReceipt, rpcErr := handler.TransactionReceiptByHash(*tx0HashInBlock4850)
673+
674+
assert.Nil(t, txReceipt)
675+
assert.Equal(t, rpccore.ErrTxnHashNotFound, rpcErr)
676+
})
677+
678+
t.Run("internal error when verifying finality status", func(t *testing.T) {
679+
mockCtrl := gomock.NewController(t)
680+
t.Cleanup(mockCtrl.Finish)
681+
682+
mockReader := mocks.NewMockReader(mockCtrl)
683+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
684+
n := &utils.Sepolia
685+
handler := rpc.New(mockReader, mockSyncReader, nil, "", n, nil)
686+
687+
client := feeder.NewTestClient(t, n)
688+
gateway := adaptfeeder.New(client)
689+
block, err := gateway.BlockByNumber(context.Background(), 4850)
690+
require.NoError(t, err)
691+
692+
tx0HashInBlock4850 := utils.HexToFelt(t, "0x236102aee88702cfa0546d84e54967e3de1ec6b784bc27364bbbdd25931140c")
693+
694+
mockReader.EXPECT().TransactionByHash(tx0HashInBlock4850).Return(block.Transactions[0], nil)
695+
mockReader.EXPECT().Receipt(tx0HashInBlock4850).Return(block.Receipts[0], block.Hash, block.Number, nil)
696+
mockReader.EXPECT().L1Head().Return(nil, errors.New("some internal error"))
697+
698+
txReceipt, rpcErr := handler.TransactionReceiptByHash(*tx0HashInBlock4850)
699+
700+
assert.Nil(t, txReceipt)
701+
assert.Equal(t, jsonrpc.Err(jsonrpc.InternalError, "some internal error"), rpcErr)
702+
})
703+
704+
t.Run("found in pending block", func(t *testing.T) {
705+
mockCtrl := gomock.NewController(t)
706+
t.Cleanup(mockCtrl.Finish)
707+
708+
mockReader := mocks.NewMockReader(mockCtrl)
709+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
710+
n := &utils.Sepolia
711+
handler := rpc.New(mockReader, mockSyncReader, nil, "", n, nil)
712+
713+
mockReader.EXPECT().TransactionByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound)
714+
715+
client := feeder.NewTestClient(t, n)
716+
gateway := adaptfeeder.New(client)
717+
mockSyncReader.EXPECT().PendingBlock().DoAndReturn(func() *core.Block {
718+
block, err := gateway.BlockByNumber(context.Background(), 4850)
719+
require.NoError(t, err)
720+
721+
return block
722+
})
723+
724+
tx0HashInBlock4850 := utils.HexToFelt(t, "0x236102aee88702cfa0546d84e54967e3de1ec6b784bc27364bbbdd25931140c")
725+
txReceipt, rpcErr := handler.TransactionReceiptByHash(*tx0HashInBlock4850)
726+
727+
expectedReceipt := rpc.TransactionReceipt{
728+
FinalityStatus: rpc.TxnAcceptedOnL2,
729+
ExecutionStatus: rpc.TxnSuccess,
730+
Type: rpc.TxnInvoke,
731+
Hash: tx0HashInBlock4850,
732+
ActualFee: &rpc.FeePayment{
733+
Amount: utils.HexToFelt(t, "0x4e7f9f784c0"),
734+
Unit: rpc.WEI,
735+
},
736+
// nil because pending block
737+
BlockHash: nil,
738+
BlockNumber: nil,
739+
MessagesSent: []*rpc.MsgToL1{},
740+
Events: []*rpc.Event{{
741+
From: utils.HexToFelt(t, "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
742+
Keys: []*felt.Felt{
743+
utils.HexToFelt(t, "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9"),
744+
},
745+
Data: []*felt.Felt{
746+
utils.HexToFelt(t, "0x60664b576dae484dc3430ed3b1036e7879712e2c2c2728f568b8dbcbbc0f655"),
747+
utils.HexToFelt(t, "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8"),
748+
utils.HexToFelt(t, "0x4e7f9f784c0"),
749+
utils.HexToFelt(t, "0x0"),
750+
},
751+
}},
752+
ExecutionResources: &rpc.ExecutionResources{
753+
ComputationResources: rpc.ComputationResources{
754+
Steps: 6172,
755+
Pedersen: 16,
756+
RangeCheck: 208,
757+
Ecdsa: 1,
758+
},
759+
},
760+
}
761+
762+
assert.Nil(t, rpcErr)
763+
assert.Equal(t, &expectedReceipt, txReceipt)
764+
})
765+
766+
t.Run("found in (non-pending) block", func(t *testing.T) {
767+
mockCtrl := gomock.NewController(t)
768+
t.Cleanup(mockCtrl.Finish)
769+
770+
mockReader := mocks.NewMockReader(mockCtrl)
771+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
772+
n := &utils.Sepolia
773+
handler := rpc.New(mockReader, mockSyncReader, nil, "", n, nil)
774+
775+
client := feeder.NewTestClient(t, n)
776+
gateway := adaptfeeder.New(client)
777+
block, err := gateway.BlockByNumber(context.Background(), 4850)
778+
require.NoError(t, err)
779+
780+
tx0HashInBlock4850 := utils.HexToFelt(t, "0x236102aee88702cfa0546d84e54967e3de1ec6b784bc27364bbbdd25931140c")
781+
782+
mockReader.EXPECT().TransactionByHash(tx0HashInBlock4850).Return(block.Transactions[0], nil)
783+
mockReader.EXPECT().Receipt(tx0HashInBlock4850).Return(block.Receipts[0], block.Hash, block.Number, nil)
784+
mockReader.EXPECT().L1Head().Return(&core.L1Head{BlockNumber: 4851}, nil) // block number not very important here
785+
786+
txReceipt, rpcErr := handler.TransactionReceiptByHash(*tx0HashInBlock4850)
787+
788+
expectedBlockNumber := uint64(4850)
789+
expectedReceipt := rpc.TransactionReceipt{
790+
FinalityStatus: rpc.TxnAcceptedOnL1,
791+
ExecutionStatus: rpc.TxnSuccess,
792+
Type: rpc.TxnInvoke,
793+
Hash: tx0HashInBlock4850,
794+
ActualFee: &rpc.FeePayment{
795+
Amount: utils.HexToFelt(t, "0x4e7f9f784c0"),
796+
Unit: rpc.WEI,
797+
},
798+
BlockHash: utils.HexToFelt(t, "0x410dfca0f99545e62aef946e228329ce3a906f6785f5e6f97389f30ad1c1088"),
799+
BlockNumber: &expectedBlockNumber,
800+
MessagesSent: []*rpc.MsgToL1{},
801+
Events: []*rpc.Event{{
802+
From: utils.HexToFelt(t, "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
803+
Keys: []*felt.Felt{
804+
utils.HexToFelt(t, "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9"),
805+
},
806+
Data: []*felt.Felt{
807+
utils.HexToFelt(t, "0x60664b576dae484dc3430ed3b1036e7879712e2c2c2728f568b8dbcbbc0f655"),
808+
utils.HexToFelt(t, "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8"),
809+
utils.HexToFelt(t, "0x4e7f9f784c0"),
810+
utils.HexToFelt(t, "0x0"),
811+
},
812+
}},
813+
ExecutionResources: &rpc.ExecutionResources{
814+
ComputationResources: rpc.ComputationResources{
815+
Steps: 6172,
816+
Pedersen: 16,
817+
RangeCheck: 208,
818+
Ecdsa: 1,
819+
},
820+
},
821+
}
822+
823+
assert.Nil(t, rpcErr)
824+
assert.Equal(t, &expectedReceipt, txReceipt)
825+
})
826+
}
827+
590828
//nolint:dupl
591829
func TestLegacyTransactionReceiptByHash(t *testing.T) {
592830
t.Skip()
@@ -1295,8 +1533,10 @@ func TestTransactionStatus(t *testing.T) {
12951533
for description, notFoundTest := range notFoundTests {
12961534
t.Run(description, func(t *testing.T) {
12971535
mockReader := mocks.NewMockReader(mockCtrl)
1536+
syncReader := mocks.NewMockSyncReader(mockCtrl)
12981537
mockReader.EXPECT().TransactionByHash(notFoundTest.hash).Return(nil, db.ErrKeyNotFound).Times(2)
1299-
handler := rpc.New(mockReader, nil, nil, "", test.network, nil)
1538+
syncReader.EXPECT().PendingBlock().Return(nil).Times(2)
1539+
handler := rpc.New(mockReader, syncReader, nil, "", test.network, nil)
13001540
_, err := handler.TransactionStatus(ctx, *notFoundTest.hash)
13011541
require.Equal(t, rpccore.ErrTxnHashNotFound.Code, err.Code)
13021542

@@ -1309,11 +1549,12 @@ func TestTransactionStatus(t *testing.T) {
13091549
}
13101550
})
13111551

1312-
// transaction no† found in db and feeder
1313-
t.Run("transaction not found in db and feeder ", func(t *testing.T) {
1552+
t.Run("transaction not found in db and feeder", func(t *testing.T) {
13141553
mockReader := mocks.NewMockReader(mockCtrl)
1554+
syncReader := mocks.NewMockSyncReader(mockCtrl)
13151555
mockReader.EXPECT().TransactionByHash(test.notFoundTxHash).Return(nil, db.ErrKeyNotFound)
1316-
handler := rpc.New(mockReader, nil, nil, "", test.network, nil).WithFeeder(client)
1556+
syncReader.EXPECT().PendingBlock().Return(nil)
1557+
handler := rpc.New(mockReader, syncReader, nil, "", test.network, nil).WithFeeder(client)
13171558

13181559
_, err := handler.TransactionStatus(ctx, *test.notFoundTxHash)
13191560
require.NotNil(t, err)

0 commit comments

Comments
 (0)