forked from blockwatch-cc/tzgo
/
operations.go
380 lines (331 loc) · 12.6 KB
/
operations.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// Copyright (c) 2020-2022 Blockwatch Data Inc.
// Author: alex@blockwatch.cc
package rpc
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/legonian/tzgo/micheline"
"github.com/legonian/tzgo/tezos"
)
// Operation represents a single operation or batch of operations included in a block
type Operation struct {
Protocol tezos.ProtocolHash `json:"protocol"`
ChainID tezos.ChainIdHash `json:"chain_id"`
Hash tezos.OpHash `json:"hash"`
Branch tezos.BlockHash `json:"branch"`
Contents OperationList `json:"contents"`
Signature tezos.Signature `json:"signature"`
Errors []OperationError `json:"error,omitempty"` // mempool only
}
// TotalCosts returns the sum of costs across all batched and internal operations.
func (o Operation) TotalCosts() tezos.Costs {
var c tezos.Costs
for _, op := range o.Contents {
c = c.Add(op.Costs())
}
return c
}
// Costs returns ta list of individual costs for all batched operations.
func (o Operation) Costs() []tezos.Costs {
list := make([]tezos.Costs, len(o.Contents))
for i, op := range o.Contents {
list[i] = op.Costs()
}
return list
}
// TypedOperation must be implemented by all operations
type TypedOperation interface {
Kind() tezos.OpType
Meta() OperationMetadata
Result() OperationResult
Costs() tezos.Costs
Limits() tezos.Limits
}
// OperationError represents data describing an error conditon that lead to a
// failed operation execution.
type OperationError struct {
GenericError
Contract *tezos.Address `json:"contract,omitempty"`
Raw json.RawMessage `json:"-"`
}
// OperationMetadata contains execution receipts for successful and failed
// operations.
type OperationMetadata struct {
BalanceUpdates BalanceUpdates `json:"balance_updates"` // fee-related
Result OperationResult `json:"operation_result"`
// transaction only
InternalResults []*InternalResult `json:"internal_operation_results,omitempty"`
// endorsement only
Delegate tezos.Address `json:"delegate"`
Slots []int `json:"slots,omitempty"`
Power int `json:"endorsement_power,omitempty"`
}
// Address returns the delegate address for endorsements.
func (m OperationMetadata) Address() tezos.Address {
return m.Delegate
}
// OperationResult contains receipts for executed operations, both success and failed.
// This type is a generic container for all possible results. Which fields are actually
// used depends on operation type and performed actions.
type OperationResult struct {
Status tezos.OpStatus `json:"status"`
BalanceUpdates BalanceUpdates `json:"balance_updates"` // burn, etc
ConsumedGas int64 `json:"consumed_gas,string"`
ConsumedMilliGas int64 `json:"consumed_milligas,string"` // v007+
Errors []OperationError `json:"errors,omitempty"`
Allocated bool `json:"allocated_destination_contract"` // tx only
Storage *micheline.Prim `json:"storage,omitempty"` // tx, orig
OriginatedContracts []tezos.Address `json:"originated_contracts"` // orig only
StorageSize int64 `json:"storage_size,string"` // tx, orig, const
PaidStorageSizeDiff int64 `json:"paid_storage_size_diff,string"` // tx, orig
BigmapDiff micheline.BigmapDiff `json:"big_map_diff,omitempty"` // tx, orig
LazyStorageDiff LazyStorageDiff `json:"lazy_storage_diff,omitempty"` // v008+ tx, orig
GlobalAddress tezos.ExprHash `json:"global_address"` // const
}
func (o OperationError) MarshalJSON() ([]byte, error) {
return o.Raw, nil
}
func (o *OperationError) UnmarshalJSON(data []byte) error {
type alias OperationError
if err := json.Unmarshal(data, (*alias)(o)); err != nil {
return err
}
o.Raw = make([]byte, len(data))
copy(o.Raw, data)
return nil
}
// Generic is the most generic operation type.
type Generic struct {
OpKind tezos.OpType `json:"kind"`
}
// Manager represents data common for all manager operations.
type Manager struct {
Generic
Source tezos.Address `json:"source"`
Fee int64 `json:"fee,string"`
Counter int64 `json:"counter,string"`
GasLimit int64 `json:"gas_limit,string"`
StorageLimit int64 `json:"storage_limit,string"`
}
// Kind returns the operation's type. Implements TypedOperation interface.
func (e Generic) Kind() tezos.OpType {
return e.OpKind
}
// Meta returns an empty operation metadata to implement TypedOperation interface.
func (e Generic) Meta() OperationMetadata {
return OperationMetadata{}
}
// Result returns an empty operation result to implement TypedOperation interface.
func (e Generic) Result() OperationResult {
return OperationResult{}
}
// Costs returns empty operation costs to implement TypedOperation interface.
func (e Generic) Costs() tezos.Costs {
return tezos.Costs{}
}
// Limits returns empty operation limits to implement TypedOperation interface.
func (e Generic) Limits() tezos.Limits {
return tezos.Limits{}
}
// Limits returns manager operation limits to implement TypedOperation interface.
func (e Manager) Limits() tezos.Limits {
return tezos.Limits{
Fee: e.Fee,
GasLimit: e.GasLimit,
StorageLimit: e.StorageLimit,
}
}
// OperationList is a slice of TypedOperation (interface type) with custom JSON unmarshaller
type OperationList []TypedOperation
// Contains returns true when the list contains an operation of kind typ.
func (o OperationList) Contains(typ tezos.OpType) bool {
for _, v := range o {
if v.Kind() == typ {
return true
}
}
return false
}
func (o OperationList) Select(typ tezos.OpType, n int) TypedOperation {
var cnt int
for _, v := range o {
if v.Kind() != typ {
continue
}
if cnt == n {
return v
}
cnt++
}
return nil
}
// UnmarshalJSON implements json.Unmarshaler
func (e *OperationList) UnmarshalJSON(data []byte) error {
if len(data) <= 2 {
return nil
}
if data[0] != '[' {
return fmt.Errorf("rpc: expected operation array")
}
// fmt.Printf("Decoding ops: %s\n", string(data))
dec := json.NewDecoder(bytes.NewReader(data))
// read open bracket
_, err := dec.Token()
if err != nil {
return fmt.Errorf("rpc: %v", err)
}
for dec.More() {
// peek into `{"kind":"...",` field
start := int(dec.InputOffset()) + 9
// after first JSON object, decoder pos is at `,`
if data[start] == '"' {
start += 1
}
end := start + bytes.IndexByte(data[start:], '"')
kind := tezos.ParseOpType(string(data[start:end]))
var op TypedOperation
switch kind {
// anonymous operations
case tezos.OpTypeActivateAccount:
op = &Activation{}
case tezos.OpTypeDoubleBakingEvidence:
op = &DoubleBaking{}
case tezos.OpTypeDoubleEndorsementEvidence,
tezos.OpTypeDoublePreendorsementEvidence:
op = &DoubleEndorsement{}
case tezos.OpTypeSeedNonceRevelation:
op = &SeedNonce{}
// consensus operations
case tezos.OpTypeEndorsement,
tezos.OpTypeEndorsementWithSlot,
tezos.OpTypePreendorsement:
op = &Endorsement{}
// amendment operations
case tezos.OpTypeProposals:
op = &Proposals{}
case tezos.OpTypeBallot:
op = &Ballot{}
// manager operations
case tezos.OpTypeTransaction:
op = &Transaction{}
case tezos.OpTypeOrigination:
op = &Origination{}
case tezos.OpTypeDelegation:
op = &Delegation{}
case tezos.OpTypeReveal:
op = &Reveal{}
case tezos.OpTypeRegisterConstant:
op = &ConstantRegistration{}
case tezos.OpTypeSetDepositsLimit:
op = &SetDepositsLimit{}
default:
return fmt.Errorf("rpc: unsupported op %q", kind)
}
if err := dec.Decode(op); err != nil {
return fmt.Errorf("rpc: operation kind %s: %w", kind, err)
}
(*e) = append(*e, op)
}
return nil
}
// GetBlockOperationHash returns a single operation hashes included in block
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operation-hashes-list-offset-operation-offset
func (c *Client) GetBlockOperationHash(ctx context.Context, id BlockID, l, n int) (tezos.OpHash, error) {
var hash tezos.OpHash
u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes/%d/%d", id, l, n)
err := c.Get(ctx, u, &hash)
return hash, err
}
// GetBlockOperationHashes returns a list of list of operation hashes included in block
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operation-hashes
func (c *Client) GetBlockOperationHashes(ctx context.Context, id BlockID) ([][]tezos.OpHash, error) {
hashes := make([][]tezos.OpHash, 0)
u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes", id)
if err := c.Get(ctx, u, &hashes); err != nil {
return nil, err
}
return hashes, nil
}
// GetBlockOperationListHashes returns a list of operation hashes included in block
// at a specified list position (i.e. validation pass) [0..3]
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operation-hashes-list-offset
func (c *Client) GetBlockOperationListHashes(ctx context.Context, id BlockID, l int) ([]tezos.OpHash, error) {
hashes := make([]tezos.OpHash, 0)
u := fmt.Sprintf("chains/main/blocks/%s/operation_hashes/%d", id, l)
if err := c.Get(ctx, u, &hashes); err != nil {
return nil, err
}
return hashes, nil
}
// GetBlockOperation returns information about a single validated Tezos operation group
// (i.e. a single operation or a batch of operations) at list l and position n
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operations-list-offset-operation-offset
func (c *Client) GetBlockOperation(ctx context.Context, id BlockID, l, n int) (*Operation, error) {
var op Operation
u := fmt.Sprintf("chains/main/blocks/%s/operations/%d/%d", id, l, n)
if err := c.Get(ctx, u, &op); err != nil {
return nil, err
}
return &op, nil
}
// GetBlockOperationList returns information about all validated Tezos operation group
// inside operation list l (i.e. validation pass) [0..3].
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operations-list-offset
func (c *Client) GetBlockOperationList(ctx context.Context, id BlockID, l int) ([]Operation, error) {
ops := make([]Operation, 0)
u := fmt.Sprintf("chains/main/blocks/%s/operations/%d", id, l)
if err := c.Get(ctx, u, &ops); err != nil {
return nil, err
}
return ops, nil
}
// GetBlockOperations returns information about all validated Tezos operation groups
// from all operation lists in block.
// https://tezos.gitlab.io/active/rpc.html#get-block-id-operations
func (c *Client) GetBlockOperations(ctx context.Context, id BlockID) ([][]Operation, error) {
ops := make([][]Operation, 0)
u := fmt.Sprintf("chains/main/blocks/%s/operations", id)
if err := c.Get(ctx, u, &ops); err != nil {
return nil, err
}
return ops, nil
}
// BroadcastOperation sends a signed operation to the network (injection).
// The call returns the operation hash on success. If theoperation was rejected
// by the node error is of type RPCError.
func (c *Client) BroadcastOperation(ctx context.Context, body []byte) (hash tezos.OpHash, err error) {
err = c.Post(ctx, "injection/operation", hex.EncodeToString(body), &hash)
return
}
// RunOperation simulates executing an operation without requiring a valid signature.
// The call returns the execution result as regular operation receipt.
func (c *Client) RunOperation(ctx context.Context, id BlockID, body, resp interface{}) error {
u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_operation", id)
return c.Post(ctx, u, body, resp)
}
// ForgeOperation uses a remote node to serialize an operation to its binary format.
// The result of this call SHOULD NEVER be used for signing the operation, it is only
// meant for validating the locally generated serialized output.
func (c *Client) ForgeOperation(ctx context.Context, id BlockID, body, resp interface{}) error {
u := fmt.Sprintf("chains/main/blocks/%s/helpers/forge/operations", id)
return c.Post(ctx, u, body, resp)
}
// RunCode simulates executing of provided code on the context of a contract at selected block.
func (c *Client) RunCode(ctx context.Context, id BlockID, body, resp interface{}) error {
u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_code", id)
return c.Post(ctx, u, body, resp)
}
// RunView simulates executing of on on-chain view on the context of a contract at selected block.
func (c *Client) RunView(ctx context.Context, id BlockID, body, resp interface{}) error {
u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_view", id)
return c.Post(ctx, u, body, resp)
}
// TraceCode simulates executing of code on the context of a contract at selected block and
// returns a full execution trace.
func (c *Client) TraceCode(ctx context.Context, id BlockID, body, resp interface{}) error {
u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/trace_code", id)
return c.Post(ctx, u, body, resp)
}