github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/les/pruner_test.go (about)

     1  // Copyright 2019 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 les
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/binary"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/hardtosaygoodbye/go-ethereum/core"
    27  	"github.com/hardtosaygoodbye/go-ethereum/light"
    28  )
    29  
    30  func TestLightPruner(t *testing.T) {
    31  	var (
    32  		waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
    33  			for {
    34  				cs, _, _ := cIndexer.Sections()
    35  				bts, _, _ := btIndexer.Sections()
    36  				if cs >= 3 && bts >= 3 {
    37  					break
    38  				}
    39  				time.Sleep(10 * time.Millisecond)
    40  			}
    41  		}
    42  		config    = light.TestClientIndexerConfig
    43  		netconfig = testnetConfig{
    44  			blocks:   int(3*config.ChtSize + config.ChtConfirms),
    45  			protocol: 3,
    46  			indexFn:  waitIndexers,
    47  			connect:  true,
    48  		}
    49  	)
    50  	server, client, tearDown := newClientServerEnv(t, netconfig)
    51  	defer tearDown()
    52  
    53  	// checkDB iterates the chain with given prefix, resolves the block number
    54  	// with given callback and ensures this entry should exist or not.
    55  	checkDB := func(from, to uint64, prefix []byte, resolve func(key, value []byte) *uint64, exist bool) bool {
    56  		it := client.db.NewIterator(prefix, nil)
    57  		defer it.Release()
    58  
    59  		var next = from
    60  		for it.Next() {
    61  			number := resolve(it.Key(), it.Value())
    62  			if number == nil || *number < from {
    63  				continue
    64  			} else if *number > to {
    65  				return true
    66  			}
    67  			if exist {
    68  				if *number != next {
    69  					return false
    70  				}
    71  				next++
    72  			} else {
    73  				return false
    74  			}
    75  		}
    76  		return true
    77  	}
    78  	// checkPruned checks and ensures the stale chain data has been pruned.
    79  	checkPruned := func(from, to uint64) {
    80  		// Iterate canonical hash
    81  		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
    82  			if len(key) == 1+8+1 && bytes.Equal(key[9:10], []byte("n")) {
    83  				n := binary.BigEndian.Uint64(key[1:9])
    84  				return &n
    85  			}
    86  			return nil
    87  		}, false) {
    88  			t.Fatalf("canonical hash mappings are not properly pruned")
    89  		}
    90  		// Iterate header
    91  		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
    92  			if len(key) == 1+8+32 {
    93  				n := binary.BigEndian.Uint64(key[1:9])
    94  				return &n
    95  			}
    96  			return nil
    97  		}, false) {
    98  			t.Fatalf("headers are not properly pruned")
    99  		}
   100  		// Iterate body
   101  		if !checkDB(from, to, []byte("b"), func(key, value []byte) *uint64 {
   102  			if len(key) == 1+8+32 {
   103  				n := binary.BigEndian.Uint64(key[1:9])
   104  				return &n
   105  			}
   106  			return nil
   107  		}, false) {
   108  			t.Fatalf("block bodies are not properly pruned")
   109  		}
   110  		// Iterate receipts
   111  		if !checkDB(from, to, []byte("r"), func(key, value []byte) *uint64 {
   112  			if len(key) == 1+8+32 {
   113  				n := binary.BigEndian.Uint64(key[1:9])
   114  				return &n
   115  			}
   116  			return nil
   117  		}, false) {
   118  			t.Fatalf("receipts are not properly pruned")
   119  		}
   120  		// Iterate td
   121  		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
   122  			if len(key) == 1+8+32+1 && bytes.Equal(key[41:42], []byte("t")) {
   123  				n := binary.BigEndian.Uint64(key[1:9])
   124  				return &n
   125  			}
   126  			return nil
   127  		}, false) {
   128  			t.Fatalf("tds are not properly pruned")
   129  		}
   130  	}
   131  	// Start light pruner.
   132  	time.Sleep(1500 * time.Millisecond) // Ensure light client has finished the syncing and indexing
   133  	newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer)
   134  
   135  	time.Sleep(1500 * time.Millisecond) // Ensure pruner have enough time to prune data.
   136  	checkPruned(1, config.ChtSize-1)
   137  
   138  	// Ensure all APIs still work after pruning.
   139  	var cases = []struct {
   140  		from, to   uint64
   141  		methodName string
   142  		method     func(uint64) bool
   143  	}{
   144  		{
   145  			1, 10, "GetHeaderByNumber",
   146  			func(n uint64) bool {
   147  				_, err := light.GetHeaderByNumber(context.Background(), client.handler.backend.odr, n)
   148  				return err == nil
   149  			},
   150  		},
   151  		{
   152  			11, 20, "GetCanonicalHash",
   153  			func(n uint64) bool {
   154  				_, err := light.GetCanonicalHash(context.Background(), client.handler.backend.odr, n)
   155  				return err == nil
   156  			},
   157  		},
   158  		{
   159  			21, 30, "GetTd",
   160  			func(n uint64) bool {
   161  				_, err := light.GetTd(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
   162  				return err == nil
   163  			},
   164  		},
   165  		{
   166  			31, 40, "GetBodyRLP",
   167  			func(n uint64) bool {
   168  				_, err := light.GetBodyRLP(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
   169  				return err == nil
   170  			},
   171  		},
   172  		{
   173  			41, 50, "GetBlock",
   174  			func(n uint64) bool {
   175  				_, err := light.GetBlock(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
   176  				return err == nil
   177  			},
   178  		},
   179  		{
   180  			51, 60, "GetBlockReceipts",
   181  			func(n uint64) bool {
   182  				_, err := light.GetBlockReceipts(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
   183  				return err == nil
   184  			},
   185  		},
   186  	}
   187  	for _, c := range cases {
   188  		for i := c.from; i <= c.to; i++ {
   189  			if !c.method(i) {
   190  				t.Fatalf("rpc method %s failed, number %d", c.methodName, i)
   191  			}
   192  		}
   193  	}
   194  	// Check GetBloombits
   195  	_, err := light.GetBloomBits(context.Background(), client.handler.backend.odr, 0, []uint64{0})
   196  	if err != nil {
   197  		t.Fatalf("Failed to retrieve bloombits of pruned section: %v", err)
   198  	}
   199  
   200  	// Ensure the ODR cached data can be cleaned by pruner.
   201  	newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer)
   202  	time.Sleep(50 * time.Millisecond) // Ensure pruner have enough time to prune data.
   203  	checkPruned(1, config.ChtSize-1)  // Ensure all cached data(by odr) is cleaned.
   204  }