github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/topicsdb/topicsdb_test.go (about)

     1  package topicsdb
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"runtime/debug"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/unicornultrafoundation/go-helios/hash"
    13  	"github.com/unicornultrafoundation/go-helios/native/idx"
    14  	"github.com/unicornultrafoundation/go-helios/u2udb/memorydb"
    15  	"github.com/unicornultrafoundation/go-u2u/common"
    16  	"github.com/unicornultrafoundation/go-u2u/core/types"
    17  
    18  	"github.com/unicornultrafoundation/go-u2u/logger"
    19  	"github.com/unicornultrafoundation/go-u2u/utils/dbutil/threads"
    20  )
    21  
    22  func TestMain(m *testing.M) {
    23  	debug.SetMaxThreads(20)
    24  
    25  	os.Exit(m.Run())
    26  }
    27  
    28  func newTestIndex() *index {
    29  	mem := threads.CountedDBProducer(memorydb.NewProducer(""))
    30  	return newIndex(mem)
    31  }
    32  
    33  func TestIndexSearchMultyVariants(t *testing.T) {
    34  	logger.SetTestMode(t)
    35  	var (
    36  		hash1 = common.BytesToHash([]byte("topic1"))
    37  		hash2 = common.BytesToHash([]byte("topic2"))
    38  		hash3 = common.BytesToHash([]byte("topic3"))
    39  		hash4 = common.BytesToHash([]byte("topic4"))
    40  		addr1 = randAddress()
    41  		addr2 = randAddress()
    42  		addr3 = randAddress()
    43  		addr4 = randAddress()
    44  	)
    45  	testdata := []*types.Log{{
    46  		BlockNumber: 1,
    47  		Address:     addr1,
    48  		Topics:      []common.Hash{hash1, hash1, hash1},
    49  	}, {
    50  		BlockNumber: 3,
    51  		Address:     addr2,
    52  		Topics:      []common.Hash{hash2, hash2, hash2},
    53  	}, {
    54  		BlockNumber: 998,
    55  		Address:     addr3,
    56  		Topics:      []common.Hash{hash3, hash3, hash3},
    57  	}, {
    58  		BlockNumber: 999,
    59  		Address:     addr4,
    60  		Topics:      []common.Hash{hash4, hash4, hash4},
    61  	},
    62  	}
    63  
    64  	index := newTestIndex()
    65  
    66  	for _, l := range testdata {
    67  		err := index.Push(l)
    68  		require.NoError(t, err)
    69  	}
    70  
    71  	// require.ElementsMatchf(testdata, got, "") doesn't work properly here,
    72  	// so use check()
    73  	check := func(require *require.Assertions, got []*types.Log) {
    74  		count := 0
    75  		for _, a := range got {
    76  			for _, b := range testdata {
    77  				if b.Address == a.Address {
    78  					require.ElementsMatch(a.Topics, b.Topics)
    79  					count++
    80  					break
    81  				}
    82  			}
    83  		}
    84  	}
    85  
    86  	pooled := withThreadPool{index}
    87  
    88  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
    89  		"index":  index.FindInBlocks,
    90  		"pooled": pooled.FindInBlocks,
    91  	} {
    92  		t.Run(dsc, func(t *testing.T) {
    93  
    94  			t.Run("With no addresses", func(t *testing.T) {
    95  				require := require.New(t)
    96  				got, err := method(nil, 0, 1000, [][]common.Hash{
    97  					{},
    98  					{hash1, hash2, hash3, hash4},
    99  					{},
   100  					{hash1, hash2, hash3, hash4},
   101  				})
   102  				require.NoError(err)
   103  				require.Equal(4, len(got))
   104  				check(require, got)
   105  			})
   106  
   107  			t.Run("With addresses", func(t *testing.T) {
   108  				require := require.New(t)
   109  				got, err := method(nil, 0, 1000, [][]common.Hash{
   110  					{addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()},
   111  					{hash1, hash2, hash3, hash4},
   112  					{},
   113  					{hash1, hash2, hash3, hash4},
   114  				})
   115  				require.NoError(err)
   116  				require.Equal(4, len(got))
   117  				check(require, got)
   118  			})
   119  
   120  			t.Run("With block range", func(t *testing.T) {
   121  				require := require.New(t)
   122  				got, err := method(nil, 2, 998, [][]common.Hash{
   123  					{addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()},
   124  					{hash1, hash2, hash3, hash4},
   125  					{},
   126  					{hash1, hash2, hash3, hash4},
   127  				})
   128  				require.NoError(err)
   129  				require.Equal(2, len(got))
   130  				check(require, got)
   131  			})
   132  
   133  			t.Run("With addresses and blocks", func(t *testing.T) {
   134  				require := require.New(t)
   135  
   136  				got1, err := method(nil, 2, 998, [][]common.Hash{
   137  					{addr1.Hash(), addr2.Hash(), addr3.Hash(), addr4.Hash()},
   138  					{hash1, hash2, hash3, hash4},
   139  					{},
   140  					{hash1, hash2, hash3, hash4},
   141  				})
   142  				require.NoError(err)
   143  				require.Equal(2, len(got1))
   144  				check(require, got1)
   145  
   146  				got2, err := method(nil, 2, 998, [][]common.Hash{
   147  					{addr4.Hash(), addr3.Hash(), addr2.Hash(), addr1.Hash()},
   148  					{hash1, hash2, hash3, hash4},
   149  					{},
   150  					{hash1, hash2, hash3, hash4},
   151  				})
   152  				require.NoError(err)
   153  				require.ElementsMatch(got1, got2)
   154  			})
   155  
   156  		})
   157  	}
   158  }
   159  
   160  func TestIndexSearchShortCircuits(t *testing.T) {
   161  	logger.SetTestMode(t)
   162  	var (
   163  		hash1 = common.BytesToHash([]byte("topic1"))
   164  		hash2 = common.BytesToHash([]byte("topic2"))
   165  		hash3 = common.BytesToHash([]byte("topic3"))
   166  		hash4 = common.BytesToHash([]byte("topic4"))
   167  		addr1 = randAddress()
   168  		addr2 = randAddress()
   169  	)
   170  	testdata := []*types.Log{{
   171  		BlockNumber: 1,
   172  		Address:     addr1,
   173  		Topics:      []common.Hash{hash1, hash2},
   174  	}, {
   175  		BlockNumber: 3,
   176  		Address:     addr1,
   177  		Topics:      []common.Hash{hash1, hash2, hash3},
   178  	}, {
   179  		BlockNumber: 998,
   180  		Address:     addr2,
   181  		Topics:      []common.Hash{hash1, hash2, hash4},
   182  	}, {
   183  		BlockNumber: 999,
   184  		Address:     addr1,
   185  		Topics:      []common.Hash{hash1, hash2, hash4},
   186  	},
   187  	}
   188  
   189  	index := newTestIndex()
   190  
   191  	for _, l := range testdata {
   192  		err := index.Push(l)
   193  		require.NoError(t, err)
   194  	}
   195  
   196  	pooled := withThreadPool{index}
   197  
   198  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
   199  		"index":  index.FindInBlocks,
   200  		"pooled": pooled.FindInBlocks,
   201  	} {
   202  		t.Run(dsc, func(t *testing.T) {
   203  
   204  			t.Run("topics count 1", func(t *testing.T) {
   205  				require := require.New(t)
   206  				got, err := method(nil, 0, 1000, [][]common.Hash{
   207  					{addr1.Hash()},
   208  					{},
   209  					{},
   210  					{hash3},
   211  				})
   212  				require.NoError(err)
   213  				require.Equal(1, len(got))
   214  			})
   215  
   216  			t.Run("topics count 2", func(t *testing.T) {
   217  				require := require.New(t)
   218  				got, err := method(nil, 0, 1000, [][]common.Hash{
   219  					{addr1.Hash()},
   220  					{},
   221  					{},
   222  					{hash3, hash4},
   223  				})
   224  				require.NoError(err)
   225  				require.Equal(2, len(got))
   226  			})
   227  
   228  			t.Run("block range", func(t *testing.T) {
   229  				require := require.New(t)
   230  				got, err := method(nil, 3, 998, [][]common.Hash{
   231  					{addr1.Hash()},
   232  					{},
   233  					{},
   234  					{hash3, hash4},
   235  				})
   236  				require.NoError(err)
   237  				require.Equal(1, len(got))
   238  			})
   239  
   240  		})
   241  	}
   242  }
   243  
   244  func TestIndexSearchSingleVariant(t *testing.T) {
   245  	logger.SetTestMode(t)
   246  
   247  	topics, recs, topics4rec := genTestData(100)
   248  
   249  	index := newTestIndex()
   250  
   251  	for _, rec := range recs {
   252  		err := index.Push(rec)
   253  		require.NoError(t, err)
   254  	}
   255  
   256  	pooled := withThreadPool{index}
   257  
   258  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
   259  		"index":  index.FindInBlocks,
   260  		"pooled": pooled.FindInBlocks,
   261  	} {
   262  		t.Run(dsc, func(t *testing.T) {
   263  			require := require.New(t)
   264  
   265  			for i := 0; i < len(topics); i++ {
   266  				from, to := topics4rec(i)
   267  				tt := topics[from : to-1]
   268  
   269  				qq := make([][]common.Hash, len(tt)+1)
   270  				for pos, t := range tt {
   271  					qq[pos+1] = []common.Hash{t}
   272  				}
   273  
   274  				got, err := method(nil, 0, 1000, qq)
   275  				require.NoError(err)
   276  
   277  				var expect []*types.Log
   278  				for j, rec := range recs {
   279  					if f, t := topics4rec(j); f != from || t != to {
   280  						continue
   281  					}
   282  					expect = append(expect, rec)
   283  				}
   284  
   285  				require.ElementsMatchf(expect, got, "step %d", i)
   286  			}
   287  
   288  		})
   289  	}
   290  }
   291  
   292  func TestIndexSearchSimple(t *testing.T) {
   293  	logger.SetTestMode(t)
   294  
   295  	var (
   296  		hash1 = common.BytesToHash([]byte("topic1"))
   297  		hash2 = common.BytesToHash([]byte("topic2"))
   298  		hash3 = common.BytesToHash([]byte("topic3"))
   299  		hash4 = common.BytesToHash([]byte("topic4"))
   300  		addr  = randAddress()
   301  	)
   302  	testdata := []*types.Log{{
   303  		BlockNumber: 1,
   304  		Address:     addr,
   305  		Topics:      []common.Hash{hash1},
   306  	}, {
   307  		BlockNumber: 2,
   308  		Address:     addr,
   309  		Topics:      []common.Hash{hash2},
   310  	}, {
   311  		BlockNumber: 998,
   312  		Address:     addr,
   313  		Topics:      []common.Hash{hash3},
   314  	}, {
   315  		BlockNumber: 999,
   316  		Address:     addr,
   317  		Topics:      []common.Hash{hash4},
   318  	},
   319  	}
   320  
   321  	index := newTestIndex()
   322  
   323  	for _, l := range testdata {
   324  		err := index.Push(l)
   325  		require.NoError(t, err)
   326  	}
   327  
   328  	var (
   329  		got []*types.Log
   330  		err error
   331  	)
   332  
   333  	pooled := withThreadPool{index}
   334  
   335  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
   336  		"index":  index.FindInBlocks,
   337  		"pooled": pooled.FindInBlocks,
   338  	} {
   339  		t.Run(dsc, func(t *testing.T) {
   340  			require := require.New(t)
   341  
   342  			got, err = method(nil, 0, 0xffffffff, [][]common.Hash{
   343  				{addr.Hash()},
   344  				{hash1},
   345  			})
   346  			require.NoError(err)
   347  			require.Equal(1, len(got))
   348  
   349  			got, err = method(nil, 0, 0xffffffff, [][]common.Hash{
   350  				{addr.Hash()},
   351  				{hash2},
   352  			})
   353  			require.NoError(err)
   354  			require.Equal(1, len(got))
   355  
   356  			got, err = method(nil, 0, 0xffffffff, [][]common.Hash{
   357  				{addr.Hash()},
   358  				{hash3},
   359  			})
   360  			require.NoError(err)
   361  			require.Equal(1, len(got))
   362  		})
   363  	}
   364  
   365  }
   366  
   367  func TestMaxTopicsCount(t *testing.T) {
   368  	logger.SetTestMode(t)
   369  
   370  	testdata := &types.Log{
   371  		BlockNumber: 1,
   372  		Address:     randAddress(),
   373  		Topics:      make([]common.Hash, maxTopicsCount),
   374  	}
   375  	pattern := make([][]common.Hash, maxTopicsCount+1)
   376  	pattern[0] = []common.Hash{testdata.Address.Hash()}
   377  	for i := range testdata.Topics {
   378  		testdata.Topics[i] = common.BytesToHash([]byte(fmt.Sprintf("topic%d", i)))
   379  		pattern[0] = append(pattern[0], testdata.Topics[i])
   380  		pattern[i+1] = []common.Hash{testdata.Topics[i]}
   381  	}
   382  
   383  	index := newTestIndex()
   384  	err := index.Push(testdata)
   385  	require.NoError(t, err)
   386  
   387  	pooled := withThreadPool{index}
   388  
   389  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
   390  		"index":  index.FindInBlocks,
   391  		"pooled": pooled.FindInBlocks,
   392  	} {
   393  		t.Run(dsc, func(t *testing.T) {
   394  			require := require.New(t)
   395  
   396  			got, err := method(nil, 0, 0xffffffff, pattern)
   397  			require.NoError(err)
   398  			require.Equal(1, len(got))
   399  			require.Equal(maxTopicsCount, len(got[0].Topics))
   400  		})
   401  	}
   402  
   403  	require.Equal(t, maxTopicsCount+1, len(pattern))
   404  	require.Equal(t, maxTopicsCount+1, len(pattern[0]))
   405  }
   406  
   407  func TestPatternLimit(t *testing.T) {
   408  	logger.SetTestMode(t)
   409  	require := require.New(t)
   410  
   411  	data := []struct {
   412  		pattern [][]common.Hash
   413  		exp     [][]common.Hash
   414  		err     error
   415  	}{
   416  		{
   417  			pattern: [][]common.Hash{},
   418  			exp:     [][]common.Hash{},
   419  			err:     ErrEmptyTopics,
   420  		},
   421  		{
   422  			pattern: [][]common.Hash{[]common.Hash{}, []common.Hash{}, []common.Hash{}},
   423  			exp:     [][]common.Hash{[]common.Hash{}, []common.Hash{}, []common.Hash{}},
   424  			err:     ErrEmptyTopics,
   425  		},
   426  		{
   427  			pattern: [][]common.Hash{
   428  				[]common.Hash{hash.FakeHash(1), hash.FakeHash(1)}, []common.Hash{hash.FakeHash(2), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}},
   429  			exp: [][]common.Hash{
   430  				[]common.Hash{hash.FakeHash(1)}, []common.Hash{hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}},
   431  			err: nil,
   432  		},
   433  		{
   434  			pattern: [][]common.Hash{
   435  				[]common.Hash{hash.FakeHash(1), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}, []common.Hash{hash.FakeHash(5), hash.FakeHash(6)}},
   436  			exp: [][]common.Hash{
   437  				[]common.Hash{hash.FakeHash(1), hash.FakeHash(2)}, []common.Hash{hash.FakeHash(3), hash.FakeHash(4)}, []common.Hash{hash.FakeHash(5), hash.FakeHash(6)}},
   438  			err: nil,
   439  		},
   440  		{
   441  			pattern: append(append(make([][]common.Hash, maxTopicsCount), []common.Hash{hash.FakeHash(1)}), []common.Hash{hash.FakeHash(1)}),
   442  			exp:     append(make([][]common.Hash, maxTopicsCount), []common.Hash{hash.FakeHash(1)}),
   443  			err:     nil,
   444  		},
   445  	}
   446  
   447  	for i, x := range data {
   448  		got, err := limitPattern(x.pattern)
   449  		require.Equal(len(x.exp), len(got))
   450  		for j := range got {
   451  			require.ElementsMatch(x.exp[j], got[j], i, j)
   452  		}
   453  		require.Equal(x.err, err, i)
   454  	}
   455  }
   456  
   457  func TestKvdbThreadsPoolLimit(t *testing.T) {
   458  	logger.SetTestMode(t)
   459  
   460  	const N = 100
   461  
   462  	_, recs, _ := genTestData(N)
   463  	index := newTestIndex()
   464  	for _, rec := range recs {
   465  		err := index.Push(rec)
   466  		require.NoError(t, err)
   467  	}
   468  
   469  	pooled := withThreadPool{index}
   470  
   471  	for dsc, method := range map[string]func(context.Context, idx.Block, idx.Block, [][]common.Hash) ([]*types.Log, error){
   472  		"index":  index.FindInBlocks,
   473  		"pooled": pooled.FindInBlocks,
   474  	} {
   475  		t.Run(dsc, func(t *testing.T) {
   476  			require := require.New(t)
   477  
   478  			topics := make([]common.Hash, threads.GlobalPool.Cap()+1)
   479  			for i := range topics {
   480  				topics[i] = hash.FakeHash(int64(i))
   481  			}
   482  			require.Less(threads.GlobalPool.Cap(), len(topics))
   483  			qq := make([][]common.Hash, 3)
   484  
   485  			// one big pattern
   486  			qq[1] = topics
   487  			got, err := method(nil, 0, 1000, qq)
   488  			require.NoError(err)
   489  			require.Equal(N, len(got))
   490  
   491  			// more than one big pattern
   492  			qq[1], qq[2] = topics, topics
   493  			got, err = method(nil, 0, 1000, qq)
   494  			switch dsc {
   495  			case "index":
   496  				require.NoError(err)
   497  				require.Equal(N, len(got))
   498  			case "pooled":
   499  				require.Equal(ErrTooBigTopics, err)
   500  				require.Equal(0, len(got))
   501  
   502  			}
   503  
   504  		})
   505  	}
   506  }
   507  
   508  func genTestData(count int) (
   509  	topics []common.Hash,
   510  	recs []*types.Log,
   511  	topics4rec func(rec int) (from, to int),
   512  ) {
   513  	const (
   514  		period = 5
   515  	)
   516  
   517  	topics = make([]common.Hash, period)
   518  	for i := range topics {
   519  		topics[i] = hash.FakeHash(int64(i))
   520  	}
   521  
   522  	topics4rec = func(rec int) (from, to int) {
   523  		from = rec % (period - 3)
   524  		to = from + 3
   525  		return
   526  	}
   527  
   528  	recs = make([]*types.Log, count)
   529  	for i := range recs {
   530  		from, to := topics4rec(i)
   531  		r := &types.Log{
   532  			BlockNumber: uint64(i / period),
   533  			BlockHash:   hash.FakeHash(int64(i / period)),
   534  			TxHash:      hash.FakeHash(int64(i % period)),
   535  			Index:       uint(i % period),
   536  			Address:     randAddress(),
   537  			Topics:      topics[from:to],
   538  			Data:        make([]byte, i),
   539  		}
   540  		_, _ = rand.Read(r.Data)
   541  		recs[i] = r
   542  	}
   543  
   544  	return
   545  }
   546  
   547  func randAddress() (addr common.Address) {
   548  	n, err := rand.Read(addr[:])
   549  	if err != nil {
   550  		panic(err)
   551  	}
   552  	if n != common.AddressLength {
   553  		panic("address is not filled")
   554  	}
   555  	return
   556  }