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 }