github.com/mavryk-network/mvgo@v1.19.9/rpc/operations.go (about)

     1  // Copyright (c) 2020-2023 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package rpc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  
    12  	"github.com/mavryk-network/mvgo/mavryk"
    13  	"github.com/mavryk-network/mvgo/micheline"
    14  )
    15  
    16  type MetadataMode string
    17  
    18  const (
    19  	MetadataModeUnset  MetadataMode = ""
    20  	MetadataModeNever  MetadataMode = "never"
    21  	MetadataModeAlways MetadataMode = "always"
    22  )
    23  
    24  // Operation represents a single operation or batch of operations included in a block
    25  type Operation struct {
    26  	Protocol  mavryk.ProtocolHash `json:"protocol"`
    27  	ChainID   mavryk.ChainIdHash  `json:"chain_id"`
    28  	Hash      mavryk.OpHash       `json:"hash"`
    29  	Branch    mavryk.BlockHash    `json:"branch"`
    30  	Contents  OperationList       `json:"contents"`
    31  	Signature mavryk.Signature    `json:"signature"`
    32  	Errors    []OperationError    `json:"error,omitempty"`    // mempool only
    33  	Metadata  string              `json:"metadata,omitempty"` // contains `too large` when stripped, this is BAD!!
    34  }
    35  
    36  // TotalCosts returns the sum of costs across all batched and internal operations.
    37  func (o Operation) TotalCosts() mavryk.Costs {
    38  	var c mavryk.Costs
    39  	for _, op := range o.Contents {
    40  		c = c.Add(op.Costs())
    41  	}
    42  	return c
    43  }
    44  
    45  // Costs returns ta list of individual costs for all batched operations.
    46  func (o Operation) Costs() []mavryk.Costs {
    47  	list := make([]mavryk.Costs, len(o.Contents))
    48  	for i, op := range o.Contents {
    49  		list[i] = op.Costs()
    50  	}
    51  	return list
    52  }
    53  
    54  // TypedOperation must be implemented by all operations
    55  type TypedOperation interface {
    56  	Kind() mavryk.OpType
    57  	Meta() OperationMetadata
    58  	Result() OperationResult
    59  	Costs() mavryk.Costs
    60  	Limits() mavryk.Limits
    61  }
    62  
    63  // OperationError represents data describing an error conditon that lead to a
    64  // failed operation execution.
    65  type OperationError struct {
    66  	GenericError
    67  	Contract *mavryk.Address `json:"contract,omitempty"`
    68  	Raw      json.RawMessage `json:"-"`
    69  }
    70  
    71  // OperationMetadata contains execution receipts for successful and failed
    72  // operations.
    73  type OperationMetadata struct {
    74  	BalanceUpdates BalanceUpdates  `json:"balance_updates"` // fee-related
    75  	Result         OperationResult `json:"operation_result"`
    76  
    77  	// transaction only
    78  	InternalResults []*InternalResult `json:"internal_operation_results,omitempty"`
    79  
    80  	// endorsement only
    81  	Delegate            mavryk.Address `json:"delegate"`
    82  	Slots               []int          `json:"slots,omitempty"`
    83  	EndorsementPower    int            `json:"endorsement_power,omitempty"`    // v12+
    84  	PreendorsementPower int            `json:"preendorsement_power,omitempty"` // v12+
    85  
    86  	// some rollup ops only, FIXME: is this correct here or is this field in result?
    87  	Level int64 `json:"level"`
    88  
    89  	// v18 slashing ops may block a baker
    90  	ForbiddenDelegate mavryk.Address `json:"forbidden_delegate"` // v18+
    91  }
    92  
    93  // Address returns the delegate address for endorsements.
    94  func (m OperationMetadata) Address() mavryk.Address {
    95  	return m.Delegate
    96  }
    97  
    98  // OperationResult contains receipts for executed operations, both success and failed.
    99  // This type is a generic container for all possible results. Which fields are actually
   100  // used depends on operation type and performed actions.
   101  type OperationResult struct {
   102  	Status               mavryk.OpStatus  `json:"status"`
   103  	BalanceUpdates       BalanceUpdates   `json:"balance_updates"`
   104  	ConsumedGas          int64            `json:"consumed_gas,string"`      // deprecated in v015
   105  	ConsumedMilliGas     int64            `json:"consumed_milligas,string"` // v007+
   106  	Errors               []OperationError `json:"errors,omitempty"`
   107  	Allocated            bool             `json:"allocated_destination_contract"` // tx only
   108  	Storage              *micheline.Prim  `json:"storage,omitempty"`              // tx, orig
   109  	OriginatedContracts  []mavryk.Address `json:"originated_contracts"`           // orig only
   110  	StorageSize          int64            `json:"storage_size,string"`            // tx, orig, const
   111  	PaidStorageSizeDiff  int64            `json:"paid_storage_size_diff,string"`  // tx, orig
   112  	BigmapDiff           json.RawMessage  `json:"big_map_diff,omitempty"`         // tx, orig, <v013
   113  	LazyStorageDiff      json.RawMessage  `json:"lazy_storage_diff,omitempty"`    // v008+ tx, orig
   114  	GlobalAddress        mavryk.ExprHash  `json:"global_address"`                 // const
   115  	TicketUpdatesCorrect []TicketUpdate   `json:"ticket_updates"`                 // v015
   116  	TicketReceipts       []TicketUpdate   `json:"ticket_receipt"`                 // v015, name on internal
   117  
   118  	// v013 tx rollup
   119  	TxRollupResult
   120  
   121  	// v016 smart rollup
   122  	SmartRollupResult
   123  
   124  	// v019 DAL
   125  	DalResult
   126  }
   127  
   128  // Always use this helper to retrieve Ticket updates. This is because due to
   129  // lack of quality control Tezos Lima protocol ended up with 2 distinct names
   130  // for ticket updates in external call receipts versus internal call receipts.
   131  func (r OperationResult) TicketUpdates() []TicketUpdate {
   132  	if len(r.TicketUpdatesCorrect) > 0 {
   133  		return r.TicketUpdatesCorrect
   134  	}
   135  	return r.TicketReceipts
   136  }
   137  
   138  func (r OperationResult) BigmapEvents() micheline.BigmapEvents {
   139  	if r.LazyStorageDiff != nil {
   140  		res := make(micheline.LazyEvents, 0)
   141  		_ = json.Unmarshal(r.LazyStorageDiff, &res)
   142  		return res.BigmapEvents()
   143  	}
   144  	if r.BigmapDiff != nil {
   145  		res := make(micheline.BigmapEvents, 0)
   146  		_ = json.Unmarshal(r.BigmapDiff, &res)
   147  		return res
   148  	}
   149  	return nil
   150  }
   151  
   152  func (r OperationResult) IsSuccess() bool {
   153  	return r.Status == mavryk.OpStatusApplied
   154  }
   155  
   156  func (r OperationResult) Gas() int64 {
   157  	if r.ConsumedMilliGas > 0 {
   158  		var corr int64
   159  		if r.ConsumedMilliGas%1000 > 0 {
   160  			corr++
   161  		}
   162  		return r.ConsumedMilliGas/1000 + corr
   163  	}
   164  	return r.ConsumedGas
   165  }
   166  
   167  func (r OperationResult) MilliGas() int64 {
   168  	if r.ConsumedMilliGas > 0 {
   169  		return r.ConsumedMilliGas
   170  	}
   171  	return r.ConsumedGas * 1000
   172  }
   173  
   174  func (o OperationError) MarshalJSON() ([]byte, error) {
   175  	return o.Raw, nil
   176  }
   177  
   178  func (o *OperationError) UnmarshalJSON(data []byte) error {
   179  	type alias OperationError
   180  	if err := json.Unmarshal(data, (*alias)(o)); err != nil {
   181  		return err
   182  	}
   183  	o.Raw = make([]byte, len(data))
   184  	copy(o.Raw, data)
   185  	return nil
   186  }
   187  
   188  // Generic is the most generic operation type.
   189  type Generic struct {
   190  	OpKind   mavryk.OpType     `json:"kind"`
   191  	Metadata OperationMetadata `json:"metadata"`
   192  }
   193  
   194  // Kind returns the operation's type. Implements TypedOperation interface.
   195  func (e Generic) Kind() mavryk.OpType {
   196  	return e.OpKind
   197  }
   198  
   199  // Meta returns an empty operation metadata to implement TypedOperation interface.
   200  func (e Generic) Meta() OperationMetadata {
   201  	return e.Metadata
   202  }
   203  
   204  // Result returns an empty operation result to implement TypedOperation interface.
   205  func (e Generic) Result() OperationResult {
   206  	return e.Metadata.Result
   207  }
   208  
   209  // Costs returns empty operation costs to implement TypedOperation interface.
   210  func (e Generic) Costs() mavryk.Costs {
   211  	return mavryk.Costs{}
   212  }
   213  
   214  // Limits returns empty operation limits to implement TypedOperation interface.
   215  func (e Generic) Limits() mavryk.Limits {
   216  	return mavryk.Limits{}
   217  }
   218  
   219  // Manager represents data common for all manager operations.
   220  type Manager struct {
   221  	Generic
   222  	Source       mavryk.Address `json:"source"`
   223  	Fee          int64          `json:"fee,string"`
   224  	Counter      int64          `json:"counter,string"`
   225  	GasLimit     int64          `json:"gas_limit,string"`
   226  	StorageLimit int64          `json:"storage_limit,string"`
   227  }
   228  
   229  // Limits returns manager operation limits to implement TypedOperation interface.
   230  func (e Manager) Limits() mavryk.Limits {
   231  	return mavryk.Limits{
   232  		Fee:          e.Fee,
   233  		GasLimit:     e.GasLimit,
   234  		StorageLimit: e.StorageLimit,
   235  	}
   236  }
   237  
   238  // OperationList is a slice of TypedOperation (interface type) with custom JSON unmarshaller
   239  type OperationList []TypedOperation
   240  
   241  // Contains returns true when the list contains an operation of kind typ.
   242  func (o OperationList) Contains(typ mavryk.OpType) bool {
   243  	for _, v := range o {
   244  		if v.Kind() == typ {
   245  			return true
   246  		}
   247  	}
   248  	return false
   249  }
   250  
   251  func (o OperationList) Select(typ mavryk.OpType, n int) TypedOperation {
   252  	var cnt int
   253  	for _, v := range o {
   254  		if v.Kind() != typ {
   255  			continue
   256  		}
   257  		if cnt == n {
   258  			return v
   259  		}
   260  		cnt++
   261  	}
   262  	return nil
   263  }
   264  
   265  func (o OperationList) Len() int {
   266  	return len(o)
   267  }
   268  
   269  func (o OperationList) N(n int) TypedOperation {
   270  	if n < 0 {
   271  		n += len(o)
   272  	}
   273  	return o[n]
   274  }
   275  
   276  // UnmarshalJSON implements json.Unmarshaler
   277  func (e *OperationList) UnmarshalJSON(data []byte) error {
   278  	if len(data) <= 2 {
   279  		return nil
   280  	}
   281  
   282  	if data[0] != '[' {
   283  		return fmt.Errorf("rpc: expected operation array")
   284  	}
   285  
   286  	// fmt.Printf("Decoding ops: %s\n", string(data))
   287  	dec := json.NewDecoder(bytes.NewReader(data))
   288  
   289  	// read open bracket
   290  	_, err := dec.Token()
   291  	if err != nil {
   292  		return fmt.Errorf("rpc: %v", err)
   293  	}
   294  
   295  	for dec.More() {
   296  		// peek into `{"kind":"...",` field
   297  		start := int(dec.InputOffset()) + 9
   298  		// after first JSON object, decoder pos is at `,`
   299  		if data[start] == '"' {
   300  			start += 1
   301  		}
   302  		end := start + bytes.IndexByte(data[start:], '"')
   303  		kind := mavryk.ParseOpType(string(data[start:end]))
   304  		var op TypedOperation
   305  		switch kind {
   306  		// anonymous operations
   307  		case mavryk.OpTypeActivateAccount:
   308  			op = &Activation{}
   309  		case mavryk.OpTypeDoubleBakingEvidence:
   310  			op = &DoubleBaking{}
   311  		case mavryk.OpTypeDoubleEndorsementEvidence,
   312  			mavryk.OpTypeDoublePreendorsementEvidence,
   313  			mavryk.OpTypeDoubleAttestationEvidence,
   314  			mavryk.OpTypeDoublePreattestationEvidence:
   315  			op = &DoubleEndorsement{}
   316  		case mavryk.OpTypeSeedNonceRevelation:
   317  			op = &SeedNonce{}
   318  		case mavryk.OpTypeDrainDelegate:
   319  			op = &DrainDelegate{}
   320  
   321  		// consensus operations
   322  		case mavryk.OpTypeEndorsement,
   323  			mavryk.OpTypeEndorsementWithSlot,
   324  			mavryk.OpTypePreendorsement,
   325  			mavryk.OpTypeAttestation,
   326  			mavryk.OpTypeAttestationWithDal,
   327  			mavryk.OpTypePreattestation:
   328  			op = &Endorsement{}
   329  
   330  		// amendment operations
   331  		case mavryk.OpTypeProposals:
   332  			op = &Proposals{}
   333  		case mavryk.OpTypeBallot:
   334  			op = &Ballot{}
   335  
   336  		// manager operations
   337  		case mavryk.OpTypeTransaction:
   338  			op = &Transaction{}
   339  		case mavryk.OpTypeOrigination:
   340  			op = &Origination{}
   341  		case mavryk.OpTypeDelegation:
   342  			op = &Delegation{}
   343  		case mavryk.OpTypeReveal:
   344  			op = &Reveal{}
   345  		case mavryk.OpTypeRegisterConstant:
   346  			op = &ConstantRegistration{}
   347  		case mavryk.OpTypeSetDepositsLimit:
   348  			op = &SetDepositsLimit{}
   349  		case mavryk.OpTypeIncreasePaidStorage:
   350  			op = &IncreasePaidStorage{}
   351  		case mavryk.OpTypeVdfRevelation:
   352  			op = &VdfRevelation{}
   353  		case mavryk.OpTypeTransferTicket:
   354  			op = &TransferTicket{}
   355  		case mavryk.OpTypeUpdateConsensusKey:
   356  			op = &UpdateConsensusKey{}
   357  
   358  			// DEPRECATED: tx rollup operations, kept for testnet backward compatibility
   359  		case mavryk.OpTypeTxRollupOrigination,
   360  			mavryk.OpTypeTxRollupSubmitBatch,
   361  			mavryk.OpTypeTxRollupCommit,
   362  			mavryk.OpTypeTxRollupReturnBond,
   363  			mavryk.OpTypeTxRollupFinalizeCommitment,
   364  			mavryk.OpTypeTxRollupRemoveCommitment,
   365  			mavryk.OpTypeTxRollupRejection,
   366  			mavryk.OpTypeTxRollupDispatchTickets:
   367  			op = &TxRollup{}
   368  
   369  		case mavryk.OpTypeSmartRollupOriginate:
   370  			op = &SmartRollupOriginate{}
   371  		case mavryk.OpTypeSmartRollupAddMessages:
   372  			op = &SmartRollupAddMessages{}
   373  		case mavryk.OpTypeSmartRollupCement:
   374  			op = &SmartRollupCement{}
   375  		case mavryk.OpTypeSmartRollupPublish:
   376  			op = &SmartRollupPublish{}
   377  		case mavryk.OpTypeSmartRollupRefute:
   378  			op = &SmartRollupRefute{}
   379  		case mavryk.OpTypeSmartRollupTimeout:
   380  			op = &SmartRollupTimeout{}
   381  		case mavryk.OpTypeSmartRollupExecuteOutboxMessage:
   382  			op = &SmartRollupExecuteOutboxMessage{}
   383  		case mavryk.OpTypeSmartRollupRecoverBond:
   384  			op = &SmartRollupRecoverBond{}
   385  		case mavryk.OpTypeDalPublishCommitment:
   386  			op = &DalPublishCommitment{}
   387  
   388  		default:
   389  			return fmt.Errorf("rpc: unsupported op %q", string(data[start:end]))
   390  		}
   391  
   392  		if err := dec.Decode(op); err != nil {
   393  			return fmt.Errorf("rpc: operation kind %s: %v", kind, err)
   394  		}
   395  		(*e) = append(*e, op)
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  // GetBlockOperationHash returns a single operation hashes included in block
   402  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operation-hashes-list-offset-operation-offset
   403  func (c *Client) GetBlockOperationHash(ctx context.Context, id BlockID, l, n int) (mavryk.OpHash, error) {
   404  	var hash mavryk.OpHash
   405  	u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes/%d/%d", id, l, n)
   406  	err := c.Get(ctx, u, &hash)
   407  	return hash, err
   408  }
   409  
   410  // GetBlockOperationHashes returns a list of list of operation hashes included in block
   411  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operation-hashes
   412  func (c *Client) GetBlockOperationHashes(ctx context.Context, id BlockID) ([][]mavryk.OpHash, error) {
   413  	hashes := make([][]mavryk.OpHash, 0)
   414  	u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes", id)
   415  	if err := c.Get(ctx, u, &hashes); err != nil {
   416  		return nil, err
   417  	}
   418  	return hashes, nil
   419  }
   420  
   421  // GetBlockOperationListHashes returns a list of operation hashes included in block
   422  // at a specified list position (i.e. validation pass) [0..3]
   423  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operation-hashes-list-offset
   424  func (c *Client) GetBlockOperationListHashes(ctx context.Context, id BlockID, l int) ([]mavryk.OpHash, error) {
   425  	hashes := make([]mavryk.OpHash, 0)
   426  	u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes/%d", id, l)
   427  	if err := c.Get(ctx, u, &hashes); err != nil {
   428  		return nil, err
   429  	}
   430  	return hashes, nil
   431  }
   432  
   433  // GetBlockOperation returns information about a single validated Tezos operation group
   434  // (i.e. a single operation or a batch of operations) at list l and position n
   435  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operations-list-offset-operation-offset
   436  func (c *Client) GetBlockOperation(ctx context.Context, id BlockID, l, n int) (*Operation, error) {
   437  	var op Operation
   438  	u := fmt.Sprintf("chains/main/blocks/%s/operations/%d/%d", id, l, n)
   439  	if c.MetadataMode != "" {
   440  		u += "?metadata=" + string(c.MetadataMode)
   441  	}
   442  	if err := c.Get(ctx, u, &op); err != nil {
   443  		return nil, err
   444  	}
   445  	return &op, nil
   446  }
   447  
   448  // GetBlockOperationList returns information about all validated Tezos operation group
   449  // inside operation list l (i.e. validation pass) [0..3].
   450  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operations-list-offset
   451  func (c *Client) GetBlockOperationList(ctx context.Context, id BlockID, l int) ([]Operation, error) {
   452  	ops := make([]Operation, 0)
   453  	u := fmt.Sprintf("chains/main/blocks/%s/operations/%d", id, l)
   454  	if c.MetadataMode != "" {
   455  		u += "?metadata=" + string(c.MetadataMode)
   456  	}
   457  	if err := c.Get(ctx, u, &ops); err != nil {
   458  		return nil, err
   459  	}
   460  	return ops, nil
   461  }
   462  
   463  // GetBlockOperations returns information about all validated Tezos operation groups
   464  // from all operation lists in block.
   465  // https://protocol.mavryk.org/active/rpc.html#get-block-id-operations
   466  func (c *Client) GetBlockOperations(ctx context.Context, id BlockID) ([][]Operation, error) {
   467  	ops := make([][]Operation, 0)
   468  	u := fmt.Sprintf("chains/main/blocks/%s/operations", id)
   469  	if c.MetadataMode != "" {
   470  		u += "?metadata=" + string(c.MetadataMode)
   471  	}
   472  	if err := c.Get(ctx, u, &ops); err != nil {
   473  		return nil, err
   474  	}
   475  	return ops, nil
   476  }