code.vegaprotocol.io/vega@v0.79.0/core/processor/gastimator.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package processor
    17  
    18  import (
    19  	"context"
    20  	"math"
    21  
    22  	"code.vegaprotocol.io/vega/core/blockchain/abci"
    23  	"code.vegaprotocol.io/vega/core/txn"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    27  )
    28  
    29  const (
    30  	batchFactor    = 0.5
    31  	pegCostFactor  = uint64(50)
    32  	stopCostFactor = 0.2
    33  	positionFactor = uint64(1)
    34  	levelFactor    = 0.1
    35  	high           = 10000
    36  	medium         = 100
    37  	low            = 1
    38  )
    39  
    40  type ExecEngine interface {
    41  	GetMarketCounters() map[string]*types.MarketCounters
    42  }
    43  
    44  type Gastimator struct {
    45  	minBlockCapacity uint64
    46  	maxGas           uint64
    47  	defaultGas       uint64
    48  	exec             ExecEngine
    49  	marketCounters   map[string]*types.MarketCounters
    50  }
    51  
    52  func NewGastimator(exec ExecEngine) *Gastimator {
    53  	return &Gastimator{
    54  		exec:           exec,
    55  		marketCounters: map[string]*types.MarketCounters{},
    56  	}
    57  }
    58  
    59  // OnBlockEnd is called at the end of the block to update the per market counters and return the max gas as defined by the network parameter.
    60  func (g *Gastimator) OnBlockEnd() uint64 {
    61  	g.marketCounters = g.exec.GetMarketCounters()
    62  	return g.maxGas
    63  }
    64  
    65  // OnMaxGasUpdate updates the max gas from the network parameter.
    66  func (g *Gastimator) OnMinBlockCapacityUpdate(ctx context.Context, minBlockCapacity *num.Uint) error {
    67  	g.minBlockCapacity = minBlockCapacity.Uint64()
    68  	return nil
    69  }
    70  
    71  // OnMaxGasUpdate updates the max gas from the network parameter.
    72  func (g *Gastimator) OnMaxGasUpdate(ctx context.Context, max *num.Uint) error {
    73  	g.maxGas = max.Uint64()
    74  	return nil
    75  }
    76  
    77  // OnDefaultGasUpdate updates the default gas wanted per transaction.
    78  func (g *Gastimator) OnDefaultGasUpdate(ctx context.Context, def *num.Uint) error {
    79  	g.defaultGas = def.Uint64()
    80  	return nil
    81  }
    82  
    83  // GetMaxGas returns the current value of max gas.
    84  func (g *Gastimator) GetMaxGas() uint64 {
    85  	return g.maxGas
    86  }
    87  
    88  func (g *Gastimator) GetPriority(tx abci.Tx) uint64 {
    89  	switch tx.Command() {
    90  	case txn.ProposeCommand, txn.BatchProposeCommand, txn.VoteCommand:
    91  		return medium
    92  	default:
    93  		if tx.Command().IsValidatorCommand() {
    94  			return high
    95  		}
    96  		return low
    97  	}
    98  }
    99  
   100  func (g *Gastimator) CalcGasWantedForTx(tx abci.Tx) (uint64, error) {
   101  	switch tx.Command() {
   102  	case txn.DelayedTransactionsWrapper:
   103  		return 0, nil
   104  	case txn.SubmitOrderCommand:
   105  		s := &commandspb.OrderSubmission{}
   106  		if err := tx.Unmarshal(s); err != nil {
   107  			return g.maxGas + 1, err
   108  		}
   109  		return g.orderGastimate(s.MarketId), nil
   110  	case txn.AmendOrderCommand:
   111  		s := &commandspb.OrderAmendment{}
   112  		if err := tx.Unmarshal(s); err != nil {
   113  			return g.maxGas + 1, err
   114  		}
   115  		return g.orderGastimate(s.MarketId), nil
   116  	case txn.CancelOrderCommand:
   117  		s := &commandspb.OrderCancellation{}
   118  		if err := tx.Unmarshal(s); err != nil {
   119  			return g.maxGas + 1, err
   120  		}
   121  		// if it is a cancel for one market
   122  		if len(s.MarketId) > 0 && len(s.OrderId) > 0 {
   123  			return g.cancelOrderGastimate(s.MarketId), nil
   124  		}
   125  		// if it is a cancel for all markets
   126  		return g.defaultGas, nil
   127  	case txn.BatchMarketInstructions:
   128  		s := &commandspb.BatchMarketInstructions{}
   129  		if err := tx.Unmarshal(s); err != nil {
   130  			return g.maxGas + 1, err
   131  		}
   132  		return g.batchGastimate(s), nil
   133  	case txn.StopOrdersSubmissionCommand:
   134  		s := &commandspb.StopOrdersSubmission{}
   135  		if err := tx.Unmarshal(s); err != nil {
   136  			return g.maxGas + 1, err
   137  		}
   138  		var marketId string
   139  		if s.FallsBelow != nil {
   140  			marketId = s.FallsBelow.OrderSubmission.MarketId
   141  		} else {
   142  			marketId = s.RisesAbove.OrderSubmission.MarketId
   143  		}
   144  
   145  		return g.orderGastimate(marketId), nil
   146  	case txn.StopOrdersCancellationCommand:
   147  		s := &commandspb.StopOrdersCancellation{}
   148  		if err := tx.Unmarshal(s); err != nil {
   149  			return g.maxGas + 1, err
   150  		}
   151  		// if it is a cancel for one market
   152  		if s.MarketId != nil && s.StopOrderId != nil {
   153  			return g.cancelOrderGastimate(*s.MarketId), nil
   154  		}
   155  		// if it is a cancel for all markets
   156  		return g.defaultGas, nil
   157  
   158  	default:
   159  		return g.defaultGas, nil
   160  	}
   161  }
   162  
   163  // gasBatch =
   164  // the full cost of the first cancellation (i.e. gasCancel)
   165  // plus batchFactor times sum of all subsequent cancellations added together (each costing gasOrder)
   166  // plus the full cost of the first amendment at gasOrder
   167  // plus batchFactor sum of all subsequent amendments added together (each costing gasOrder)
   168  // plus the full cost of the first limit order at gasOrder
   169  // plus batchFactor sum of all subsequent limit orders added together (each costing gasOrder)
   170  // gasBatch = min(maxGas-1,batchFactor).
   171  func (g *Gastimator) batchGastimate(batch *commandspb.BatchMarketInstructions) uint64 {
   172  	totalBatchGas := 0.0
   173  	for i, os := range batch.Submissions {
   174  		factor := batchFactor
   175  		if i == 0 {
   176  			factor = 1.0
   177  		}
   178  		orderGas := g.orderGastimate(os.MarketId)
   179  		totalBatchGas += factor * float64(orderGas)
   180  	}
   181  	for i, os := range batch.Amendments {
   182  		factor := batchFactor
   183  		if i == 0 {
   184  			factor = 1.0
   185  		}
   186  		orderGas := g.orderGastimate(os.MarketId)
   187  		totalBatchGas += factor * float64(orderGas)
   188  	}
   189  	for i, os := range batch.Cancellations {
   190  		factor := batchFactor
   191  		if i == 0 {
   192  			factor = 1.0
   193  		}
   194  		orderGas := g.cancelOrderGastimate(os.MarketId)
   195  		totalBatchGas += factor * float64(orderGas)
   196  	}
   197  	for i, os := range batch.StopOrdersCancellation {
   198  		factor := batchFactor
   199  		if i == 0 {
   200  			factor = 1.0
   201  		}
   202  		if os.MarketId == nil {
   203  			totalBatchGas += factor * float64(g.defaultGas)
   204  		}
   205  		orderGas := g.cancelOrderGastimate(*os.MarketId)
   206  		totalBatchGas += factor * float64(orderGas)
   207  	}
   208  	for i, os := range batch.StopOrdersSubmission {
   209  		factor := batchFactor
   210  		if i == 0 {
   211  			factor = 1.0
   212  		}
   213  		var marketId string
   214  		// if both are nil, marketId will be empty string, yielding default gas
   215  		// the order is invalid, but validation is applied later.
   216  		if os.FallsBelow != nil && os.FallsBelow.OrderSubmission != nil {
   217  			marketId = os.FallsBelow.OrderSubmission.MarketId
   218  		} else if os.RisesAbove != nil && os.RisesAbove.OrderSubmission != nil {
   219  			marketId = os.RisesAbove.OrderSubmission.MarketId
   220  		}
   221  		orderGas := g.orderGastimate(marketId)
   222  		totalBatchGas += factor * float64(orderGas)
   223  	}
   224  	return uint64(math.Min(float64(uint64(totalBatchGas)), float64(g.maxGas-1)))
   225  }
   226  
   227  // gasOrder = network.transaction.defaultgas + peg cost factor x pegs
   228  // + position factor x positions
   229  // + level factor x levels
   230  // gasOrder = min(maxGas-1,gasOrder).
   231  func (g *Gastimator) orderGastimate(marketID string) uint64 {
   232  	if marketCounters, ok := g.marketCounters[marketID]; ok {
   233  		return uint64(math.Min(float64(
   234  			g.defaultGas+
   235  				uint64(stopCostFactor*float64(marketCounters.StopOrderCounter))+
   236  				pegCostFactor*marketCounters.PeggedOrderCounter+
   237  				positionFactor*marketCounters.PositionCount+
   238  				uint64(levelFactor*float64(marketCounters.OrderbookLevelCount))),
   239  			math.Max(1.0, float64(g.maxGas/g.minBlockCapacity-1))))
   240  	}
   241  	return g.defaultGas
   242  }
   243  
   244  // gasCancel = network.transaction.defaultgas + peg cost factor x pegs
   245  // + level factor x levels
   246  // gasCancel = min(maxGas-1,gasCancel).
   247  func (g *Gastimator) cancelOrderGastimate(marketID string) uint64 {
   248  	if marketCounters, ok := g.marketCounters[marketID]; ok {
   249  		return uint64(math.Min(float64(
   250  			g.defaultGas+
   251  				uint64(stopCostFactor*float64(marketCounters.StopOrderCounter))+
   252  				pegCostFactor*marketCounters.PeggedOrderCounter+
   253  				uint64(0.1*float64(marketCounters.OrderbookLevelCount))),
   254  			math.Max(1.0, float64(g.maxGas/g.minBlockCapacity-1))))
   255  	}
   256  	return g.defaultGas
   257  }