github.com/arcology-network/consensus-engine@v1.9.0/state/execution_test.go (about)

     1  package state_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	abci "github.com/arcology-network/consensus-engine/abci/types"
    13  	"github.com/arcology-network/consensus-engine/crypto"
    14  	"github.com/arcology-network/consensus-engine/crypto/ed25519"
    15  	cryptoenc "github.com/arcology-network/consensus-engine/crypto/encoding"
    16  	"github.com/arcology-network/consensus-engine/crypto/tmhash"
    17  	"github.com/arcology-network/consensus-engine/libs/log"
    18  	mmock "github.com/arcology-network/consensus-engine/mempool/mock"
    19  	tmproto "github.com/arcology-network/consensus-engine/proto/tendermint/types"
    20  	tmversion "github.com/arcology-network/consensus-engine/proto/tendermint/version"
    21  	"github.com/arcology-network/consensus-engine/proxy"
    22  	sm "github.com/arcology-network/consensus-engine/state"
    23  	"github.com/arcology-network/consensus-engine/state/mocks"
    24  	"github.com/arcology-network/consensus-engine/types"
    25  	tmtime "github.com/arcology-network/consensus-engine/types/time"
    26  	"github.com/arcology-network/consensus-engine/version"
    27  )
    28  
    29  var (
    30  	chainID             = "execution_chain"
    31  	testPartSize uint32 = 65536
    32  	nTxsPerBlock        = 10
    33  )
    34  
    35  func TestApplyBlock(t *testing.T) {
    36  	app := &testApp{}
    37  	cc := proxy.NewLocalClientCreator(app)
    38  	proxyApp := proxy.NewAppConns(cc)
    39  	err := proxyApp.Start()
    40  	require.Nil(t, err)
    41  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
    42  
    43  	state, stateDB, _ := makeState(1, 1)
    44  	stateStore := sm.NewStore(stateDB)
    45  
    46  	blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
    47  		mmock.Mempool{}, sm.EmptyEvidencePool{})
    48  
    49  	block := makeBlock(state, 1)
    50  	blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
    51  
    52  	state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block)
    53  	require.Nil(t, err)
    54  	assert.EqualValues(t, retainHeight, 1)
    55  
    56  	// TODO check state and mempool
    57  	assert.EqualValues(t, 1, state.Version.Consensus.App, "App version wasn't updated")
    58  }
    59  
    60  // TestBeginBlockValidators ensures we send absent validators list.
    61  func TestBeginBlockValidators(t *testing.T) {
    62  	app := &testApp{}
    63  	cc := proxy.NewLocalClientCreator(app)
    64  	proxyApp := proxy.NewAppConns(cc)
    65  	err := proxyApp.Start()
    66  	require.Nil(t, err)
    67  	defer proxyApp.Stop() //nolint:errcheck // no need to check error again
    68  
    69  	state, stateDB, _ := makeState(2, 2)
    70  	stateStore := sm.NewStore(stateDB)
    71  
    72  	prevHash := state.LastBlockID.Hash
    73  	prevParts := types.PartSetHeader{}
    74  	prevBlockID := types.BlockID{Hash: prevHash, PartSetHeader: prevParts}
    75  
    76  	var (
    77  		now        = tmtime.Now()
    78  		commitSig0 = types.NewCommitSigForBlock(
    79  			[]byte("Signature1"),
    80  			state.Validators.Validators[0].Address,
    81  			now)
    82  		commitSig1 = types.NewCommitSigForBlock(
    83  			[]byte("Signature2"),
    84  			state.Validators.Validators[1].Address,
    85  			now)
    86  		absentSig = types.NewCommitSigAbsent()
    87  	)
    88  
    89  	testCases := []struct {
    90  		desc                     string
    91  		lastCommitSigs           []types.CommitSig
    92  		expectedAbsentValidators []int
    93  	}{
    94  		{"none absent", []types.CommitSig{commitSig0, commitSig1}, []int{}},
    95  		{"one absent", []types.CommitSig{commitSig0, absentSig}, []int{1}},
    96  		{"multiple absent", []types.CommitSig{absentSig, absentSig}, []int{0, 1}},
    97  	}
    98  
    99  	for _, tc := range testCases {
   100  		lastCommit := types.NewCommit(1, 0, prevBlockID, tc.lastCommitSigs)
   101  
   102  		// block for height 2
   103  		block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address)
   104  
   105  		_, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateStore, 1)
   106  		require.Nil(t, err, tc.desc)
   107  
   108  		// -> app receives a list of validators with a bool indicating if they signed
   109  		ctr := 0
   110  		for i, v := range app.CommitVotes {
   111  			if ctr < len(tc.expectedAbsentValidators) &&
   112  				tc.expectedAbsentValidators[ctr] == i {
   113  
   114  				assert.False(t, v.SignedLastBlock)
   115  				ctr++
   116  			} else {
   117  				assert.True(t, v.SignedLastBlock)
   118  			}
   119  		}
   120  	}
   121  }
   122  
   123  // TestBeginBlockByzantineValidators ensures we send byzantine validators list.
   124  func TestBeginBlockByzantineValidators(t *testing.T) {
   125  	app := &testApp{}
   126  	cc := proxy.NewLocalClientCreator(app)
   127  	proxyApp := proxy.NewAppConns(cc)
   128  	err := proxyApp.Start()
   129  	require.Nil(t, err)
   130  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
   131  
   132  	state, stateDB, privVals := makeState(1, 1)
   133  	stateStore := sm.NewStore(stateDB)
   134  
   135  	defaultEvidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
   136  	privVal := privVals[state.Validators.Validators[0].Address.String()]
   137  	blockID := makeBlockID([]byte("headerhash"), 1000, []byte("partshash"))
   138  	header := &types.Header{
   139  		Version:            tmversion.Consensus{Block: version.BlockProtocol, App: 1},
   140  		ChainID:            state.ChainID,
   141  		Height:             10,
   142  		Time:               defaultEvidenceTime,
   143  		LastBlockID:        blockID,
   144  		LastCommitHash:     crypto.CRandBytes(tmhash.Size),
   145  		DataHash:           crypto.CRandBytes(tmhash.Size),
   146  		ValidatorsHash:     state.Validators.Hash(),
   147  		NextValidatorsHash: state.Validators.Hash(),
   148  		ConsensusHash:      crypto.CRandBytes(tmhash.Size),
   149  		AppHash:            crypto.CRandBytes(tmhash.Size),
   150  		LastResultsHash:    crypto.CRandBytes(tmhash.Size),
   151  		EvidenceHash:       crypto.CRandBytes(tmhash.Size),
   152  		ProposerAddress:    crypto.CRandBytes(crypto.AddressSize),
   153  	}
   154  
   155  	// we don't need to worry about validating the evidence as long as they pass validate basic
   156  	dve := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID)
   157  	dve.ValidatorPower = 1000
   158  	lcae := &types.LightClientAttackEvidence{
   159  		ConflictingBlock: &types.LightBlock{
   160  			SignedHeader: &types.SignedHeader{
   161  				Header: header,
   162  				Commit: types.NewCommit(10, 0, makeBlockID(header.Hash(), 100, []byte("partshash")), []types.CommitSig{{
   163  					BlockIDFlag:      types.BlockIDFlagNil,
   164  					ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
   165  					Timestamp:        defaultEvidenceTime,
   166  					Signature:        crypto.CRandBytes(types.MaxSignatureSize),
   167  				}}),
   168  			},
   169  			ValidatorSet: state.Validators,
   170  		},
   171  		CommonHeight:        8,
   172  		ByzantineValidators: []*types.Validator{state.Validators.Validators[0]},
   173  		TotalVotingPower:    12,
   174  		Timestamp:           defaultEvidenceTime,
   175  	}
   176  
   177  	ev := []types.Evidence{dve, lcae}
   178  
   179  	abciEv := []abci.Evidence{
   180  		{
   181  			Type:             abci.EvidenceType_DUPLICATE_VOTE,
   182  			Height:           3,
   183  			Time:             defaultEvidenceTime,
   184  			Validator:        types.TM2PB.Validator(state.Validators.Validators[0]),
   185  			TotalVotingPower: 10,
   186  		},
   187  		{
   188  			Type:             abci.EvidenceType_LIGHT_CLIENT_ATTACK,
   189  			Height:           8,
   190  			Time:             defaultEvidenceTime,
   191  			Validator:        types.TM2PB.Validator(state.Validators.Validators[0]),
   192  			TotalVotingPower: 12,
   193  		},
   194  	}
   195  
   196  	evpool := &mocks.EvidencePool{}
   197  	evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return(ev, int64(100))
   198  	evpool.On("Update", mock.AnythingOfType("state.State"), mock.AnythingOfType("types.EvidenceList")).Return()
   199  	evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil)
   200  
   201  	blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(),
   202  		mmock.Mempool{}, evpool)
   203  
   204  	block := makeBlock(state, 1)
   205  	block.Evidence = types.EvidenceData{Evidence: ev}
   206  	block.Header.EvidenceHash = block.Evidence.Hash()
   207  	blockID = types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
   208  
   209  	state, retainHeight, err := blockExec.ApplyBlock(state, blockID, block)
   210  	require.Nil(t, err)
   211  	assert.EqualValues(t, retainHeight, 1)
   212  
   213  	// TODO check state and mempool
   214  	assert.Equal(t, abciEv, app.ByzantineValidators)
   215  }
   216  
   217  func TestValidateValidatorUpdates(t *testing.T) {
   218  	pubkey1 := ed25519.GenPrivKey().PubKey()
   219  	pubkey2 := ed25519.GenPrivKey().PubKey()
   220  	pk1, err := cryptoenc.PubKeyToProto(pubkey1)
   221  	assert.NoError(t, err)
   222  	pk2, err := cryptoenc.PubKeyToProto(pubkey2)
   223  	assert.NoError(t, err)
   224  
   225  	defaultValidatorParams := tmproto.ValidatorParams{PubKeyTypes: []string{types.ABCIPubKeyTypeEd25519}}
   226  
   227  	testCases := []struct {
   228  		name string
   229  
   230  		abciUpdates     []abci.ValidatorUpdate
   231  		validatorParams tmproto.ValidatorParams
   232  
   233  		shouldErr bool
   234  	}{
   235  		{
   236  			"adding a validator is OK",
   237  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: 20}},
   238  			defaultValidatorParams,
   239  			false,
   240  		},
   241  		{
   242  			"updating a validator is OK",
   243  			[]abci.ValidatorUpdate{{PubKey: pk1, Power: 20}},
   244  			defaultValidatorParams,
   245  			false,
   246  		},
   247  		{
   248  			"removing a validator is OK",
   249  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
   250  			defaultValidatorParams,
   251  			false,
   252  		},
   253  		{
   254  			"adding a validator with negative power results in error",
   255  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: -100}},
   256  			defaultValidatorParams,
   257  			true,
   258  		},
   259  	}
   260  
   261  	for _, tc := range testCases {
   262  		tc := tc
   263  		t.Run(tc.name, func(t *testing.T) {
   264  			err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams)
   265  			if tc.shouldErr {
   266  				assert.Error(t, err)
   267  			} else {
   268  				assert.NoError(t, err)
   269  			}
   270  		})
   271  	}
   272  }
   273  
   274  func TestUpdateValidators(t *testing.T) {
   275  	pubkey1 := ed25519.GenPrivKey().PubKey()
   276  	val1 := types.NewValidator(pubkey1, 10)
   277  	pubkey2 := ed25519.GenPrivKey().PubKey()
   278  	val2 := types.NewValidator(pubkey2, 20)
   279  
   280  	pk, err := cryptoenc.PubKeyToProto(pubkey1)
   281  	require.NoError(t, err)
   282  	pk2, err := cryptoenc.PubKeyToProto(pubkey2)
   283  	require.NoError(t, err)
   284  
   285  	testCases := []struct {
   286  		name string
   287  
   288  		currentSet  *types.ValidatorSet
   289  		abciUpdates []abci.ValidatorUpdate
   290  
   291  		resultingSet *types.ValidatorSet
   292  		shouldErr    bool
   293  	}{
   294  		{
   295  			"adding a validator is OK",
   296  			types.NewValidatorSet([]*types.Validator{val1}),
   297  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: 20}},
   298  			types.NewValidatorSet([]*types.Validator{val1, val2}),
   299  			false,
   300  		},
   301  		{
   302  			"updating a validator is OK",
   303  			types.NewValidatorSet([]*types.Validator{val1}),
   304  			[]abci.ValidatorUpdate{{PubKey: pk, Power: 20}},
   305  			types.NewValidatorSet([]*types.Validator{types.NewValidator(pubkey1, 20)}),
   306  			false,
   307  		},
   308  		{
   309  			"removing a validator is OK",
   310  			types.NewValidatorSet([]*types.Validator{val1, val2}),
   311  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
   312  			types.NewValidatorSet([]*types.Validator{val1}),
   313  			false,
   314  		},
   315  		{
   316  			"removing a non-existing validator results in error",
   317  			types.NewValidatorSet([]*types.Validator{val1}),
   318  			[]abci.ValidatorUpdate{{PubKey: pk2, Power: 0}},
   319  			types.NewValidatorSet([]*types.Validator{val1}),
   320  			true,
   321  		},
   322  	}
   323  
   324  	for _, tc := range testCases {
   325  		tc := tc
   326  		t.Run(tc.name, func(t *testing.T) {
   327  			updates, err := types.PB2TM.ValidatorUpdates(tc.abciUpdates)
   328  			assert.NoError(t, err)
   329  			err = tc.currentSet.UpdateWithChangeSet(updates)
   330  			if tc.shouldErr {
   331  				assert.Error(t, err)
   332  			} else {
   333  				assert.NoError(t, err)
   334  				require.Equal(t, tc.resultingSet.Size(), tc.currentSet.Size())
   335  
   336  				assert.Equal(t, tc.resultingSet.TotalVotingPower(), tc.currentSet.TotalVotingPower())
   337  
   338  				assert.Equal(t, tc.resultingSet.Validators[0].Address, tc.currentSet.Validators[0].Address)
   339  				if tc.resultingSet.Size() > 1 {
   340  					assert.Equal(t, tc.resultingSet.Validators[1].Address, tc.currentSet.Validators[1].Address)
   341  				}
   342  			}
   343  		})
   344  	}
   345  }
   346  
   347  // TestEndBlockValidatorUpdates ensures we update validator set and send an event.
   348  func TestEndBlockValidatorUpdates(t *testing.T) {
   349  	app := &testApp{}
   350  	cc := proxy.NewLocalClientCreator(app)
   351  	proxyApp := proxy.NewAppConns(cc)
   352  	err := proxyApp.Start()
   353  	require.Nil(t, err)
   354  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
   355  
   356  	state, stateDB, _ := makeState(1, 1)
   357  	stateStore := sm.NewStore(stateDB)
   358  
   359  	blockExec := sm.NewBlockExecutor(
   360  		stateStore,
   361  		log.TestingLogger(),
   362  		proxyApp.Consensus(),
   363  		mmock.Mempool{},
   364  		sm.EmptyEvidencePool{},
   365  	)
   366  
   367  	eventBus := types.NewEventBus()
   368  	err = eventBus.Start()
   369  	require.NoError(t, err)
   370  	defer eventBus.Stop() //nolint:errcheck // ignore for tests
   371  
   372  	blockExec.SetEventBus(eventBus)
   373  
   374  	updatesSub, err := eventBus.Subscribe(
   375  		context.Background(),
   376  		"TestEndBlockValidatorUpdates",
   377  		types.EventQueryValidatorSetUpdates,
   378  	)
   379  	require.NoError(t, err)
   380  
   381  	block := makeBlock(state, 1)
   382  	blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
   383  
   384  	pubkey := ed25519.GenPrivKey().PubKey()
   385  	pk, err := cryptoenc.PubKeyToProto(pubkey)
   386  	require.NoError(t, err)
   387  	app.ValidatorUpdates = []abci.ValidatorUpdate{
   388  		{PubKey: pk, Power: 10},
   389  	}
   390  
   391  	state, _, err = blockExec.ApplyBlock(state, blockID, block)
   392  	require.Nil(t, err)
   393  	// test new validator was added to NextValidators
   394  	if assert.Equal(t, state.Validators.Size()+1, state.NextValidators.Size()) {
   395  		idx, _ := state.NextValidators.GetByAddress(pubkey.Address())
   396  		if idx < 0 {
   397  			t.Fatalf("can't find address %v in the set %v", pubkey.Address(), state.NextValidators)
   398  		}
   399  	}
   400  
   401  	// test we threw an event
   402  	select {
   403  	case msg := <-updatesSub.Out():
   404  		event, ok := msg.Data().(types.EventDataValidatorSetUpdates)
   405  		require.True(t, ok, "Expected event of type EventDataValidatorSetUpdates, got %T", msg.Data())
   406  		if assert.NotEmpty(t, event.ValidatorUpdates) {
   407  			assert.Equal(t, pubkey, event.ValidatorUpdates[0].PubKey)
   408  			assert.EqualValues(t, 10, event.ValidatorUpdates[0].VotingPower)
   409  		}
   410  	case <-updatesSub.Cancelled():
   411  		t.Fatalf("updatesSub was cancelled (reason: %v)", updatesSub.Err())
   412  	case <-time.After(1 * time.Second):
   413  		t.Fatal("Did not receive EventValidatorSetUpdates within 1 sec.")
   414  	}
   415  }
   416  
   417  // TestEndBlockValidatorUpdatesResultingInEmptySet checks that processing validator updates that
   418  // would result in empty set causes no panic, an error is raised and NextValidators is not updated
   419  func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) {
   420  	app := &testApp{}
   421  	cc := proxy.NewLocalClientCreator(app)
   422  	proxyApp := proxy.NewAppConns(cc)
   423  	err := proxyApp.Start()
   424  	require.Nil(t, err)
   425  	defer proxyApp.Stop() //nolint:errcheck // ignore for tests
   426  
   427  	state, stateDB, _ := makeState(1, 1)
   428  	stateStore := sm.NewStore(stateDB)
   429  	blockExec := sm.NewBlockExecutor(
   430  		stateStore,
   431  		log.TestingLogger(),
   432  		proxyApp.Consensus(),
   433  		mmock.Mempool{},
   434  		sm.EmptyEvidencePool{},
   435  	)
   436  
   437  	block := makeBlock(state, 1)
   438  	blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(testPartSize).Header()}
   439  
   440  	vp, err := cryptoenc.PubKeyToProto(state.Validators.Validators[0].PubKey)
   441  	require.NoError(t, err)
   442  	// Remove the only validator
   443  	app.ValidatorUpdates = []abci.ValidatorUpdate{
   444  		{PubKey: vp, Power: 0},
   445  	}
   446  
   447  	assert.NotPanics(t, func() { state, _, err = blockExec.ApplyBlock(state, blockID, block) })
   448  	assert.NotNil(t, err)
   449  	assert.NotEmpty(t, state.NextValidators.Validators)
   450  }
   451  
   452  func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
   453  	var (
   454  		h   = make([]byte, tmhash.Size)
   455  		psH = make([]byte, tmhash.Size)
   456  	)
   457  	copy(h, hash)
   458  	copy(psH, partSetHash)
   459  	return types.BlockID{
   460  		Hash: h,
   461  		PartSetHeader: types.PartSetHeader{
   462  			Total: partSetSize,
   463  			Hash:  psH,
   464  		},
   465  	}
   466  }