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

     1  // Copyright (c) 2020-2022 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package rpc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/hex"
    10  	"fmt"
    11  
    12  	"github.com/mavryk-network/mvgo/codec"
    13  	"github.com/mavryk-network/mvgo/mavryk"
    14  	"github.com/mavryk-network/mvgo/micheline"
    15  	"github.com/mavryk-network/mvgo/signer"
    16  )
    17  
    18  const ExtraSafetyMargin int64 = 100 // used to adjust gas and storage estimations
    19  
    20  var (
    21  	// for reveal
    22  	DefaultRevealLimits = mavryk.Limits{
    23  		Fee:      1000,
    24  		GasLimit: 1000,
    25  	}
    26  	// for transfers to mv1/2/3
    27  	DefaultTransferLimitsEOA = mavryk.Limits{
    28  		Fee:      1000,
    29  		GasLimit: 1420, // 1820 when source is emptied
    30  	}
    31  	// for transfers to manager.tz
    32  	DefaultTransferLimitsKT1 = mavryk.Limits{
    33  		Fee:      1000,
    34  		GasLimit: 2078,
    35  	}
    36  	// for delegation
    37  	DefaultDelegationLimitsEOA = mavryk.Limits{
    38  		Fee:      1000,
    39  		GasLimit: 1000,
    40  	}
    41  	// for baker registration
    42  	DefaultBakerRegistrationLimits = mavryk.Limits{
    43  		Fee:      1000,
    44  		GasLimit: 1000,
    45  	}
    46  	// for simulating contract calls and other operations
    47  	// used when no explicit costs are set
    48  	DefaultSimulationLimits = mavryk.Limits{
    49  		GasLimit:     mavryk.DefaultParams.HardGasLimitPerOperation,
    50  		StorageLimit: mavryk.DefaultParams.HardStorageLimitPerOperation,
    51  	}
    52  )
    53  
    54  type CallOptions struct {
    55  	Confirmations     int64          // number of confirmations to wait after broadcast
    56  	MaxFee            int64          // max acceptable fee, optional (default = 0)
    57  	TTL               int64          // max lifetime for operations in blocks
    58  	IgnoreLimits      bool           // ignore simulated limits and use user-defined limits from op
    59  	ExtraGasMargin    int64          // safety margin in case simulation underestimates future usage
    60  	SimulationBlockID BlockID        // custom block id to simulate operation (default is head, use to select a past block)
    61  	SimulationOffset  int64          // custom block offset for future block simulations
    62  	Signer            signer.Signer  // optional signer interface to use for signing the transaction
    63  	Sender            mavryk.Address // optional address to sign for (use when signer manages multiple addresses)
    64  	Observer          *Observer      // optional custom block observer for waiting on confirmations
    65  }
    66  
    67  var DefaultOptions = CallOptions{
    68  	Confirmations:    2,
    69  	TTL:              mavryk.DefaultParams.MaxOperationsTTL - 2,
    70  	MaxFee:           1_000_000,
    71  	ExtraGasMargin:   ExtraSafetyMargin,
    72  	SimulationOffset: 5, // use pessimistic value to prevent gas exhausted errors (node's default is 3)
    73  }
    74  
    75  func NewCallOptions() *CallOptions {
    76  	o := DefaultOptions
    77  	return &o
    78  }
    79  
    80  type RunOperationRequest struct {
    81  	Operation *codec.Op          `json:"operation"`
    82  	ChainId   mavryk.ChainIdHash `json:"chain_id"`
    83  	Latency   int64              `json:"latency,omitempty"`
    84  }
    85  
    86  type RunViewRequest struct {
    87  	Contract     mavryk.Address     `json:"contract"`
    88  	Entrypoint   string             `json:"entrypoint,omitempty"`
    89  	View         string             `json:"view,omitempty"`
    90  	Input        micheline.Prim     `json:"input"`
    91  	ChainId      mavryk.ChainIdHash `json:"chain_id"`
    92  	Source       mavryk.Address     `json:"source"`
    93  	Payer        mavryk.Address     `json:"payer"`
    94  	Gas          mavryk.N           `json:"gas"`
    95  	Mode         string             `json:"unparsing_mode"`          // "Readable" | "Optimized"
    96  	UnlimitedGas bool               `json:"unlimited_gas,omitempty"` // view
    97  	Now          string             `json:"now,omitempty"`           // view
    98  }
    99  
   100  type RunViewResponse struct {
   101  	Data micheline.Prim `json:"data"`
   102  }
   103  
   104  type RunCodeRequest struct {
   105  	ChainId    mavryk.ChainIdHash `json:"chain_id"`
   106  	Script     micheline.Code     `json:"script"`
   107  	Storage    micheline.Prim     `json:"storage"`
   108  	Input      micheline.Prim     `json:"input"`
   109  	Amount     mavryk.N           `json:"amount"`
   110  	Balance    mavryk.N           `json:"balance"`
   111  	Source     *mavryk.Address    `json:"source,omitempty"`
   112  	Payer      *mavryk.Address    `json:"payer,omitempty"`
   113  	Gas        *mavryk.N          `json:"gas,omitempty"`
   114  	Entrypoint string             `json:"entrypoint,omitempty"`
   115  }
   116  
   117  // RunCodeResponse -
   118  type RunCodeResponse struct {
   119  	Operations      []Operation            `json:"operations"`
   120  	Storage         micheline.Prim         `json:"storage"`
   121  	BigmapDiff      micheline.BigmapEvents `json:"big_map_diff,omitempty"`
   122  	LazyStorageDiff micheline.LazyEvents   `json:"lazy_storage_diff,omitempty"`
   123  }
   124  
   125  // Complete ensures an operation is compatible with the current source account's
   126  // on-chain state. Sets branch for TTL control, replay counters, and reveals
   127  // the sender's pubkey if not published yet.
   128  func (c *Client) Complete(ctx context.Context, o *codec.Op, key mavryk.Key) error {
   129  	needBranch := !o.Branch.IsValid()
   130  	needCounter := o.NeedCounter()
   131  	mayNeedReveal := len(o.Contents) > 0 && o.Contents[0].Kind() != mavryk.OpTypeReveal
   132  
   133  	if !needBranch && !mayNeedReveal && !needCounter {
   134  		return nil
   135  	}
   136  
   137  	// add branch for TTL control
   138  	if needBranch {
   139  		ofs := o.Params.MaxOperationsTTL - o.TTL
   140  		hash, err := c.GetBlockHash(ctx, NewBlockOffset(Head, -ofs))
   141  		if err != nil {
   142  			return err
   143  		}
   144  		o.WithBranch(hash)
   145  	}
   146  
   147  	if needCounter || mayNeedReveal {
   148  		// fetch current state
   149  		state, err := c.GetContractExt(ctx, key.Address(), Head)
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		// add reveal if necessary
   155  		if mayNeedReveal && !state.IsRevealed() {
   156  			reveal := &codec.Reveal{
   157  				Manager: codec.Manager{
   158  					Source: key.Address(),
   159  				},
   160  				PublicKey: key,
   161  			}
   162  			reveal.WithLimits(DefaultRevealLimits)
   163  			o.WithContentsFront(reveal)
   164  			needCounter = true
   165  		}
   166  
   167  		// add counters
   168  		if needCounter {
   169  			nextCounter := state.Counter + 1
   170  			for _, op := range o.Contents {
   171  				// skip non-manager ops
   172  				if op.GetCounter() < 0 {
   173  					continue
   174  				}
   175  				op.WithCounter(nextCounter)
   176  				nextCounter++
   177  			}
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  // Simulate dry-runs the execution of the operation against the current state
   184  // of a Tezos node in order to estimate execution costs and fees (fee/burn/gas/storage).
   185  func (c *Client) Simulate(ctx context.Context, o *codec.Op, opts *CallOptions) (*Receipt, error) {
   186  	sim := &codec.Op{
   187  		Branch:    o.Branch,
   188  		Contents:  o.Contents,
   189  		Signature: mavryk.ZeroSignature,
   190  		TTL:       o.TTL,
   191  		Params:    c.Params,
   192  	}
   193  
   194  	if opts == nil {
   195  		opts = &DefaultOptions
   196  	}
   197  
   198  	if sim.TTL == 0 && opts != nil {
   199  		sim.TTL = opts.TTL
   200  	}
   201  
   202  	if !sim.Branch.IsValid() {
   203  		ofs := o.Params.MaxOperationsTTL - sim.TTL
   204  		hash, err := c.GetBlockHash(ctx, NewBlockOffset(Head, -ofs))
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  		sim.Branch = hash
   209  	}
   210  
   211  	if !opts.IgnoreLimits {
   212  		// use default gas/storage limits, set min fee
   213  		for _, op := range o.Contents {
   214  			l := op.Limits()
   215  			if l.GasLimit == 0 {
   216  				l.GasLimit = DefaultSimulationLimits.GasLimit / int64(len(o.Contents))
   217  			}
   218  			if l.StorageLimit == 0 {
   219  				l.StorageLimit = DefaultSimulationLimits.StorageLimit / int64(len(o.Contents))
   220  			}
   221  			op.WithLimits(l)
   222  		}
   223  	}
   224  
   225  	req := RunOperationRequest{
   226  		Operation: sim,
   227  		ChainId:   c.ChainId,
   228  	}
   229  	var err error
   230  	resp := &Operation{}
   231  
   232  	// select simulation method based on requested block
   233  	if opts.SimulationBlockID != nil {
   234  		// simulate in the past
   235  		err = c.RunOperation(ctx, opts.SimulationBlockID, req, resp)
   236  	} else {
   237  		// simulate in the future
   238  		req.Latency = opts.SimulationOffset
   239  		err = c.SimulateOperation(ctx, Head, req, resp)
   240  	}
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	// TODO: adjust min fee using known gas units before return so that res.Cost()
   246  	// reflects the entire cost that Send() will pay
   247  	rcpt := &Receipt{
   248  		Op: resp,
   249  	}
   250  
   251  	// fail with Tezos error when simulation failed
   252  	if !rcpt.IsSuccess() {
   253  		return rcpt, rcpt.Error()
   254  	}
   255  	return rcpt, nil
   256  }
   257  
   258  // Validate compares local serializiation against remote RPC serialization of the
   259  // operation and returns an error on mismatch.
   260  func (c *Client) Validate(ctx context.Context, o *codec.Op) error {
   261  	op := &codec.Op{
   262  		Branch:   o.Branch,
   263  		Contents: o.Contents,
   264  	}
   265  	local := op.Bytes()
   266  	var remote mavryk.HexBytes
   267  	if err := c.ForgeOperation(ctx, Head, op, &remote); err != nil {
   268  		return err
   269  	}
   270  	if !bytes.Equal(local, remote.Bytes()) {
   271  		return fmt.Errorf("tezos: mismatch between local and remote serialized operations:\n local=%s\n remote=%s",
   272  			hex.EncodeToString(local), hex.EncodeToString(remote))
   273  	}
   274  	return nil
   275  }
   276  
   277  // Broadcast sends the signed operation to network and returns the operation hash
   278  // on successful pre-validation.
   279  func (c *Client) Broadcast(ctx context.Context, o *codec.Op) (mavryk.OpHash, error) {
   280  	return c.BroadcastOperation(ctx, o.Bytes())
   281  }
   282  
   283  // Send is a convenience wrapper for sending operations. It auto-completes gas and storage limit,
   284  // ensures minimum fees are set, protects against fee overpayment, signs and broadcasts the final
   285  // operation and waits for a defined number of confirmations.
   286  func (c *Client) Send(ctx context.Context, op *codec.Op, opts *CallOptions) (*Receipt, error) {
   287  	if opts == nil {
   288  		opts = &DefaultOptions
   289  	}
   290  
   291  	signer := c.Signer
   292  	if opts.Signer != nil {
   293  		signer = opts.Signer
   294  	}
   295  
   296  	// identify the sender address for signing the message
   297  	addr := opts.Sender
   298  	if !addr.IsValid() {
   299  		addrs, err := signer.ListAddresses(ctx)
   300  		if err != nil {
   301  			return nil, err
   302  		}
   303  		addr = addrs[0]
   304  	}
   305  
   306  	key, err := signer.GetKey(ctx, addr)
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  
   311  	// use custom observer when provided
   312  	mon := c.BlockObserver
   313  	if opts.Observer != nil {
   314  		mon = opts.Observer
   315  	}
   316  
   317  	// ensure block observer is running
   318  	mon.Listen(c)
   319  
   320  	// set source and params on all ops
   321  	op.WithSource(key.Address()).WithParams(c.Params)
   322  
   323  	// auto-complete op with branch/ttl, source counter, reveal
   324  	err = c.Complete(ctx, op, key)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	// simulate to check tx validity and estimate cost
   330  	sim, err := c.Simulate(ctx, op, opts)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	// fail with Tezos error when simulation failed
   336  	if !sim.IsSuccess() {
   337  		return nil, sim.Error()
   338  	}
   339  
   340  	// apply simulated cost as limits to tx list
   341  	if !opts.IgnoreLimits {
   342  		op.WithLimits(sim.MinLimits(), opts.ExtraGasMargin)
   343  	}
   344  
   345  	// log info about tx costs
   346  	c.logDebug(func() {
   347  		costs := sim.Costs()
   348  		for i, v := range op.Contents {
   349  			verb := "used"
   350  			if opts.IgnoreLimits {
   351  				verb = "forced"
   352  			}
   353  			limits := v.Limits()
   354  			c.Log.Debugf("OP#%03d: %s gas_used(sim)=%d storage_used(sim)=%d storage_burn(sim)=%d alloc_burn(sim)=%d fee(%s)=%d gas_limit(%s)=%d storage_limit(%s)=%d ",
   355  				i, v.Kind(), costs[i].GasUsed, costs[i].StorageUsed, costs[i].StorageBurn, costs[i].AllocationBurn,
   356  				verb, limits.Fee, verb, limits.GasLimit, verb, limits.StorageLimit,
   357  			)
   358  		}
   359  	})
   360  
   361  	// check minFee calc against maxFee if set
   362  	if opts.MaxFee > 0 {
   363  		if l := op.Limits(); l.Fee > opts.MaxFee {
   364  			return nil, fmt.Errorf("estimated cost %d > max %d", l.Fee, opts.MaxFee)
   365  		}
   366  	}
   367  
   368  	// sign digest
   369  	sig, err := signer.SignOperation(ctx, addr, op)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  	op.WithSignature(sig)
   374  
   375  	// trace what we'll broadcast
   376  	c.logTrace(func() {
   377  		buf, _ := op.MarshalJSON()
   378  		c.Log.Tracef("Broadcast: %s", string(buf))
   379  	})
   380  
   381  	// broadcast
   382  	hash, err := c.Broadcast(ctx, op)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  
   387  	// wait for confirmations
   388  	res := NewResult(hash).WithTTL(op.TTL).WithConfirmations(opts.Confirmations)
   389  
   390  	// wait for confirmations
   391  	res.Listen(mon)
   392  	res.WaitContext(ctx)
   393  	if err := res.Err(); err != nil {
   394  		return nil, err
   395  	}
   396  
   397  	// return receipt
   398  	return res.GetReceipt(ctx)
   399  }
   400  
   401  // RunOperation simulates executing an operation without requiring a valid signature.
   402  // The call returns the execution result as regular operation receipt.
   403  func (c *Client) RunOperation(ctx context.Context, id BlockID, body, resp interface{}) error {
   404  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_operation", id)
   405  	return c.Post(ctx, u, body, resp)
   406  }
   407  
   408  // RunCode simulates executing of provided code on the context of a contract at selected block.
   409  func (c *Client) RunCode(ctx context.Context, id BlockID, body, resp interface{}) error {
   410  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_code", id)
   411  	return c.Post(ctx, u, body, resp)
   412  }
   413  
   414  // RunCallback simulates executing of TZip4 view on the context of a contract at selected block.
   415  func (c *Client) RunCallback(ctx context.Context, id BlockID, body, resp interface{}) error {
   416  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_view", id)
   417  	return c.Post(ctx, u, body, resp)
   418  }
   419  
   420  // RunView simulates executing of on on-chain view on the context of a contract at selected block.
   421  func (c *Client) RunView(ctx context.Context, id BlockID, body, resp interface{}) error {
   422  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/run_script_view", id)
   423  	return c.Post(ctx, u, body, resp)
   424  }
   425  
   426  // TraceCode simulates executing of code on the context of a contract at selected block and
   427  // returns a full execution trace.
   428  func (c *Client) TraceCode(ctx context.Context, id BlockID, body, resp interface{}) error {
   429  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/trace_code", id)
   430  	return c.Post(ctx, u, body, resp)
   431  }
   432  
   433  // BroadcastOperation sends a signed operation to the network (injection).
   434  // The call returns the operation hash on success. If theoperation was rejected
   435  // by the node error is of type RPCError.
   436  func (c *Client) BroadcastOperation(ctx context.Context, body []byte) (hash mavryk.OpHash, err error) {
   437  	err = c.Post(ctx, "injection/operation", hex.EncodeToString(body), &hash)
   438  	return
   439  }
   440  
   441  // ForgeOperation uses a remote node to serialize an operation to its binary format.
   442  // The result of this call SHOULD NEVER be used for signing the operation, it is only
   443  // meant for validating the locally generated serialized output.
   444  func (c *Client) ForgeOperation(ctx context.Context, id BlockID, body, resp interface{}) error {
   445  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/forge/operations", id)
   446  	return c.Post(ctx, u, body, resp)
   447  }
   448  
   449  // SimulateOperation simulates executing an operation without requiring a valid signature.
   450  // The call returns the execution result as regular operation receipt with estimated
   451  // future gas usage.
   452  //
   453  // Note gas consumption may differ based on whether a contract is cached inside a node
   454  // at the time of operation inclusion in a block. The contract cache is dynamic
   455  // so under rare circumstances the simulation can underestimates real gas cost
   456  // and a contract call may fail. In such cases attempt to resend the transaction with a
   457  // higher gas margin (CallOptions.ExtraGasMargin > ExtraSafetyMargin).
   458  //
   459  // For simulation purposes a future cache state is predicted. You can control the
   460  // future simulation point via RunOperationRequest.Latency (in blocks).
   461  func (c *Client) SimulateOperation(ctx context.Context, id BlockID, body, resp interface{}) error {
   462  	u := fmt.Sprintf("chains/main/blocks/%s/helpers/scripts/simulate_operation", id)
   463  	return c.Post(ctx, u, body, resp)
   464  }