Skip to content

Commit 221bc34

Browse files
feat(rpcv10): add support for proof_facts in subscription tags for starknet_subscribeNewTransactions (#3390)
* implement response_flags * linter * linter, comments * linter * linter, comments * unit tests * minor code cleanups * cleanups * comments * second round of comments * comments * linter * implement response_flags * linter * add support for subscription tags * linter, fix for the unit tests, address the comments * linter * comments * fixes after rebase * fixes after rebase * increase codecov
1 parent 1eb924f commit 221bc34

File tree

6 files changed

+476
-53
lines changed

6 files changed

+476
-53
lines changed

rpc/handlers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ func (h *Handler) MethodsV0_10() ([]jsonrpc.Method, string) {
337337
Params: []jsonrpc.Parameter{
338338
{Name: "finality_status", Optional: true},
339339
{Name: "sender_address", Optional: true},
340+
{Name: "tags", Optional: true},
340341
},
341342
Handler: h.rpcv10Handler.SubscribeNewTransactions,
342343
},

rpc/v10/block_test.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
rpcv10 "github.com/NethermindEth/juno/rpc/v10"
1616
rpcv6 "github.com/NethermindEth/juno/rpc/v6"
1717
rpcv9 "github.com/NethermindEth/juno/rpc/v9"
18+
adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder"
1819
"github.com/NethermindEth/juno/utils"
1920
"github.com/stretchr/testify/assert"
2021
"github.com/stretchr/testify/require"
@@ -890,3 +891,201 @@ func nilToOne(f *felt.Felt) *felt.Felt {
890891
}
891892
return f
892893
}
894+
895+
func TestBlockWithTxsWithResponseFlags(t *testing.T) {
896+
mockCtrl := gomock.NewController(t)
897+
t.Cleanup(mockCtrl.Finish)
898+
899+
network := &utils.Sepolia
900+
client := feeder.NewTestClient(t, network)
901+
gw := adaptfeeder.New(client)
902+
903+
block, err := gw.BlockByNumber(t.Context(), 4072139)
904+
require.NoError(t, err)
905+
require.NotNil(t, block)
906+
require.Greater(t, len(block.Transactions), 0)
907+
908+
// Count invoke v3 transactions with proof_facts and total invoke v3 transactions
909+
var invokeV3WithProofFactsCount int
910+
var invokeV3Count int
911+
for _, tx := range block.Transactions {
912+
if invokeTx, ok := tx.(*core.InvokeTransaction); ok {
913+
if invokeTx.Version != nil && invokeTx.Version.Is(3) {
914+
invokeV3Count++
915+
if invokeTx.ProofFacts != nil {
916+
invokeV3WithProofFactsCount++
917+
}
918+
}
919+
}
920+
}
921+
require.Greater(
922+
t,
923+
invokeV3WithProofFactsCount,
924+
0,
925+
"Block should contain at least one invoke v3 transaction with proof_facts",
926+
)
927+
928+
mockReader := mocks.NewMockReader(mockCtrl)
929+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
930+
931+
blockID := rpcv9.BlockIDFromNumber(block.Header.Number)
932+
mockReader.EXPECT().BlockByNumber(block.Header.Number).Return(block, nil).AnyTimes()
933+
mockReader.EXPECT().Network().Return(network).AnyTimes()
934+
mockReader.EXPECT().L1Head().Return(core.L1Head{}, nil).AnyTimes()
935+
936+
mockReader.EXPECT().BlockCommitmentsByNumber(block.Header.Number).Return(&core.BlockCommitments{
937+
TransactionCommitment: &felt.Zero,
938+
EventCommitment: &felt.Zero,
939+
ReceiptCommitment: &felt.Zero,
940+
StateDiffCommitment: &felt.Zero,
941+
}, nil).AnyTimes()
942+
mockReader.EXPECT().StateUpdateByNumber(block.Header.Number).Return(&core.StateUpdate{
943+
StateDiff: &core.StateDiff{},
944+
}, nil).AnyTimes()
945+
946+
handler := rpcv10.New(mockReader, mockSyncReader, nil, utils.NewNopZapLogger())
947+
948+
t.Run("WithResponseFlag", func(t *testing.T) {
949+
responseFlags := rpcv10.ResponseFlags{IncludeProofFacts: true}
950+
blockWithTxs, rpcErr := handler.BlockWithTxs(&blockID, responseFlags)
951+
require.Nil(t, rpcErr)
952+
require.NotNil(t, blockWithTxs)
953+
954+
// Verify total number of transactions is the same
955+
require.Equal(t, len(block.Transactions), len(blockWithTxs.Transactions))
956+
957+
// Count transactions with proof_facts in response
958+
var txsWithProofFactsCount int
959+
for _, tx := range blockWithTxs.Transactions {
960+
if tx.ProofFacts != nil {
961+
txsWithProofFactsCount++
962+
}
963+
}
964+
965+
// Verify number of transactions with proof_facts matches expected
966+
require.Equal(
967+
t,
968+
invokeV3WithProofFactsCount,
969+
txsWithProofFactsCount,
970+
"Number of transactions with proof_facts should match",
971+
)
972+
})
973+
974+
t.Run("WithoutResponseFlag", func(t *testing.T) {
975+
blockWithTxs, rpcErr := handler.BlockWithTxs(&blockID, rpcv10.ResponseFlags{})
976+
require.Nil(t, rpcErr)
977+
require.NotNil(t, blockWithTxs)
978+
979+
// Verify total number of transactions is the same
980+
require.Equal(t, len(block.Transactions), len(blockWithTxs.Transactions))
981+
982+
// Verify no transactions have proof_facts when flag is not set
983+
for _, tx := range blockWithTxs.Transactions {
984+
require.Nil(t, tx.ProofFacts, "proof_facts should not be included when flag is not set")
985+
}
986+
})
987+
}
988+
989+
func TestBlockWithReceiptsWithResponseFlags(t *testing.T) {
990+
mockCtrl := gomock.NewController(t)
991+
t.Cleanup(mockCtrl.Finish)
992+
993+
network := &utils.Sepolia
994+
client := feeder.NewTestClient(t, network)
995+
gw := adaptfeeder.New(client)
996+
997+
block, err := gw.BlockByNumber(t.Context(), 4072139)
998+
require.NoError(t, err)
999+
require.NotNil(t, block)
1000+
require.Greater(t, len(block.Transactions), 0)
1001+
require.Equal(
1002+
t,
1003+
len(block.Transactions),
1004+
len(block.Receipts),
1005+
"Block should have receipts for all transactions",
1006+
)
1007+
1008+
// Count invoke v3 transactions with proof_facts
1009+
var invokeV3WithProofFactsCount int
1010+
for _, tx := range block.Transactions {
1011+
if invokeTx, ok := tx.(*core.InvokeTransaction); ok {
1012+
if invokeTx.Version != nil && invokeTx.Version.Is(3) && invokeTx.ProofFacts != nil {
1013+
invokeV3WithProofFactsCount++
1014+
}
1015+
}
1016+
}
1017+
require.Greater(
1018+
t,
1019+
invokeV3WithProofFactsCount, 0,
1020+
"Block should contain at least one invoke v3 transaction with proof_facts",
1021+
)
1022+
1023+
// Count all transactions
1024+
totalTxCount := len(block.Transactions)
1025+
1026+
mockReader := mocks.NewMockReader(mockCtrl)
1027+
mockSyncReader := mocks.NewMockSyncReader(mockCtrl)
1028+
1029+
blockID := rpcv9.BlockIDFromNumber(block.Header.Number)
1030+
mockReader.EXPECT().BlockByNumber(block.Header.Number).Return(block, nil).AnyTimes()
1031+
mockReader.EXPECT().Network().Return(network).AnyTimes()
1032+
mockReader.EXPECT().L1Head().Return(core.L1Head{}, nil).AnyTimes()
1033+
1034+
mockReader.EXPECT().BlockCommitmentsByNumber(block.Header.Number).Return(&core.BlockCommitments{
1035+
TransactionCommitment: &felt.Zero,
1036+
EventCommitment: &felt.Zero,
1037+
ReceiptCommitment: &felt.Zero,
1038+
StateDiffCommitment: &felt.Zero,
1039+
}, nil).AnyTimes()
1040+
mockReader.EXPECT().StateUpdateByNumber(block.Header.Number).Return(&core.StateUpdate{
1041+
StateDiff: &core.StateDiff{},
1042+
}, nil).AnyTimes()
1043+
1044+
handler := rpcv10.New(mockReader, mockSyncReader, nil, utils.NewNopZapLogger())
1045+
1046+
t.Run("WithResponseFlag", func(t *testing.T) {
1047+
responseFlags := rpcv10.ResponseFlags{IncludeProofFacts: true}
1048+
blockWithReceipts, rpcErr := handler.BlockWithReceipts(&blockID, responseFlags)
1049+
require.Nil(t, rpcErr)
1050+
require.NotNil(t, blockWithReceipts)
1051+
1052+
// Verify total number of transactions is the same
1053+
require.Equal(t, totalTxCount, len(blockWithReceipts.Transactions))
1054+
1055+
// Count transactions with proof_facts in response
1056+
var txsWithProofFactsCount int
1057+
for _, txWithReceipt := range blockWithReceipts.Transactions {
1058+
if txWithReceipt.Transaction.ProofFacts != nil {
1059+
txsWithProofFactsCount++
1060+
}
1061+
}
1062+
1063+
// Verify number of transactions with proof_facts matches expected
1064+
require.Equal(
1065+
t,
1066+
invokeV3WithProofFactsCount,
1067+
txsWithProofFactsCount,
1068+
"Number of transactions with proof_facts should match",
1069+
)
1070+
})
1071+
1072+
t.Run("WithoutResponseFlag", func(t *testing.T) {
1073+
t.Run("WithoutResponseFlag", func(t *testing.T) {
1074+
blockWithReceipts, rpcErr := handler.BlockWithReceipts(&blockID, rpcv10.ResponseFlags{})
1075+
require.Nil(t, rpcErr)
1076+
require.NotNil(t, blockWithReceipts)
1077+
1078+
// Verify total number of transactions is the same
1079+
require.Equal(t, len(block.Transactions), len(blockWithReceipts.Transactions))
1080+
1081+
// Verify no transactions have proof_facts when flag is not set
1082+
for _, tx := range blockWithReceipts.Transactions {
1083+
require.Nil(
1084+
t,
1085+
tx.Transaction.ProofFacts,
1086+
"proof_facts should not be included when flag is not set",
1087+
)
1088+
}
1089+
})
1090+
})
1091+
}

