Skip to content

Commit

Permalink
Split Status field into FinalityStatus and ExecutionStatus in RPCs
Browse files Browse the repository at this point in the history
- Add ExecutionStatus to receipt type
- Replace Status with FinalityStatus on receipt type
- Return a struct from juno_getTransactionStatus containing the
  execution and finality statuses
  • Loading branch information
joshklop committed Jul 21, 2023
1 parent 65458e4 commit 3c3ee0f
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 55 deletions.
5 changes: 5 additions & 0 deletions clients/feeder/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ type BuiltinInstanceCounter struct {
}

type TransactionReceipt struct {
// NOTE: finality_status is included on receipts retrieved from the get_transaction_receipt
// endpoint, but is not included when receipt is in a block. We do not include the field
// with an omitempty tag since it could cause very confusing behaviour. If the finality
// status is needed, use get_block.

ActualFee *felt.Felt `json:"actual_fee"`
Events []*Event `json:"events"`
ExecutionStatus ExecutionStatus `json:"execution_status"`
Expand Down
19 changes: 0 additions & 19 deletions rpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package rpc
import (
"encoding/json"
"errors"
"fmt"

"github.com/NethermindEth/juno/core/felt"
)
Expand Down Expand Up @@ -33,24 +32,6 @@ func (s BlockStatus) MarshalJSON() ([]byte, error) {
}
}

func (s *BlockStatus) UnmarshalJSON(bytes []byte) (err error) {
str := string(bytes)
switch str {
case "PENDING":
*s = BlockPending
case "ACCEPTED_ON_L2":
*s = BlockAcceptedL2
case "ACCEPTED_ON_L1":
*s = BlockAcceptedL1
case "REJECTED":
*s = BlockRejected
default:
err = fmt.Errorf("unknown block status %q", str)
}

return
}

