mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-03-14 03:28:39 +00:00
rpc: implement getblocknotifications API
Add new RPC method to retrieve block notifications organized by trigger type. Signed-off-by: Ricardo Prado <ricardo.prado@simpli.com.br> Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
parent
8d728b4ec1
commit
bd170fb849
7 changed files with 191 additions and 0 deletions
|
@ -248,6 +248,11 @@ block. It can be removed in future versions, but at the moment you can use it
|
|||
to see how much GAS is burned with a particular block (because system fees are
|
||||
burned).
|
||||
|
||||
#### `getblocknotifications` call
|
||||
|
||||
This method returns notifications from a block organized by trigger type.
|
||||
Supports filtering by contract and event name.
|
||||
|
||||
#### Historic calls
|
||||
|
||||
A set of `*historic` extension methods provide the ability of interacting with
|
||||
|
|
|
@ -3168,3 +3168,8 @@ func (bc *Blockchain) GetStoragePrice() int64 {
|
|||
}
|
||||
return bc.contracts.Policy.GetStoragePriceInternal(bc.dao)
|
||||
}
|
||||
|
||||
// GetTrimmedBlock returns a block with only the header and transaction hashes.
|
||||
func (bc *Blockchain) GetTrimmedBlock(hash util.Uint256) (*block.Block, error) {
|
||||
return bc.dao.GetBlock(hash)
|
||||
}
|
||||
|
|
12
pkg/neorpc/result/block_notifications.go
Normal file
12
pkg/neorpc/result/block_notifications.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
)
|
||||
|
||||
// BlockNotifications represents notifications from a block organized by trigger type.
|
||||
type BlockNotifications struct {
|
||||
PrePersistNotifications []state.ContainedNotificationEvent `json:"prepersist,omitempty"`
|
||||
TxNotifications []state.ContainedNotificationEvent `json:"transactions,omitempty"`
|
||||
PostPersistNotifications []state.ContainedNotificationEvent `json:"postpersist,omitempty"`
|
||||
}
|
|
@ -972,3 +972,12 @@ func (c *Client) GetRawNotaryPool() (*result.RawNotaryPool, error) {
|
|||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBlockNotifications returns notifications from a block organized by trigger type.
|
||||
func (c *Client) GetBlockNotifications(blockHash util.Uint256, filters ...*neorpc.NotificationFilter) (*result.BlockNotifications, error) {
|
||||
var resp = &result.BlockNotifications{}
|
||||
if err := c.performRequest("getblocknotifications", []any{blockHash.StringLE(), filters}, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
|
65
pkg/services/rpcsrv/notification_comparator.go
Normal file
65
pkg/services/rpcsrv/notification_comparator.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package rpcsrv
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/rpcevent"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
)
|
||||
|
||||
// notificationEventComparator is a comparator for notification events.
|
||||
type notificationEventComparator struct {
|
||||
filter neorpc.SubscriptionFilter
|
||||
}
|
||||
|
||||
// EventID returns the event ID for the notification event comparator.
|
||||
func (s notificationEventComparator) EventID() neorpc.EventID {
|
||||
return neorpc.NotificationEventID
|
||||
}
|
||||
|
||||
// Filter returns the filter for the notification event comparator.
|
||||
func (c notificationEventComparator) Filter() neorpc.SubscriptionFilter {
|
||||
return c.filter
|
||||
}
|
||||
|
||||
// notificationEventContainer is a container for a notification event.
|
||||
type notificationEventContainer struct {
|
||||
ntf *state.ContainedNotificationEvent
|
||||
}
|
||||
|
||||
// EventID returns the event ID for the notification event container.
|
||||
func (c notificationEventContainer) EventID() neorpc.EventID {
|
||||
return neorpc.NotificationEventID
|
||||
}
|
||||
|
||||
// EventPayload returns the payload for the notification event container.
|
||||
func (c notificationEventContainer) EventPayload() any {
|
||||
return c.ntf
|
||||
}
|
||||
|
||||
func processAppExecResults(aers []state.AppExecResult, filter *neorpc.NotificationFilter) []state.ContainedNotificationEvent {
|
||||
var notifications []state.ContainedNotificationEvent
|
||||
for _, aer := range aers {
|
||||
if aer.VMState == vmstate.Halt {
|
||||
notifications = append(notifications, filterEvents(aer.Events, aer.Container, filter)...)
|
||||
}
|
||||
}
|
||||
return notifications
|
||||
}
|
||||
|
||||
func filterEvents(events []state.NotificationEvent, container util.Uint256, filter *neorpc.NotificationFilter) []state.ContainedNotificationEvent {
|
||||
var notifications []state.ContainedNotificationEvent
|
||||
for _, evt := range events {
|
||||
ntf := state.ContainedNotificationEvent{
|
||||
Container: container,
|
||||
NotificationEvent: evt,
|
||||
}
|
||||
if filter == nil || rpcevent.Matches(¬ificationEventComparator{
|
||||
filter: *filter,
|
||||
}, ¬ificationEventContainer{ntf: &ntf}) {
|
||||
notifications = append(notifications, ntf)
|
||||
}
|
||||
}
|
||||
return notifications
|
||||
}
|
|
@ -112,6 +112,7 @@ type (
|
|||
VerifyWitness(util.Uint160, hash.Hashable, *transaction.Witness, int64) (int64, error)
|
||||
mempool.Feer // fee interface
|
||||
ContractStorageSeeker
|
||||
GetTrimmedBlock(hash util.Uint256) (*block.Block, error)
|
||||
}
|
||||
|
||||
// ContractStorageSeeker is the interface `findstorage*` handlers need to be able to
|
||||
|
@ -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,51 @@ 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.GetTrimmedBlock(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))
|
||||
}
|
||||
}
|
||||
|
||||
notifications := &result.BlockNotifications{}
|
||||
|
||||
aers, err := s.chain.GetAppExecResults(block.Hash(), trigger.OnPersist)
|
||||
if err == nil && len(aers) > 0 {
|
||||
notifications.PrePersistNotifications = processAppExecResults([]state.AppExecResult{aers[0]}, filter)
|
||||
}
|
||||
|
||||
for _, txHash := range block.Transactions {
|
||||
aers, err := s.chain.GetAppExecResults(txHash.Hash(), trigger.Application)
|
||||
if err != nil {
|
||||
return nil, neorpc.NewInternalServerError("failed to get app exec results")
|
||||
}
|
||||
notifications.TxNotifications = append(notifications.TxNotifications, processAppExecResults(aers, filter)...)
|
||||
}
|
||||
|
||||
aers, err = s.chain.GetAppExecResults(block.Hash(), trigger.PostPersist)
|
||||
if err == nil && len(aers) > 0 {
|
||||
notifications.PostPersistNotifications = processAppExecResults([]state.AppExecResult{aers[0]}, filter)
|
||||
}
|
||||
|
||||
return notifications, nil
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
|
||||
|
@ -2274,6 +2275,50 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
errCode: neorpc.InvalidParamsCode,
|
||||
},
|
||||
},
|
||||
"getblocknotifications": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["` + genesisBlockHash + `"]`,
|
||||
result: func(e *executor) any { return &result.BlockNotifications{} },
|
||||
check: func(t *testing.T, e *executor, acc any) {
|
||||
res, ok := acc.(*result.BlockNotifications)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, res)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive with filter",
|
||||
params: `["` + genesisBlockHash + `", {"contract":"` + nativehashes.NeoToken.StringLE() + `", "name":"Transfer"}]`,
|
||||
result: func(e *executor) any { return &result.BlockNotifications{} },
|
||||
check: func(t *testing.T, e *executor, acc any) {
|
||||
res, ok := acc.(*result.BlockNotifications)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, res)
|
||||
for _, ne := range res.TxNotifications {
|
||||
require.Equal(t, nativehashes.NeoToken, ne.ScriptHash)
|
||||
require.Equal(t, "Transfer", ne.Name)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid hash",
|
||||
params: `["invalid"]`,
|
||||
fail: true,
|
||||
errCode: neorpc.InvalidParamsCode,
|
||||
},
|
||||
{
|
||||
name: "unknown block",
|
||||
params: `["` + util.Uint256{}.StringLE() + `"]`,
|
||||
fail: true,
|
||||
errCode: neorpc.ErrUnknownBlockCode,
|
||||
},
|
||||
{
|
||||
name: "invalid filter",
|
||||
params: `["` + genesisBlockHash + `", {"contract":"invalid"}]`,
|
||||
fail: true,
|
||||
errCode: neorpc.InvalidParamsCode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestRPC(t *testing.T) {
|
||||
|
|
Loading…
Add table
Reference in a new issue