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  }