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 }