tutus-chain/pkg/tutusrpc/filters.go

309 lines
10 KiB
Go
Executable File

package tutusrpc
import (
"errors"
"fmt"
"slices"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/interop/runtime"
"git.marketally.com/tutus-one/tutus-chain/pkg/core/mempoolevent"
"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
"git.marketally.com/tutus-one/tutus-chain/pkg/util"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
"git.marketally.com/tutus-one/tutus-chain/pkg/vm/vmstate"
)
// MaxNotificationFilterParametersCount is a reasonable filter's parameter limit
// that does not allow attackers to increase node resources usage but that
// also should be enough for real applications.
const MaxNotificationFilterParametersCount = 16
type (
// BlockFilter is a wrapper structure for the block event filter. It allows
// to filter blocks by primary index and/or by block index (allowing blocks
// since/till the specified index inclusively). nil value treated as missing
// filter.
BlockFilter struct {
Primary *byte `json:"primary,omitzero"`
Since *uint32 `json:"since,omitzero"`
Till *uint32 `json:"till,omitzero"`
}
// TxFilter is a wrapper structure for the transaction event filter. It
// allows to filter transactions by senders and/or signers. nil value treated
// as missing filter.
TxFilter struct {
Sender *util.Uint160 `json:"sender,omitzero"`
Signer *util.Uint160 `json:"signer,omitzero"`
}
// NotificationFilter is a wrapper structure representing a filter used for
// notifications generated during transaction execution. Notifications can
// be filtered by contract hash, by event name and/or by notification
// parameters. Notification parameter filters will be applied in the order
// corresponding to a produced notification's parameters. Not more than
// [MaxNotificationFilterParametersCount] parameters are accepted (see also
// [NotificationFilter.IsValid]). `Any`-typed parameter with zero value
// allows any notification parameter. Supported parameter types:
// - [smartcontract.AnyType]
// - [smartcontract.BoolType]
// - [smartcontract.IntegerType]
// - [smartcontract.ByteArrayType]
// - [smartcontract.StringType]
// - [smartcontract.Hash160Type]
// - [smartcontract.Hash256Type]
// - [smartcontract.PublicKeyType]
// - [smartcontract.SignatureType]
// nil value treated as missing filter.
NotificationFilter struct {
Contract *util.Uint160 `json:"contract,omitzero"`
Name *string `json:"name,omitzero"`
Parameters []smartcontract.Parameter `json:"parameters,omitzero"`
parametersCache []stackitem.Item
}
// ExecutionFilter is a wrapper structure used for transaction and persisting
// scripts execution events. It allows to choose failing or successful
// transactions and persisting scripts based on their VM state and/or to
// choose execution event with the specified container. nil value treated as
// missing filter.
ExecutionFilter struct {
State *string `json:"state,omitzero"`
Container *util.Uint256 `json:"container,omitzero"`
}
// NotaryRequestFilter is a wrapper structure used for notary request events.
// It allows to choose notary request events with the specified request sender,
// main transaction signer and/or type. nil value treated as missing filter.
NotaryRequestFilter struct {
Sender *util.Uint160 `json:"sender,omitzero"`
Signer *util.Uint160 `json:"signer,omitzero"`
Type *mempoolevent.Type `json:"type,omitzero"`
}
// MempoolEventFilter is a wrapper structure used for filtering mempool events.
// It allows to filter mempool events by transaction sender, signer, and/or
// event type. nil value treated as missing filter.
MempoolEventFilter struct {
Sender *util.Uint160 `json:"sender,omitzero"`
Signer *util.Uint160 `json:"signer,omitzero"`
Type *mempoolevent.Type `json:"type,omitzero"`
}
)
// SubscriptionFilter is an interface for all subscription filters.
type SubscriptionFilter interface {
// IsValid checks whether the filter is valid and returns
// a specific [ErrInvalidSubscriptionFilter] error if not.
IsValid() error
}
// ErrInvalidSubscriptionFilter is returned when the subscription filter is invalid.
var ErrInvalidSubscriptionFilter = errors.New("invalid subscription filter")
// Copy creates a deep copy of the BlockFilter. It handles nil BlockFilter correctly.
func (f *BlockFilter) Copy() *BlockFilter {
if f == nil {
return nil
}
var res = new(BlockFilter)
if f.Primary != nil {
res.Primary = new(byte)
*res.Primary = *f.Primary
}
if f.Since != nil {
res.Since = new(uint32)
*res.Since = *f.Since
}
if f.Till != nil {
res.Till = new(uint32)
*res.Till = *f.Till
}
return res
}
// IsValid implements SubscriptionFilter interface.
func (f BlockFilter) IsValid() error {
return nil
}
// Copy creates a deep copy of the TxFilter. It handles nil TxFilter correctly.
func (f *TxFilter) Copy() *TxFilter {
if f == nil {
return nil
}
var res = new(TxFilter)
if f.Sender != nil {
res.Sender = new(util.Uint160)
*res.Sender = *f.Sender
}
if f.Signer != nil {
res.Signer = new(util.Uint160)
*res.Signer = *f.Signer
}
return res
}
// IsValid implements SubscriptionFilter interface.
func (f TxFilter) IsValid() error {
return nil
}
// Copy creates a deep copy of the NotificationFilter. It handles nil
// NotificationFilter correctly.
func (f *NotificationFilter) Copy() *NotificationFilter {
if f == nil {
return nil
}
var res = new(NotificationFilter)
if f.Contract != nil {
res.Contract = new(util.Uint160)
*res.Contract = *f.Contract
}
if f.Name != nil {
res.Name = new(string)
*res.Name = *f.Name
}
if len(f.Parameters) != 0 {
res.Parameters = slices.Clone(f.Parameters)
}
return res
}
// ParametersAsStackItems returns [stackitem.Item] version of [NotificationFilter.Parameters]
// according to [smartcontract.Parameter.ToStackItem]; Notice that the result is cached
// internally in [NotificationFilter] for efficiency, so once you call this method it will
// not change even if you change any structure fields. If you need to update parameters, use
// [NotificationFilter.Copy]. It mainly should be used by server code. Must not be used
// concurrently.
func (f *NotificationFilter) ParametersAsStackItems() ([]stackitem.Item, error) {
if len(f.Parameters) == 0 {
return nil, nil
}
if f.parametersCache == nil {
f.parametersCache = make([]stackitem.Item, 0, len(f.Parameters))
for i, p := range f.Parameters {
si, err := p.ToStackItem()
if err != nil {
f.parametersCache = nil
return nil, fmt.Errorf("converting %d parameter to stack item: %w", i, err)
}
f.parametersCache = append(f.parametersCache, si)
}
}
return f.parametersCache, nil
}
// IsValid implements SubscriptionFilter interface.
func (f NotificationFilter) IsValid() error {
if f.Name != nil && len(*f.Name) > runtime.MaxEventNameLen {
return fmt.Errorf("%w: NotificationFilter name parameter must be less than %d", ErrInvalidSubscriptionFilter, runtime.MaxEventNameLen)
}
l := len(f.Parameters)
noopFilter := l > 0
if l > 0 {
if l > MaxNotificationFilterParametersCount {
return fmt.Errorf("%w: NotificationFilter's parameters number exceeded: %d > %d", ErrInvalidSubscriptionFilter, l, MaxNotificationFilterParametersCount)
}
for i, parameter := range f.Parameters {
switch parameter.Type {
case smartcontract.BoolType,
smartcontract.IntegerType,
smartcontract.ByteArrayType,
smartcontract.StringType,
smartcontract.Hash160Type,
smartcontract.Hash256Type,
smartcontract.PublicKeyType,
smartcontract.SignatureType:
noopFilter = false
case smartcontract.AnyType:
default:
return fmt.Errorf("%w: NotificationFilter type parameter %d is unsupported: %s", ErrInvalidSubscriptionFilter, i, parameter.Type)
}
if _, err := parameter.ToStackItem(); err != nil {
return fmt.Errorf("%w: NotificationFilter %d filter parameter does not correspond to any stack item: %w", ErrInvalidSubscriptionFilter, i, err)
}
}
}
if noopFilter {
return fmt.Errorf("%w: NotificationFilter cannot have all parameters of type %s", ErrInvalidSubscriptionFilter, smartcontract.AnyType)
}
return nil
}
// Copy creates a deep copy of the ExecutionFilter. It handles nil ExecutionFilter correctly.
func (f *ExecutionFilter) Copy() *ExecutionFilter {
if f == nil {
return nil
}
var res = new(ExecutionFilter)
if f.State != nil {
res.State = new(string)
*res.State = *f.State
}
if f.Container != nil {
res.Container = new(util.Uint256)
*res.Container = *f.Container
}
return res
}
// IsValid implements SubscriptionFilter interface.
func (f ExecutionFilter) IsValid() error {
if f.State != nil {
if *f.State != vmstate.Halt.String() && *f.State != vmstate.Fault.String() {
return fmt.Errorf("%w: ExecutionFilter state parameter must be either %s or %s", ErrInvalidSubscriptionFilter, vmstate.Halt, vmstate.Fault)
}
}
return nil
}
// Copy creates a deep copy of the NotaryRequestFilter. It handles nil NotaryRequestFilter correctly.
func (f *NotaryRequestFilter) Copy() *NotaryRequestFilter {
if f == nil {
return nil
}
var res = new(NotaryRequestFilter)
if f.Sender != nil {
res.Sender = new(util.Uint160)
*res.Sender = *f.Sender
}
if f.Signer != nil {
res.Signer = new(util.Uint160)
*res.Signer = *f.Signer
}
if f.Type != nil {
res.Type = new(mempoolevent.Type)
*res.Type = *f.Type
}
return res
}
// IsValid implements SubscriptionFilter interface.
func (f NotaryRequestFilter) IsValid() error {
return nil
}
// Copy creates a deep copy of the MempoolEventFilter. It handles nil MempoolEventFilter correctly.
func (f *MempoolEventFilter) Copy() *MempoolEventFilter {
if f == nil {
return nil
}
var res = new(MempoolEventFilter)
if f.Sender != nil {
res.Sender = new(util.Uint160)
*res.Sender = *f.Sender
}
if f.Signer != nil {
res.Signer = new(util.Uint160)
*res.Signer = *f.Signer
}
if f.Type != nil {
res.Type = new(mempoolevent.Type)
*res.Type = *f.Type
}
return res
}
// IsValid implements SubscriptionFilter interface.
func (f MempoolEventFilter) IsValid() error {
return nil
}