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  }