// https://github.com/starkware-libs/starknet-specs/blob/a789ccc3432c57777beceaa53a34a7ae2f25fda0/api/starknet_api_openrpc.json#L520-L534
type BlockHashAndNumber struct {
Hash *felt.Felt `json:"block_hash"`
Expand Down
55 changes: 41 additions & 14 deletions rpc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ func (h *Handler) TransactionReceiptByHash(hash felt.Felt) (*TransactionReceipt,
}

var receiptBlockNumber *uint64
status := BlockAcceptedL2
status := TxnAcceptedOnL2

if blockHash != nil {
receiptBlockNumber = &blockNumber
Expand All @@ -432,12 +432,20 @@ func (h *Handler) TransactionReceiptByHash(hash felt.Felt) (*TransactionReceipt,
}

if isL1Verified(blockNumber, l1H) {
status = BlockAcceptedL1
status = TxnAcceptedOnL1
}
}

var es TxnExecutionStatus
if receipt.Reverted {
es = TxnFailure
} else {
es = TxnSuccess
}

return &TransactionReceipt{
Status: status,
FinalityStatus: status,
ExecutionStatus: es,
Type: txn.Type,
Hash: txn.Hash,
ActualFee: receipt.Fee,
Expand Down Expand Up @@ -1039,24 +1047,43 @@ func (h *Handler) Call(call FunctionCall, id BlockID) ([]*felt.Felt, *jsonrpc.Er
return res, nil
}

func (h *Handler) TransactionStatus(hash felt.Felt) (BlockStatus, *jsonrpc.Error) {
var status BlockStatus
func (h *Handler) TransactionStatus(hash felt.Felt) (*TransactionStatus, *jsonrpc.Error) {
var status *TransactionStatus

receipt, txErr := h.TransactionReceiptByHash(hash)
if txErr == nil {
status = receipt.Status
} else if txErr == ErrTxnHashNotFound {
switch txErr {
case nil:
status = &TransactionStatus{
Finality: receipt.FinalityStatus,
Execution: receipt.ExecutionStatus,
}
case ErrTxnHashNotFound:
txStatus, err := h.feederClient.Transaction(context.Background(), &hash)
if err != nil {
return 0, jsonrpc.Err(jsonrpc.InternalError, err.Error())
return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error())
}

err = status.UnmarshalJSON([]byte(txStatus.Status))
if err != nil {
return 0, jsonrpc.Err(jsonrpc.InternalError, err.Error())
status = new(TransactionStatus)

switch txStatus.FinalityStatus {
case feeder.AcceptedOnL1:
status.Finality = TxnAcceptedOnL1
case feeder.AcceptedOnL2:
status.Finality = TxnAcceptedOnL2
default:
return nil, jsonrpc.Err(jsonrpc.InternalError, "unknown FinalityStatus")
}
} else {
return 0, txErr

switch txStatus.ExecutionStatus {
case feeder.Succeeded:
status.Execution = TxnSuccess
case feeder.Reverted:
status.Execution = TxnFailure
default:
return nil, jsonrpc.Err(jsonrpc.InternalError, "unknown ExecutionStatus")
}
default:
return nil, txErr
}

return status, nil
Expand Down
41 changes: 28 additions & 13 deletions rpc/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,8 @@ func TestTransactionReceiptByHash(t *testing.T) {
"type": "DEPLOY",
"transaction_hash": "0xe0a2e45a80bb827967e096bcf58874f6c01c191e0a0530624cba66a508ae75",
"actual_fee": "0x0",
"status": "ACCEPTED_ON_L2",
"finality_status": "ACCEPTED_ON_L2",
"execution_status": "SUCCESS",
"block_hash": "0x47c3637b57c2b079b93c61539950c17e868a28f46cdef28f88521067f21e943",
"block_number": 0,
"messages_sent": [],
Expand All @@ -852,7 +853,8 @@ func TestTransactionReceiptByHash(t *testing.T) {
"type": "INVOKE",
"transaction_hash": "0xce54bbc5647e1c1ea4276c01a708523f740db0ff5474c77734f73beec2624",
"actual_fee": "0x0",
"status": "ACCEPTED_ON_L2",
"finality_status": "ACCEPTED_ON_L2",
"execution_status": "SUCCESS",
"block_hash": "0x47c3637b57c2b079b93c61539950c17e868a28f46cdef28f88521067f21e943",
"block_number": 0,
"messages_sent": [
Expand Down Expand Up @@ -886,7 +888,8 @@ func TestTransactionReceiptByHash(t *testing.T) {
"type": "INVOKE",
"transaction_hash": "0xce54bbc5647e1c1ea4276c01a708523f740db0ff5474c77734f73beec2624",
"actual_fee": "0x0",
"status": "ACCEPTED_ON_L2",
"finality_status": "ACCEPTED_ON_L2",
"execution_status": "SUCCESS",
"messages_sent": [
{
"from_address": "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6",
Expand All @@ -913,7 +916,8 @@ func TestTransactionReceiptByHash(t *testing.T) {
"type": "INVOKE",
"transaction_hash": "0xce54bbc5647e1c1ea4276c01a708523f740db0ff5474c77734f73beec2624",
"actual_fee": "0x0",
"status": "ACCEPTED_ON_L1",
"finality_status": "ACCEPTED_ON_L1",
"execution_status": "SUCCESS",
"block_hash": "0x47c3637b57c2b079b93c61539950c17e868a28f46cdef28f88521067f21e943",
"block_number": 0,
"messages_sent": [
Expand Down Expand Up @@ -1953,15 +1957,15 @@ func TestVersion(t *testing.T) {
func TestTransactionStatus(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)
hash := utils.HexToFelt(t, "0x1b4d9f09276629d496af1af8ff00173c11ff146affacb1b5c858d7aa89001ae")

client, closeFn := feeder.NewTestClient(utils.MAINNET)
network := utils.INTEGRATION
client, closeFn := feeder.NewTestClient(network)
t.Cleanup(closeFn)

t.Run("found l1", func(t *testing.T) {
gw := adaptfeeder.New(client)

block, err := gw.BlockByNumber(context.Background(), 1)
block, err := gw.BlockLatest(context.Background())
require.NoError(t, err)

tx := block.Transactions[0]
Expand All @@ -1972,11 +1976,15 @@ func TestTransactionStatus(t *testing.T) {
mockReader.EXPECT().Receipt(tx.Hash()).Return(block.Receipts[0], block.Hash, block.Number, nil)
mockReader.EXPECT().L1Head().Return(nil, nil)

handler := rpc.New(mockReader, nil, utils.MAINNET, nil, nil, "", nil)
handler := rpc.New(mockReader, nil, network, nil, nil, "", nil)

want := &rpc.TransactionStatus{
Finality: rpc.TxnAcceptedOnL2,
Execution: rpc.TxnSuccess,
}
status, rpcErr := handler.TransactionStatus(*tx.Hash())
require.Nil(t, rpcErr)
require.Equal(t, rpc.BlockAcceptedL2, status)
require.Equal(t, want, status)
})
t.Run("verified", func(t *testing.T) {
mockReader := mocks.NewMockReader(mockCtrl)
Expand All @@ -1986,21 +1994,28 @@ func TestTransactionStatus(t *testing.T) {
BlockNumber: block.Number + 1,
}, nil)

handler := rpc.New(mockReader, nil, utils.MAINNET, nil, nil, "", nil)
handler := rpc.New(mockReader, nil, network, nil, nil, "", nil)

want := &rpc.TransactionStatus{
Finality: rpc.TxnAcceptedOnL1,
Execution: rpc.TxnSuccess,
}
status, rpcErr := handler.TransactionStatus(*tx.Hash())
require.Nil(t, rpcErr)
require.Equal(t, rpc.BlockAcceptedL1, status)
require.Equal(t, want, status)
})
})
t.Run("transaction not found in db", func(t *testing.T) {
hash := utils.HexToFelt(t, "0x5e91283c1c04c3f88e4a98070df71227fb44dea04ce349c7eb379f85a10d1c3")

mockReader := mocks.NewMockReader(mockCtrl)
mockReader.EXPECT().TransactionByHash(hash).Return(nil, db.ErrKeyNotFound)
handler := rpc.New(mockReader, nil, utils.MAINNET, nil, client, "", nil)
handler := rpc.New(mockReader, nil, network, nil, client, "", nil)

status, err := handler.TransactionStatus(*hash)
require.Nil(t, err)
require.Equal(t, rpc.BlockAcceptedL1, status)
require.Equal(t, rpc.TxnAcceptedOnL1, status.Finality)
require.Equal(t, rpc.TxnSuccess, status.Execution)
})
}

Expand Down
60 changes: 51 additions & 9 deletions rpc/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ func (t *TransactionType) UnmarshalJSON(data []byte) error {
return nil
}

type TxnExecutionStatus uint8

const (
TxnSuccess TxnExecutionStatus = iota + 1
TxnFailure
)

func (es TxnExecutionStatus) MarshalJSON() ([]byte, error) {
switch es {
case TxnSuccess:
return []byte("\"SUCCESS\""), nil
case TxnFailure:
return []byte("\"FAILURE\""), nil
default:
return nil, errors.New("unknown ExecutionStatus")
}
}

type TxnFinalityStatus uint8

const (
TxnAcceptedOnL1 TxnFinalityStatus = iota + 1
TxnAcceptedOnL2
)

func (fs TxnFinalityStatus) MarshalJSON() ([]byte, error) {
switch fs {
case TxnAcceptedOnL1:
return []byte("\"ACCEPTED_ON_L1\""), nil
case TxnAcceptedOnL2:
return []byte("\"ACCEPTED_ON_L2\""), nil
default:
return nil, errors.New("unknown FinalityStatus")
}
}

// https://github.com/starkware-libs/starknet-specs/blob/a789ccc3432c57777beceaa53a34a7ae2f25fda0/api/starknet_api_openrpc.json#L1252
//
//nolint:lll
Expand All @@ -85,6 +121,11 @@ type Transaction struct {
CompiledClassHash *felt.Felt `json:"compiled_class_hash,omitempty" validate:"required_if=Type DECLARE Version 0x2"`
}

type TransactionStatus struct {
Finality TxnFinalityStatus `json:"finality_status"`
Execution TxnExecutionStatus `json:"execution_status"`
}

type MsgToL1 struct {
From *felt.Felt `json:"from_address"`
To common.Address `json:"to_address"`
Expand All @@ -99,15 +140,16 @@ type Event struct {

// https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json#L1871
type TransactionReceipt struct {
Type TransactionType `json:"type"`
Hash *felt.Felt `json:"transaction_hash"`
ActualFee *felt.Felt `json:"actual_fee"`
Status BlockStatus `json:"status"`
BlockHash *felt.Felt `json:"block_hash,omitempty"`
BlockNumber *uint64 `json:"block_number,omitempty"`
MessagesSent []*MsgToL1 `json:"messages_sent"`
Events []*Event `json:"events"`
ContractAddress *felt.Felt `json:"contract_address,omitempty"`
Type TransactionType `json:"type"`
Hash *felt.Felt `json:"transaction_hash"`
ActualFee *felt.Felt `json:"actual_fee"`
ExecutionStatus TxnExecutionStatus `json:"execution_status"`
FinalityStatus TxnFinalityStatus `json:"finality_status"`
BlockHash *felt.Felt `json:"block_hash,omitempty"`
BlockNumber *uint64 `json:"block_number,omitempty"`
MessagesSent []*MsgToL1 `json:"messages_sent"`
Events []*Event `json:"events"`
ContractAddress *felt.Felt `json:"contract_address,omitempty"`
}

type AddInvokeTxResponse struct {
Expand Down

0 comments on commit 3c3ee0f

Please sign in to comment.