diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 0771a6e6d9..9dbef08551 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -31,6 +31,8 @@ type Reader interface { BlockHeaderByNumber(number uint64) (header *core.Header, err error) BlockHeaderByHash(hash *felt.Felt) (header *core.Header, err error) + BlockNumberByHash(hash *felt.Felt) (uint64, error) + TransactionByHash(hash *felt.Felt) (transaction core.Transaction, err error) TransactionByBlockNumberAndIndex(blockNumber, index uint64) (transaction core.Transaction, err error) Receipt(hash *felt.Felt) (receipt *core.TransactionReceipt, blockHash *felt.Felt, blockNumber uint64, err error) @@ -150,6 +152,11 @@ func (b *Blockchain) BlockHeaderByNumber(number uint64) (*core.Header, error) { return core.GetBlockHeaderByNumber(b.database, number) } +func (b *Blockchain) BlockNumberByHash(hash *felt.Felt) (uint64, error) { + b.listener.OnRead("BlockNumberByHash") + return core.GetBlockHeaderNumberByHash(b.database, hash) +} + func (b *Blockchain) BlockByHash(hash *felt.Felt) (*core.Block, error) { b.listener.OnRead("BlockByHash") blockNum, err := core.GetBlockHeaderNumberByHash(b.database, hash) diff --git a/mocks/mock_blockchain.go b/mocks/mock_blockchain.go index 193726354f..70157f42ec 100644 --- a/mocks/mock_blockchain.go +++ b/mocks/mock_blockchain.go @@ -74,6 +74,19 @@ func (mr *MockReaderMockRecorder) BlockByNumber(number any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockByNumber", reflect.TypeOf((*MockReader)(nil).BlockByNumber), number) } +func (m *MockReader) BlockNumberByHash(hash *felt.Felt) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockNumberByHash", hash) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (mr *MockReaderMockRecorder) BlockNumberByHash(hash any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockNumberByHash", reflect.TypeOf((*MockReader)(nil).BlockNumberByHash), hash) +} + // BlockCommitmentsByNumber mocks base method. func (m *MockReader) BlockCommitmentsByNumber(blockNumber uint64) (*core.BlockCommitments, error) { m.ctrl.T.Helper() diff --git a/rpc/v6/transaction.go b/rpc/v6/transaction.go index 760e27697c..4a1e5b85a2 100644 --- a/rpc/v6/transaction.go +++ b/rpc/v6/transaction.go @@ -484,11 +484,16 @@ func (h *Handler) TransactionByHash(hash felt.Felt) (*Transaction, *jsonrpc.Erro // // It follows the specification defined here: // https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json#L184 -func (h *Handler) TransactionByBlockIDAndIndex(id BlockID, txIndex int) (*Transaction, *jsonrpc.Error) { +func (h *Handler) TransactionByBlockIDAndIndex( + id BlockID, + txIndex int, +) (*Transaction, *jsonrpc.Error) { if txIndex < 0 { return nil, rpccore.ErrInvalidTxIndex } + var blockNumber uint64 + var err error if id.Pending { pending, err := h.PendingData() if err != nil { @@ -500,14 +505,22 @@ func (h *Handler) TransactionByBlockIDAndIndex(id BlockID, txIndex int) (*Transa } return AdaptTransaction(pending.GetBlock().Transactions[txIndex]), nil + } else if id.Latest { + header, err := h.bcReader.HeadsHeader() + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + blockNumber = header.Number + } else if id.Hash != nil { + blockNumber, err = h.bcReader.BlockNumberByHash(id.Hash) + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + } else { + blockNumber = id.Number } - header, rpcErr := h.blockHeaderByID(&id) - if rpcErr != nil { - return nil, rpcErr - } - - txn, err := h.bcReader.TransactionByBlockNumberAndIndex(header.Number, uint64(txIndex)) + txn, err := h.bcReader.TransactionByBlockNumberAndIndex(blockNumber, uint64(txIndex)) if err != nil { return nil, rpccore.ErrInvalidTxIndex } diff --git a/rpc/v6/transaction_test.go b/rpc/v6/transaction_test.go index 83c4bd8ccc..4de6660863 100644 --- a/rpc/v6/transaction_test.go +++ b/rpc/v6/transaction_test.go @@ -476,7 +476,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block hash", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().BlockNumberByHash(gomock.Any()).Return(uint64(0), db.ErrKeyNotFound) txn, rpcErr := handler.TransactionByBlockIDAndIndex( rpc.BlockID{Hash: new(felt.Felt).SetBytes([]byte("random"))}, rand.Int()) @@ -485,11 +485,13 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block number", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByNumber(gomock.Any()).Return(nil, db.ErrKeyNotFound) + blockNumber := rpc.BlockID{Number: rand.Uint64()} - txn, rpcErr := handler.TransactionByBlockIDAndIndex(rpc.BlockID{Number: rand.Uint64()}, rand.Int()) + mockReader.EXPECT().TransactionByBlockNumberAndIndex(blockNumber.Number, + gomock.Any()).Return(nil, errors.New("invalid index")) + txn, rpcErr := handler.TransactionByBlockIDAndIndex(blockNumber, rand.Int()) assert.Nil(t, txn) - assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) + assert.Equal(t, rpccore.ErrInvalidTxIndex, rpcErr) }) t.Run("negative index", func(t *testing.T) { @@ -533,7 +535,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - hash", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByHash(latestBlockHash).Return(latestBlock.Header, nil) + mockReader.EXPECT().BlockNumberByHash(latestBlockHash).Return(latestBlock.Number, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(uint64(latestBlockNumber), uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -555,7 +557,6 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - number", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByNumber(uint64(latestBlockNumber)).Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(uint64(latestBlockNumber), uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil diff --git a/rpc/v7/transaction.go b/rpc/v7/transaction.go index a1f55e2c86..a9cef03db0 100644 --- a/rpc/v7/transaction.go +++ b/rpc/v7/transaction.go @@ -392,12 +392,16 @@ func adaptRPCTxToFeederTx(rpcTx *Transaction) *starknet.Transaction { // // It follows the specification defined here: // https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json#L184 -func (h *Handler) TransactionByBlockIDAndIndex(id BlockID, txIndex int) (*Transaction, *jsonrpc.Error) { +func (h *Handler) TransactionByBlockIDAndIndex( + id BlockID, txIndex int, +) (*Transaction, *jsonrpc.Error) { if txIndex < 0 { return nil, rpccore.ErrInvalidTxIndex } - if id.Pending { + var blockNumber uint64 + var err error + if id.IsPending() { pending, err := h.PendingData() if err != nil { return nil, rpccore.ErrBlockNotFound @@ -408,14 +412,22 @@ func (h *Handler) TransactionByBlockIDAndIndex(id BlockID, txIndex int) (*Transa } return AdaptTransaction(pending.GetBlock().Transactions[txIndex]), nil + } else if id.IsLatest() { + header, err := h.bcReader.HeadsHeader() + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + blockNumber = header.Number + } else if id.Hash != nil { + blockNumber, err = h.bcReader.BlockNumberByHash(id.Hash) + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + } else { + blockNumber = id.Number } - header, rpcErr := h.blockHeaderByID(&id) - if rpcErr != nil { - return nil, rpcErr - } - - txn, err := h.bcReader.TransactionByBlockNumberAndIndex(header.Number, uint64(txIndex)) + txn, err := h.bcReader.TransactionByBlockNumberAndIndex(blockNumber, uint64(txIndex)) if err != nil { return nil, rpccore.ErrInvalidTxIndex } diff --git a/rpc/v7/transaction_test.go b/rpc/v7/transaction_test.go index d4a4cb89b2..60fad477b6 100644 --- a/rpc/v7/transaction_test.go +++ b/rpc/v7/transaction_test.go @@ -45,20 +45,22 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block hash", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().BlockNumberByHash(gomock.Any()).Return(uint64(0), db.ErrKeyNotFound) txn, rpcErr := handler.TransactionByBlockIDAndIndex( rpc.BlockID{Hash: new(felt.Felt).SetBytes([]byte("random"))}, rand.Int()) + assert.Nil(t, txn) assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) }) t.Run("non-existent block number", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByNumber(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().TransactionByBlockNumberAndIndex(gomock.Any(), + gomock.Any()).Return(nil, errors.New("invalid index")) txn, rpcErr := handler.TransactionByBlockIDAndIndex(rpc.BlockID{Number: rand.Uint64()}, rand.Int()) assert.Nil(t, txn) - assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) + assert.Equal(t, rpccore.ErrInvalidTxIndex, rpcErr) }) t.Run("negative index", func(t *testing.T) { @@ -107,7 +109,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - hash", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByHash(latestBlockHash).Return(latestBlock.Header, nil) + mockReader.EXPECT().BlockNumberByHash(latestBlockHash).Return(latestBlock.Number, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(uint64(latestBlockNumber), uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -134,7 +136,6 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - number", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByNumber(uint64(latestBlockNumber)).Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(uint64(latestBlockNumber), uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil diff --git a/rpc/v8/transaction.go b/rpc/v8/transaction.go index e3a742cd95..51de163e30 100644 --- a/rpc/v8/transaction.go +++ b/rpc/v8/transaction.go @@ -509,7 +509,10 @@ func (h *Handler) TransactionByBlockIDAndIndex( return nil, rpccore.ErrInvalidTxIndex } - if blockID.IsPending() { + var blockNumber uint64 + var err error + switch blockID.Type() { + case pending: pending, err := h.PendingData() if err != nil { return nil, rpccore.ErrBlockNotFound @@ -520,14 +523,25 @@ func (h *Handler) TransactionByBlockIDAndIndex( } return AdaptTransaction(pending.GetBlock().Transactions[txIndex]), nil + case latest: + header, err := h.bcReader.HeadsHeader() + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + blockNumber = header.Number + case hash: + blockNumber, err = h.bcReader.BlockNumberByHash(blockID.Hash()) + case number: + blockNumber = blockID.Number() + default: + panic("unknown block type id") } - header, rpcErr := h.blockHeaderByID(blockID) - if rpcErr != nil { - return nil, rpcErr + if err != nil { + return nil, rpccore.ErrBlockNotFound } - txn, err := h.bcReader.TransactionByBlockNumberAndIndex(header.Number, uint64(txIndex)) + txn, err := h.bcReader.TransactionByBlockNumberAndIndex(blockNumber, uint64(txIndex)) if err != nil { return nil, rpccore.ErrInvalidTxIndex } diff --git a/rpc/v8/transaction_test.go b/rpc/v8/transaction_test.go index 911fd82cb5..97ca1f21cd 100644 --- a/rpc/v8/transaction_test.go +++ b/rpc/v8/transaction_test.go @@ -476,7 +476,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block hash", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().BlockNumberByHash(gomock.Any()).Return(uint64(0), db.ErrKeyNotFound) blockID := blockIDHash(t, new(felt.Felt).SetBytes([]byte("random"))) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, rand.Int()) @@ -485,12 +485,13 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block number", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByNumber(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().TransactionByBlockNumberAndIndex(gomock.Any(), + gomock.Any()).Return(nil, errors.New("invalid index")) blockID := blockIDNumber(t, rand.Uint64()) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, rand.Int()) assert.Nil(t, txn) - assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) + assert.Equal(t, rpccore.ErrInvalidTxIndex, rpcErr) }) t.Run("negative index", func(t *testing.T) { @@ -537,7 +538,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - hash", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByHash(latestBlockHash).Return(latestBlock.Header, nil) + mockReader.EXPECT().BlockNumberByHash(latestBlockHash).Return(latestBlock.Number, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -560,7 +561,6 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - number", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByNumber(latestBlockNumber).Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil diff --git a/rpc/v9/transaction.go b/rpc/v9/transaction.go index ce22b49d30..7e1954136e 100644 --- a/rpc/v9/transaction.go +++ b/rpc/v9/transaction.go @@ -537,7 +537,10 @@ func (h *Handler) TransactionByBlockIDAndIndex( return nil, rpccore.ErrInvalidTxIndex } - if blockID.IsPreConfirmed() { + var blockNumber uint64 + var err error + switch blockID.Type() { + case preConfirmed: pending, err := h.PendingData() if err != nil { return nil, rpccore.ErrBlockNotFound @@ -548,14 +551,32 @@ func (h *Handler) TransactionByBlockIDAndIndex( } return AdaptTransaction(pending.GetBlock().Transactions[txIndex]), nil + case latest: + header, err := h.bcReader.HeadsHeader() + if err != nil { + return nil, rpccore.ErrBlockNotFound + } + blockNumber = header.Number + case hash: + blockNumber, err = h.bcReader.BlockNumberByHash(blockID.Hash()) + case number: + blockNumber = blockID.Number() + case l1Accepted: + var l1Head core.L1Head + l1Head, err = h.bcReader.L1Head() + if err != nil { + break + } + blockNumber = l1Head.BlockNumber + default: + panic("unknown block type id") } - header, rpcErr := h.blockHeaderByID(blockID) - if rpcErr != nil { - return nil, rpcErr + if err != nil { + return nil, rpccore.ErrBlockNotFound } - txn, err := h.bcReader.TransactionByBlockNumberAndIndex(header.Number, uint64(txIndex)) + txn, err := h.bcReader.TransactionByBlockNumberAndIndex(blockNumber, uint64(txIndex)) if err != nil { return nil, rpccore.ErrInvalidTxIndex } diff --git a/rpc/v9/transaction_test.go b/rpc/v9/transaction_test.go index d805fe1a18..ca4582c073 100644 --- a/rpc/v9/transaction_test.go +++ b/rpc/v9/transaction_test.go @@ -526,7 +526,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { handler := rpc.New(mockReader, mockSyncReader, nil, nil) t.Run("empty blockchain", func(t *testing.T) { - mockReader.EXPECT().HeadsHeader().Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().HeadsHeader().Return(nil, errors.New("empty blockchain")) blockID := blockIDLatest(t) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, rand.Int()) @@ -535,7 +535,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block hash", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByHash(gomock.Any()).Return(nil, db.ErrKeyNotFound) + mockReader.EXPECT().BlockNumberByHash(gomock.Any()).Return(uint64(0), db.ErrKeyNotFound) blockID := blockIDHash(t, felt.NewFromBytes[felt.Felt]([]byte("random"))) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, rand.Int()) @@ -544,15 +544,17 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }) t.Run("non-existent block number", func(t *testing.T) { - mockReader.EXPECT().BlockHeaderByNumber(gomock.Any()).Return(nil, db.ErrKeyNotFound) - + mockReader.EXPECT().TransactionByBlockNumberAndIndex( + gomock.Any(), gomock.Any()).Return(nil, db.ErrKeyNotFound) blockID := blockIDNumber(t, rand.Uint64()) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, rand.Int()) assert.Nil(t, txn) - assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) + assert.Equal(t, rpccore.ErrInvalidTxIndex, rpcErr) }) t.Run("negative index", func(t *testing.T) { + mockReader.EXPECT().HeadsHeader().Return(nil, errors.New("negative index")) + blockID := blockIDLatest(t) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, -1) assert.Nil(t, txn) @@ -561,19 +563,16 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("invalid index", func(t *testing.T) { mockReader.EXPECT().HeadsHeader().Return(latestBlock.Header, nil) - mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, - latestBlock.TransactionCount).Return(nil, errors.New("invalid index")) blockID := blockIDLatest(t) txn, rpcErr := handler.TransactionByBlockIDAndIndex(&blockID, len(latestBlock.Transactions)) assert.Nil(t, txn) - assert.Equal(t, rpccore.ErrInvalidTxIndex, rpcErr) + assert.Equal(t, rpccore.ErrBlockNotFound, rpcErr) }) t.Run("blockID - latest", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().HeadsHeader().Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -589,7 +588,7 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - hash", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByHash(latestBlockHash).Return(latestBlock.Header, nil) + mockReader.EXPECT().BlockNumberByHash(latestBlockHash).Return(latestBlock.Number, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -605,7 +604,6 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { t.Run("blockID - number", func(t *testing.T) { index := rand.Intn(int(latestBlock.TransactionCount)) - mockReader.EXPECT().BlockHeaderByNumber(latestBlockNumber).Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil @@ -629,7 +627,6 @@ func TestTransactionByBlockIdAndIndex(t *testing.T) { }, nil, ) - mockReader.EXPECT().BlockHeaderByNumber(latestBlockNumber).Return(latestBlock.Header, nil) mockReader.EXPECT().TransactionByBlockNumberAndIndex(latestBlockNumber, uint64(index)).DoAndReturn(func(number, index uint64) (core.Transaction, error) { return latestBlock.Transactions[index], nil