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