-
Notifications
You must be signed in to change notification settings - Fork 81
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
Get block notifications API #3781
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This extension should be supported at the RPC client side ( |
||
"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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need some |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return internal error? This means some DB inconsistency and the result can be incorrect. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vote up for error return. |
||
} | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to keep the sequence correct and check for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may additionally extend There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the use cases? People interested in block-level things (and not a lot happens there) probably pull block applogs directly and otherwise it's all |
||
aers, err := s.chain.GetAppExecResults(block.Hash(), trigger.Application) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if err == nil && len(aers) > 0 { | ||
aer := aers[0] | ||
if aer.VMState == vmstate.Halt { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this code can be generalized in some way (helper function?), it's the same for blocks/transactions. |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can move these out into a separate file (along with the logic for appending to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I'd say that |
||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"}]`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Genesis block doesn't contain notifications from testContract. Let's use native Gas or Neo hash as a filter. |
||
result: func(e *executor) any { return []state.ContainedNotificationEvent{} }, | ||
check: func(t *testing.T, e *executor, acc any) { | ||
res, ok := acc.([]state.ContainedNotificationEvent) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check for expected contents as well. |
||
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) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This extension should be documented at
docs/rpc.md
, see### Extensions
section.