github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/state/notification_event.go (about)

     1  package state
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/io"
     9  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    10  	"github.com/nspcc-dev/neo-go/pkg/util"
    11  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
    13  )
    14  
    15  // NotificationEvent is a tuple of the scripthash that has emitted the Item as a
    16  // notification and the item itself.
    17  type NotificationEvent struct {
    18  	ScriptHash util.Uint160     `json:"contract"`
    19  	Name       string           `json:"eventname"`
    20  	Item       *stackitem.Array `json:"state"`
    21  }
    22  
    23  // AppExecResult represents the result of the script execution, gathering together
    24  // all resulting notifications, state, stack and other metadata.
    25  type AppExecResult struct {
    26  	Container util.Uint256
    27  	Execution
    28  }
    29  
    30  // ContainedNotificationEvent represents a wrapper for a notification from script execution.
    31  type ContainedNotificationEvent struct {
    32  	// Container hash is the hash of script container which is either a block or a transaction.
    33  	Container util.Uint256
    34  	NotificationEvent
    35  }
    36  
    37  // EncodeBinary implements the Serializable interface.
    38  func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
    39  	ne.EncodeBinaryWithContext(w, stackitem.NewSerializationContext())
    40  }
    41  
    42  // EncodeBinaryWithContext is the same as EncodeBinary, but allows to efficiently reuse
    43  // stack item serialization context.
    44  func (ne *NotificationEvent) EncodeBinaryWithContext(w *io.BinWriter, sc *stackitem.SerializationContext) {
    45  	ne.ScriptHash.EncodeBinary(w)
    46  	w.WriteString(ne.Name)
    47  	b, err := sc.Serialize(ne.Item, false)
    48  	if err != nil {
    49  		w.Err = err
    50  		return
    51  	}
    52  	w.WriteBytes(b)
    53  }
    54  
    55  // DecodeBinary implements the Serializable interface.
    56  func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
    57  	ne.ScriptHash.DecodeBinary(r)
    58  	ne.Name = r.ReadString()
    59  	item := stackitem.DecodeBinary(r)
    60  	if r.Err != nil {
    61  		return
    62  	}
    63  	arr, ok := item.Value().([]stackitem.Item)
    64  	if !ok {
    65  		r.Err = errors.New("Array or Struct expected")
    66  		return
    67  	}
    68  	ne.Item = stackitem.NewArray(arr)
    69  }
    70  
    71  // EncodeBinary implements the Serializable interface.
    72  func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
    73  	aer.EncodeBinaryWithContext(w, stackitem.NewSerializationContext())
    74  }
    75  
    76  // EncodeBinaryWithContext is the same as EncodeBinary, but allows to efficiently reuse
    77  // stack item serialization context.
    78  func (aer *AppExecResult) EncodeBinaryWithContext(w *io.BinWriter, sc *stackitem.SerializationContext) {
    79  	w.WriteBytes(aer.Container[:])
    80  	w.WriteB(byte(aer.Trigger))
    81  	w.WriteB(byte(aer.VMState))
    82  	w.WriteU64LE(uint64(aer.GasConsumed))
    83  	// Stack items are expected to be marshaled one by one.
    84  	w.WriteVarUint(uint64(len(aer.Stack)))
    85  	for _, it := range aer.Stack {
    86  		b, err := sc.Serialize(it, true)
    87  		if err != nil {
    88  			w.Err = err
    89  			return
    90  		}
    91  		w.WriteBytes(b)
    92  	}
    93  	w.WriteVarUint(uint64(len(aer.Events)))
    94  	for i := range aer.Events {
    95  		aer.Events[i].EncodeBinaryWithContext(w, sc)
    96  	}
    97  	w.WriteVarBytes([]byte(aer.FaultException))
    98  }
    99  
   100  // DecodeBinary implements the Serializable interface.
   101  func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
   102  	r.ReadBytes(aer.Container[:])
   103  	aer.Trigger = trigger.Type(r.ReadB())
   104  	aer.VMState = vmstate.State(r.ReadB())
   105  	aer.GasConsumed = int64(r.ReadU64LE())
   106  	sz := r.ReadVarUint()
   107  	if stackitem.MaxDeserialized < sz && r.Err == nil {
   108  		r.Err = errors.New("invalid format")
   109  	}
   110  	if r.Err != nil {
   111  		return
   112  	}
   113  	arr := make([]stackitem.Item, sz)
   114  	for i := 0; i < int(sz); i++ {
   115  		arr[i] = stackitem.DecodeBinaryProtected(r)
   116  		if r.Err != nil {
   117  			return
   118  		}
   119  	}
   120  	aer.Stack = arr
   121  	r.ReadArray(&aer.Events)
   122  	aer.FaultException = r.ReadString()
   123  }
   124  
   125  // notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling.
   126  type notificationEventAux struct {
   127  	ScriptHash util.Uint160    `json:"contract"`
   128  	Name       string          `json:"eventname"`
   129  	Item       json.RawMessage `json:"state"`
   130  }
   131  
   132  // MarshalJSON implements the json.Marshaler interface.
   133  func (ne NotificationEvent) MarshalJSON() ([]byte, error) {
   134  	item, err := stackitem.ToJSONWithTypes(ne.Item)
   135  	if err != nil {
   136  		item = []byte(fmt.Sprintf(`"error: %v"`, err))
   137  	}
   138  	return json.Marshal(&notificationEventAux{
   139  		ScriptHash: ne.ScriptHash,
   140  		Name:       ne.Name,
   141  		Item:       item,
   142  	})
   143  }
   144  
   145  // UnmarshalJSON implements the json.Unmarshaler interface.
   146  func (ne *NotificationEvent) UnmarshalJSON(data []byte) error {
   147  	aux := new(notificationEventAux)
   148  	if err := json.Unmarshal(data, aux); err != nil {
   149  		return err
   150  	}
   151  	item, err := stackitem.FromJSONWithTypes(aux.Item)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	if t := item.Type(); t != stackitem.ArrayT {
   156  		return fmt.Errorf("failed to convert notification event state of type %s to array", t.String())
   157  	}
   158  	ne.Item = item.(*stackitem.Array)
   159  	ne.Name = aux.Name
   160  	ne.ScriptHash = aux.ScriptHash
   161  	return nil
   162  }
   163  
   164  // appExecResultAux is an auxiliary struct for JSON marshalling.
   165  type appExecResultAux struct {
   166  	Container util.Uint256 `json:"container"`
   167  }
   168  
   169  // MarshalJSON implements the json.Marshaler interface.
   170  func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
   171  	h, err := json.Marshal(&appExecResultAux{
   172  		Container: aer.Container,
   173  	})
   174  	if err != nil {
   175  		return nil, fmt.Errorf("failed to marshal hash: %w", err)
   176  	}
   177  	exec, err := json.Marshal(aer.Execution)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("failed to marshal execution: %w", err)
   180  	}
   181  
   182  	if h[len(h)-1] != '}' || exec[0] != '{' {
   183  		return nil, errors.New("can't merge internal jsons")
   184  	}
   185  	h[len(h)-1] = ','
   186  	h = append(h, exec[1:]...)
   187  	return h, nil
   188  }
   189  
   190  // UnmarshalJSON implements the json.Unmarshaler interface.
   191  func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
   192  	aux := new(appExecResultAux)
   193  	if err := json.Unmarshal(data, aux); err != nil {
   194  		return err
   195  	}
   196  	if err := json.Unmarshal(data, &aer.Execution); err != nil {
   197  		return err
   198  	}
   199  	aer.Container = aux.Container
   200  	return nil
   201  }
   202  
   203  // Execution represents the result of a single script execution, gathering together
   204  // all resulting notifications, state, stack and other metadata.
   205  type Execution struct {
   206  	Trigger        trigger.Type
   207  	VMState        vmstate.State
   208  	GasConsumed    int64
   209  	Stack          []stackitem.Item
   210  	Events         []NotificationEvent
   211  	FaultException string
   212  }
   213  
   214  // executionAux represents an auxiliary struct for Execution JSON marshalling.
   215  type executionAux struct {
   216  	Trigger        string              `json:"trigger"`
   217  	VMState        string              `json:"vmstate"`
   218  	GasConsumed    int64               `json:"gasconsumed,string"`
   219  	Stack          json.RawMessage     `json:"stack"`
   220  	Events         []NotificationEvent `json:"notifications"`
   221  	FaultException *string             `json:"exception"`
   222  }
   223  
   224  // MarshalJSON implements the json.Marshaler interface.
   225  func (e Execution) MarshalJSON() ([]byte, error) {
   226  	arr := make([]json.RawMessage, len(e.Stack))
   227  	for i := range arr {
   228  		data, err := stackitem.ToJSONWithTypes(e.Stack[i])
   229  		if err != nil {
   230  			data = []byte(fmt.Sprintf(`"error: %v"`, err))
   231  		}
   232  		arr[i] = data
   233  	}
   234  	st, err := json.Marshal(arr)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	var exception *string
   239  	if e.FaultException != "" {
   240  		exception = &e.FaultException
   241  	}
   242  	return json.Marshal(&executionAux{
   243  		Trigger:        e.Trigger.String(),
   244  		VMState:        e.VMState.String(),
   245  		GasConsumed:    e.GasConsumed,
   246  		Stack:          st,
   247  		Events:         e.Events,
   248  		FaultException: exception,
   249  	})
   250  }
   251  
   252  // UnmarshalJSON implements the json.Unmarshaler interface.
   253  func (e *Execution) UnmarshalJSON(data []byte) error {
   254  	aux := new(executionAux)
   255  	if err := json.Unmarshal(data, aux); err != nil {
   256  		return err
   257  	}
   258  	var arr []json.RawMessage
   259  	if err := json.Unmarshal(aux.Stack, &arr); err == nil {
   260  		st := make([]stackitem.Item, len(arr))
   261  		for i := range arr {
   262  			st[i], err = stackitem.FromJSONWithTypes(arr[i])
   263  			if err != nil {
   264  				var s string
   265  				if json.Unmarshal(arr[i], &s) != nil {
   266  					break
   267  				}
   268  				err = nil
   269  			}
   270  		}
   271  		if err == nil {
   272  			e.Stack = st
   273  		}
   274  	}
   275  	trigger, err := trigger.FromString(aux.Trigger)
   276  	if err != nil {
   277  		return err
   278  	}
   279  	e.Trigger = trigger
   280  	state, err := vmstate.FromString(aux.VMState)
   281  	if err != nil {
   282  		return err
   283  	}
   284  	e.VMState = state
   285  	e.Events = aux.Events
   286  	e.GasConsumed = aux.GasConsumed
   287  	if aux.FaultException != nil {
   288  		e.FaultException = *aux.FaultException
   289  	}
   290  	return nil
   291  }
   292  
   293  // containedNotificationEventAux is an auxiliary struct for JSON marshalling.
   294  type containedNotificationEventAux struct {
   295  	Container util.Uint256 `json:"container"`
   296  }
   297  
   298  // MarshalJSON implements the json.Marshaler interface.
   299  func (ne *ContainedNotificationEvent) MarshalJSON() ([]byte, error) {
   300  	h, err := json.Marshal(&containedNotificationEventAux{
   301  		Container: ne.Container,
   302  	})
   303  	if err != nil {
   304  		return nil, fmt.Errorf("failed to marshal hash: %w", err)
   305  	}
   306  	exec, err := json.Marshal(ne.NotificationEvent)
   307  	if err != nil {
   308  		return nil, fmt.Errorf("failed to marshal execution: %w", err)
   309  	}
   310  
   311  	if h[len(h)-1] != '}' || exec[0] != '{' {
   312  		return nil, errors.New("can't merge internal jsons")
   313  	}
   314  	h[len(h)-1] = ','
   315  	h = append(h, exec[1:]...)
   316  	return h, nil
   317  }
   318  
   319  // UnmarshalJSON implements the json.Unmarshaler interface.
   320  func (ne *ContainedNotificationEvent) UnmarshalJSON(data []byte) error {
   321  	aux := new(containedNotificationEventAux)
   322  	if err := json.Unmarshal(data, aux); err != nil {
   323  		return err
   324  	}
   325  	if err := json.Unmarshal(data, &ne.NotificationEvent); err != nil {
   326  		return err
   327  	}
   328  	ne.Container = aux.Container
   329  	return nil
   330  }