Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding): Add rpc endpoint juno_getBlockWithTxnHashesAndReceipts #2429

Closed
Closed
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
38 changes: 34 additions & 4 deletions rpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ type BlockWithReceipts struct {
Transactions []TransactionWithReceipt `json:"transactions"`
}

type BlockWithTxHashesAndReceipts struct {
Status BlockStatus `json:"status,omitempty"`
BlockHeader
TxnHashes []*felt.Felt `json:"transactions_hashes"`
Transactions []TransactionWithReceipt `json:"transactions_with_receipts"`
}

/****************************************************
Block Handlers
*****************************************************/
Expand Down Expand Up @@ -205,7 +212,7 @@ func (h *Handler) BlockWithTxHashes(id BlockID) (*BlockWithTxHashes, *jsonrpc.Er

return &BlockWithTxHashes{
Status: status,
BlockHeader: adaptBlockHeader(block.Header),
BlockHeader: AdaptBlockHeader(block.Header),
TxnHashes: txnHashes,
}, nil
}
Expand Down Expand Up @@ -254,11 +261,34 @@ func (h *Handler) BlockWithReceipts(id BlockID) (*BlockWithReceipts, *jsonrpc.Er

return &BlockWithReceipts{
Status: blockStatus,
BlockHeader: adaptBlockHeader(block.Header),
BlockHeader: AdaptBlockHeader(block.Header),
Transactions: txsWithReceipts,
}, nil
}

func (h *Handler) BlockWithTxHashesAndReceipts(id BlockID) (*BlockWithTxHashesAndReceipts, *jsonrpc.Error) {
// Get tx receipts
block, err := h.BlockWithReceipts(id)
if err != nil {
return nil, err
}

// Get tx hashes
txnHashes := make([]*felt.Felt, len(block.Transactions))
for index, txn := range block.Transactions {
// Get hash from Receipt and not Transaction as `starknet_getBlockWithReceipts`'s
// array of txs does not contain hash
txnHashes[index] = txn.Receipt.Hash
}

return &BlockWithTxHashesAndReceipts{
Status: block.Status,
BlockHeader: block.BlockHeader,
Transactions: block.Transactions,
TxnHashes: txnHashes,
}, nil
}

// BlockWithTxs returns the block information with full transactions given a block ID.
//
// It follows the specification defined here:
Expand All @@ -281,7 +311,7 @@ func (h *Handler) BlockWithTxs(id BlockID) (*BlockWithTxs, *jsonrpc.Error) {

return &BlockWithTxs{
Status: status,
BlockHeader: adaptBlockHeader(block.Header),
BlockHeader: AdaptBlockHeader(block.Header),
Transactions: txs,
}, nil
}
Expand All @@ -302,7 +332,7 @@ func (h *Handler) blockStatus(id BlockID, block *core.Block) (BlockStatus, *json
return status, nil
}

