github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/neorpc/types.go (about)

     1  /*
     2  Package neorpc contains a set of types used for JSON-RPC communication with Neo servers.
     3  It defines basic request/response types as well as a set of errors and additional
     4  parameters used for specific requests/responses.
     5  */
     6  package neorpc
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    14  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    15  	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
    16  	"github.com/nspcc-dev/neo-go/pkg/util"
    17  )
    18  
    19  const (
    20  	// JSONRPCVersion is the only JSON-RPC protocol version supported.
    21  	JSONRPCVersion = "2.0"
    22  )
    23  
    24  type (
    25  	// Request represents JSON-RPC request. It's generic enough to be used in many
    26  	// generic JSON-RPC communication scenarios, yet at the same time it's
    27  	// tailored for NeoGo RPC Client needs.
    28  	Request struct {
    29  		// JSONRPC is the protocol version, only valid when it contains JSONRPCVersion.
    30  		JSONRPC string `json:"jsonrpc"`
    31  		// Method is the method being called.
    32  		Method string `json:"method"`
    33  		// Params is a set of method-specific parameters passed to the call. They
    34  		// can be anything as long as they can be marshaled to JSON correctly and
    35  		// used by the method implementation on the server side. While JSON-RPC
    36  		// technically allows it to be an object, all Neo calls expect params
    37  		// to be an array.
    38  		Params []any `json:"params"`
    39  		// ID is an identifier associated with this request. JSON-RPC itself allows
    40  		// any strings to be used for it as well, but NeoGo RPC client uses numeric
    41  		// identifiers.
    42  		ID uint64 `json:"id"`
    43  	}
    44  
    45  	// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version).
    46  	Header struct {
    47  		ID      json.RawMessage `json:"id"`
    48  		JSONRPC string          `json:"jsonrpc"`
    49  	}
    50  
    51  	// HeaderAndError adds an Error (that can be empty) to the Header, it's used
    52  	// to construct type-specific responses.
    53  	HeaderAndError struct {
    54  		Header
    55  		Error *Error `json:"error,omitempty"`
    56  	}
    57  
    58  	// Response represents a standard raw JSON-RPC 2.0
    59  	// response: http://www.jsonrpc.org/specification#response_object.
    60  	Response struct {
    61  		HeaderAndError
    62  		Result json.RawMessage `json:"result,omitempty"`
    63  	}
    64  
    65  	// Notification is a type used to represent wire format of events, they're
    66  	// special in that they look like requests but they don't have IDs and their
    67  	// "method" is actually an event name.
    68  	Notification struct {
    69  		JSONRPC string  `json:"jsonrpc"`
    70  		Event   EventID `json:"method"`
    71  		Payload []any   `json:"params"`
    72  	}
    73  
    74  	// SignerWithWitness represents transaction's signer with the corresponding witness.
    75  	SignerWithWitness struct {
    76  		transaction.Signer
    77  		transaction.Witness
    78  	}
    79  )
    80  
    81  // signerWithWitnessAux is an auxiliary struct for JSON marshalling. We need it because of
    82  // DisallowUnknownFields JSON marshaller setting.
    83  type signerWithWitnessAux struct {
    84  	Account            string                    `json:"account"`
    85  	Scopes             json.RawMessage           `json:"scopes"`
    86  	AllowedContracts   []util.Uint160            `json:"allowedcontracts,omitempty"`
    87  	AllowedGroups      []*keys.PublicKey         `json:"allowedgroups,omitempty"`
    88  	Rules              []transaction.WitnessRule `json:"rules,omitempty"`
    89  	InvocationScript   []byte                    `json:"invocation,omitempty"`
    90  	VerificationScript []byte                    `json:"verification,omitempty"`
    91  }
    92  
    93  // MarshalJSON implements the json.Marshaler interface.
    94  func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
    95  	sc, err := s.Scopes.MarshalJSON()
    96  	if err != nil {
    97  		return nil, fmt.Errorf("failed to marshal scopes: %w", err)
    98  	}
    99  	signer := &signerWithWitnessAux{
   100  		Account:            `0x` + s.Account.StringLE(),
   101  		Scopes:             sc,
   102  		AllowedContracts:   s.AllowedContracts,
   103  		AllowedGroups:      s.AllowedGroups,
   104  		Rules:              s.Rules,
   105  		InvocationScript:   s.InvocationScript,
   106  		VerificationScript: s.VerificationScript,
   107  	}
   108  	return json.Marshal(signer)
   109  }
   110  
   111  // UnmarshalJSON implements the json.Unmarshaler interface.
   112  func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
   113  	aux := new(signerWithWitnessAux)
   114  	err := json.Unmarshal(data, aux)
   115  	if err != nil {
   116  		return fmt.Errorf("not a signer: %w", err)
   117  	}
   118  	if len(aux.AllowedContracts) > transaction.MaxAttributes {
   119  		return fmt.Errorf("invalid number of AllowedContracts: got %d, allowed %d at max", len(aux.AllowedContracts), transaction.MaxAttributes)
   120  	}
   121  	if len(aux.AllowedGroups) > transaction.MaxAttributes {
   122  		return fmt.Errorf("invalid number of AllowedGroups: got %d, allowed %d at max", len(aux.AllowedGroups), transaction.MaxAttributes)
   123  	}
   124  	if len(aux.Rules) > transaction.MaxAttributes {
   125  		return fmt.Errorf("invalid number of Rules: got %d, allowed %d at max", len(aux.Rules), transaction.MaxAttributes)
   126  	}
   127  	acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
   128  	if err != nil {
   129  		acc, err = address.StringToUint160(aux.Account)
   130  	}
   131  	if err != nil {
   132  		return fmt.Errorf("not a signer: %w", err)
   133  	}
   134  	var (
   135  		jStr   string
   136  		jByte  byte
   137  		scopes transaction.WitnessScope
   138  	)
   139  	if len(aux.Scopes) != 0 {
   140  		if err := json.Unmarshal(aux.Scopes, &jStr); err == nil {
   141  			scopes, err = transaction.ScopesFromString(jStr)
   142  			if err != nil {
   143  				return fmt.Errorf("failed to retrieve scopes from string: %w", err)
   144  			}
   145  		} else {
   146  			err := json.Unmarshal(aux.Scopes, &jByte)
   147  			if err != nil {
   148  				return fmt.Errorf("failed to unmarshal scopes from byte: %w", err)
   149  			}
   150  			scopes, err = transaction.ScopesFromByte(jByte)
   151  			if err != nil {
   152  				return fmt.Errorf("failed to retrieve scopes from byte: %w", err)
   153  			}
   154  		}
   155  	}
   156  	s.Signer = transaction.Signer{
   157  		Account:          acc,
   158  		Scopes:           scopes,
   159  		AllowedContracts: aux.AllowedContracts,
   160  		AllowedGroups:    aux.AllowedGroups,
   161  		Rules:            aux.Rules,
   162  	}
   163  	s.Witness = transaction.Witness{
   164  		InvocationScript:   aux.InvocationScript,
   165  		VerificationScript: aux.VerificationScript,
   166  	}
   167  	return nil
   168  }
   169  
   170  // EventID implements EventContainer interface and returns notification ID.
   171  func (n *Notification) EventID() EventID {
   172  	return n.Event
   173  }
   174  
   175  // EventPayload implements EventContainer interface and returns notification
   176  // object.
   177  func (n *Notification) EventPayload() any {
   178  	return n.Payload[0]
   179  }