rpc/v10/response_flags.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,27 @@ func (r *ResponseFlags) UnmarshalJSON(data []byte) error {
2727

2828
return nil
2929
}
30+
31+
type SubscriptionTags struct {
32+
IncludeProofFacts bool
33+
}
34+
35+
func (r *SubscriptionTags) UnmarshalJSON(data []byte) error {
36+
var flags []string
37+
if err := json.Unmarshal(data, &flags); err != nil {
38+
return err
39+
}
40+
41+
*r = SubscriptionTags{}
42+
43+
for _, flag := range flags {
44+
switch flag {
45+
case "INCLUDE_PROOF_FACTS":
46+
r.IncludeProofFacts = true
47+
default:
48+
return fmt.Errorf("unknown flag: %s", flag)
49+
}
50+
}
51+
52+
return nil
53+
}

rpc/v10/response_flags_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,57 @@ func TestResponseFlags_UnmarshalJSON(t *testing.T) {
5656
})
5757
}
5858
}
59+
60+
func TestSubscriptionTags_UnmarshalJSON(t *testing.T) {
61+
t.Parallel()
62+
63+
tests := []struct {
64+
name string
65+
json string
66+
expected rpcv10.SubscriptionTags
67+
expectedError string
68+
}{
69+
{
70+
name: "empty array",
71+
json: `[]`,
72+
expected: rpcv10.SubscriptionTags{IncludeProofFacts: false},
73+
},
74+
{
75+
name: "array with INCLUDE_PROOF_FACTS",
76+
json: `["INCLUDE_PROOF_FACTS"]`,
77+
expected: rpcv10.SubscriptionTags{IncludeProofFacts: true},
78+
},
79+
{
80+
name: "array with unknown flag and valid flag",
81+
json: `["INCLUDE_PROOF_FACTS", "UNKNOWN_FLAG"]`,
82+
expectedError: "unknown flag: UNKNOWN_FLAG",
83+
},
84+
{
85+
name: "case sensitive",
86+
json: `["include_proof_facts"]`,
87+
expectedError: "unknown flag: include_proof_facts",
88+
},
89+
{
90+
name: "invalid JSON",
91+
json: `{"not": "an array"}`,
92+
expectedError: "cannot unmarshal",
93+
},
94+
}
95+
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
t.Parallel()
99+
var tags rpcv10.SubscriptionTags
100+
err := json.Unmarshal([]byte(tt.json), &tags)
101+
102+
if tt.expectedError != "" {
103+
require.Error(t, err)
104+
require.Contains(t, err.Error(), tt.expectedError)
105+
return
106+
}
107+
108+
require.NoError(t, err)
109+
require.Equal(t, tt.expected, tags)
110+
})
111+
}
112+
}

0 commit comments

Comments
 (0)