github.com/core-coin/go-core/v2@v2.1.9/les/pruner_test.go (about)

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