From c1849a56ff3ecc923f488e519803e0922e923c1b Mon Sep 17 00:00:00 2001 From: Ricardo Prado Date: Mon, 6 Jan 2025 16:04:00 -0300 Subject: [PATCH] Get block notifications API --- pkg/services/rpcsrv/server.go | 101 +++++++++++++++++++++++++++++ pkg/services/rpcsrv/server_test.go | 37 +++++++++++ 2 files changed, 138 insertions(+) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 194ddf1624..ceceb66b62 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -56,6 +56,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" "go.uber.org/zap" ) @@ -219,6 +220,7 @@ var rpcHandlers = map[string]func(*Server, params.Params) (any, *neorpc.Error){ "getblockhash": (*Server).getBlockHash, "getblockheader": (*Server).getBlockHeader, "getblockheadercount": (*Server).getBlockHeaderCount, + "getblocknotifications": (*Server).getBlockNotifications, "getblocksysfee": (*Server).getBlockSysFee, "getcandidates": (*Server).getCandidates, "getcommittee": (*Server).getCommittee, @@ -3202,3 +3204,102 @@ func (s *Server) getRawNotaryTransaction(reqParams params.Params) (any, *neorpc. } return tx.Bytes(), nil } + +// getBlockNotifications returns notifications from a specific block with optional filtering. +func (s *Server) getBlockNotifications(reqParams params.Params) (any, *neorpc.Error) { + param := reqParams.Value(0) + hash, respErr := s.blockHashFromParam(param) + if respErr != nil { + return nil, respErr + } + + block, err := s.chain.GetBlock(hash) + if err != nil { + return nil, neorpc.ErrUnknownBlock + } + + var filter *neorpc.NotificationFilter + if len(reqParams) > 1 { + filter = new(neorpc.NotificationFilter) + err := json.Unmarshal(reqParams[1].RawMessage, filter) + if err != nil { + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid filter: %s", err)) + } + if err := filter.IsValid(); err != nil { + return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid filter: %s", err)) + } + } + + var notifications []state.ContainedNotificationEvent + for _, tx := range block.Transactions { + aers, err := s.chain.GetAppExecResults(tx.Hash(), trigger.Application) + if err != nil { + continue + } + for _, aer := range aers { + if aer.VMState == vmstate.Halt { + for _, evt := range aer.Events { + ntf := state.ContainedNotificationEvent{ + Container: aer.Container, + NotificationEvent: evt, + } + if filter == nil || rpcevent.Matches(&testComparator{ + id: neorpc.NotificationEventID, + filter: *filter, + }, &testContainer{ntf: &ntf}) { + notifications = append(notifications, ntf) + } + } + } + } + } + + // Also check for notifications from the block itself (PostPersist) + aers, err := s.chain.GetAppExecResults(block.Hash(), trigger.Application) + if err == nil && len(aers) > 0 { + aer := aers[0] + if aer.VMState == vmstate.Halt { + for _, evt := range aer.Events { + ntf := state.ContainedNotificationEvent{ + Container: aer.Container, + NotificationEvent: evt, + } + if filter == nil || rpcevent.Matches(&testComparator{ + id: neorpc.NotificationEventID, + filter: *filter, + }, &testContainer{ntf: &ntf}) { + notifications = append(notifications, ntf) + } + } + } + } + + return notifications, nil +} + +// testComparator is a helper type for notification filtering +type testComparator struct { + id neorpc.EventID + filter neorpc.NotificationFilter +} + +func (t *testComparator) EventID() neorpc.EventID { + return t.id +} + +func (t *testComparator) Filter() neorpc.SubscriptionFilter { + return t.filter +} + +// testContainer is a helper type for notification filtering +type testContainer struct { + ntf *state.ContainedNotificationEvent +} + +func (n *testContainer) EventID() neorpc.EventID { + return neorpc.NotificationEventID +} + +func (n *testContainer) EventPayload() any { + return n.ntf +} diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index da4f7beeb2..c830a13695 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -2274,6 +2274,43 @@ var rpcTestCases = map[string][]rpcTestCase{ errCode: neorpc.InvalidParamsCode, }, }, + "getblocknotifications": { + { + name: "positive", + params: `["` + genesisBlockHash + `"]`, + result: func(e *executor) any { return []state.ContainedNotificationEvent{} }, + check: func(t *testing.T, e *executor, acc any) { + res, ok := acc.([]state.ContainedNotificationEvent) + require.True(t, ok) + require.NotNil(t, res) + }, + }, + { + name: "positive with filter", + params: `["` + genesisBlockHash + `", {"contract":"` + testContractHashLE + `", "name":"Transfer"}]`, + result: func(e *executor) any { return []state.ContainedNotificationEvent{} }, + check: func(t *testing.T, e *executor, acc any) { + res, ok := acc.([]state.ContainedNotificationEvent) + require.True(t, ok) + require.NotNil(t, res) + }, + }, + { + name: "invalid hash", + params: `["invalid"]`, + fail: true, + }, + { + name: "unknown block", + params: `["` + util.Uint256{}.StringLE() + `"]`, + fail: true, + }, + { + name: "invalid filter", + params: `["` + genesisBlockHash + `", {"invalid":"filter"}]`, + fail: true, + }, + }, } func TestRPC(t *testing.T) {