309 lines
10 KiB
Go
Executable File
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
|
|
}
|