Skip to content

Commit

Permalink
Get block notifications API
Browse files Browse the repository at this point in the history
  • Loading branch information
lock9 committed Jan 6, 2025
1 parent 4d2b88d commit c1849a5
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
101 changes: 101 additions & 0 deletions pkg/services/rpcsrv/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
37 changes: 37 additions & 0 deletions pkg/services/rpcsrv/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit c1849a5

Please sign in to comment.