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

     1  // Copyright 2020 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 rawdb
    18  
    19  import (
    20  	"math/big"
    21  	"reflect"
    22  	"sort"
    23  	"sync"
    24  	"testing"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core/types"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  )
    30  
    31  func TestChainIterator(t *testing.T) {
    32  	// Construct test chain db
    33  	chainDb := NewMemoryDatabase()
    34  
    35  	var block *types.Block
    36  	var txs []*types.Transaction
    37  	to := common.BytesToAddress([]byte{0x11})
    38  	block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block
    39  	WriteBlock(chainDb, block)
    40  	WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
    41  	for i := uint64(1); i <= 10; i++ {
    42  		var tx *types.Transaction
    43  		if i%2 == 0 {
    44  			tx = types.NewTx(&types.LegacyTx{
    45  				Nonce:    i,
    46  				GasPrice: big.NewInt(11111),
    47  				Gas:      1111,
    48  				To:       &to,
    49  				Value:    big.NewInt(111),
    50  				Data:     []byte{0x11, 0x11, 0x11},
    51  			})
    52  		} else {
    53  			tx = types.NewTx(&types.AccessListTx{
    54  				ChainID:  big.NewInt(1337),
    55  				Nonce:    i,
    56  				GasPrice: big.NewInt(11111),
    57  				Gas:      1111,
    58  				To:       &to,
    59  				Value:    big.NewInt(111),
    60  				Data:     []byte{0x11, 0x11, 0x11},
    61  			})
    62  		}
    63  		txs = append(txs, tx)
    64  		block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
    65  		WriteBlock(chainDb, block)
    66  		WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
    67  	}
    68  
    69  	var cases = []struct {
    70  		from, to uint64
    71  		reverse  bool
    72  		expect   []int
    73  	}{
    74  		{0, 11, true, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
    75  		{0, 0, true, nil},
    76  		{0, 5, true, []int{4, 3, 2, 1, 0}},
    77  		{10, 11, true, []int{10}},
    78  		{0, 11, false, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
    79  		{0, 0, false, nil},
    80  		{10, 11, false, []int{10}},
    81  	}
    82  	for i, c := range cases {
    83  		var numbers []int
    84  		hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil)
    85  		if hashCh != nil {
    86  			for h := range hashCh {
    87  				numbers = append(numbers, int(h.number))
    88  				if len(h.hashes) > 0 {
    89  					if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp {
    90  						t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp)
    91  					}
    92  				}
    93  			}
    94  		}
    95  		if !c.reverse {
    96  			sort.Ints(numbers)
    97  		} else {
    98  			sort.Sort(sort.Reverse(sort.IntSlice(numbers)))
    99  		}
   100  		if !reflect.DeepEqual(numbers, c.expect) {
   101  			t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers)
   102  		}
   103  	}
   104  }
   105  
   106  func initDatabaseWithTransactions(db ethdb.Database) ([]*types.Block, []*types.Transaction) {
   107  	var blocks []*types.Block
   108  	var txs []*types.Transaction
   109  	to := common.BytesToAddress([]byte{0x11})
   110  
   111  	// Write empty genesis block
   112  	block := types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher())
   113  	WriteBlock(db, block)
   114  	WriteCanonicalHash(db, block.Hash(), block.NumberU64())
   115  	blocks = append(blocks, block)
   116  
   117  	// Create transactions.
   118  	for i := uint64(1); i <= 10; i++ {
   119  		var tx *types.Transaction
   120  		if i%2 == 0 {
   121  			tx = types.NewTx(&types.LegacyTx{
   122  				Nonce:    i,
   123  				GasPrice: big.NewInt(11111),
   124  				Gas:      1111,
   125  				To:       &to,
   126  				Value:    big.NewInt(111),
   127  				Data:     []byte{0x11, 0x11, 0x11},
   128  			})
   129  		} else {
   130  			tx = types.NewTx(&types.AccessListTx{
   131  				ChainID:  big.NewInt(1337),
   132  				Nonce:    i,
   133  				GasPrice: big.NewInt(11111),
   134  				Gas:      1111,
   135  				To:       &to,
   136  				Value:    big.NewInt(111),
   137  				Data:     []byte{0x11, 0x11, 0x11},
   138  			})
   139  		}
   140  		txs = append(txs, tx)
   141  		block := types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
   142  		WriteBlock(db, block)
   143  		WriteCanonicalHash(db, block.Hash(), block.NumberU64())
   144  		blocks = append(blocks, block)
   145  	}
   146  
   147  	return blocks, txs
   148  }
   149  
   150  func TestIndexTransactions(t *testing.T) {
   151  	// Construct test chain db
   152  	chainDB := NewMemoryDatabase()
   153  
   154  	_, txs := initDatabaseWithTransactions(chainDB)
   155  
   156  	// verify checks whether the tx indices in the range [from, to)
   157  	// is expected.
   158  	verify := func(from, to int, exist bool, tail uint64) {
   159  		for i := from; i < to; i++ {
   160  			if i == 0 {
   161  				continue
   162  			}
   163  			number := ReadTxLookupEntry(chainDB, txs[i-1].Hash())
   164  			if exist && number == nil {
   165  				t.Fatalf("Transaction index %d missing", i)
   166  			}
   167  			if !exist && number != nil {
   168  				t.Fatalf("Transaction index %d is not deleted", i)
   169  			}
   170  		}
   171  		number := ReadTxIndexTail(chainDB)
   172  		if number == nil || *number != tail {
   173  			t.Fatalf("Transaction tail mismatch")
   174  		}
   175  	}
   176  	IndexTransactions(chainDB, 5, 11, nil, false)
   177  	verify(5, 11, true, 5)
   178  	verify(0, 5, false, 5)
   179  
   180  	IndexTransactions(chainDB, 0, 5, nil, false)
   181  	verify(0, 11, true, 0)
   182  
   183  	UnindexTransactions(chainDB, 0, 5, nil, false)
   184  	verify(5, 11, true, 5)
   185  	verify(0, 5, false, 5)
   186  
   187  	UnindexTransactions(chainDB, 5, 11, nil, false)
   188  	verify(0, 11, false, 11)
   189  
   190  	// Testing corner cases
   191  	signal := make(chan struct{})
   192  	var once sync.Once
   193  	indexTransactionsForTesting(chainDB, 5, 11, signal, func(n uint64) bool {
   194  		if n <= 8 {
   195  			once.Do(func() {
   196  				close(signal)
   197  			})
   198  			return false
   199  		}
   200  		return true
   201  	})
   202  	verify(9, 11, true, 9)
   203  	verify(0, 9, false, 9)
   204  	IndexTransactions(chainDB, 0, 9, nil, false)
   205  
   206  	signal = make(chan struct{})
   207  	var once2 sync.Once
   208  	unindexTransactionsForTesting(chainDB, 0, 11, signal, func(n uint64) bool {
   209  		if n >= 8 {
   210  			once2.Do(func() {
   211  				close(signal)
   212  			})
   213  			return false
   214  		}
   215  		return true
   216  	})
   217  	verify(8, 11, true, 8)
   218  	verify(0, 8, false, 8)
   219  }
   220  
   221  func TestPruneTransactionIndex(t *testing.T) {
   222  	chainDB := NewMemoryDatabase()
   223  	blocks, _ := initDatabaseWithTransactions(chainDB)
   224  	lastBlock := blocks[len(blocks)-1].NumberU64()
   225  	pruneBlock := lastBlock - 3
   226  
   227  	IndexTransactions(chainDB, 0, lastBlock+1, nil, false)
   228  
   229  	// Check all transactions are in index.
   230  	for _, block := range blocks {
   231  		for _, tx := range block.Transactions() {
   232  			num := ReadTxLookupEntry(chainDB, tx.Hash())
   233  			if num == nil || *num != block.NumberU64() {
   234  				t.Fatalf("wrong TxLookup entry: %x -> %v", tx.Hash(), num)
   235  			}
   236  		}
   237  	}
   238  
   239  	PruneTransactionIndex(chainDB, pruneBlock)
   240  
   241  	// Check transactions from old blocks not included.
   242  	for _, block := range blocks {
   243  		for _, tx := range block.Transactions() {
   244  			num := ReadTxLookupEntry(chainDB, tx.Hash())
   245  			if block.NumberU64() < pruneBlock && num != nil {
   246  				t.Fatalf("TxLookup entry not removed: %x -> %v", tx.Hash(), num)
   247  			}
   248  			if block.NumberU64() >= pruneBlock && (num == nil || *num != block.NumberU64()) {
   249  				t.Fatalf("wrong TxLookup entry after pruning: %x -> %v", tx.Hash(), num)
   250  			}
   251  		}
   252  	}
   253  }