github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/evidence_test.go (about)

     1  package types
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"math"
     7  	mrand "math/rand"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/ari-anchor/sei-tendermint/crypto"
    15  	"github.com/ari-anchor/sei-tendermint/crypto/ed25519"
    16  	tmrand "github.com/ari-anchor/sei-tendermint/libs/rand"
    17  	tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types"
    18  	"github.com/ari-anchor/sei-tendermint/version"
    19  )
    20  
    21  var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
    22  
    23  func TestEvidenceList(t *testing.T) {
    24  	ctx, cancel := context.WithCancel(context.Background())
    25  	defer cancel()
    26  
    27  	ev := randomDuplicateVoteEvidence(ctx, t)
    28  	evl := EvidenceList([]Evidence{ev})
    29  
    30  	assert.NotNil(t, evl.Hash())
    31  	assert.True(t, evl.Has(ev))
    32  	assert.False(t, evl.Has(&DuplicateVoteEvidence{}))
    33  }
    34  
    35  // TestEvidenceListProtoBuf to ensure parity in protobuf output and input
    36  func TestEvidenceListProtoBuf(t *testing.T) {
    37  	ctx, cancel := context.WithCancel(context.Background())
    38  	defer cancel()
    39  
    40  	const chainID = "mychain"
    41  	ev, err := NewMockDuplicateVoteEvidence(ctx, math.MaxInt64, time.Now(), chainID)
    42  	require.NoError(t, err)
    43  	data := EvidenceList{ev}
    44  	testCases := []struct {
    45  		msg      string
    46  		data1    *EvidenceList
    47  		expPass1 bool
    48  		expPass2 bool
    49  	}{
    50  		{"success", &data, true, true},
    51  		{"empty evidenceData", &EvidenceList{}, true, true},
    52  		{"fail nil Data", nil, false, false},
    53  	}
    54  
    55  	for _, tc := range testCases {
    56  		protoData, err := tc.data1.ToProto()
    57  		if tc.expPass1 {
    58  			require.NoError(t, err, tc.msg)
    59  		} else {
    60  			require.Error(t, err, tc.msg)
    61  		}
    62  
    63  		eviD := new(EvidenceList)
    64  		err = eviD.FromProto(protoData)
    65  		if tc.expPass2 {
    66  			require.NoError(t, err, tc.msg)
    67  			require.Equal(t, tc.data1, eviD, tc.msg)
    68  		} else {
    69  			require.Error(t, err, tc.msg)
    70  		}
    71  	}
    72  }
    73  func randomDuplicateVoteEvidence(ctx context.Context, t *testing.T) *DuplicateVoteEvidence {
    74  	t.Helper()
    75  	val := NewMockPV()
    76  	blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
    77  	blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
    78  	const chainID = "mychain"
    79  	return &DuplicateVoteEvidence{
    80  		VoteA:            makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime),
    81  		VoteB:            makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)),
    82  		TotalVotingPower: 30,
    83  		ValidatorPower:   10,
    84  		Timestamp:        defaultVoteTime,
    85  	}
    86  }
    87  
    88  func TestDuplicateVoteEvidence(t *testing.T) {
    89  	const height = int64(13)
    90  	ctx, cancel := context.WithCancel(context.Background())
    91  	defer cancel()
    92  
    93  	ev, err := NewMockDuplicateVoteEvidence(ctx, height, time.Now(), "mock-chain-id")
    94  	require.NoError(t, err)
    95  	assert.Equal(t, ev.Hash(), crypto.Checksum(ev.Bytes()))
    96  	assert.NotNil(t, ev.String())
    97  	assert.Equal(t, ev.Height(), height)
    98  }
    99  
   100  func TestDuplicateVoteEvidenceValidation(t *testing.T) {
   101  	val := NewMockPV()
   102  	blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   103  	blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   104  	const chainID = "mychain"
   105  
   106  	ctx, cancel := context.WithCancel(context.Background())
   107  	defer cancel()
   108  
   109  	testCases := []struct {
   110  		testName         string
   111  		malleateEvidence func(*DuplicateVoteEvidence)
   112  		expectErr        bool
   113  	}{
   114  		{"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false},
   115  		{"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true},
   116  		{"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true},
   117  		{"Nil votes", func(ev *DuplicateVoteEvidence) {
   118  			ev.VoteA = nil
   119  			ev.VoteB = nil
   120  		}, true},
   121  		{"Invalid vote type", func(ev *DuplicateVoteEvidence) {
   122  			ev.VoteA = makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime)
   123  		}, true},
   124  		{"Invalid vote order", func(ev *DuplicateVoteEvidence) {
   125  			swap := ev.VoteA.Copy()
   126  			ev.VoteA = ev.VoteB.Copy()
   127  			ev.VoteB = swap
   128  		}, true},
   129  	}
   130  	for _, tc := range testCases {
   131  		tc := tc
   132  		t.Run(tc.testName, func(t *testing.T) {
   133  			vote1 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime)
   134  			vote2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime)
   135  			valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(ctx, 10)})
   136  			ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet)
   137  			require.NoError(t, err)
   138  			tc.malleateEvidence(ev)
   139  			assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   140  		})
   141  	}
   142  }
   143  
   144  func TestLightClientAttackEvidenceBasic(t *testing.T) {
   145  	ctx, cancel := context.WithCancel(context.Background())
   146  	defer cancel()
   147  
   148  	height := int64(5)
   149  	commonHeight := height - 1
   150  	nValidators := 10
   151  	voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1)
   152  
   153  	header := makeHeaderRandom()
   154  	header.Height = height
   155  	blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   156  	extCommit, err := makeExtCommit(ctx, blockID, height, 1, voteSet, privVals, defaultVoteTime)
   157  	require.NoError(t, err)
   158  	commit := extCommit.ToCommit()
   159  
   160  	lcae := &LightClientAttackEvidence{
   161  		ConflictingBlock: &LightBlock{
   162  			SignedHeader: &SignedHeader{
   163  				Header: header,
   164  				Commit: commit,
   165  			},
   166  			ValidatorSet: valSet,
   167  		},
   168  		CommonHeight:        commonHeight,
   169  		TotalVotingPower:    valSet.TotalVotingPower(),
   170  		Timestamp:           header.Time,
   171  		ByzantineValidators: valSet.Validators[:nValidators/2],
   172  	}
   173  	assert.NotNil(t, lcae.String())
   174  	assert.NotNil(t, lcae.Hash())
   175  	assert.Equal(t, lcae.Height(), commonHeight) // Height should be the common Height
   176  	assert.NotNil(t, lcae.Bytes())
   177  
   178  	// maleate evidence to test hash uniqueness
   179  	testCases := []struct {
   180  		testName         string
   181  		malleateEvidence func(*LightClientAttackEvidence)
   182  	}{
   183  		{"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }},
   184  		{"Different common height", func(ev *LightClientAttackEvidence) {
   185  			ev.CommonHeight = height + 1
   186  		}},
   187  	}
   188  
   189  	for _, tc := range testCases {
   190  		lcae := &LightClientAttackEvidence{
   191  			ConflictingBlock: &LightBlock{
   192  				SignedHeader: &SignedHeader{
   193  					Header: header,
   194  					Commit: commit,
   195  				},
   196  				ValidatorSet: valSet,
   197  			},
   198  			CommonHeight:        commonHeight,
   199  			TotalVotingPower:    valSet.TotalVotingPower(),
   200  			Timestamp:           header.Time,
   201  			ByzantineValidators: valSet.Validators[:nValidators/2],
   202  		}
   203  		hash := lcae.Hash()
   204  		tc.malleateEvidence(lcae)
   205  		assert.NotEqual(t, hash, lcae.Hash(), tc.testName)
   206  	}
   207  }
   208  
   209  func TestLightClientAttackEvidenceValidation(t *testing.T) {
   210  	ctx, cancel := context.WithCancel(context.Background())
   211  	defer cancel()
   212  
   213  	height := int64(5)
   214  	commonHeight := height - 1
   215  	nValidators := 10
   216  	voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1)
   217  
   218  	header := makeHeaderRandom()
   219  	header.Height = height
   220  	header.ValidatorsHash = valSet.Hash()
   221  	blockID := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   222  	extCommit, err := makeExtCommit(ctx, blockID, height, 1, voteSet, privVals, time.Now())
   223  	require.NoError(t, err)
   224  	commit := extCommit.ToCommit()
   225  
   226  	lcae := &LightClientAttackEvidence{
   227  		ConflictingBlock: &LightBlock{
   228  			SignedHeader: &SignedHeader{
   229  				Header: header,
   230  				Commit: commit,
   231  			},
   232  			ValidatorSet: valSet,
   233  		},
   234  		CommonHeight:        commonHeight,
   235  		TotalVotingPower:    valSet.TotalVotingPower(),
   236  		Timestamp:           header.Time,
   237  		ByzantineValidators: valSet.Validators[:nValidators/2],
   238  	}
   239  	assert.NoError(t, lcae.ValidateBasic())
   240  
   241  	testCases := []struct {
   242  		testName         string
   243  		malleateEvidence func(*LightClientAttackEvidence)
   244  		expectErr        bool
   245  	}{
   246  		{"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false},
   247  		{"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true},
   248  		{"Height is greater than divergent block", func(ev *LightClientAttackEvidence) {
   249  			ev.CommonHeight = height + 1
   250  		}, true},
   251  		{"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) {
   252  			ev.CommonHeight = height
   253  		}, false},
   254  		{"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true},
   255  		{"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true},
   256  		{"Nil validator set", func(ev *LightClientAttackEvidence) {
   257  			ev.ConflictingBlock.ValidatorSet = &ValidatorSet{}
   258  		}, true},
   259  		{"Negative total voting power", func(ev *LightClientAttackEvidence) {
   260  			ev.TotalVotingPower = -1
   261  		}, true},
   262  	}
   263  	for _, tc := range testCases {
   264  		tc := tc
   265  		t.Run(tc.testName, func(t *testing.T) {
   266  			lcae := &LightClientAttackEvidence{
   267  				ConflictingBlock: &LightBlock{
   268  					SignedHeader: &SignedHeader{
   269  						Header: header,
   270  						Commit: commit,
   271  					},
   272  					ValidatorSet: valSet,
   273  				},
   274  				CommonHeight:        commonHeight,
   275  				TotalVotingPower:    valSet.TotalVotingPower(),
   276  				Timestamp:           header.Time,
   277  				ByzantineValidators: valSet.Validators[:nValidators/2],
   278  			}
   279  			tc.malleateEvidence(lcae)
   280  			if tc.expectErr {
   281  				assert.Error(t, lcae.ValidateBasic(), tc.testName)
   282  			} else {
   283  				assert.NoError(t, lcae.ValidateBasic(), tc.testName)
   284  			}
   285  		})
   286  	}
   287  
   288  }
   289  
   290  func TestMockEvidenceValidateBasic(t *testing.T) {
   291  	ctx, cancel := context.WithCancel(context.Background())
   292  	defer cancel()
   293  
   294  	goodEvidence, err := NewMockDuplicateVoteEvidence(ctx, int64(1), time.Now(), "mock-chain-id")
   295  	require.NoError(t, err)
   296  	assert.Nil(t, goodEvidence.ValidateBasic())
   297  }
   298  
   299  func makeVote(
   300  	ctx context.Context,
   301  	t *testing.T,
   302  	val PrivValidator,
   303  	chainID string,
   304  	valIndex int32,
   305  	height int64,
   306  	round int32,
   307  	step int,
   308  	blockID BlockID,
   309  	time time.Time,
   310  ) *Vote {
   311  	pubKey, err := val.GetPubKey(ctx)
   312  	require.NoError(t, err)
   313  	v := &Vote{
   314  		ValidatorAddress: pubKey.Address(),
   315  		ValidatorIndex:   valIndex,
   316  		Height:           height,
   317  		Round:            round,
   318  		Type:             tmproto.SignedMsgType(step),
   319  		BlockID:          blockID,
   320  		Timestamp:        time,
   321  	}
   322  
   323  	vpb := v.ToProto()
   324  	err = val.SignVote(ctx, chainID, vpb)
   325  	require.NoError(t, err)
   326  
   327  	v.Signature = vpb.Signature
   328  	return v
   329  }
   330  
   331  func makeHeaderRandom() *Header {
   332  	return &Header{
   333  		Version:            version.Consensus{Block: version.BlockProtocol, App: 1},
   334  		ChainID:            tmrand.Str(12),
   335  		Height:             int64(mrand.Uint32() + 1),
   336  		Time:               time.Now(),
   337  		LastBlockID:        makeBlockIDRandom(),
   338  		LastCommitHash:     crypto.CRandBytes(crypto.HashSize),
   339  		DataHash:           crypto.CRandBytes(crypto.HashSize),
   340  		ValidatorsHash:     crypto.CRandBytes(crypto.HashSize),
   341  		NextValidatorsHash: crypto.CRandBytes(crypto.HashSize),
   342  		ConsensusHash:      crypto.CRandBytes(crypto.HashSize),
   343  		AppHash:            crypto.CRandBytes(crypto.HashSize),
   344  		LastResultsHash:    crypto.CRandBytes(crypto.HashSize),
   345  		EvidenceHash:       crypto.CRandBytes(crypto.HashSize),
   346  		ProposerAddress:    crypto.CRandBytes(crypto.AddressSize),
   347  	}
   348  }
   349  
   350  func TestEvidenceProto(t *testing.T) {
   351  	ctx, cancel := context.WithCancel(context.Background())
   352  	defer cancel()
   353  
   354  	// -------- Votes --------
   355  	val := NewMockPV()
   356  	blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   357  	blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   358  	const chainID = "mychain"
   359  	v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
   360  	v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
   361  
   362  	tests := []struct {
   363  		testName     string
   364  		evidence     Evidence
   365  		toProtoErr   bool
   366  		fromProtoErr bool
   367  	}{
   368  		{"nil fail", nil, true, true},
   369  		{"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true},
   370  		{"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true},
   371  		{"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true},
   372  		{"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false},
   373  	}
   374  	for _, tt := range tests {
   375  		tt := tt
   376  		t.Run(tt.testName, func(t *testing.T) {
   377  			pb, err := EvidenceToProto(tt.evidence)
   378  			if tt.toProtoErr {
   379  				assert.Error(t, err, tt.testName)
   380  				return
   381  			}
   382  			assert.NoError(t, err, tt.testName)
   383  
   384  			evi, err := EvidenceFromProto(pb)
   385  			if tt.fromProtoErr {
   386  				assert.Error(t, err, tt.testName)
   387  				return
   388  			}
   389  			require.Equal(t, tt.evidence, evi, tt.testName)
   390  		})
   391  	}
   392  }
   393  
   394  func TestEvidenceVectors(t *testing.T) {
   395  	ctx, cancel := context.WithCancel(context.Background())
   396  	defer cancel()
   397  
   398  	// Votes for duplicateEvidence
   399  	val := NewMockPV()
   400  	val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key
   401  	blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   402  	blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   403  	const chainID = "mychain"
   404  	v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
   405  	v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
   406  
   407  	// Data for LightClientAttackEvidence
   408  	height := int64(5)
   409  	commonHeight := height - 1
   410  	nValidators := 10
   411  	voteSet, valSet, privVals := deterministicVoteSet(ctx, t, height, 1, tmproto.PrecommitType, 1)
   412  	header := &Header{
   413  		Version:            version.Consensus{Block: 1, App: 1},
   414  		ChainID:            chainID,
   415  		Height:             height,
   416  		Time:               time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC),
   417  		LastBlockID:        BlockID{},
   418  		LastCommitHash:     []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   419  		DataHash:           []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   420  		ValidatorsHash:     valSet.Hash(),
   421  		NextValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   422  		ConsensusHash:      []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   423  		AppHash:            []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   424  
   425  		LastResultsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   426  
   427  		EvidenceHash:    []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
   428  		ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"),
   429  	}
   430  	blockID3 := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash")))
   431  	extCommit, err := makeExtCommit(ctx, blockID3, height, 1, voteSet, privVals, defaultVoteTime)
   432  	require.NoError(t, err)
   433  	lcae := &LightClientAttackEvidence{
   434  		ConflictingBlock: &LightBlock{
   435  			SignedHeader: &SignedHeader{
   436  				Header: header,
   437  				Commit: extCommit.ToCommit(),
   438  			},
   439  			ValidatorSet: valSet,
   440  		},
   441  		CommonHeight:        commonHeight,
   442  		TotalVotingPower:    valSet.TotalVotingPower(),
   443  		Timestamp:           header.Time,
   444  		ByzantineValidators: valSet.Validators[:nValidators/2],
   445  	}
   446  	// assert.NoError(t, lcae.ValidateBasic())
   447  
   448  	testCases := []struct {
   449  		testName string
   450  		evList   EvidenceList
   451  		expBytes string
   452  	}{
   453  		{"duplicateVoteEvidence",
   454  			EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}},
   455  			"a9ce28d13bb31001fc3e5b7927051baf98f86abdbd64377643a304164c826923",
   456  		},
   457  		{"LightClientAttackEvidence",
   458  			EvidenceList{lcae},
   459  			"2f8782163c3905b26e65823ababc977fe54e97b94e60c0360b1e4726b668bb8e",
   460  		},
   461  		{"LightClientAttackEvidence & DuplicateVoteEvidence",
   462  			EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}, lcae},
   463  			"eedb4b47d6dbc9d43f53da8aa50bb826e8d9fc7d897da777c8af6a04aa74163e",
   464  		},
   465  	}
   466  
   467  	for _, tc := range testCases {
   468  		tc := tc
   469  		hash := tc.evList.Hash()
   470  		require.Equal(t, tc.expBytes, hex.EncodeToString(hash), tc.testName)
   471  	}
   472  }