package tutusrpc import ( "errors" "fmt" "slices" "github.com/tutus-one/tutus-chain/pkg/core/interop/runtime" "github.com/tutus-one/tutus-chain/pkg/core/mempoolevent" "github.com/tutus-one/tutus-chain/pkg/smartcontract" "github.com/tutus-one/tutus-chain/pkg/util" "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" "github.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 }