code.vegaprotocol.io/vega@v0.79.0/core/statevar/state_var_test.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_test 17 18 import ( 19 "context" 20 "errors" 21 "strconv" 22 "testing" 23 "time" 24 25 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 26 "code.vegaprotocol.io/vega/core/events" 27 "code.vegaprotocol.io/vega/core/statevar" 28 "code.vegaprotocol.io/vega/core/statevar/mocks" 29 types "code.vegaprotocol.io/vega/core/types/statevar" 30 "code.vegaprotocol.io/vega/libs/num" 31 "code.vegaprotocol.io/vega/logging" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/require" 35 ) 36 37 type testEngine struct { 38 engine *statevar.Engine 39 topology *mocks.MockTopology 40 broker *bmocks.MockBroker 41 commander *mocks.MockCommander 42 } 43 44 // this is how state param bundles would be created: 45 // a native data structure 46 // and a converter to/from bundle type. 47 type sampleParams struct { 48 param1 num.Decimal 49 param2 []num.Decimal 50 } 51 52 type converter struct{} 53 54 var now = time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC) 55 56 func (converter) BundleToInterface(kvb *types.KeyValueBundle) types.StateVariableResult { 57 return &sampleParams{ 58 param1: kvb.KVT[0].Val.(*types.DecimalScalar).Val, 59 param2: kvb.KVT[1].Val.(*types.DecimalVector).Val, 60 } 61 } 62 63 func (converter) InterfaceToBundle(res types.StateVariableResult) *types.KeyValueBundle { 64 value := res.(*sampleParams) 65 return &types.KeyValueBundle{ 66 KVT: []types.KeyValueTol{ 67 {Key: "param1", Val: &types.DecimalScalar{Val: value.param1}, Tolerance: num.DecimalFromFloat(1)}, 68 {Key: "param2", Val: &types.DecimalVector{Val: value.param2}, Tolerance: num.DecimalFromFloat(2)}, 69 }, 70 } 71 } 72 73 func getTestEngine(t *testing.T, startTime time.Time) *testEngine { 74 t.Helper() 75 conf := statevar.NewDefaultConfig() 76 ctrl := gomock.NewController(t) 77 broker := bmocks.NewMockBroker(ctrl) 78 logger := logging.NewTestLogger() 79 topology := mocks.NewMockTopology(ctrl) 80 commander := mocks.NewMockCommander(ctrl) 81 82 engine := statevar.New(logger, conf, broker, topology, commander) 83 engine.OnTick(context.Background(), startTime) 84 return &testEngine{ 85 engine: engine, 86 topology: topology, 87 broker: broker, 88 commander: commander, 89 } 90 } 91 92 func getValidators(t *testing.T, now time.Time, numValidators int) []*testEngine { 93 t.Helper() 94 validators := make([]*testEngine, 0, numValidators) 95 for i := 0; i < numValidators; i++ { 96 validators = append(validators, getTestEngine(t, now)) 97 validators[i].engine.OnDefaultValidatorsVoteRequiredUpdate(context.Background(), num.DecimalFromFloat(0.67)) 98 validators[i].engine.OnFloatingPointUpdatesDurationUpdate(context.Background(), 10*time.Second) 99 validators[i].engine.OnTick(context.Background(), now) 100 } 101 return validators 102 } 103 104 func generateStateVariableForValidator(t *testing.T, testEngine *testEngine, startCalc func(string, types.FinaliseCalculation), resultCallback func(context.Context, types.StateVariableResult) error) error { 105 t.Helper() 106 kvb1 := &types.KeyValueBundle{} 107 kvb1.KVT = append(kvb1.KVT, types.KeyValueTol{ 108 Key: "scalar value", 109 Val: &types.DecimalScalar{Val: num.DecimalFromFloat(1.23456)}, 110 Tolerance: num.DecimalFromInt64(1), 111 }) 112 113 return testEngine.engine.RegisterStateVariable("asset", "market", "name", converter{}, startCalc, []types.EventType{types.EventTypeMarketEnactment, types.EventTypeTimeTrigger}, resultCallback) 114 } 115 116 func defaultStartCalc() func(string, types.FinaliseCalculation) { 117 return func(string, types.FinaliseCalculation) {} 118 } 119 120 func defaultResultBack() func(context.Context, types.StateVariableResult) error { 121 return func(context.Context, types.StateVariableResult) error { return nil } 122 } 123 124 func setupValidators(t *testing.T, numValidators int, startCalc func(string, types.FinaliseCalculation), resultCallback func(context.Context, types.StateVariableResult) error) []*testEngine { 125 t.Helper() 126 validators := getValidators(t, now, numValidators) 127 allNodeIds := []string{"0", "1", "2", "3", "4"} 128 votingPower := map[string]int64{"0": 10, "1": 20, "2": 30, "3": 40, "4": 50} 129 for i, v := range validators { 130 err := generateStateVariableForValidator(t, v, startCalc, resultCallback) 131 require.NoError(t, err) 132 v.topology.EXPECT().IsValidator().Return(true).AnyTimes() 133 v.topology.EXPECT().IsValidatorVegaPubKey(gomock.Any()).DoAndReturn(func(nodeID string) bool { 134 ID, err := strconv.Atoi(nodeID) 135 return err == nil && ID >= 0 && ID < len(allNodeIds) 136 }).AnyTimes() 137 v.topology.EXPECT().AllNodeIDs().Return(allNodeIds).AnyTimes() 138 v.commander.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 139 v.topology.EXPECT().SelfNodeID().Return(allNodeIds[i]).AnyTimes() 140 v.topology.EXPECT().GetVotingPower(gomock.Any()).DoAndReturn(func(nodeID string) int64 { 141 return votingPower[nodeID] 142 }).AnyTimes() 143 v.topology.EXPECT().GetTotalVotingPower().Return(int64(100)).AnyTimes() 144 } 145 return validators 146 } 147 148 func TestStateVar(t *testing.T) { 149 now = time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC) 150 t.Run("test converters from/to native data type/key value bundle", testConverters) 151 t.Run("new event comes in, no previous active event - triggers calculation", testEventTriggeredNoPreviousEvent) 152 t.Run("new event comes in aborting an existing event", testEventTriggeredWithPreviousEvent) 153 t.Run("new event comes in and triggers a calculation that result in an error", testEventTriggeredCalculationError) 154 t.Run("perfect match through quorum", testBundleReceivedPerfectMatchOfQuorum) 155 t.Run("reach consensus through random selection of one that is within reach of 2/3+1 of the others", testBundleReceivedReachingConsensusSuccessfuly) 156 t.Run("no consensus can be reached", testBundleReceivedReachingConsensusNotSuccessful) 157 t.Run("time based trigger", testTimeBasedEvent) 158 } 159 160 func testConverters(t *testing.T) { 161 c := converter{} 162 sampleP := &sampleParams{ 163 param1: num.DecimalFromFloat(1.23456), 164 param2: []num.Decimal{num.DecimalFromFloat(1.1), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}, 165 } 166 167 asBundle := c.InterfaceToBundle(sampleP) 168 require.Equal(t, 2, len(asBundle.KVT)) 169 require.Equal(t, "param1", asBundle.KVT[0].Key) 170 require.Equal(t, num.DecimalFromFloat(1), asBundle.KVT[0].Tolerance) 171 require.Equal(t, "param2", asBundle.KVT[1].Key) 172 require.Equal(t, num.DecimalFromFloat(2), asBundle.KVT[1].Tolerance) 173 174 // check roundtrip - f^(f(a)) = a 175 backToInterface := c.BundleToInterface(asBundle) 176 require.Equal(t, sampleP, backToInterface) 177 require.Equal(t, num.DecimalFromFloat(1.23456), backToInterface.(*sampleParams).param1) 178 require.Equal(t, []num.Decimal{num.DecimalFromFloat(1.1), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}, backToInterface.(*sampleParams).param2) 179 180 // check double roundtrip - g^(g(b)) = b 181 backAsBundle := c.InterfaceToBundle(backToInterface) 182 require.Equal(t, asBundle, backAsBundle) 183 } 184 185 func testEventTriggeredNoPreviousEvent(t *testing.T) { 186 validators := setupValidators(t, 4, defaultStartCalc(), defaultResultBack()) 187 brokerEvents := make([]events.Event, 0, len(validators)) 188 for _, v := range validators { 189 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 190 brokerEvents = append(brokerEvents, events...) 191 }) 192 } 193 194 for _, v := range validators { 195 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 196 } 197 198 time.Sleep(10 * time.Millisecond) 199 for _, v := range validators { 200 v.engine.OnBlockEnd(context.Background()) 201 } 202 203 require.Equal(t, len(validators), len(brokerEvents)) 204 for _, bes := range brokerEvents { 205 evt := events.StateVarEventFromStream(context.Background(), bes.StreamMessage()) 206 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt.EventID) 207 require.Equal(t, "consensus_calc_started", evt.State) 208 } 209 } 210 211 func testEventTriggeredWithPreviousEvent(t *testing.T) { 212 validators := setupValidators(t, 4, defaultStartCalc(), defaultResultBack()) 213 214 brokerEvents := make([]events.Event, 0, len(validators)) 215 for _, v := range validators { 216 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 217 brokerEvents = append(brokerEvents, events...) 218 }) 219 } 220 221 for _, v := range validators { 222 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 223 } 224 225 time.Sleep(10 * time.Millisecond) 226 for _, v := range validators { 227 v.engine.OnBlockEnd(context.Background()) 228 v.engine.OnTick(context.Background(), now.Add(1*time.Second)) 229 } 230 231 require.Equal(t, len(validators), len(brokerEvents)) 232 for _, bes := range brokerEvents { 233 evt := events.StateVarEventFromStream(context.Background(), bes.StreamMessage()) 234 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt.EventID) 235 require.Equal(t, "consensus_calc_started", evt.State) 236 } 237 238 for _, v := range validators { 239 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 240 } 241 242 time.Sleep(100 * time.Millisecond) 243 for _, v := range validators { 244 v.engine.OnBlockEnd(context.Background()) 245 v.engine.OnTick(context.Background(), now.Add(2*time.Second)) 246 } 247 248 require.Equal(t, 3*len(validators), len(brokerEvents)) 249 250 for i := 4; i < 3*len(validators); i += 2 { 251 evt1 := events.StateVarEventFromStream(context.Background(), brokerEvents[i].StreamMessage()) 252 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt1.EventID) 253 require.Equal(t, "consensus_calc_aborted", evt1.State) 254 255 evt2 := events.StateVarEventFromStream(context.Background(), brokerEvents[i+1].StreamMessage()) 256 require.Equal(t, "asset_market_G8FFe2zipFM1jPoS3X31grPi7QrcJ1QF", evt2.EventID) 257 require.Equal(t, "consensus_calc_started", evt2.State) 258 } 259 } 260 261 func testEventTriggeredCalculationError(t *testing.T) { 262 startCalc := func(eventID string, f types.FinaliseCalculation) { 263 f.CalculationFinished(eventID, nil, errors.New("error")) 264 } 265 validators := setupValidators(t, 4, startCalc, defaultResultBack()) 266 267 brokerEvents := make([]events.Event, 0, len(validators)) 268 for _, v := range validators { 269 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 270 brokerEvents = append(brokerEvents, events...) 271 }) 272 } 273 274 for _, v := range validators { 275 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 276 } 277 278 time.Sleep(10 * time.Millisecond) 279 for _, v := range validators { 280 v.engine.OnBlockEnd(context.Background()) 281 v.engine.OnTick(context.Background(), now.Add(1*time.Second)) 282 } 283 284 require.Equal(t, 2*len(validators), len(brokerEvents)) 285 for i := 0; i < 2*len(validators); i += 2 { 286 evt1 := events.StateVarEventFromStream(context.Background(), brokerEvents[i].StreamMessage()) 287 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt1.EventID) 288 require.Equal(t, "consensus_calc_started", evt1.State) 289 290 evt2 := events.StateVarEventFromStream(context.Background(), brokerEvents[i+1].StreamMessage()) 291 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt2.EventID) 292 require.Equal(t, "error", evt2.State) 293 } 294 } 295 296 func testBundleReceivedPerfectMatchOfQuorum(t *testing.T) { 297 res := &sampleParams{ 298 param1: num.DecimalFromFloat(1.23456), 299 param2: []num.Decimal{num.DecimalFromFloat(1.1), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}, 300 } 301 startCalc := func(eventID string, f types.FinaliseCalculation) { 302 f.CalculationFinished(eventID, res, nil) 303 } 304 305 counter := 0 306 resultCallback := func(_ context.Context, r types.StateVariableResult) error { 307 counter++ 308 require.Equal(t, res, r) 309 return nil 310 } 311 312 // start sending publishing the results from each validators (they all would match so after 2/3+1 we should get the result back) 313 validators := setupValidators(t, 5, startCalc, resultCallback) 314 brokerEvents := make([]events.Event, 0, len(validators)) 315 for _, v := range validators { 316 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 317 brokerEvents = append(brokerEvents, events...) 318 }) 319 } 320 321 // event for the right asset/market 322 for _, v := range validators { 323 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 324 } 325 326 // send an unexpected results from all validators to all others, so that there would have been a quorum had it been the right event id 327 c := converter{} 328 bundle := c.InterfaceToBundle(res) 329 for i := 0; i < len(validators); i++ { 330 iAsString := strconv.Itoa(i) 331 332 for j := 0; j < len(validators); j++ { 333 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_name", iAsString, "eventID2", bundle) 334 } 335 } 336 require.Equal(t, 0, counter) 337 338 // send 5 results from non validator nodes, should be all ignored although it's for the right event 339 for i := 5; i < 10; i++ { 340 iAsString := strconv.Itoa(i) 341 342 for j := 0; j < len(validators); j++ { 343 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_name", iAsString, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", bundle) 344 } 345 } 346 require.Equal(t, 0, counter) 347 348 // because the voting power for the validators is 10,20,30,40,50 - a majority is reached when the second and last validators 349 // send bundles from >2/3 of the voting power 350 submittingValidators := []string{"1", "4"} 351 for i := 0; i < len(submittingValidators); i++ { 352 iAsString := submittingValidators[i] 353 354 for j := 0; j < len(validators); j++ { 355 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_name", iAsString, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", bundle) 356 } 357 } 358 // this means that the result callback has been called with the same result for all of them 359 require.Equal(t, 5, counter) 360 361 time.Sleep(10 * time.Millisecond) 362 for _, v := range validators { 363 v.engine.OnBlockEnd(context.Background()) 364 v.engine.OnTick(context.Background(), now.Add(1*time.Second)) 365 } 366 367 // we exepct there to be 10 events emitted, 5 for starting and 5 for perfect match 368 require.Equal(t, 10, len(brokerEvents)) 369 for i := 0; i < len(validators); i++ { 370 evt := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i].StreamMessage()) 371 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt.EventID) 372 require.Equal(t, "consensus_calc_started", evt.State) 373 374 evt2 := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i+1].StreamMessage()) 375 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt2.EventID) 376 require.Equal(t, "perfect_match", evt2.State) 377 } 378 } 379 380 func testBundleReceivedReachingConsensusSuccessfuly(t *testing.T) { 381 // 4 of the results are within the acceptable tolerance, the other one is far off and are received first, so will require 4 good results to be received to reach consensus 382 // therefore consensus is possible 383 validatorResults := []*sampleParams{ 384 {param1: num.DecimalFromFloat(0.23456), param2: []num.Decimal{num.DecimalFromFloat(31), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 385 {param1: num.DecimalFromFloat(1.234), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.3), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 386 {param1: num.DecimalFromFloat(1.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 387 {param1: num.DecimalFromFloat(1.23456), param2: []num.Decimal{num.DecimalFromFloat(31.1), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 388 {param1: num.DecimalFromFloat(2.23456), param2: []num.Decimal{num.DecimalFromFloat(3), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 389 } 390 391 startCalcFuncs := make([]func(eventID string, f types.FinaliseCalculation), 0, len(validatorResults)) 392 for i := range validatorResults { 393 startCalcFuncs = append(startCalcFuncs, func(eventID string, f types.FinaliseCalculation) { 394 f.CalculationFinished(eventID, validatorResults[i], nil) 395 }) 396 } 397 398 counter := 0 399 resultCallback := func(_ context.Context, r types.StateVariableResult) error { 400 counter++ 401 require.Equal(t, validatorResults[0], r) 402 return nil 403 } 404 405 validators := make([]*testEngine, 0, 5) 406 for i := range startCalcFuncs { 407 validators = append(validators, setupValidators(t, 1, startCalcFuncs[i], resultCallback)[0]) 408 } 409 410 // start sending publishing the results from each validators (they all would match so after 2/3+1 we should get the result back) 411 brokerEvents := make([]events.Event, 0, len(validators)) 412 for _, v := range validators { 413 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 414 brokerEvents = append(brokerEvents, events...) 415 }) 416 } 417 418 for _, v := range validators { 419 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 420 } 421 422 c := converter{} 423 424 for i := 0; i < len(validators); i++ { 425 iAsString := strconv.Itoa(i) 426 427 for j := 0; j < len(validators); j++ { 428 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_name", iAsString, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", c.InterfaceToBundle(validatorResults[i])) 429 } 430 } 431 // this means that the result callback has been called with the same result for all of them 432 require.Equal(t, 5, counter) 433 434 time.Sleep(10 * time.Millisecond) 435 for _, v := range validators { 436 v.engine.OnBlockEnd(context.Background()) 437 v.engine.OnTick(context.Background(), now.Add(1*time.Second)) 438 } 439 440 // we exepct there to be 10 events emitted, 5 for starting and 5 for consensus reached 441 require.Equal(t, 10, len(brokerEvents)) 442 for i := 0; i < len(validators); i++ { 443 evt := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i].StreamMessage()) 444 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt.EventID) 445 require.Equal(t, "consensus_calc_started", evt.State) 446 447 evt2 := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i+1].StreamMessage()) 448 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt2.EventID) 449 require.Equal(t, "consensus_reached", evt2.State) 450 } 451 } 452 453 func testBundleReceivedReachingConsensusNotSuccessful(t *testing.T) { 454 // no 3 are within the required tolerance so consensus cannot be reached 455 validatorResults := []*sampleParams{ 456 {param1: num.DecimalFromFloat(100.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 457 {param1: num.DecimalFromFloat(25.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 458 {param1: num.DecimalFromFloat(10.23456), param2: []num.Decimal{num.DecimalFromFloat(1.1), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 459 {param1: num.DecimalFromFloat(5.23456), param2: []num.Decimal{num.DecimalFromFloat(3), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 460 {param1: num.DecimalFromFloat(0.23456), param2: []num.Decimal{num.DecimalFromFloat(0.11), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 461 } 462 463 startCalcFuncs := make([]func(eventID string, f types.FinaliseCalculation), 0, len(validatorResults)) 464 for i := range validatorResults { 465 startCalcFuncs = append(startCalcFuncs, func(eventID string, f types.FinaliseCalculation) { 466 f.CalculationFinished(eventID, validatorResults[i], nil) 467 }) 468 } 469 470 resultCallback := func(_ context.Context, r types.StateVariableResult) error { 471 require.Fail(t, "expecting no consensus") 472 return nil 473 } 474 475 validators := make([]*testEngine, 0, 5) 476 for i := range startCalcFuncs { 477 validators = append(validators, setupValidators(t, 1, startCalcFuncs[i], resultCallback)[0]) 478 } 479 480 // start sending publishing the results from each validators (they all would match so after 2/3+1 we should get the result back) 481 brokerEvents := make([]events.Event, 0, len(validators)) 482 for _, v := range validators { 483 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 484 brokerEvents = append(brokerEvents, events...) 485 }) 486 } 487 488 for _, v := range validators { 489 v.engine.NewEvent("asset", "market", types.EventTypeMarketEnactment) 490 } 491 492 // send an unexpected results from all validators to all others, so that there would have been a quorum had it been the right event id 493 c := converter{} 494 495 for i := 0; i < len(validators); i++ { 496 iAsString := strconv.Itoa(i) 497 498 for j := 0; j < len(validators); j++ { 499 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", iAsString, "asset_market_OcskiC47WpCBO63KYKtLbEUctsTRRkwF_1", c.InterfaceToBundle(validatorResults[i])) 500 } 501 } 502 503 time.Sleep(10 * time.Millisecond) 504 for _, v := range validators { 505 v.engine.OnBlockEnd(context.Background()) 506 v.engine.OnTick(context.Background(), now.Add(1*time.Second)) 507 } 508 509 // we exepct there to be 5 events emitted 510 require.Equal(t, 5, len(brokerEvents)) 511 for i := 0; i < len(validators); i++ { 512 evt := events.StateVarEventFromStream(context.Background(), brokerEvents[i].StreamMessage()) 513 require.Equal(t, "asset_market_8SQcDlWbkRMBvCoawjhbLStINMoO9wwo", evt.EventID) 514 require.Equal(t, "consensus_calc_started", evt.State) 515 } 516 } 517 518 func testTimeBasedEvent(t *testing.T) { 519 now = time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC) 520 // 4 of the results are within the acceptable tolerance, the other two are far off and are received first, so will require all 5 results to be received to reach consensus 521 // therefore consensus is possible 522 validatorResults := []*sampleParams{ 523 {param1: num.DecimalFromFloat(1.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 524 {param1: num.DecimalFromFloat(1.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 525 {param1: num.DecimalFromFloat(0.23456), param2: []num.Decimal{num.DecimalFromFloat(30), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 526 {param1: num.DecimalFromFloat(1.23456), param2: []num.Decimal{num.DecimalFromFloat(31), num.DecimalFromFloat(2.2), num.DecimalFromFloat(3.3), num.DecimalFromFloat(4.4)}}, 527 {param1: num.DecimalFromFloat(2.23456), param2: []num.Decimal{num.DecimalFromFloat(3), num.DecimalFromFloat(0.2), num.DecimalFromFloat(1.3), num.DecimalFromFloat(2.4)}}, 528 } 529 530 startCalcFuncs := make([]func(eventID string, f types.FinaliseCalculation), 0, len(validatorResults)) 531 for i := range validatorResults { 532 startCalcFuncs = append(startCalcFuncs, func(eventID string, f types.FinaliseCalculation) { 533 f.CalculationFinished(eventID, validatorResults[i], nil) 534 }) 535 } 536 537 counter := 0 538 resultCallback := func(_ context.Context, r types.StateVariableResult) error { 539 counter++ 540 require.Equal(t, validatorResults[2], r) 541 return nil 542 } 543 544 validators := make([]*testEngine, 0, 5) 545 for i := range startCalcFuncs { 546 validators = append(validators, setupValidators(t, 1, startCalcFuncs[i], resultCallback)[0]) 547 } 548 549 for _, validator := range validators { 550 validator.engine.ReadyForTimeTrigger("asset", "market") 551 } 552 553 // start sending publishing the results from each validators (they all would match so after 2/3+1 we should get the result back) 554 brokerEvents := make([]events.Event, 0, len(validators)) 555 for _, v := range validators { 556 v.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes().Do(func(events []events.Event) { 557 brokerEvents = append(brokerEvents, events...) 558 }) 559 } 560 561 now = now.Add(time.Second * 10) 562 for _, v := range validators { 563 v.engine.OnBlockEnd(context.Background()) 564 v.engine.OnTick(context.Background(), now) 565 } 566 time.Sleep(10 * time.Millisecond) 567 568 // send an unexpected results from all validators to all others, so that there would have been a quorum had it been the right event id 569 c := converter{} 570 571 for i := 0; i < len(validators); i++ { 572 iAsString := strconv.Itoa(i) 573 574 for j := 0; j < len(validators); j++ { 575 validators[j].engine.ProposedValueReceived(context.Background(), "asset_market_name", iAsString, "20210221_011040", c.InterfaceToBundle(validatorResults[i])) 576 } 577 } 578 579 now = now.Add(time.Second * 1) 580 581 for _, v := range validators { 582 v.engine.OnBlockEnd(context.Background()) 583 v.engine.OnTick(context.Background(), now) 584 } 585 586 time.Sleep(10 * time.Millisecond) 587 588 // this means that the result callback has been called with the same result for all of them 589 require.Equal(t, 5, counter) 590 591 // we exepct there to be 10 events emitted, 5 for starting and 5 for consensus reached 592 require.Equal(t, 10, len(brokerEvents)) 593 for i := 0; i < len(validators); i++ { 594 evt := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i].StreamMessage()) 595 require.Equal(t, "20210221_011040", evt.EventID) 596 require.Equal(t, "consensus_calc_started", evt.State) 597 598 evt2 := events.StateVarEventFromStream(context.Background(), brokerEvents[2*i+1].StreamMessage()) 599 require.Equal(t, "20210221_011040", evt2.EventID) 600 require.Equal(t, "consensus_reached", evt2.State) 601 } 602 603 // advance 9 more seconds to get another time trigger 604 now = now.Add(time.Second * 9) 605 for _, v := range validators { 606 v.engine.OnBlockEnd(context.Background()) 607 v.engine.OnTick(context.Background(), now) 608 } 609 brokerEvents = []events.Event{} 610 611 // start another block for events to be emitted 612 now = now.Add(time.Second * 1) 613 for _, v := range validators { 614 v.engine.OnBlockEnd(context.Background()) 615 v.engine.OnTick(context.Background(), now) 616 } 617 time.Sleep(10 * time.Millisecond) 618 619 require.Equal(t, 5, len(brokerEvents)) 620 for i := 0; i < len(validators); i++ { 621 evt := events.StateVarEventFromStream(context.Background(), brokerEvents[i].StreamMessage()) 622 require.Equal(t, "20210221_011050", evt.EventID) 623 require.Equal(t, "consensus_calc_started", evt.State) 624 } 625 626 // Remove time trigger events 627 for _, v := range validators { 628 v.engine.UnregisterStateVariable("asset", "market") 629 } 630 631 // advance even more to when we should have triggered 632 brokerEvents = []events.Event{} 633 now = now.Add(time.Second * 9) 634 for _, v := range validators { 635 v.engine.OnBlockEnd(context.Background()) 636 v.engine.OnTick(context.Background(), now) 637 } 638 639 // expected no events 640 brokerEvents = []events.Event{} 641 require.Equal(t, 0, len(brokerEvents)) 642 }