code.vegaprotocol.io/vega@v0.79.0/core/statevar/engine.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 statevar
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"math/rand"
    22  	"sort"
    23  	"strings"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/events"
    27  	"code.vegaprotocol.io/vega/core/txn"
    28  	"code.vegaprotocol.io/vega/core/types"
    29  	"code.vegaprotocol.io/vega/core/types/statevar"
    30  	"code.vegaprotocol.io/vega/core/validators"
    31  	"code.vegaprotocol.io/vega/libs/num"
    32  	"code.vegaprotocol.io/vega/logging"
    33  
    34  	"github.com/cenkalti/backoff"
    35  	"github.com/golang/protobuf/proto"
    36  )
    37  
    38  var (
    39  	// ErrUnknownStateVar is returned when we get a request (vote, result) for a state variable we don't have.
    40  	ErrUnknownStateVar  = errors.New("unknown state variable")
    41  	ErrNameAlreadyExist = errors.New("state variable already exists with the same name")
    42  	chars               = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
    43  )
    44  
    45  //go:generate go run github.com/golang/mock/mockgen -destination mocks/commander_mock.go -package mocks code.vegaprotocol.io/vega/core/statevar Commander
    46  type Commander interface {
    47  	Command(ctx context.Context, cmd txn.Command, payload proto.Message, f func(string, error), bo *backoff.ExponentialBackOff)
    48  }
    49  
    50  // Broker send events.
    51  type Broker interface {
    52  	SendBatch(events []events.Event)
    53  }
    54  
    55  // Topology the topology service.
    56  //
    57  //go:generate go run github.com/golang/mock/mockgen -destination mocks/topology_mock.go -package mocks code.vegaprotocol.io/vega/core/statevar Topology
    58  type Topology interface {
    59  	IsValidatorVegaPubKey(node string) bool
    60  	AllNodeIDs() []string
    61  	Get(key string) *validators.ValidatorData
    62  	IsValidator() bool
    63  	SelfNodeID() string
    64  	GetTotalVotingPower() int64
    65  	GetVotingPower(pubkey string) int64
    66  }
    67  
    68  // EpochEngine for being notified on epochs.
    69  type EpochEngine interface {
    70  	NotifyOnEpoch(f func(context.Context, types.Epoch))
    71  }
    72  
    73  // Engine is an engine for creating consensus for floaing point "state variables".
    74  type Engine struct {
    75  	log                    *logging.Logger
    76  	config                 Config
    77  	broker                 Broker
    78  	top                    Topology
    79  	rng                    *rand.Rand
    80  	cmd                    Commander
    81  	eventTypeToStateVar    map[statevar.EventType][]*StateVariable
    82  	stateVars              map[string]*StateVariable
    83  	currentTime            time.Time
    84  	validatorVotesRequired num.Decimal
    85  	seq                    int
    86  	updateFrequency        time.Duration
    87  	readyForTimeTrigger    map[string]struct{}
    88  	stateVarToNextCalc     map[string]time.Time
    89  	ss                     *snapshotState
    90  }
    91  
    92  // New instantiates the state variable engine.
    93  func New(log *logging.Logger, config Config, broker Broker, top Topology, cmd Commander) *Engine {
    94  	lg := log.Named(namedLogger)
    95  	lg.SetLevel(config.Level.Get())
    96  	e := &Engine{
    97  		log:                 lg,
    98  		config:              config,
    99  		broker:              broker,
   100  		top:                 top,
   101  		cmd:                 cmd,
   102  		eventTypeToStateVar: map[statevar.EventType][]*StateVariable{},
   103  		stateVars:           map[string]*StateVariable{},
   104  		seq:                 0,
   105  		readyForTimeTrigger: map[string]struct{}{},
   106  		stateVarToNextCalc:  map[string]time.Time{},
   107  		ss:                  &snapshotState{},
   108  	}
   109  
   110  	return e
   111  }
   112  
   113  // generate an id for the variable.
   114  func (e *Engine) generateID(asset, market, name string) string {
   115  	return asset + "_" + market + "_" + name
   116  }
   117  
   118  // generate a random event identifier.
   119  func (e *Engine) generateEventID(asset, market string) string {
   120  	// using the pseudorandomness here to avoid saving a sequence number to the snapshot
   121  	b := make([]rune, 32)
   122  	for i := range b {
   123  		b[i] = chars[e.rng.Intn(len(chars))]
   124  	}
   125  	prefix := asset + "_" + market
   126  	e.seq++
   127  	suffix := string(b)
   128  	return prefix + "_" + suffix
   129  }
   130  
   131  // OnFloatingPointUpdatesDurationUpdate updates the update frequency from the network parameter.
   132  func (e *Engine) OnFloatingPointUpdatesDurationUpdate(ctx context.Context, updateFrequency time.Duration) error {
   133  	e.log.Info("updating floating point update frequency", logging.String("updateFrequency", updateFrequency.String()))
   134  	e.updateFrequency = updateFrequency
   135  	return nil
   136  }
   137  
   138  // OnDefaultValidatorsVoteRequiredUpdate updates the required majority for a vote on a proposed value.
   139  func (e *Engine) OnDefaultValidatorsVoteRequiredUpdate(ctx context.Context, d num.Decimal) error {
   140  	e.validatorVotesRequired = d
   141  	e.log.Info("ValidatorsVoteRequired updated", logging.String("validatorVotesRequired", e.validatorVotesRequired.String()))
   142  	return nil
   143  }
   144  
   145  // NewEvent triggers calculation of state variables that depend on the event type.
   146  func (e *Engine) NewEvent(asset, market string, eventType statevar.EventType) {
   147  	// disabling for now until wiring all state variables
   148  	// if _, ok := e.eventTypeToStateVar[eventType]; !ok {
   149  	// 	e.log.Panic("Unexpected event received", logging.Int("event-type", int(eventType)), logging.String("asset", asset), logging.String("market", market))
   150  	// }
   151  	// generate a unique event id
   152  	eventID := e.generateEventID(asset, market)
   153  	if e.log.GetLevel() <= logging.DebugLevel {
   154  		e.log.Debug("New event for state variable received", logging.String("eventID", eventID), logging.String("asset", asset), logging.String("market", market))
   155  	}
   156  
   157  	for _, sv := range e.eventTypeToStateVar[eventType] {
   158  		if sv.market != market || sv.asset != asset {
   159  			continue
   160  		}
   161  		sv.eventTriggered(eventID)
   162  		// if the sv is time triggered - reset the next run to be now + frequency
   163  		if _, ok := e.stateVarToNextCalc[sv.ID]; ok {
   164  			e.stateVarToNextCalc[sv.ID] = e.currentTime.Add(e.updateFrequency)
   165  		}
   166  	}
   167  }
   168  
   169  // OnBlockEnd calls all state vars to notify them that the block ended and its time to flush events.
   170  func (e *Engine) OnBlockEnd(ctx context.Context) {
   171  	allStateVarIDs := make([]string, 0, len(e.stateVars))
   172  	for ID := range e.stateVars {
   173  		allStateVarIDs = append(allStateVarIDs, ID)
   174  	}
   175  	sort.Strings(allStateVarIDs)
   176  
   177  	for _, ID := range allStateVarIDs {
   178  		e.stateVars[ID].endBlock(ctx)
   179  	}
   180  }
   181  
   182  // OnTick triggers the calculation of state variables whose next scheduled calculation is due.
   183  func (e *Engine) OnTick(_ context.Context, t time.Time) {
   184  	e.currentTime = t
   185  	e.rng = rand.New(rand.NewSource(t.Unix()))
   186  
   187  	// update all state vars on the new block so they can send the batch of events from the previous block
   188  	allStateVarIDs := make([]string, 0, len(e.stateVars))
   189  	for ID := range e.stateVars {
   190  		allStateVarIDs = append(allStateVarIDs, ID)
   191  	}
   192  	sort.Strings(allStateVarIDs)
   193  
   194  	for _, ID := range allStateVarIDs {
   195  		e.stateVars[ID].startBlock(t)
   196  	}
   197  
   198  	// get all the state var with time triggers whose time to tick has come and call them
   199  	stateVarIDs := []string{}
   200  	for ID, nextTime := range e.stateVarToNextCalc {
   201  		if nextTime.UnixNano() <= t.UnixNano() {
   202  			stateVarIDs = append(stateVarIDs, ID)
   203  		}
   204  	}
   205  
   206  	sort.Strings(stateVarIDs)
   207  	eventID := t.Format("20060102_150405.999999999")
   208  	for _, ID := range stateVarIDs {
   209  		sv := e.stateVars[ID]
   210  
   211  		if e.log.GetLevel() <= logging.DebugLevel {
   212  			e.log.Debug("New time based event for state variable received", logging.String("state-var", ID), logging.String("eventID", eventID))
   213  		}
   214  		sv.eventTriggered(eventID)
   215  		e.stateVarToNextCalc[ID] = t.Add(e.updateFrequency)
   216  	}
   217  }
   218  
   219  // ReadyForTimeTrigger is called when the market is ready for time triggered event and sets the next time to run for all state variables of that market that are time triggered.
   220  // This is expected to be called at the end of the opening auction for the market.
   221  func (e *Engine) ReadyForTimeTrigger(asset, mktID string) {
   222  	if e.log.IsDebug() {
   223  		e.log.Debug("ReadyForTimeTrigger", logging.String("asset", asset), logging.String("market-id", mktID))
   224  	}
   225  	if _, ok := e.readyForTimeTrigger[asset+mktID]; !ok {
   226  		e.readyForTimeTrigger[asset+mktID] = struct{}{}
   227  		for _, sv := range e.eventTypeToStateVar[statevar.EventTypeTimeTrigger] {
   228  			if sv.asset == asset && sv.market == mktID {
   229  				e.stateVarToNextCalc[sv.ID] = e.currentTime.Add(e.updateFrequency)
   230  			}
   231  		}
   232  	}
   233  }
   234  
   235  // RegisterStateVariable register a new state variable for which consensus should be managed.
   236  // converter - converts from the native format of the variable and the key value bundle format and vice versa
   237  // startCalculation - a callback to trigger an asynchronous state var calc - the result of which is given through the FinaliseCalculation interface
   238  // trigger - a slice of events that should trigger the calculation of the state variable
   239  // frequency - if time based triggering the frequency to trigger, Duration(0) for no time based trigger
   240  // result - a callback for returning the result converted to the native structure.
   241  func (e *Engine) RegisterStateVariable(asset, market, name string, converter statevar.Converter, startCalculation func(string, statevar.FinaliseCalculation), trigger []statevar.EventType, result func(context.Context, statevar.StateVariableResult) error) error {
   242  	ID := e.generateID(asset, market, name)
   243  	if _, ok := e.stateVars[ID]; ok {
   244  		return ErrNameAlreadyExist
   245  	}
   246  
   247  	if e.log.IsDebug() {
   248  		e.log.Debug("added state variable", logging.String("id", ID), logging.String("asset", asset), logging.String("market", market))
   249  	}
   250  
   251  	sv := NewStateVar(e.log, e.broker, e.top, e.cmd, e.currentTime, ID, asset, market, converter, startCalculation, trigger, result)
   252  	sv.currentTime = e.currentTime
   253  	e.stateVars[ID] = sv
   254  	for _, t := range trigger {
   255  		if _, ok := e.eventTypeToStateVar[t]; !ok {
   256  			e.eventTypeToStateVar[t] = []*StateVariable{}
   257  		}
   258  		e.eventTypeToStateVar[t] = append(e.eventTypeToStateVar[t], sv)
   259  	}
   260  	return nil
   261  }
   262  
   263  // UnregisterStateVariable when a market is settled it no longer exists in the execution engine, and so we don't need to keep setting off
   264  // the time triggered events for it anymore.
   265  func (e *Engine) UnregisterStateVariable(asset, market string) {
   266  	if e.log.IsDebug() {
   267  		e.log.Debug("unregistering state-variables for", logging.String("market", market))
   268  	}
   269  	prefix := e.generateID(asset, market, "")
   270  
   271  	toRemove := make([]string, 0)
   272  	for id := range e.stateVars {
   273  		if strings.HasPrefix(id, prefix) {
   274  			toRemove = append(toRemove, id)
   275  		}
   276  	}
   277  
   278  	for _, id := range toRemove {
   279  		// removing this is also necessary for snapshots. Otherwise the statevars will be included in the snapshot for markets that no longer exist
   280  		// then when we come to restore the snapshot we will have state-vars in the snapshot that are not registered.
   281  		delete(e.stateVarToNextCalc, id)
   282  		delete(e.stateVars, id)
   283  	}
   284  }
   285  
   286  // ProposedValueReceived is called when we receive a result from another node with a proposed result for the calculation triggered by an event.
   287  func (e *Engine) ProposedValueReceived(ctx context.Context, ID, nodeID, eventID string, bundle *statevar.KeyValueBundle) error {
   288  	if e.log.IsDebug() {
   289  		e.log.Debug("bundle received", logging.String("id", ID), logging.String("from-node", nodeID), logging.String("event-id", eventID))
   290  	}
   291  
   292  	if sv, ok := e.stateVars[ID]; ok {
   293  		sv.bundleReceived(ctx, nodeID, eventID, bundle, e.rng, e.validatorVotesRequired)
   294  		return nil
   295  	}
   296  	e.log.Error("ProposedValueReceived called with unknown var", logging.String("id", ID), logging.String("from-node", nodeID))
   297  	return ErrUnknownStateVar
   298  }