github.com/mavryk-network/mvgo@v1.19.9/rpc/receipt.go (about) 1 // Copyright (c) 2020-2022 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc 3 4 package rpc 5 6 import ( 7 "context" 8 "errors" 9 "sync" 10 11 "github.com/mavryk-network/mvgo/mavryk" 12 ) 13 14 var ( 15 Canceled = errors.New("operation confirm canceled") 16 TTLExceeded = errors.New("operation ttl exceeded") 17 ) 18 19 type Receipt struct { 20 Block mavryk.BlockHash 21 Height int64 22 List int 23 Pos int 24 Op *Operation 25 } 26 27 // TotalCosts returns the sum of costs across all batched and internal operations. 28 func (r *Receipt) TotalCosts() mavryk.Costs { 29 if r.Op != nil { 30 return r.Op.TotalCosts() 31 } 32 return mavryk.Costs{} 33 } 34 35 // Costs returns a list of individual costs for all batched operations. 36 func (r *Receipt) Costs() []mavryk.Costs { 37 if r.Op != nil { 38 return r.Op.Costs() 39 } 40 return nil 41 } 42 43 // IsSuccess returns true when all operations in this group have been applied successfully. 44 func (r *Receipt) IsSuccess() bool { 45 for _, v := range r.Op.Contents { 46 switch v.Result().Status { 47 case mavryk.OpStatusApplied: 48 return true 49 case mavryk.OpStatusInvalid: 50 // only manager ops contain a status field 51 return true 52 default: 53 return false 54 } 55 } 56 return true 57 } 58 59 // Error returns the first execution error found in this operation group or one of 60 // its internal results that is of status failed. This helper only exports the error 61 // as GenericError. To access error details or all errors, visit 62 // r.Op.Contents[].OperationResult.Errors[] and 63 // r.Op.Contents[].Metadata.InternalResults.Result.Errors[] 64 func (r *Receipt) Error() error { 65 for _, v := range r.Op.Contents { 66 res := v.Result() 67 if len(res.Errors) > 0 && res.Status != mavryk.OpStatusApplied { 68 return res.Errors[len(res.Errors)-1].GenericError 69 } 70 for _, vv := range v.Meta().InternalResults { 71 res := vv.Result 72 if len(res.Errors) > 0 && res.Status != mavryk.OpStatusApplied { 73 return res.Errors[len(res.Errors)-1].GenericError 74 } 75 } 76 } 77 return nil 78 } 79 80 // OriginatedContract returns the first contract address deployed by the operation. 81 func (r *Receipt) OriginatedContract() (mavryk.Address, bool) { 82 if r.IsSuccess() { 83 for _, contents := range r.Op.Contents { 84 if contents.Kind() == mavryk.OpTypeOrigination { 85 result := contents.Result() 86 if len(result.OriginatedContracts) > 0 { 87 return result.OriginatedContracts[0], true 88 } 89 } 90 } 91 } 92 return mavryk.InvalidAddress, false 93 } 94 95 // MinLimits returns a list of individual operation costs mapped to limits for use 96 // in simulation results. Fee is reset to zero to prevent higher simulation fee from 97 // spilling over into real fees paid. 98 func (r *Receipt) MinLimits() []mavryk.Limits { 99 lims := make([]mavryk.Limits, len(r.Op.Contents)) 100 for i, v := range r.Op.Costs() { 101 lims[i].Fee = 0 102 lims[i].GasLimit = v.GasUsed 103 lims[i].StorageLimit = v.StorageUsed + v.AllocationBurn/mavryk.DefaultParams.CostPerByte 104 } 105 return lims 106 } 107 108 type Result struct { 109 oh mavryk.OpHash // the operation hash to watch 110 block mavryk.BlockHash // the block hash where op was included 111 height int64 // block height 112 list int // the list where op was included 113 pos int // the list position where op was included 114 err error // saves any error 115 ttl int64 // number of blocks before wait fails 116 wait int64 // number of confirmations required 117 blocks int64 // number of confirmation blocks seen 118 obs *Observer // blockchain observer 119 subId int // monitor subscription id 120 done chan struct{} // channel used to signal completion 121 once sync.Once // ensures only one completion state exists 122 } 123 124 func NewResult(oh mavryk.OpHash) *Result { 125 return &Result{ 126 oh: oh, 127 wait: 1, 128 done: make(chan struct{}), 129 } 130 } 131 132 func (r *Result) Hash() mavryk.OpHash { 133 return r.oh 134 } 135 136 func (r *Result) Listen(o *Observer) { 137 if o != nil { 138 r.obs = o 139 r.subId = r.obs.Subscribe(r.oh, r.callback) 140 } 141 } 142 143 func (r *Result) Cancel() { 144 r.once.Do(func() { 145 if r.subId > 0 { 146 r.obs.Unsubscribe(r.subId) 147 r.err = Canceled 148 r.subId = 0 149 } 150 close(r.done) 151 }) 152 } 153 154 func (r *Result) WithConfirmations(n int64) *Result { 155 r.wait = n 156 return r 157 } 158 159 func (r *Result) WithTTL(n int64) *Result { 160 r.ttl = n 161 return r 162 } 163 164 func (r *Result) Confirmations() int64 { 165 return r.blocks 166 } 167 168 func (r *Result) Done() <-chan struct{} { 169 return r.done 170 } 171 172 func (r *Result) Err() error { 173 return r.err 174 } 175 176 func (r *Result) GetReceipt(ctx context.Context) (*Receipt, error) { 177 if r.err != nil { 178 return nil, r.err 179 } 180 rec := &Receipt{ 181 Block: r.block, 182 Height: r.height, 183 Pos: r.pos, 184 List: r.list, 185 } 186 if r.obs != nil { 187 op, err := r.obs.c.GetBlockOperation(ctx, r.block, r.list, r.pos) 188 if err != nil { 189 return rec, err 190 } 191 rec.Op = op 192 } 193 return rec, nil 194 } 195 196 func (r *Result) Wait() { 197 <-r.done 198 } 199 200 func (r *Result) WaitContext(ctx context.Context) bool { 201 select { 202 case <-ctx.Done(): 203 r.err = context.Canceled 204 return false 205 case <-r.done: 206 return true 207 } 208 } 209 210 func (r *Result) callback(block *BlockHeaderLogEntry, height int64, list, pos int, force bool) bool { 211 if force { 212 r.block = block.Hash 213 r.height = height 214 r.list = list 215 r.pos = pos 216 return false 217 } 218 if !r.block.IsValid() { 219 r.block = block.Hash 220 r.height = height 221 r.list = list 222 r.pos = pos 223 } 224 r.blocks++ 225 if r.ttl > 0 && r.blocks >= r.ttl { 226 r.once.Do(func() { 227 r.err = TTLExceeded 228 r.subId = 0 229 close(r.done) 230 }) 231 return true 232 } 233 if r.blocks >= r.wait { 234 r.once.Do(func() { 235 r.subId = 0 236 close(r.done) 237 }) 238 return true 239 } 240 return false 241 }