func adaptBlockHeader(header *core.Header) BlockHeader {
func AdaptBlockHeader(header *core.Header) BlockHeader {
var blockNumber *uint64
// if header.Hash == nil it's a pending block
if header.Hash != nil {
Expand Down
121 changes: 121 additions & 0 deletions rpc/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,127 @@ func TestBlockWithReceipts(t *testing.T) {
})
}

func TestBlockWithTxHashesAndReceipts(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

var mockSyncReader *mocks.MockSyncReader

mockReader := mocks.NewMockReader(mockCtrl)
mockSyncReader = mocks.NewMockSyncReader(mockCtrl)

n := utils.Ptr(utils.Sepolia)
handler := rpc.New(mockReader, mockSyncReader, nil, "", nil)

client := feeder.NewTestClient(t, n)
gw := adaptfeeder.New(client)

latestBlockNumber := uint64(56377)
latestBlock, err := gw.BlockByNumber(context.Background(), latestBlockNumber)
require.NoError(t, err)

// Helper function to assert actual returned block against expected one
assertBlock := func(t *testing.T, b *rpc.BlockWithTxHashesAndReceipts, blockStatus rpc.BlockStatus, txFinalityStatus rpc.TxnFinalityStatus) {
t.Helper()

// Assert block status
assert.Equal(t, blockStatus, b.Status)

// Assert block header
assert.Equal(t, rpc.AdaptBlockHeader(latestBlock.Header), b.BlockHeader)

// Assert transaction hashes
assert.Equal(t, len(latestBlock.Transactions), len(b.TxnHashes))
for i := range latestBlock.Transactions {
assert.Equal(t, latestBlock.Transactions[i].Hash(), b.TxnHashes[i])
}

// Build receipts from expected block
var txsWithReceipt []rpc.TransactionWithReceipt
for i, tx := range latestBlock.Transactions {
receipt := latestBlock.Receipts[i]
adaptedTx := rpc.AdaptTransaction(tx)
adaptedTx.Hash = nil

txsWithReceipt = append(txsWithReceipt, rpc.TransactionWithReceipt{
Transaction: adaptedTx,
Receipt: rpc.AdaptReceipt(receipt, tx, txFinalityStatus, nil, 0),
})
}
// Assert receipts
assert.Equal(t, txsWithReceipt, b.Transactions)
}

t.Run("block not found", func(t *testing.T) {
mockReader.EXPECT().BlockByNumber(latestBlock.Number).Return(nil, db.ErrKeyNotFound)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Number: latestBlock.Number})

require.Nil(t, block)
require.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

t.Run("L1Head failure", func(t *testing.T) {
expectedError := errors.New("L1Head failure")

mockReader.EXPECT().BlockByHash(latestBlock.Hash).Return(latestBlock, nil)
mockReader.EXPECT().L1Head().Return(nil, expectedError)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Hash: latestBlock.Hash})

require.Nil(t, block)
require.Equal(t, rpc.ErrInternal.CloneWithData(expectedError.Error()), rpcErr)
})

t.Run("blockID - latest", func(t *testing.T) {
mockReader.EXPECT().Head().Return(latestBlock, nil)
mockReader.EXPECT().L1Head().Return(nil, nil)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Latest: true})

require.Nil(t, rpcErr)
assertBlock(t, block, rpc.BlockAcceptedL2, rpc.TxnAcceptedOnL2)
})

t.Run("blockID - hash", func(t *testing.T) {
mockReader.EXPECT().BlockByHash(latestBlock.Hash).Return(latestBlock, nil)
mockReader.EXPECT().L1Head().Return(nil, nil)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Hash: latestBlock.Hash})

require.Nil(t, rpcErr)
assertBlock(t, block, rpc.BlockAcceptedL2, rpc.TxnAcceptedOnL2)
})

t.Run("blockID - pending", func(t *testing.T) {
mockSyncReader.EXPECT().Pending().Return(
&sync.Pending{Block: latestBlock},
nil,
)
mockReader.EXPECT().L1Head().Return(nil, nil)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Pending: true})

require.Nil(t, rpcErr)
assertBlock(t, block, rpc.BlockPending, rpc.TxnAcceptedOnL2)
})

t.Run("blockID - number accepted on l1", func(t *testing.T) {
mockReader.EXPECT().BlockByNumber(latestBlock.Number).Return(latestBlock, nil)
mockReader.EXPECT().L1Head().Return(
&core.L1Head{
BlockNumber: latestBlock.Number,
},
nil,
)

block, rpcErr := handler.BlockWithTxHashesAndReceipts(rpc.BlockID{Number: latestBlockNumber})

require.Nil(t, rpcErr)
assertBlock(t, block, rpc.BlockAcceptedL1, rpc.TxnAcceptedOnL1)
})
}

func TestRpcBlockAdaptation(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)
Expand Down
5 changes: 5 additions & 0 deletions rpc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,11 @@ func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen
Params: []jsonrpc.Parameter{{Name: "transaction_hash"}},
Handler: h.GetMessageStatus,
},
{
Name: "juno_getBlockWithTxnHashesAndReceipts",
Params: []jsonrpc.Parameter{{Name: "block_id"}},
Handler: h.BlockWithTxHashesAndReceipts,
},
}, "/v0_8"
}

Expand Down
2 changes: 1 addition & 1 deletion rpc/subscriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ func (h *Handler) sendHeader(w jsonrpc.Conn, header *core.Header, id uint64) err
Method: "starknet_subscriptionNewHeads",
Params: map[string]any{
"subscription_id": id,
"result": adaptBlockHeader(header),
"result": AdaptBlockHeader(header),
},
})
if err != nil {
Expand Down