github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/light/postprocess.go (about)

     1  // Copyright 2017 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 light
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/binary"
    23  	"errors"
    24  	"fmt"
    25  	"math/big"
    26  	"time"
    27  
    28  	"github.com/tacshi/go-ethereum/common"
    29  	"github.com/tacshi/go-ethereum/common/bitutil"
    30  	"github.com/tacshi/go-ethereum/core"
    31  	"github.com/tacshi/go-ethereum/core/rawdb"
    32  	"github.com/tacshi/go-ethereum/core/types"
    33  	"github.com/tacshi/go-ethereum/ethdb"
    34  	"github.com/tacshi/go-ethereum/log"
    35  	"github.com/tacshi/go-ethereum/params"
    36  	"github.com/tacshi/go-ethereum/rlp"
    37  	"github.com/tacshi/go-ethereum/trie"
    38  )
    39  
    40  // IndexerConfig includes a set of configs for chain indexers.
    41  type IndexerConfig struct {
    42  	// The block frequency for creating CHTs.
    43  	ChtSize uint64
    44  
    45  	// The number of confirmations needed to generate/accept a canonical hash help trie.
    46  	ChtConfirms uint64
    47  
    48  	// The block frequency for creating new bloom bits.
    49  	BloomSize uint64
    50  
    51  	// The number of confirmation needed before a bloom section is considered probably final and its rotated bits
    52  	// are calculated.
    53  	BloomConfirms uint64
    54  
    55  	// The block frequency for creating BloomTrie.
    56  	BloomTrieSize uint64
    57  
    58  	// The number of confirmations needed to generate/accept a bloom trie.
    59  	BloomTrieConfirms uint64
    60  }
    61  
    62  var (
    63  	// DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side.
    64  	DefaultServerIndexerConfig = &IndexerConfig{
    65  		ChtSize:           params.CHTFrequency,
    66  		ChtConfirms:       params.HelperTrieProcessConfirmations,
    67  		BloomSize:         params.BloomBitsBlocks,
    68  		BloomConfirms:     params.BloomConfirms,
    69  		BloomTrieSize:     params.BloomTrieFrequency,
    70  		BloomTrieConfirms: params.HelperTrieProcessConfirmations,
    71  	}
    72  	// DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side.
    73  	DefaultClientIndexerConfig = &IndexerConfig{
    74  		ChtSize:           params.CHTFrequency,
    75  		ChtConfirms:       params.HelperTrieConfirmations,
    76  		BloomSize:         params.BloomBitsBlocksClient,
    77  		BloomConfirms:     params.HelperTrieConfirmations,
    78  		BloomTrieSize:     params.BloomTrieFrequency,
    79  		BloomTrieConfirms: params.HelperTrieConfirmations,
    80  	}
    81  	// TestServerIndexerConfig wraps a set of configs as a test indexer config for server side.
    82  	TestServerIndexerConfig = &IndexerConfig{
    83  		ChtSize:           128,
    84  		ChtConfirms:       1,
    85  		BloomSize:         16,
    86  		BloomConfirms:     1,
    87  		BloomTrieSize:     128,
    88  		BloomTrieConfirms: 1,
    89  	}
    90  	// TestClientIndexerConfig wraps a set of configs as a test indexer config for client side.
    91  	TestClientIndexerConfig = &IndexerConfig{
    92  		ChtSize:           128,
    93  		ChtConfirms:       8,
    94  		BloomSize:         128,
    95  		BloomConfirms:     8,
    96  		BloomTrieSize:     128,
    97  		BloomTrieConfirms: 8,
    98  	}
    99  )
   100  
   101  var (
   102  	errNoTrustedCht       = errors.New("no trusted canonical hash trie")
   103  	errNoTrustedBloomTrie = errors.New("no trusted bloom trie")
   104  	errNoHeader           = errors.New("header not found")
   105  )
   106  
   107  // ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
   108  type ChtNode struct {
   109  	Hash common.Hash
   110  	Td   *big.Int
   111  }
   112  
   113  // GetChtRoot reads the CHT root associated to the given section from the database
   114  func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
   115  	var encNumber [8]byte
   116  	binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
   117  	data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...))
   118  	return common.BytesToHash(data)
   119  }
   120  
   121  // StoreChtRoot writes the CHT root associated to the given section into the database
   122  func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
   123  	var encNumber [8]byte
   124  	binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
   125  	db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
   126  }
   127  
   128  // ChtIndexerBackend implements core.ChainIndexerBackend.
   129  type ChtIndexerBackend struct {
   130  	disablePruning       bool
   131  	diskdb, trieTable    ethdb.Database
   132  	odr                  OdrBackend
   133  	triedb               *trie.Database
   134  	section, sectionSize uint64
   135  	lastHash             common.Hash
   136  	trie                 *trie.Trie
   137  }
   138  
   139  // NewChtIndexer creates a Cht chain indexer
   140  func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer {
   141  	trieTable := rawdb.NewTable(db, string(rawdb.ChtTablePrefix))
   142  	backend := &ChtIndexerBackend{
   143  		diskdb:         db,
   144  		odr:            odr,
   145  		trieTable:      trieTable,
   146  		triedb:         trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down
   147  		sectionSize:    size,
   148  		disablePruning: disablePruning,
   149  	}
   150  	return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht")
   151  }
   152  
   153  // fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the
   154  // ODR backend in order to be able to add new entries and calculate subsequent root hashes
   155  func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
   156  	batch := c.trieTable.NewBatch()
   157  	r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()}
   158  	for {
   159  		err := c.odr.Retrieve(ctx, r)
   160  		switch err {
   161  		case nil:
   162  			r.Proof.Store(batch)
   163  			return batch.Write()
   164  		case ErrNoPeers:
   165  			// if there are no peers to serve, retry later
   166  			select {
   167  			case <-ctx.Done():
   168  				return ctx.Err()
   169  			case <-time.After(time.Second * 10):
   170  				// stay in the loop and try again
   171  			}
   172  		default:
   173  			return err
   174  		}
   175  	}
   176  }
   177  
   178  // Reset implements core.ChainIndexerBackend
   179  func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
   180  	var root common.Hash
   181  	if section > 0 {
   182  		root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
   183  	}
   184  	var err error
   185  	c.trie, err = trie.New(trie.TrieID(root), c.triedb)
   186  
   187  	if err != nil && c.odr != nil {
   188  		err = c.fetchMissingNodes(ctx, section, root)
   189  		if err == nil {
   190  			c.trie, err = trie.New(trie.TrieID(root), c.triedb)
   191  		}
   192  	}
   193  	c.section = section
   194  	return err
   195  }
   196  
   197  // Process implements core.ChainIndexerBackend
   198  func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error {
   199  	hash, num := header.Hash(), header.Number.Uint64()
   200  	c.lastHash = hash
   201  
   202  	td := rawdb.ReadTd(c.diskdb, hash, num)
   203  	if td == nil {
   204  		panic(nil)
   205  	}
   206  	var encNumber [8]byte
   207  	binary.BigEndian.PutUint64(encNumber[:], num)
   208  	data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
   209  	c.trie.Update(encNumber[:], data)
   210  	return nil
   211  }
   212  
   213  // Commit implements core.ChainIndexerBackend
   214  func (c *ChtIndexerBackend) Commit() error {
   215  	root, nodes := c.trie.Commit(false)
   216  	// Commit trie changes into trie database in case it's not nil.
   217  	if nodes != nil {
   218  		if err := c.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
   219  			return err
   220  		}
   221  		if err := c.triedb.Commit(root, false); err != nil {
   222  			return err
   223  		}
   224  	}
   225  	// Re-create trie with newly generated root and updated database.
   226  	var err error
   227  	c.trie, err = trie.New(trie.TrieID(root), c.triedb)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	// Pruning historical trie nodes if necessary.
   232  	if !c.disablePruning {
   233  		it := c.trieTable.NewIterator(nil, nil)
   234  		defer it.Release()
   235  
   236  		var (
   237  			deleted int
   238  			batch   = c.trieTable.NewBatch()
   239  			t       = time.Now()
   240  		)
   241  		hashes := make(map[common.Hash]struct{})
   242  		if nodes != nil {
   243  			for _, hash := range nodes.Hashes() {
   244  				hashes[hash] = struct{}{}
   245  			}
   246  		}
   247  		for it.Next() {
   248  			trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix)
   249  			if len(trimmed) == common.HashLength {
   250  				if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
   251  					batch.Delete(trimmed)
   252  					deleted += 1
   253  				}
   254  			}
   255  		}
   256  		if err := batch.Write(); err != nil {
   257  			return err
   258  		}
   259  		log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
   260  	}
   261  	log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
   262  	StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
   263  	return nil
   264  }
   265  
   266  // Prune implements core.ChainIndexerBackend which deletes all chain data
   267  // (except hash<->number mappings) older than the specified threshold.
   268  func (c *ChtIndexerBackend) Prune(threshold uint64) error {
   269  	// Short circuit if the light pruning is disabled.
   270  	if c.disablePruning {
   271  		return nil
   272  	}
   273  	t := time.Now()
   274  	// Always keep genesis header in database.
   275  	start, end := uint64(1), (threshold+1)*c.sectionSize
   276  
   277  	var batch = c.diskdb.NewBatch()
   278  	for {
   279  		numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240)
   280  		if len(numbers) == 0 {
   281  			break
   282  		}
   283  		for i := 0; i < len(numbers); i++ {
   284  			// Keep hash<->number mapping in database otherwise the hash based
   285  			// API(e.g. GetReceipt, GetLogs) will be broken.
   286  			//
   287  			// Storage size wise, the size of a mapping is ~41bytes. For one
   288  			// section is about 1.3MB which is acceptable.
   289  			//
   290  			// In order to totally get rid of this index, we need an additional
   291  			// flag to specify how many historical data light client can serve.
   292  			rawdb.DeleteCanonicalHash(batch, numbers[i])
   293  			rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i])
   294  		}
   295  		if batch.ValueSize() > ethdb.IdealBatchSize {
   296  			if err := batch.Write(); err != nil {
   297  				return err
   298  			}
   299  			batch.Reset()
   300  		}
   301  		start = numbers[len(numbers)-1] + 1
   302  	}
   303  	if err := batch.Write(); err != nil {
   304  		return err
   305  	}
   306  	log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t)))
   307  	return nil
   308  }
   309  
   310  // GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database
   311  func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
   312  	var encNumber [8]byte
   313  	binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
   314  	data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...))
   315  	return common.BytesToHash(data)
   316  }
   317  
   318  // StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database
   319  func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
   320  	var encNumber [8]byte
   321  	binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
   322  	db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
   323  }
   324  
   325  // BloomTrieIndexerBackend implements core.ChainIndexerBackend
   326  type BloomTrieIndexerBackend struct {
   327  	disablePruning    bool
   328  	diskdb, trieTable ethdb.Database
   329  	triedb            *trie.Database
   330  	odr               OdrBackend
   331  	section           uint64
   332  	parentSize        uint64
   333  	size              uint64
   334  	bloomTrieRatio    uint64
   335  	trie              *trie.Trie
   336  	sectionHeads      []common.Hash
   337  }
   338  
   339  // NewBloomTrieIndexer creates a BloomTrie chain indexer
   340  func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer {
   341  	trieTable := rawdb.NewTable(db, string(rawdb.BloomTrieTablePrefix))
   342  	backend := &BloomTrieIndexerBackend{
   343  		diskdb:         db,
   344  		odr:            odr,
   345  		trieTable:      trieTable,
   346  		triedb:         trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down
   347  		parentSize:     parentSize,
   348  		size:           size,
   349  		disablePruning: disablePruning,
   350  	}
   351  	backend.bloomTrieRatio = size / parentSize
   352  	backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
   353  	return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie")
   354  }
   355  
   356  // fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the
   357  // ODR backend in order to be able to add new entries and calculate subsequent root hashes
   358  func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
   359  	indexCh := make(chan uint, types.BloomBitLength)
   360  	type res struct {
   361  		nodes *NodeSet
   362  		err   error
   363  	}
   364  	resCh := make(chan res, types.BloomBitLength)
   365  	for i := 0; i < 20; i++ {
   366  		go func() {
   367  			for bitIndex := range indexCh {
   368  				r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()}
   369  				for {
   370  					if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers {
   371  						// if there are no peers to serve, retry later
   372  						select {
   373  						case <-ctx.Done():
   374  							resCh <- res{nil, ctx.Err()}
   375  							return
   376  						case <-time.After(time.Second * 10):
   377  							// stay in the loop and try again
   378  						}
   379  					} else {
   380  						resCh <- res{r.Proofs, err}
   381  						break
   382  					}
   383  				}
   384  			}
   385  		}()
   386  	}
   387  	for i := uint(0); i < types.BloomBitLength; i++ {
   388  		indexCh <- i
   389  	}
   390  	close(indexCh)
   391  	batch := b.trieTable.NewBatch()
   392  	for i := uint(0); i < types.BloomBitLength; i++ {
   393  		res := <-resCh
   394  		if res.err != nil {
   395  			return res.err
   396  		}
   397  		res.nodes.Store(batch)
   398  	}
   399  	return batch.Write()
   400  }
   401  
   402  // Reset implements core.ChainIndexerBackend
   403  func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
   404  	var root common.Hash
   405  	if section > 0 {
   406  		root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
   407  	}
   408  	var err error
   409  	b.trie, err = trie.New(trie.TrieID(root), b.triedb)
   410  	if err != nil && b.odr != nil {
   411  		err = b.fetchMissingNodes(ctx, section, root)
   412  		if err == nil {
   413  			b.trie, err = trie.New(trie.TrieID(root), b.triedb)
   414  		}
   415  	}
   416  	b.section = section
   417  	return err
   418  }
   419  
   420  // Process implements core.ChainIndexerBackend
   421  func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error {
   422  	num := header.Number.Uint64() - b.section*b.size
   423  	if (num+1)%b.parentSize == 0 {
   424  		b.sectionHeads[num/b.parentSize] = header.Hash()
   425  	}
   426  	return nil
   427  }
   428  
   429  // Commit implements core.ChainIndexerBackend
   430  func (b *BloomTrieIndexerBackend) Commit() error {
   431  	var compSize, decompSize uint64
   432  
   433  	for i := uint(0); i < types.BloomBitLength; i++ {
   434  		var encKey [10]byte
   435  		binary.BigEndian.PutUint16(encKey[0:2], uint16(i))
   436  		binary.BigEndian.PutUint64(encKey[2:10], b.section)
   437  		var decomp []byte
   438  		for j := uint64(0); j < b.bloomTrieRatio; j++ {
   439  			data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j])
   440  			if err != nil {
   441  				return err
   442  			}
   443  			decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8))
   444  			if err2 != nil {
   445  				return err2
   446  			}
   447  			decomp = append(decomp, decompData...)
   448  		}
   449  		comp := bitutil.CompressBytes(decomp)
   450  
   451  		decompSize += uint64(len(decomp))
   452  		compSize += uint64(len(comp))
   453  		if len(comp) > 0 {
   454  			b.trie.Update(encKey[:], comp)
   455  		} else {
   456  			b.trie.Delete(encKey[:])
   457  		}
   458  	}
   459  	root, nodes := b.trie.Commit(false)
   460  	// Commit trie changes into trie database in case it's not nil.
   461  	if nodes != nil {
   462  		if err := b.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
   463  			return err
   464  		}
   465  		if err := b.triedb.Commit(root, false); err != nil {
   466  			return err
   467  		}
   468  	}
   469  	// Re-create trie with newly generated root and updated database.
   470  	var err error
   471  	b.trie, err = trie.New(trie.TrieID(root), b.triedb)
   472  	if err != nil {
   473  		return err
   474  	}
   475  	// Pruning historical trie nodes if necessary.
   476  	if !b.disablePruning {
   477  		it := b.trieTable.NewIterator(nil, nil)
   478  		defer it.Release()
   479  
   480  		var (
   481  			deleted int
   482  			batch   = b.trieTable.NewBatch()
   483  			t       = time.Now()
   484  		)
   485  		hashes := make(map[common.Hash]struct{})
   486  		if nodes != nil {
   487  			for _, hash := range nodes.Hashes() {
   488  				hashes[hash] = struct{}{}
   489  			}
   490  		}
   491  		for it.Next() {
   492  			trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix)
   493  			if len(trimmed) == common.HashLength {
   494  				if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
   495  					batch.Delete(trimmed)
   496  					deleted += 1
   497  				}
   498  			}
   499  		}
   500  		if err := batch.Write(); err != nil {
   501  			return err
   502  		}
   503  		log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
   504  	}
   505  	sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
   506  	StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
   507  	log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
   508  
   509  	return nil
   510  }
   511  
   512  // Prune implements core.ChainIndexerBackend which deletes all
   513  // bloombits which older than the specified threshold.
   514  func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error {
   515  	// Short circuit if the light pruning is disabled.
   516  	if b.disablePruning {
   517  		return nil
   518  	}
   519  	start := time.Now()
   520  	for i := uint(0); i < types.BloomBitLength; i++ {
   521  		rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio)
   522  	}
   523  	log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start)))
   524  	return nil
   525  }