github.com/ethereum/go-ethereum@v1.16.1/core/txindexer_test.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"math/big"
    21  	"testing"
    22  
    23  	"github.com/ethereum/go-ethereum/common"
    24  	"github.com/ethereum/go-ethereum/consensus/ethash"
    25  	"github.com/ethereum/go-ethereum/core/rawdb"
    26  	"github.com/ethereum/go-ethereum/core/types"
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/params"
    30  )
    31  
    32  func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, exist bool) {
    33  	for _, tx := range block.Transactions() {
    34  		lookup := rawdb.ReadTxLookupEntry(db, tx.Hash())
    35  		if exist && lookup == nil {
    36  			t.Fatalf("missing %d %x", block.NumberU64(), tx.Hash().Hex())
    37  		}
    38  		if !exist && lookup != nil {
    39  			t.Fatalf("unexpected %d %x", block.NumberU64(), tx.Hash().Hex())
    40  		}
    41  	}
    42  }
    43  
    44  func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, expTail uint64) {
    45  	tail := rawdb.ReadTxIndexTail(db)
    46  	if tail == nil {
    47  		t.Fatal("Failed to write tx index tail")
    48  		return
    49  	}
    50  	if *tail != expTail {
    51  		t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail)
    52  	}
    53  	for _, b := range blocks {
    54  		if b.Number().Uint64() < *tail {
    55  			verifyIndexes(t, db, b, false)
    56  		} else {
    57  			verifyIndexes(t, db, b, true)
    58  		}
    59  	}
    60  }
    61  
    62  func verifyNoIndex(t *testing.T, db ethdb.Database, blocks []*types.Block) {
    63  	tail := rawdb.ReadTxIndexTail(db)
    64  	if tail != nil {
    65  		t.Fatalf("Unexpected tx index tail %d", *tail)
    66  	}
    67  	for _, b := range blocks {
    68  		verifyIndexes(t, db, b, false)
    69  	}
    70  }
    71  
    72  // TestTxIndexer tests the functionalities for managing transaction indexes.
    73  func TestTxIndexer(t *testing.T) {
    74  	var (
    75  		testBankKey, _  = crypto.GenerateKey()
    76  		testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
    77  		testBankFunds   = big.NewInt(1000000000000000000)
    78  
    79  		gspec = &Genesis{
    80  			Config:  params.TestChainConfig,
    81  			Alloc:   types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
    82  			BaseFee: big.NewInt(params.InitialBaseFee),
    83  		}
    84  		engine    = ethash.NewFaker()
    85  		nonce     = uint64(0)
    86  		chainHead = uint64(128)
    87  	)
    88  	_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
    89  		tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
    90  		gen.AddTx(tx)
    91  		nonce += 1
    92  	})
    93  	var cases = []struct {
    94  		limits []uint64
    95  		tails  []uint64
    96  	}{
    97  		{
    98  			limits: []uint64{0, 1, 64, 129, 0},
    99  			tails:  []uint64{0, 128, 65, 0, 0},
   100  		},
   101  		{
   102  			limits: []uint64{64, 1, 64, 0},
   103  			tails:  []uint64{65, 128, 65, 0},
   104  		},
   105  		{
   106  			limits: []uint64{127, 1, 64, 0},
   107  			tails:  []uint64{2, 128, 65, 0},
   108  		},
   109  		{
   110  			limits: []uint64{128, 1, 64, 0},
   111  			tails:  []uint64{1, 128, 65, 0},
   112  		},
   113  		{
   114  			limits: []uint64{129, 1, 64, 0},
   115  			tails:  []uint64{0, 128, 65, 0},
   116  		},
   117  	}
   118  	for _, c := range cases {
   119  		db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
   120  		rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
   121  
   122  		// Index the initial blocks from ancient store
   123  		indexer := &txIndexer{
   124  			limit: 0,
   125  			db:    db,
   126  		}
   127  		for i, limit := range c.limits {
   128  			indexer.limit = limit
   129  			indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
   130  			verify(t, db, blocks, c.tails[i])
   131  		}
   132  		db.Close()
   133  	}
   134  }
   135  
   136  func TestTxIndexerRepair(t *testing.T) {
   137  	var (
   138  		testBankKey, _  = crypto.GenerateKey()
   139  		testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
   140  		testBankFunds   = big.NewInt(1000000000000000000)
   141  
   142  		gspec = &Genesis{
   143  			Config:  params.TestChainConfig,
   144  			Alloc:   types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
   145  			BaseFee: big.NewInt(params.InitialBaseFee),
   146  		}
   147  		engine    = ethash.NewFaker()
   148  		nonce     = uint64(0)
   149  		chainHead = uint64(128)
   150  	)
   151  	_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
   152  		tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
   153  		gen.AddTx(tx)
   154  		nonce += 1
   155  	})
   156  	tailPointer := func(n uint64) *uint64 {
   157  		return &n
   158  	}
   159  	var cases = []struct {
   160  		limit   uint64
   161  		head    uint64
   162  		cutoff  uint64
   163  		expTail *uint64
   164  	}{
   165  		// if *tail > head => purge indexes
   166  		{
   167  			limit:   0,
   168  			head:    chainHead / 2,
   169  			cutoff:  0,
   170  			expTail: tailPointer(0),
   171  		},
   172  		{
   173  			limit:   1,             // tail = 128
   174  			head:    chainHead / 2, // newhead = 64
   175  			cutoff:  0,
   176  			expTail: nil,
   177  		},
   178  		{
   179  			limit:   64,            // tail = 65
   180  			head:    chainHead / 2, // newhead = 64
   181  			cutoff:  0,
   182  			expTail: nil,
   183  		},
   184  		{
   185  			limit:   65,            // tail = 64
   186  			head:    chainHead / 2, // newhead = 64
   187  			cutoff:  0,
   188  			expTail: tailPointer(64),
   189  		},
   190  		{
   191  			limit:   66,            // tail = 63
   192  			head:    chainHead / 2, // newhead = 64
   193  			cutoff:  0,
   194  			expTail: tailPointer(63),
   195  		},
   196  
   197  		// if tail < cutoff => remove indexes below cutoff
   198  		{
   199  			limit:   0,         // tail = 0
   200  			head:    chainHead, // head = 128
   201  			cutoff:  chainHead, // cutoff = 128
   202  			expTail: tailPointer(chainHead),
   203  		},
   204  		{
   205  			limit:   1,         // tail = 128
   206  			head:    chainHead, // head = 128
   207  			cutoff:  chainHead, // cutoff = 128
   208  			expTail: tailPointer(128),
   209  		},
   210  		{
   211  			limit:   2,         // tail = 127
   212  			head:    chainHead, // head = 128
   213  			cutoff:  chainHead, // cutoff = 128
   214  			expTail: tailPointer(chainHead),
   215  		},
   216  		{
   217  			limit:   2,             // tail = 127
   218  			head:    chainHead,     // head = 128
   219  			cutoff:  chainHead / 2, // cutoff = 64
   220  			expTail: tailPointer(127),
   221  		},
   222  
   223  		// if head < cutoff => purge indexes
   224  		{
   225  			limit:   0,             // tail = 0
   226  			head:    chainHead,     // head = 128
   227  			cutoff:  2 * chainHead, // cutoff = 256
   228  			expTail: nil,
   229  		},
   230  		{
   231  			limit:   64,            // tail = 65
   232  			head:    chainHead,     // head = 128
   233  			cutoff:  chainHead / 2, // cutoff = 64
   234  			expTail: tailPointer(65),
   235  		},
   236  	}
   237  	for _, c := range cases {
   238  		db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
   239  		encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
   240  		rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
   241  
   242  		// Index the initial blocks from ancient store
   243  		indexer := &txIndexer{
   244  			limit: c.limit,
   245  			db:    db,
   246  		}
   247  		indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
   248  
   249  		indexer.cutoff = c.cutoff
   250  		indexer.repair(c.head)
   251  
   252  		if c.expTail == nil {
   253  			verifyNoIndex(t, db, blocks)
   254  		} else {
   255  			verify(t, db, blocks, *c.expTail)
   256  		}
   257  		db.Close()
   258  	}
   259  }
   260  
   261  func TestTxIndexerReport(t *testing.T) {
   262  	var (
   263  		testBankKey, _  = crypto.GenerateKey()
   264  		testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
   265  		testBankFunds   = big.NewInt(1000000000000000000)
   266  
   267  		gspec = &Genesis{
   268  			Config:  params.TestChainConfig,
   269  			Alloc:   types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
   270  			BaseFee: big.NewInt(params.InitialBaseFee),
   271  		}
   272  		engine    = ethash.NewFaker()
   273  		nonce     = uint64(0)
   274  		chainHead = uint64(128)
   275  	)
   276  	_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) {
   277  		tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
   278  		gen.AddTx(tx)
   279  		nonce += 1
   280  	})
   281  	tailPointer := func(n uint64) *uint64 {
   282  		return &n
   283  	}
   284  	var cases = []struct {
   285  		head         uint64
   286  		limit        uint64
   287  		cutoff       uint64
   288  		tail         *uint64
   289  		expIndexed   uint64
   290  		expRemaining uint64
   291  	}{
   292  		// The entire chain is supposed to be indexed
   293  		{
   294  			// head = 128, limit = 0, cutoff = 0 => all: 129
   295  			head:   chainHead,
   296  			limit:  0,
   297  			cutoff: 0,
   298  
   299  			// tail = 0
   300  			tail:         tailPointer(0),
   301  			expIndexed:   129,
   302  			expRemaining: 0,
   303  		},
   304  		{
   305  			// head = 128, limit = 0, cutoff = 0 => all: 129
   306  			head:   chainHead,
   307  			limit:  0,
   308  			cutoff: 0,
   309  
   310  			// tail = 1
   311  			tail:         tailPointer(1),
   312  			expIndexed:   128,
   313  			expRemaining: 1,
   314  		},
   315  		{
   316  			// head = 128, limit = 0, cutoff = 0 => all: 129
   317  			head:   chainHead,
   318  			limit:  0,
   319  			cutoff: 0,
   320  
   321  			// tail = 128
   322  			tail:         tailPointer(chainHead),
   323  			expIndexed:   1,
   324  			expRemaining: 128,
   325  		},
   326  		{
   327  			// head = 128, limit = 256, cutoff = 0 => all: 129
   328  			head:   chainHead,
   329  			limit:  256,
   330  			cutoff: 0,
   331  
   332  			// tail = 0
   333  			tail:         tailPointer(0),
   334  			expIndexed:   129,
   335  			expRemaining: 0,
   336  		},
   337  
   338  		// The chain with specific range is supposed to be indexed
   339  		{
   340  			// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
   341  			head:   chainHead,
   342  			limit:  64,
   343  			cutoff: 0,
   344  
   345  			// tail = 0, part of them need to be unindexed
   346  			tail:         tailPointer(0),
   347  			expIndexed:   129,
   348  			expRemaining: 0,
   349  		},
   350  		{
   351  			// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
   352  			head:   chainHead,
   353  			limit:  64,
   354  			cutoff: 0,
   355  
   356  			// tail = 64, one of them needs to be unindexed
   357  			tail:         tailPointer(64),
   358  			expIndexed:   65,
   359  			expRemaining: 0,
   360  		},
   361  		{
   362  			// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
   363  			head:   chainHead,
   364  			limit:  64,
   365  			cutoff: 0,
   366  
   367  			// tail = 65, all of them have been indexed
   368  			tail:         tailPointer(65),
   369  			expIndexed:   64,
   370  			expRemaining: 0,
   371  		},
   372  		{
   373  			// head = 128, limit = 64, cutoff = 0 => index: [65, 128]
   374  			head:   chainHead,
   375  			limit:  64,
   376  			cutoff: 0,
   377  
   378  			// tail = 66, one of them has to be indexed
   379  			tail:         tailPointer(66),
   380  			expIndexed:   63,
   381  			expRemaining: 1,
   382  		},
   383  
   384  		// The chain with configured cutoff, the chain range could be capped
   385  		{
   386  			// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
   387  			head:   chainHead,
   388  			limit:  64,
   389  			cutoff: 66,
   390  
   391  			// tail = 0, part of them need to be unindexed
   392  			tail:         tailPointer(0),
   393  			expIndexed:   129,
   394  			expRemaining: 0,
   395  		},
   396  		{
   397  			// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
   398  			head:   chainHead,
   399  			limit:  64,
   400  			cutoff: 66,
   401  
   402  			// tail = 66, all of them have been indexed
   403  			tail:         tailPointer(66),
   404  			expIndexed:   63,
   405  			expRemaining: 0,
   406  		},
   407  		{
   408  			// head = 128, limit = 64, cutoff = 66 => index: [66, 128]
   409  			head:   chainHead,
   410  			limit:  64,
   411  			cutoff: 66,
   412  
   413  			// tail = 67, one of them has to be indexed
   414  			tail:         tailPointer(67),
   415  			expIndexed:   62,
   416  			expRemaining: 1,
   417  		},
   418  		{
   419  			// head = 128, limit = 64, cutoff = 256 => index: [66, 128]
   420  			head:         chainHead,
   421  			limit:        0,
   422  			cutoff:       256,
   423  			tail:         nil,
   424  			expIndexed:   0,
   425  			expRemaining: 0,
   426  		},
   427  	}
   428  	for _, c := range cases {
   429  		db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
   430  		encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
   431  		rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
   432  
   433  		// Index the initial blocks from ancient store
   434  		indexer := &txIndexer{
   435  			limit:  c.limit,
   436  			cutoff: c.cutoff,
   437  			db:     db,
   438  		}
   439  		p := indexer.report(c.head, c.tail)
   440  		if p.Indexed != c.expIndexed {
   441  			t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed)
   442  		}
   443  		if p.Remaining != c.expRemaining {
   444  			t.Fatalf("Unexpected remaining: %d, expected: %d", p.Remaining, c.expRemaining)
   445  		}
   446  		db.Close()
   447  	}
   448  }