diff --git a/light/lightchain.go b/light/lightchain.go
deleted file mode 100644
index 617658b85..000000000
--- a/light/lightchain.go
+++ /dev/null
@@ -1,531 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package light implements on-demand retrieval capable state and chain objects
-// for the Ethereum Light Client.
-package light
-
-import (
- "context"
- "errors"
- "math/big"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var (
- bodyCacheLimit = 256
- blockCacheLimit = 256
-)
-
-// LightChain represents a canonical chain that by default only handles block
-// headers, downloading block bodies and receipts on demand through an ODR
-// interface. It only does header validation during chain insertion.
-type LightChain struct {
- hc *core.HeaderChain
- indexerConfig *IndexerConfig
- chainDb ethdb.Database
- engine consensus.Engine
- odr OdrBackend
- chainFeed event.Feed
- chainSideFeed event.Feed
- chainHeadFeed event.Feed
- scope event.SubscriptionScope
- genesisBlock *types.Block
- forker *core.ForkChoice
-
- bodyCache *lru.Cache[common.Hash, *types.Body]
- bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
- blockCache *lru.Cache[common.Hash, *types.Block]
-
- chainmu sync.RWMutex // protects header inserts
- quit chan struct{}
- wg sync.WaitGroup
-
- // Atomic boolean switches:
- stopped atomic.Bool // whether LightChain is stopped or running
- procInterrupt atomic.Bool // interrupts chain insert
-}
-
-// NewLightChain returns a fully initialised light chain using information
-// available in the database. It initialises the default Ethereum header
-// validator.
-func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) {
- bc := &LightChain{
- chainDb: odr.Database(),
- indexerConfig: odr.IndexerConfig(),
- odr: odr,
- quit: make(chan struct{}),
- bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
- bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
- blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
- engine: engine,
- }
- bc.forker = core.NewForkChoice(bc, nil)
- var err error
- bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt)
- if err != nil {
- return nil, err
- }
- bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0)
- if bc.genesisBlock == nil {
- return nil, core.ErrNoGenesis
- }
- if err := bc.loadLastState(); err != nil {
- return nil, err
- }
- // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
- for hash := range core.BadHashes {
- if header := bc.GetHeaderByHash(hash); header != nil {
- log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
- bc.SetHead(header.Number.Uint64() - 1)
- log.Info("Chain rewind was successful, resuming normal operation")
- }
- }
- return bc, nil
-}
-
-func (lc *LightChain) getProcInterrupt() bool {
- return lc.procInterrupt.Load()
-}
-
-// Odr returns the ODR backend of the chain
-func (lc *LightChain) Odr() OdrBackend {
- return lc.odr
-}
-
-// HeaderChain returns the underlying header chain.
-func (lc *LightChain) HeaderChain() *core.HeaderChain {
- return lc.hc
-}
-
-// loadLastState loads the last known chain state from the database. This method
-// assumes that the chain manager mutex is held.
-func (lc *LightChain) loadLastState() error {
- if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) {
- // Corrupt or empty database, init from scratch
- lc.Reset()
- } else {
- header := lc.GetHeaderByHash(head)
- if header == nil {
- // Corrupt or empty database, init from scratch
- lc.Reset()
- } else {
- lc.hc.SetCurrentHeader(header)
- }
- }
- // Issue a status log and return
- header := lc.hc.CurrentHeader()
- headerTd := lc.GetTd(header.Hash(), header.Number.Uint64())
- log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0)))
- return nil
-}
-
-// SetHead rewinds the local chain to a new head. Everything above the new
-// head will be deleted and the new one set.
-func (lc *LightChain) SetHead(head uint64) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.hc.SetHead(head, nil, nil)
- return lc.loadLastState()
-}
-
-// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
-// the given timestamp. Everything above the new head will be deleted and the
-// new one set.
-func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.hc.SetHeadWithTimestamp(timestamp, nil, nil)
- return lc.loadLastState()
-}
-
-// GasLimit returns the gas limit of the current HEAD block.
-func (lc *LightChain) GasLimit() uint64 {
- return lc.hc.CurrentHeader().GasLimit
-}
-
-// Reset purges the entire blockchain, restoring it to its genesis state.
-func (lc *LightChain) Reset() {
- lc.ResetWithGenesisBlock(lc.genesisBlock)
-}
-
-// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
-// specified genesis state.
-func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
- // Dump the entire block chain and purge the caches
- lc.SetHead(0)
-
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- // Prepare the genesis block and reinitialise the chain
- batch := lc.chainDb.NewBatch()
- rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
- rawdb.WriteBlock(batch, genesis)
- rawdb.WriteHeadHeaderHash(batch, genesis.Hash())
- if err := batch.Write(); err != nil {
- log.Crit("Failed to reset genesis block", "err", err)
- }
- lc.genesisBlock = genesis
- lc.hc.SetGenesis(lc.genesisBlock.Header())
- lc.hc.SetCurrentHeader(lc.genesisBlock.Header())
-}
-
-// Accessors
-
-// Engine retrieves the light chain's consensus engine.
-func (lc *LightChain) Engine() consensus.Engine { return lc.engine }
-
-// Genesis returns the genesis block
-func (lc *LightChain) Genesis() *types.Block {
- return lc.genesisBlock
-}
-
-func (lc *LightChain) StateCache() state.Database {
- panic("not implemented")
-}
-
-// GetBody retrieves a block body (transactions and uncles) from the database
-// or ODR service by hash, caching it if found.
-func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Body, error) {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := lc.bodyCache.Get(hash); ok {
- return cached, nil
- }
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- body, err := GetBody(ctx, lc.odr, hash, *number)
- if err != nil {
- return nil, err
- }
- // Cache the found body for next time and return
- lc.bodyCache.Add(hash, body)
- return body, nil
-}
-
-// GetBodyRLP retrieves a block body in RLP encoding from the database or
-// ODR service by hash, caching it if found.
-func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.RawValue, error) {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := lc.bodyRLPCache.Get(hash); ok {
- return cached, nil
- }
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- body, err := GetBodyRLP(ctx, lc.odr, hash, *number)
- if err != nil {
- return nil, err
- }
- // Cache the found body for next time and return
- lc.bodyRLPCache.Add(hash, body)
- return body, nil
-}
-
-// HasBlock checks if a block is fully present in the database or not, caching
-// it if present.
-func (lc *LightChain) HasBlock(hash common.Hash, number uint64) bool {
- blk, _ := lc.GetBlock(NoOdr, hash, number)
- return blk != nil
-}
-
-// GetBlock retrieves a block from the database or ODR service by hash and number,
-// caching it if found.
-func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) {
- // Short circuit if the block's already in the cache, retrieve otherwise
- if block, ok := lc.blockCache.Get(hash); ok {
- return block, nil
- }
- block, err := GetBlock(ctx, lc.odr, hash, number)
- if err != nil {
- return nil, err
- }
- // Cache the found block for next time and return
- lc.blockCache.Add(block.Hash(), block)
- return block, nil
-}
-
-// GetBlockByHash retrieves a block from the database or ODR service by hash,
-// caching it if found.
-func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- return lc.GetBlock(ctx, hash, *number)
-}
-
-// GetBlockByNumber retrieves a block from the database or ODR service by
-// number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) {
- hash, err := GetCanonicalHash(ctx, lc.odr, number)
- if hash == (common.Hash{}) || err != nil {
- return nil, err
- }
- return lc.GetBlock(ctx, hash, number)
-}
-
-// Stop stops the blockchain service. If any imports are currently in progress
-// it will abort them using the procInterrupt.
-func (lc *LightChain) Stop() {
- if !lc.stopped.CompareAndSwap(false, true) {
- return
- }
- close(lc.quit)
- lc.StopInsert()
- lc.wg.Wait()
- log.Info("Blockchain stopped")
-}
-
-// StopInsert interrupts all insertion methods, causing them to return
-// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
-// calling this method.
-func (lc *LightChain) StopInsert() {
- lc.procInterrupt.Store(true)
-}
-
-// Rollback is designed to remove a chain of links from the database that aren't
-// certain enough to be valid.
-func (lc *LightChain) Rollback(chain []common.Hash) {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- batch := lc.chainDb.NewBatch()
- for i := len(chain) - 1; i >= 0; i-- {
- hash := chain[i]
-
- // Degrade the chain markers if they are explicitly reverted.
- // In theory we should update all in-memory markers in the
- // last step, however the direction of rollback is from high
- // to low, so it's safe the update in-memory markers directly.
- if head := lc.hc.CurrentHeader(); head.Hash() == hash {
- rawdb.WriteHeadHeaderHash(batch, head.ParentHash)
- lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1))
- }
- }
- if err := batch.Write(); err != nil {
- log.Crit("Failed to rollback light chain", "error", err)
- }
-}
-
-func (lc *LightChain) InsertHeader(header *types.Header) error {
- // Verify the header first before obtaining the lock
- headers := []*types.Header{header}
- if _, err := lc.hc.ValidateHeaderChain(headers); err != nil {
- return err
- }
- // Make sure only one thread manipulates the chain at once
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- _, err := lc.hc.WriteHeaders(headers)
- log.Info("Inserted header", "number", header.Number, "hash", header.Hash())
- return err
-}
-
-func (lc *LightChain) SetCanonical(header *types.Header) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- if err := lc.hc.Reorg([]*types.Header{header}); err != nil {
- return err
- }
- // Emit events
- block := types.NewBlockWithHeader(header)
- lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
- lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
- log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash())
- return nil
-}
-
-// InsertHeaderChain attempts to insert the given header chain in to the local
-// chain, possibly creating a reorg. If an error is returned, it will return the
-// index number of the failing header as well an error describing what went wrong.
-
-// In the case of a light chain, InsertHeaderChain also creates and posts light
-// chain events when necessary.
-func (lc *LightChain) InsertHeaderChain(chain []*types.Header) (int, error) {
- if len(chain) == 0 {
- return 0, nil
- }
- start := time.Now()
- if i, err := lc.hc.ValidateHeaderChain(chain); err != nil {
- return i, err
- }
-
- // Make sure only one thread manipulates the chain at once
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- status, err := lc.hc.InsertHeaderChain(chain, start, lc.forker)
- if err != nil || len(chain) == 0 {
- return 0, err
- }
-
- // Create chain event for the new head block of this insertion.
- var (
- lastHeader = chain[len(chain)-1]
- block = types.NewBlockWithHeader(lastHeader)
- )
- switch status {
- case core.CanonStatTy:
- lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
- lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
- case core.SideStatTy:
- lc.chainSideFeed.Send(core.ChainSideEvent{Block: block})
- }
- return 0, err
-}
-
-// CurrentHeader retrieves the current head header of the canonical chain. The
-// header is retrieved from the HeaderChain's internal cache.
-func (lc *LightChain) CurrentHeader() *types.Header {
- return lc.hc.CurrentHeader()
-}
-
-// GetTd retrieves a block's total difficulty in the canonical chain from the
-// database by hash and number, caching it if found.
-func (lc *LightChain) GetTd(hash common.Hash, number uint64) *big.Int {
- return lc.hc.GetTd(hash, number)
-}
-
-// GetTdOdr retrieves the total difficult from the database or
-// network by hash and number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int {
- td := lc.GetTd(hash, number)
- if td != nil {
- return td
- }
- td, _ = GetTd(ctx, lc.odr, hash, number)
- return td
-}
-
-// GetHeader retrieves a block header from the database by hash and number,
-// caching it if found.
-func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header {
- return lc.hc.GetHeader(hash, number)
-}
-
-// GetHeaderByHash retrieves a block header from the database by hash, caching it if
-// found.
-func (lc *LightChain) GetHeaderByHash(hash common.Hash) *types.Header {
- return lc.hc.GetHeaderByHash(hash)
-}
-
-// HasHeader checks if a block header is present in the database or not, caching
-// it if present.
-func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool {
- return lc.hc.HasHeader(hash, number)
-}
-
-// GetCanonicalHash returns the canonical hash for a given block number
-func (bc *LightChain) GetCanonicalHash(number uint64) common.Hash {
- return bc.hc.GetCanonicalHash(number)
-}
-
-// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
-// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
-// number of blocks to be individually checked before we reach the canonical chain.
-//
-// Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
-func (lc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
- return lc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
-}
-
-// GetHeaderByNumber retrieves a block header from the database by number,
-// caching it (associated with its hash) if found.
-func (lc *LightChain) GetHeaderByNumber(number uint64) *types.Header {
- return lc.hc.GetHeaderByNumber(number)
-}
-
-// GetHeaderByNumberOdr retrieves a block header from the database or network
-// by number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (*types.Header, error) {
- if header := lc.hc.GetHeaderByNumber(number); header != nil {
- return header, nil
- }
- return GetHeaderByNumber(ctx, lc.odr, number)
-}
-
-// Config retrieves the header chain's chain configuration.
-func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() }
-
-// LockChain locks the chain mutex for reading so that multiple canonical hashes can be
-// retrieved while it is guaranteed that they belong to the same version of the chain
-func (lc *LightChain) LockChain() {
- lc.chainmu.RLock()
-}
-
-// UnlockChain unlocks the chain mutex
-func (lc *LightChain) UnlockChain() {
- lc.chainmu.RUnlock()
-}
-
-// SubscribeChainEvent registers a subscription of ChainEvent.
-func (lc *LightChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
- return lc.scope.Track(lc.chainFeed.Subscribe(ch))
-}
-
-// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent.
-func (lc *LightChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
- return lc.scope.Track(lc.chainHeadFeed.Subscribe(ch))
-}
-
-// SubscribeChainSideEvent registers a subscription of ChainSideEvent.
-func (lc *LightChain) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
- return lc.scope.Track(lc.chainSideFeed.Subscribe(ch))
-}
-
-// SubscribeLogsEvent implements the interface of filters.Backend
-// LightChain does not send logs events, so return an empty subscription.
-func (lc *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return lc.scope.Track(new(event.Feed).Subscribe(ch))
-}
-
-// SubscribeRemovedLogsEvent implements the interface of filters.Backend
-// LightChain does not send core.RemovedLogsEvent, so return an empty subscription.
-func (lc *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
- return lc.scope.Track(new(event.Feed).Subscribe(ch))
-}
diff --git a/light/lightchain_test.go b/light/lightchain_test.go
deleted file mode 100644
index 5694ca72c..000000000
--- a/light/lightchain_test.go
+++ /dev/null
@@ -1,358 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-// So we can deterministically seed different blockchains
-var (
- canonicalSeed = 1
- forkSeed = 2
-)
-
-// makeHeaderChain creates a deterministic chain of headers rooted at parent.
-func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
- blocks, _ := core.GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(parent), ethash.NewFaker(), db, n, func(i int, b *core.BlockGen) {
- b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
- })
- headers := make([]*types.Header, len(blocks))
- for i, block := range blocks {
- headers[i] = block.Header()
- }
- return headers
-}
-
-// newCanonical creates a chain database, and injects a deterministic canonical
-// chain. Depending on the full flag, if creates either a full block chain or a
-// header only chain.
-func newCanonical(n int) (ethdb.Database, *LightChain, error) {
- db := rawdb.NewMemoryDatabase()
- gspec := core.Genesis{Config: params.TestChainConfig}
- genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
- blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker())
-
- // Create and inject the requested chain
- if n == 0 {
- return db, blockchain, nil
- }
- // Header-only chain requested
- headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
- _, err := blockchain.InsertHeaderChain(headers)
- return db, blockchain, err
-}
-
-// newTestLightChain creates a LightChain that doesn't validate anything.
-func newTestLightChain() *LightChain {
- db := rawdb.NewMemoryDatabase()
- gspec := &core.Genesis{
- Difficulty: big.NewInt(1),
- Config: params.TestChainConfig,
- }
- gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
- lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker())
- if err != nil {
- panic(err)
- }
- return lc
-}
-
-// Test fork of length N starting from block i
-func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td1, td2 *big.Int)) {
- // Copy old chain up to #i into a new db
- db, LightChain2, err := newCanonical(i)
- if err != nil {
- t.Fatal("could not make new canonical in testFork", err)
- }
- // Assert the chains have the same header/block at #i
- var hash1, hash2 common.Hash
- hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash()
- hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash()
- if hash1 != hash2 {
- t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
- }
- // Extend the newly created chain
- headerChainB := makeHeaderChain(LightChain2.CurrentHeader(), n, db, forkSeed)
- if _, err := LightChain2.InsertHeaderChain(headerChainB); err != nil {
- t.Fatalf("failed to insert forking chain: %v", err)
- }
- // Sanity check that the forked chain can be imported into the original
- var tdPre, tdPost *big.Int
- cur := LightChain.CurrentHeader()
- tdPre = LightChain.GetTd(cur.Hash(), cur.Number.Uint64())
- if err := testHeaderChainImport(headerChainB, LightChain); err != nil {
- t.Fatalf("failed to import forked header chain: %v", err)
- }
- last := headerChainB[len(headerChainB)-1]
- tdPost = LightChain.GetTd(last.Hash(), last.Number.Uint64())
- // Compare the total difficulties of the chains
- comparator(tdPre, tdPost)
-}
-
-// testHeaderChainImport tries to process a chain of header, writing them into
-// the database if successful.
-func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error {
- for _, header := range chain {
- // Try and validate the header
- if err := lightchain.engine.VerifyHeader(lightchain.hc, header); err != nil {
- return err
- }
- // Manually insert the header into the database, but don't reorganize (allows subsequent testing)
- lightchain.chainmu.Lock()
- rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(),
- new(big.Int).Add(header.Difficulty, lightchain.GetTd(header.ParentHash, header.Number.Uint64()-1)))
- rawdb.WriteHeader(lightchain.chainDb, header)
- lightchain.chainmu.Unlock()
- }
- return nil
-}
-
-// Tests that given a starting canonical chain of a given size, it can be extended
-// with various length chains.
-func TestExtendCanonicalHeaders(t *testing.T) {
- length := 5
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- better := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
- }
- }
- // Start fork from current height
- testFork(t, processor, length, 1, better)
- testFork(t, processor, length, 2, better)
- testFork(t, processor, length, 5, better)
- testFork(t, processor, length, 10, better)
-}
-
-// Tests that given a starting canonical chain of a given size, creating shorter
-// forks do not take canonical ownership.
-func TestShorterForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- worse := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) >= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1)
- }
- }
- // Sum of numbers must be less than `length` for this to be a shorter fork
- testFork(t, processor, 0, 3, worse)
- testFork(t, processor, 0, 7, worse)
- testFork(t, processor, 1, 1, worse)
- testFork(t, processor, 1, 7, worse)
- testFork(t, processor, 5, 3, worse)
- testFork(t, processor, 5, 4, worse)
-}
-
-// Tests that given a starting canonical chain of a given size, creating longer
-// forks do take canonical ownership.
-func TestLongerForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- better := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
- }
- }
- // Sum of numbers must be greater than `length` for this to be a longer fork
- testFork(t, processor, 0, 11, better)
- testFork(t, processor, 0, 15, better)
- testFork(t, processor, 1, 10, better)
- testFork(t, processor, 1, 12, better)
- testFork(t, processor, 5, 6, better)
- testFork(t, processor, 5, 8, better)
-}
-
-// Tests that given a starting canonical chain of a given size, creating equal
-// forks do take canonical ownership.
-func TestEqualForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- equal := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) != 0 {
- t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1)
- }
- }
- // Sum of numbers must be equal to `length` for this to be an equal fork
- testFork(t, processor, 0, 10, equal)
- testFork(t, processor, 1, 9, equal)
- testFork(t, processor, 2, 8, equal)
- testFork(t, processor, 5, 5, equal)
- testFork(t, processor, 6, 4, equal)
- testFork(t, processor, 9, 1, equal)
-}
-
-// Tests that chains missing links do not get accepted by the processor.
-func TestBrokenHeaderChain(t *testing.T) {
- // Make chain starting from genesis
- db, LightChain, err := newCanonical(10)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Create a forked chain, and try to insert with a missing link
- chain := makeHeaderChain(LightChain.CurrentHeader(), 5, db, forkSeed)[1:]
- if err := testHeaderChainImport(chain, LightChain); err == nil {
- t.Errorf("broken header chain not reported")
- }
-}
-
-func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
- var chain []*types.Header
- for i, difficulty := range d {
- header := &types.Header{
- Coinbase: common.Address{seed},
- Number: big.NewInt(int64(i + 1)),
- Difficulty: big.NewInt(int64(difficulty)),
- UncleHash: types.EmptyUncleHash,
- TxHash: types.EmptyTxsHash,
- ReceiptHash: types.EmptyReceiptsHash,
- }
- if i == 0 {
- header.ParentHash = genesis.Hash()
- } else {
- header.ParentHash = chain[i-1].Hash()
- }
- chain = append(chain, types.CopyHeader(header))
- }
- return chain
-}
-
-type dummyOdr struct {
- OdrBackend
- db ethdb.Database
- indexerConfig *IndexerConfig
-}
-
-func (odr *dummyOdr) Database() ethdb.Database {
- return odr.db
-}
-
-func (odr *dummyOdr) Retrieve(ctx context.Context, req OdrRequest) error {
- return nil
-}
-
-func (odr *dummyOdr) IndexerConfig() *IndexerConfig {
- return odr.indexerConfig
-}
-
-// Tests that reorganizing a long difficult chain after a short easy one
-// overwrites the canonical numbers and links in the database.
-func TestReorgLongHeaders(t *testing.T) {
- testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10)
-}
-
-// Tests that reorganizing a short difficult chain after a long easy one
-// overwrites the canonical numbers and links in the database.
-func TestReorgShortHeaders(t *testing.T) {
- testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11)
-}
-
-func testReorg(t *testing.T, first, second []int, td int64) {
- bc := newTestLightChain()
-
- // Insert an easy and a difficult chain afterwards
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11))
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22))
- // Check that the chain is valid number and link wise
- prev := bc.CurrentHeader()
- for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) {
- if prev.ParentHash != header.Hash() {
- t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
- }
- }
- // Make sure the chain total difficulty is the correct one
- want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td))
- if have := bc.GetTd(bc.CurrentHeader().Hash(), bc.CurrentHeader().Number.Uint64()); have.Cmp(want) != 0 {
- t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
- }
-}
-
-// Tests that the insertion functions detect banned hashes.
-func TestBadHeaderHashes(t *testing.T) {
- bc := newTestLightChain()
-
- // Create a chain, ban a hash and try to import
- var err error
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
- core.BadHashes[headers[2].Hash()] = true
- if _, err = bc.InsertHeaderChain(headers); !errors.Is(err, core.ErrBannedHash) {
- t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBannedHash)
- }
-}
-
-// Tests that bad hashes are detected on boot, and the chan rolled back to a
-// good state prior to the bad hash.
-func TestReorgBadHeaderHashes(t *testing.T) {
- bc := newTestLightChain()
-
- // Create a chain, import and ban afterwards
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
-
- if _, err := bc.InsertHeaderChain(headers); err != nil {
- t.Fatalf("failed to import headers: %v", err)
- }
- if bc.CurrentHeader().Hash() != headers[3].Hash() {
- t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash())
- }
- core.BadHashes[headers[3].Hash()] = true
- defer func() { delete(core.BadHashes, headers[3].Hash()) }()
-
- // Create a new LightChain and check that it rolled back the state.
- ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
- if err != nil {
- t.Fatalf("failed to create new chain manager: %v", err)
- }
- if ncm.CurrentHeader().Hash() != headers[2].Hash() {
- t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
- }
-}
diff --git a/light/odr.go b/light/odr.go
deleted file mode 100644
index 39f626ee2..000000000
--- a/light/odr.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// NoOdr is the default context passed to an ODR capable function when the ODR
-// service is not required.
-var NoOdr = context.Background()
-
-// ErrNoPeers is returned if no peers capable of serving a queued request are available
-var ErrNoPeers = errors.New("no suitable peers available")
-
-// OdrBackend is an interface to a backend service that handles ODR retrievals type
-type OdrBackend interface {
- Database() ethdb.Database
- ChtIndexer() *core.ChainIndexer
- BloomTrieIndexer() *core.ChainIndexer
- BloomIndexer() *core.ChainIndexer
- Retrieve(ctx context.Context, req OdrRequest) error
- RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
- IndexerConfig() *IndexerConfig
-}
-
-// OdrRequest is an interface for retrieval requests
-type OdrRequest interface {
- StoreResult(db ethdb.Database)
-}
-
-// TrieID identifies a state or account storage trie
-type TrieID struct {
- BlockHash common.Hash
- BlockNumber uint64
- StateRoot common.Hash
- Root common.Hash
- AccountAddress []byte
-}
-
-// StateTrieID returns a TrieID for a state trie belonging to a certain block
-// header.
-func StateTrieID(header *types.Header) *TrieID {
- return &TrieID{
- BlockHash: header.Hash(),
- BlockNumber: header.Number.Uint64(),
- StateRoot: header.Root,
- Root: header.Root,
- AccountAddress: nil,
- }
-}
-
-// StorageTrieID returns a TrieID for a contract storage trie at a given account
-// of a given state trie. It also requires the root hash of the trie for
-// checking Merkle proofs.
-func StorageTrieID(state *TrieID, address common.Address, root common.Hash) *TrieID {
- return &TrieID{
- BlockHash: state.BlockHash,
- BlockNumber: state.BlockNumber,
- StateRoot: state.StateRoot,
- AccountAddress: address[:],
- Root: root,
- }
-}
-
-// TrieRequest is the ODR request type for state/storage trie entries
-type TrieRequest struct {
- Id *TrieID
- Key []byte
- Proof *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *TrieRequest) StoreResult(db ethdb.Database) {
- req.Proof.Store(db)
-}
-
-// CodeRequest is the ODR request type for retrieving contract code
-type CodeRequest struct {
- Id *TrieID // references storage trie of the account
- Hash common.Hash
- Data []byte
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *CodeRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteCode(db, req.Hash, req.Data)
-}
-
-// BlockRequest is the ODR request type for retrieving block bodies
-type BlockRequest struct {
- Hash common.Hash
- Number uint64
- Header *types.Header
- Rlp []byte
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *BlockRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp)
-}
-
-// ReceiptsRequest is the ODR request type for retrieving receipts.
-type ReceiptsRequest struct {
- Hash common.Hash
- Number uint64
- Header *types.Header
- Receipts types.Receipts
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
-}
-
-// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie
-type ChtRequest struct {
- Config *IndexerConfig
- ChtNum, BlockNum uint64
- ChtRoot common.Hash
- Header *types.Header
- Td *big.Int
- Proof *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *ChtRequest) StoreResult(db ethdb.Database) {
- hash, num := req.Header.Hash(), req.Header.Number.Uint64()
- rawdb.WriteHeader(db, req.Header)
- rawdb.WriteTd(db, hash, num, req.Td)
- rawdb.WriteCanonicalHash(db, hash, num)
-}
-
-// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure
-type BloomRequest struct {
- OdrRequest
- Config *IndexerConfig
- BloomTrieNum uint64
- BitIdx uint
- SectionIndexList []uint64
- BloomTrieRoot common.Hash
- BloomBits [][]byte
- Proofs *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *BloomRequest) StoreResult(db ethdb.Database) {
- for i, sectionIdx := range req.SectionIndexList {
- sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*req.Config.BloomTrieSize-1)
- // if we don't have the canonical hash stored for this section head number, we'll still store it under
- // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical
- // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the
- // bit vector again from the network.
- rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i])
- }
-}
-
-// TxStatus describes the status of a transaction
-type TxStatus struct {
- Status txpool.TxStatus
- Lookup *rawdb.LegacyTxLookupEntry `rlp:"nil"`
- Error string
-}
-
-// TxStatusRequest is the ODR request type for retrieving transaction status
-type TxStatusRequest struct {
- Hashes []common.Hash
- Status []TxStatus
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *TxStatusRequest) StoreResult(db ethdb.Database) {}
diff --git a/light/odr_test.go b/light/odr_test.go
deleted file mode 100644
index de12f9b7e..000000000
--- a/light/odr_test.go
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "bytes"
- "context"
- "errors"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
- testBankFunds = big.NewInt(1_000_000_000_000_000_000)
-
- acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
- acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
- acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
- acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
-
- testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
- testContractAddr common.Address
-)
-
-type testOdr struct {
- OdrBackend
- indexerConfig *IndexerConfig
- sdb, ldb ethdb.Database
- serverState state.Database
- disable bool
-}
-
-func (odr *testOdr) Database() ethdb.Database {
- return odr.ldb
-}
-
-var ErrOdrDisabled = errors.New("ODR disabled")
-
-func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
- if odr.disable {
- return ErrOdrDisabled
- }
- switch req := req.(type) {
- case *BlockRequest:
- number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
- if number != nil {
- req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number)
- }
- case *ReceiptsRequest:
- number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
- if number != nil {
- req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number)
- }
- case *TrieRequest:
- var (
- err error
- t state.Trie
- )
- if len(req.Id.AccountAddress) > 0 {
- t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root, nil)
- } else {
- t, err = odr.serverState.OpenTrie(req.Id.Root)
- }
- if err != nil {
- panic(err)
- }
- nodes := trienode.NewProofSet()
- t.Prove(req.Key, nodes)
- req.Proof = nodes
- case *CodeRequest:
- req.Data = rawdb.ReadCode(odr.sdb, req.Hash)
- }
- req.StoreResult(odr.ldb)
- return nil
-}
-
-func (odr *testOdr) IndexerConfig() *IndexerConfig {
- return odr.indexerConfig
-}
-
-type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error)
-
-func TestOdrGetBlockLes2(t *testing.T) { testChainOdr(t, 1, odrGetBlock) }
-
-func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- var block *types.Block
- if bc != nil {
- block = bc.GetBlockByHash(bhash)
- } else {
- block, _ = lc.GetBlockByHash(ctx, bhash)
- }
- if block == nil {
- return nil, nil
- }
- rlp, _ := rlp.EncodeToBytes(block)
- return rlp, nil
-}
-
-func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) }
-
-func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- var receipts types.Receipts
- if bc != nil {
- if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
- if header := rawdb.ReadHeader(db, bhash, *number); header != nil {
- receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, bc.Config())
- }
- }
- } else {
- number := rawdb.ReadHeaderNumber(db, bhash)
- if number != nil {
- receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number)
- }
- }
- if receipts == nil {
- return nil, nil
- }
- rlp, _ := rlp.EncodeToBytes(receipts)
- return rlp, nil
-}
-
-func TestOdrAccountsLes2(t *testing.T) { testChainOdr(t, 1, odrAccounts) }
-
-func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
- acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
-
- var st *state.StateDB
- if bc == nil {
- header := lc.GetHeaderByHash(bhash)
- st = NewState(ctx, header, lc.Odr())
- } else {
- header := bc.GetHeaderByHash(bhash)
- st, _ = state.New(header.Root, bc.StateCache(), nil)
- }
-
- var res []byte
- for _, addr := range acc {
- bal := st.GetBalance(addr)
- rlp, _ := rlp.EncodeToBytes(bal)
- res = append(res, rlp...)
- }
- return res, st.Error()
-}
-
-func TestOdrContractCallLes2(t *testing.T) { testChainOdr(t, 1, odrContractCall) }
-
-func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
- config := params.TestChainConfig
-
- var res []byte
- for i := 0; i < 3; i++ {
- data[35] = byte(i)
-
- var (
- st *state.StateDB
- header *types.Header
- chain core.ChainContext
- )
- if bc == nil {
- chain = lc
- header = lc.GetHeaderByHash(bhash)
- st = NewState(ctx, header, lc.Odr())
- } else {
- chain = bc
- header = bc.GetHeaderByHash(bhash)
- st, _ = state.New(header.Root, bc.StateCache(), nil)
- }
-
- // Perform read-only call.
- st.SetBalance(testBankAddress, math.MaxBig256)
- msg := &core.Message{
- From: testBankAddress,
- To: &testContractAddr,
- Value: new(big.Int),
- GasLimit: 1000000,
- GasPrice: big.NewInt(params.InitialBaseFee),
- GasFeeCap: big.NewInt(params.InitialBaseFee),
- GasTipCap: new(big.Int),
- Data: data,
- SkipAccountChecks: true,
- }
- txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(header, chain, nil)
- vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
- gp := new(core.GasPool).AddGas(math.MaxUint64)
- result, _ := core.ApplyMessage(vmenv, msg, gp)
- res = append(res, result.Return()...)
- if st.Error() != nil {
- return res, st.Error()
- }
- }
- return res, nil
-}
-
-func testChainGen(i int, block *core.BlockGen) {
- signer := types.HomesteadSigner{}
- switch i {
- case 0:
- // In block 1, the test bank sends account #1 some ether.
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
- block.AddTx(tx)
- case 1:
- // In block 2, the test bank sends some more ether to account #1.
- // acc1Addr passes it on to account #2.
- // acc1Addr creates a test contract.
- tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
- nonce := block.TxNonce(acc1Addr)
- tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key)
- nonce++
- tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, block.BaseFee(), testContractCode), signer, acc1Key)
- testContractAddr = crypto.CreateAddress(acc1Addr, nonce)
- block.AddTx(tx1)
- block.AddTx(tx2)
- block.AddTx(tx3)
- case 2:
- // Block 3 is empty but was mined by account #2.
- block.SetCoinbase(acc2Addr)
- block.SetExtra([]byte("yeehaw"))
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
- block.AddTx(tx)
- case 3:
- // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
- b2 := block.PrevBlock(1).Header()
- b2.Extra = []byte("foo")
- block.AddUncle(b2)
- b3 := block.PrevBlock(2).Header()
- b3.Extra = []byte("foo")
- block.AddUncle(b3)
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
- block.AddTx(tx)
- }
-}
-
-func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
- var (
- sdb = rawdb.NewMemoryDatabase()
- ldb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- // Assemble the test environment
- blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- t.Fatal(err)
- }
-
- gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
- odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker())
- if err != nil {
- t.Fatal(err)
- }
- headers := make([]*types.Header, len(gchain))
- for i, block := range gchain {
- headers[i] = block.Header()
- }
- if _, err := lightchain.InsertHeaderChain(headers); err != nil {
- t.Fatal(err)
- }
-
- test := func(expFail int) {
- for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ {
- bhash := rawdb.ReadCanonicalHash(sdb, i)
- b1, err := fn(NoOdr, sdb, blockchain, nil, bhash)
- if err != nil {
- t.Fatalf("error in full-node test for block %d: %v", i, err)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
- defer cancel()
-
- exp := i < uint64(expFail)
- b2, err := fn(ctx, ldb, nil, lightchain, bhash)
- if err != nil && exp {
- t.Errorf("error in ODR test for block %d: %v", i, err)
- }
-
- eq := bytes.Equal(b1, b2)
- if exp && !eq {
- t.Errorf("ODR test output for block %d doesn't match full node", i)
- }
- }
- }
-
- // expect retrievals to fail (except genesis block) without a les peer
- t.Log("checking without ODR")
- odr.disable = true
- test(1)
-
- // expect all retrievals to pass with ODR enabled
- t.Log("checking with ODR")
- odr.disable = false
- test(len(gchain))
-
- // still expect all retrievals to pass, now data should be cached locally
- t.Log("checking without ODR, should be cached")
- odr.disable = true
- test(len(gchain))
-}
diff --git a/light/odr_util.go b/light/odr_util.go
deleted file mode 100644
index 9cac7df4f..000000000
--- a/light/odr_util.go
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// errNonCanonicalHash is returned if the requested chain data doesn't belong
-// to the canonical chain. ODR can only retrieve the canonical chain data covered
-// by the CHT or Bloom trie for verification.
-var errNonCanonicalHash = errors.New("hash is not currently canonical")
-
-// GetHeaderByNumber retrieves the canonical block header corresponding to the
-// given number. The returned header is proven by local CHT.
-func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
- // Try to find it in the local database first.
- db := odr.Database()
- hash := rawdb.ReadCanonicalHash(db, number)
-
- // If there is a canonical hash, there should have a header too.
- // But if it's pruned, re-fetch from network again.
- if (hash != common.Hash{}) {
- if header := rawdb.ReadHeader(db, hash, number); header != nil {
- return header, nil
- }
- }
- // Retrieve the header via ODR, ensure the requested header is covered
- // by local trusted CHT.
- chts, _, chtHead := odr.ChtIndexer().Sections()
- if number >= chts*odr.IndexerConfig().ChtSize {
- return nil, errNoTrustedCht
- }
- r := &ChtRequest{
- ChtRoot: GetChtRoot(db, chts-1, chtHead),
- ChtNum: chts - 1,
- BlockNum: number,
- Config: odr.IndexerConfig(),
- }
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- return r.Header, nil
-}
-
-// GetCanonicalHash retrieves the canonical block hash corresponding to the number.
-func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
- hash := rawdb.ReadCanonicalHash(odr.Database(), number)
- if hash != (common.Hash{}) {
- return hash, nil
- }
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return common.Hash{}, err
- }
- // number -> canonical mapping already be stored in db, get it.
- return header.Hash(), nil
-}
-
-// GetTd retrieves the total difficulty corresponding to the number and hash.
-func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) {
- td := rawdb.ReadTd(odr.Database(), hash, number)
- if td != nil {
- return td, nil
- }
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, err
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- // -> td mapping already be stored in db, get it.
- return rawdb.ReadTd(odr.Database(), hash, number), nil
-}
-
-// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
-func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) {
- if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil {
- return data, nil
- }
- // Retrieve the block header first and pass it for verification.
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- r := &BlockRequest{Hash: hash, Number: number, Header: header}
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- return r.Rlp, nil
-}
-
-// GetBody retrieves the block body (transactions, uncles) corresponding to the
-// hash.
-func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) {
- data, err := GetBodyRLP(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- body := new(types.Body)
- if err := rlp.DecodeBytes(data, body); err != nil {
- return nil, err
- }
- return body, nil
-}
-
-// GetBlock retrieves an entire block corresponding to the hash, assembling it
-// back from the stored header and body.
-func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) {
- // Retrieve the block header and body contents
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- body, err := GetBody(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- // Reassemble the block and return
- return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles), nil
-}
-
-// GetBlockReceipts retrieves the receipts generated by the transactions included
-// in a block given by its hash. Receipts will be filled in with context data.
-func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
- // Assume receipts are already stored locally and attempt to retrieve.
- receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
- if receipts == nil {
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- r := &ReceiptsRequest{Hash: hash, Number: number, Header: header}
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- receipts = r.Receipts
- }
- // If the receipts are incomplete, fill the derived fields
- if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) {
- block, err := GetBlock(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
- config := rawdb.ReadChainConfig(odr.Database(), genesis)
-
- var blobGasPrice *big.Int
- excessBlobGas := block.ExcessBlobGas()
- if excessBlobGas != nil {
- blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas)
- }
-
- if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil {
- return nil, err
- }
- rawdb.WriteReceipts(odr.Database(), hash, number, receipts)
- }
- return receipts, nil
-}
-
-// GetBlockLogs retrieves the logs generated by the transactions included in a
-// block given by its hash. Logs will be filled in with context data.
-func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
- receipts, err := GetBlockReceipts(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- logs := make([][]*types.Log, len(receipts))
- for i, receipt := range receipts {
- logs[i] = receipt.Logs
- }
- return logs, nil
-}
-
-// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to
-// the given bit index and section indexes.
-func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) {
- var (
- reqIndex []int
- reqSections []uint64
- db = odr.Database()
- result = make([][]byte, len(sections))
- )
- blooms, _, sectionHead := odr.BloomTrieIndexer().Sections()
- for i, section := range sections {
- sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1)
- // If we don't have the canonical hash stored for this section head number,
- // we'll still look for an entry with a zero sectionHead (we store it with
- // zero section head too if we don't know it at the time of the retrieval)
- if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 {
- result[i] = bloomBits
- continue
- }
- // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index
- if section >= blooms {
- return nil, errNoTrustedBloomTrie
- }
- reqSections = append(reqSections, section)
- reqIndex = append(reqIndex, i)
- }
- // Find all bloombits in database, nothing to query via odr, return.
- if reqSections == nil {
- return result, nil
- }
- // Send odr request to retrieve missing bloombits.
- r := &BloomRequest{
- BloomTrieRoot: GetBloomTrieRoot(db, blooms-1, sectionHead),
- BloomTrieNum: blooms - 1,
- BitIdx: bit,
- SectionIndexList: reqSections,
- Config: odr.IndexerConfig(),
- }
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- for i, idx := range reqIndex {
- result[idx] = r.BloomBits[i]
- }
- return result, nil
-}
-
-// GetTransaction retrieves a canonical transaction by hash and also returns
-// its position in the chain. There is no guarantee in the LES protocol that
-// the mined transaction will be retrieved back for sure because of different
-// reasons(the transaction is unindexed, the malicious server doesn't reply it
-// deliberately, etc). Therefore, unretrieved transactions will receive a certain
-// number of retries, thus giving a weak guarantee.
-func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
- r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
- if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != txpool.TxStatusIncluded {
- return nil, common.Hash{}, 0, 0, err
- }
- pos := r.Status[0].Lookup
- // first ensure that we have the header, otherwise block body retrieval will fail
- // also verify if this is a canonical block by getting the header by number and checking its hash
- if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash {
- return nil, common.Hash{}, 0, 0, err
- }
- body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex)
- if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash {
- return nil, common.Hash{}, 0, 0, err
- }
- return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil
-}
diff --git a/light/postprocess.go b/light/postprocess.go
deleted file mode 100644
index a317e30b9..000000000
--- a/light/postprocess.go
+++ /dev/null
@@ -1,538 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "bytes"
- "context"
- "encoding/binary"
- "errors"
- "fmt"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/bitutil"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// IndexerConfig includes a set of configs for chain indexers.
-type IndexerConfig struct {
- // The block frequency for creating CHTs.
- ChtSize uint64
-
- // The number of confirmations needed to generate/accept a canonical hash help trie.
- ChtConfirms uint64
-
- // The block frequency for creating new bloom bits.
- BloomSize uint64
-
- // The number of confirmation needed before a bloom section is considered probably final and its rotated bits
- // are calculated.
- BloomConfirms uint64
-
- // The block frequency for creating BloomTrie.
- BloomTrieSize uint64
-
- // The number of confirmations needed to generate/accept a bloom trie.
- BloomTrieConfirms uint64
-}
-
-var (
- // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side.
- DefaultServerIndexerConfig = &IndexerConfig{
- ChtSize: params.CHTFrequency,
- ChtConfirms: params.HelperTrieProcessConfirmations,
- BloomSize: params.BloomBitsBlocks,
- BloomConfirms: params.BloomConfirms,
- BloomTrieSize: params.BloomTrieFrequency,
- BloomTrieConfirms: params.HelperTrieProcessConfirmations,
- }
- // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side.
- DefaultClientIndexerConfig = &IndexerConfig{
- ChtSize: params.CHTFrequency,
- ChtConfirms: params.HelperTrieConfirmations,
- BloomSize: params.BloomBitsBlocksClient,
- BloomConfirms: params.HelperTrieConfirmations,
- BloomTrieSize: params.BloomTrieFrequency,
- BloomTrieConfirms: params.HelperTrieConfirmations,
- }
- // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side.
- TestServerIndexerConfig = &IndexerConfig{
- ChtSize: 128,
- ChtConfirms: 1,
- BloomSize: 16,
- BloomConfirms: 1,
- BloomTrieSize: 128,
- BloomTrieConfirms: 1,
- }
- // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side.
- TestClientIndexerConfig = &IndexerConfig{
- ChtSize: 128,
- ChtConfirms: 8,
- BloomSize: 128,
- BloomConfirms: 8,
- BloomTrieSize: 128,
- BloomTrieConfirms: 8,
- }
-)
-
-var (
- errNoTrustedCht = errors.New("no trusted canonical hash trie")
- errNoTrustedBloomTrie = errors.New("no trusted bloom trie")
- errNoHeader = errors.New("header not found")
-)
-
-// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
-type ChtNode struct {
- Hash common.Hash
- Td *big.Int
-}
-
-// GetChtRoot reads the CHT root associated to the given section from the database
-func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...))
- return common.BytesToHash(data)
-}
-
-// StoreChtRoot writes the CHT root associated to the given section into the database
-func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
-}
-
-// ChtIndexerBackend implements core.ChainIndexerBackend.
-type ChtIndexerBackend struct {
- disablePruning bool
- diskdb, trieTable ethdb.Database
- odr OdrBackend
- triedb *trie.Database
- section, sectionSize uint64
- lastHash common.Hash
- trie *trie.Trie
- originRoot common.Hash
-}
-
-// NewChtIndexer creates a Cht chain indexer
-func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer {
- trieTable := rawdb.NewTable(db, string(rawdb.ChtTablePrefix))
- backend := &ChtIndexerBackend{
- diskdb: db,
- odr: odr,
- trieTable: trieTable,
- triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
- sectionSize: size,
- disablePruning: disablePruning,
- }
- return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht")
-}
-
-// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the
-// ODR backend in order to be able to add new entries and calculate subsequent root hashes
-func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
- batch := c.trieTable.NewBatch()
- r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()}
- for {
- err := c.odr.Retrieve(ctx, r)
- switch err {
- case nil:
- r.Proof.Store(batch)
- return batch.Write()
- case ErrNoPeers:
- // if there are no peers to serve, retry later
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-time.After(time.Second * 10):
- // stay in the loop and try again
- }
- default:
- return err
- }
- }
-}
-
-// Reset implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
- root := types.EmptyRootHash
- if section > 0 {
- root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
- }
- var err error
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
-
- if err != nil && c.odr != nil {
- err = c.fetchMissingNodes(ctx, section, root)
- if err == nil {
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
- }
- }
- c.section = section
- c.originRoot = root
- return err
-}
-
-// Process implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error {
- hash, num := header.Hash(), header.Number.Uint64()
- c.lastHash = hash
-
- td := rawdb.ReadTd(c.diskdb, hash, num)
- if td == nil {
- panic(nil)
- }
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], num)
- data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
- return c.trie.Update(encNumber[:], data)
-}
-
-// Commit implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Commit() error {
- root, nodes, err := c.trie.Commit(false)
- if err != nil {
- return err
- }
- // Commit trie changes into trie database in case it's not nil.
- if nodes != nil {
- if err := c.triedb.Update(root, c.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- return err
- }
- if err := c.triedb.Commit(root, false); err != nil {
- return err
- }
- }
- // Re-create trie with newly generated root and updated database.
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
- if err != nil {
- return err
- }
- // Pruning historical trie nodes if necessary.
- if !c.disablePruning {
- it := c.trieTable.NewIterator(nil, nil)
- defer it.Release()
-
- var (
- deleted int
- batch = c.trieTable.NewBatch()
- t = time.Now()
- )
- hashes := make(map[common.Hash]struct{})
- if nodes != nil {
- for _, hash := range nodes.Hashes() {
- hashes[hash] = struct{}{}
- }
- }
- for it.Next() {
- trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix)
- if len(trimmed) == common.HashLength {
- if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
- batch.Delete(trimmed)
- deleted += 1
- }
- }
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
- }
- log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
- StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
- return nil
-}
-
-// Prune implements core.ChainIndexerBackend which deletes all chain data
-// (except hash<->number mappings) older than the specified threshold.
-func (c *ChtIndexerBackend) Prune(threshold uint64) error {
- // Short circuit if the light pruning is disabled.
- if c.disablePruning {
- return nil
- }
- t := time.Now()
- // Always keep genesis header in database.
- start, end := uint64(1), (threshold+1)*c.sectionSize
-
- var batch = c.diskdb.NewBatch()
- for {
- numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240)
- if len(numbers) == 0 {
- break
- }
- for i := 0; i < len(numbers); i++ {
- // Keep hash<->number mapping in database otherwise the hash based
- // API(e.g. GetReceipt, GetLogs) will be broken.
- //
- // Storage size wise, the size of a mapping is ~41bytes. For one
- // section is about 1.3MB which is acceptable.
- //
- // In order to totally get rid of this index, we need an additional
- // flag to specify how many historical data light client can serve.
- rawdb.DeleteCanonicalHash(batch, numbers[i])
- rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i])
- }
- if batch.ValueSize() > ethdb.IdealBatchSize {
- if err := batch.Write(); err != nil {
- return err
- }
- batch.Reset()
- }
- start = numbers[len(numbers)-1] + 1
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t)))
- return nil
-}
-
-// GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database
-func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...))
- return common.BytesToHash(data)
-}
-
-// StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database
-func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
-}
-
-// BloomTrieIndexerBackend implements core.ChainIndexerBackend
-type BloomTrieIndexerBackend struct {
- disablePruning bool
- diskdb, trieTable ethdb.Database
- triedb *trie.Database
- odr OdrBackend
- section uint64
- parentSize uint64
- size uint64
- bloomTrieRatio uint64
- trie *trie.Trie
- originRoot common.Hash
- sectionHeads []common.Hash
-}
-
-// NewBloomTrieIndexer creates a BloomTrie chain indexer
-func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer {
- trieTable := rawdb.NewTable(db, string(rawdb.BloomTrieTablePrefix))
- backend := &BloomTrieIndexerBackend{
- diskdb: db,
- odr: odr,
- trieTable: trieTable,
- triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
- parentSize: parentSize,
- size: size,
- disablePruning: disablePruning,
- }
- backend.bloomTrieRatio = size / parentSize
- backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
- return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie")
-}
-
-// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the
-// ODR backend in order to be able to add new entries and calculate subsequent root hashes
-func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
- indexCh := make(chan uint, types.BloomBitLength)
- type res struct {
- nodes *trienode.ProofSet
- err error
- }
- resCh := make(chan res, types.BloomBitLength)
- for i := 0; i < 20; i++ {
- go func() {
- for bitIndex := range indexCh {
- r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()}
- for {
- if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers {
- // if there are no peers to serve, retry later
- select {
- case <-ctx.Done():
- resCh <- res{nil, ctx.Err()}
- return
- case <-time.After(time.Second * 10):
- // stay in the loop and try again
- }
- } else {
- resCh <- res{r.Proofs, err}
- break
- }
- }
- }
- }()
- }
- for i := uint(0); i < types.BloomBitLength; i++ {
- indexCh <- i
- }
- close(indexCh)
- batch := b.trieTable.NewBatch()
- for i := uint(0); i < types.BloomBitLength; i++ {
- res := <-resCh
- if res.err != nil {
- return res.err
- }
- res.nodes.Store(batch)
- }
- return batch.Write()
-}
-
-// Reset implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
- root := types.EmptyRootHash
- if section > 0 {
- root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
- }
- var err error
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- if err != nil && b.odr != nil {
- err = b.fetchMissingNodes(ctx, section, root)
- if err == nil {
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- }
- }
- b.section = section
- b.originRoot = root
- return err
-}
-
-// Process implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error {
- num := header.Number.Uint64() - b.section*b.size
- if (num+1)%b.parentSize == 0 {
- b.sectionHeads[num/b.parentSize] = header.Hash()
- }
- return nil
-}
-
-// Commit implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Commit() error {
- var compSize, decompSize uint64
-
- for i := uint(0); i < types.BloomBitLength; i++ {
- var encKey [10]byte
- binary.BigEndian.PutUint16(encKey[0:2], uint16(i))
- binary.BigEndian.PutUint64(encKey[2:10], b.section)
- var decomp []byte
- for j := uint64(0); j < b.bloomTrieRatio; j++ {
- data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j])
- if err != nil {
- return err
- }
- decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8))
- if err2 != nil {
- return err2
- }
- decomp = append(decomp, decompData...)
- }
- comp := bitutil.CompressBytes(decomp)
-
- decompSize += uint64(len(decomp))
- compSize += uint64(len(comp))
-
- var terr error
- if len(comp) > 0 {
- terr = b.trie.Update(encKey[:], comp)
- } else {
- terr = b.trie.Delete(encKey[:])
- }
- if terr != nil {
- return terr
- }
- }
- root, nodes, err := b.trie.Commit(false)
- if err != nil {
- return err
- }
- // Commit trie changes into trie database in case it's not nil.
- if nodes != nil {
- if err := b.triedb.Update(root, b.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- return err
- }
- if err := b.triedb.Commit(root, false); err != nil {
- return err
- }
- }
- // Re-create trie with newly generated root and updated database.
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- if err != nil {
- return err
- }
- // Pruning historical trie nodes if necessary.
- if !b.disablePruning {
- it := b.trieTable.NewIterator(nil, nil)
- defer it.Release()
-
- var (
- deleted int
- batch = b.trieTable.NewBatch()
- t = time.Now()
- )
- hashes := make(map[common.Hash]struct{})
- if nodes != nil {
- for _, hash := range nodes.Hashes() {
- hashes[hash] = struct{}{}
- }
- }
- for it.Next() {
- trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix)
- if len(trimmed) == common.HashLength {
- if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
- batch.Delete(trimmed)
- deleted += 1
- }
- }
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
- }
- sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
- StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
- log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
-
- return nil
-}
-
-// Prune implements core.ChainIndexerBackend which deletes all
-// bloombits which older than the specified threshold.
-func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error {
- // Short circuit if the light pruning is disabled.
- if b.disablePruning {
- return nil
- }
- start := time.Now()
- for i := uint(0); i < types.BloomBitLength; i++ {
- rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio)
- }
- log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start)))
- return nil
-}
diff --git a/light/trie.go b/light/trie.go
deleted file mode 100644
index 1d93bdf41..000000000
--- a/light/trie.go
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- sha3Nil = crypto.Keccak256Hash(nil)
-)
-
-func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB {
- state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil)
- return state
-}
-
-func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database {
- return &odrDatabase{ctx, StateTrieID(head), odr}
-}
-
-type odrDatabase struct {
- ctx context.Context
- id *TrieID
- backend OdrBackend
-}
-
-func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) {
- return &odrTrie{db: db, id: db.id}, nil
-}
-
-func (db *odrDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, _ state.Trie) (state.Trie, error) {
- return &odrTrie{db: db, id: StorageTrieID(db.id, address, root)}, nil
-}
-
-func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie {
- switch t := t.(type) {
- case *odrTrie:
- cpy := &odrTrie{db: t.db, id: t.id}
- if t.trie != nil {
- cpy.trie = t.trie.Copy()
- }
- return cpy
- default:
- panic(fmt.Errorf("unknown trie type %T", t))
- }
-}
-
-func (db *odrDatabase) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
- if codeHash == sha3Nil {
- return nil, nil
- }
- code := rawdb.ReadCode(db.backend.Database(), codeHash)
- if len(code) != 0 {
- return code, nil
- }
- id := *db.id
- id.AccountAddress = addr[:]
- req := &CodeRequest{Id: &id, Hash: codeHash}
- err := db.backend.Retrieve(db.ctx, req)
- return req.Data, err
-}
-
-func (db *odrDatabase) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
- code, err := db.ContractCode(addr, codeHash)
- return len(code), err
-}
-
-func (db *odrDatabase) TrieDB() *trie.Database {
- return nil
-}
-
-func (db *odrDatabase) DiskDB() ethdb.KeyValueStore {
- panic("not implemented")
-}
-
-type odrTrie struct {
- db *odrDatabase
- id *TrieID
- trie *trie.Trie
-}
-
-func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
- key = crypto.Keccak256(key)
- var enc []byte
- err := t.do(key, func() (err error) {
- enc, err = t.trie.Get(key)
- return err
- })
- if err != nil || len(enc) == 0 {
- return nil, err
- }
- _, content, _, err := rlp.Split(enc)
- return content, err
-}
-
-func (t *odrTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
- var (
- enc []byte
- key = crypto.Keccak256(address.Bytes())
- )
- err := t.do(key, func() (err error) {
- enc, err = t.trie.Get(key)
- return err
- })
- if err != nil || len(enc) == 0 {
- return nil, err
- }
- acct := new(types.StateAccount)
- if err := rlp.DecodeBytes(enc, acct); err != nil {
- return nil, err
- }
- return acct, nil
-}
-
-func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error {
- key := crypto.Keccak256(address.Bytes())
- value, err := rlp.EncodeToBytes(acc)
- if err != nil {
- return fmt.Errorf("decoding error in account update: %w", err)
- }
- return t.do(key, func() error {
- return t.trie.Update(key, value)
- })
-}
-
-func (t *odrTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
- return nil
-}
-
-func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error {
- key = crypto.Keccak256(key)
- v, _ := rlp.EncodeToBytes(value)
- return t.do(key, func() error {
- return t.trie.Update(key, v)
- })
-}
-
-func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error {
- key = crypto.Keccak256(key)
- return t.do(key, func() error {
- return t.trie.Delete(key)
- })
-}
-
-// DeleteAccount abstracts an account deletion from the trie.
-func (t *odrTrie) DeleteAccount(address common.Address) error {
- key := crypto.Keccak256(address.Bytes())
- return t.do(key, func() error {
- return t.trie.Delete(key)
- })
-}
-
-func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
- if t.trie == nil {
- return t.id.Root, nil, nil
- }
- return t.trie.Commit(collectLeaf)
-}
-
-func (t *odrTrie) Hash() common.Hash {
- if t.trie == nil {
- return t.id.Root
- }
- return t.trie.Hash()
-}
-
-func (t *odrTrie) NodeIterator(startkey []byte) (trie.NodeIterator, error) {
- return newNodeIterator(t, startkey), nil
-}
-
-func (t *odrTrie) GetKey(sha []byte) []byte {
- return nil
-}
-
-func (t *odrTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
- return errors.New("not implemented, needs client/server interface split")
-}
-
-// do tries and retries to execute a function until it returns with no error or
-// an error type other than MissingNodeError
-func (t *odrTrie) do(key []byte, fn func() error) error {
- for {
- var err error
- if t.trie == nil {
- var id *trie.ID
- if len(t.id.AccountAddress) > 0 {
- id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
- } else {
- id = trie.StateTrieID(t.id.StateRoot)
- }
- triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
- t.trie, err = trie.New(id, triedb)
- }
- if err == nil {
- err = fn()
- }
- if _, ok := err.(*trie.MissingNodeError); !ok {
- return err
- }
- r := &TrieRequest{Id: t.id, Key: key}
- if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil {
- return err
- }
- }
-}
-
-type nodeIterator struct {
- trie.NodeIterator
- t *odrTrie
- err error
-}
-
-func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator {
- it := &nodeIterator{t: t}
- // Open the actual non-ODR trie if that hasn't happened yet.
- if t.trie == nil {
- it.do(func() error {
- var id *trie.ID
- if len(t.id.AccountAddress) > 0 {
- id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
- } else {
- id = trie.StateTrieID(t.id.StateRoot)
- }
- triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
- t, err := trie.New(id, triedb)
- if err == nil {
- it.t.trie = t
- }
- return err
- })
- }
- it.do(func() error {
- var err error
- it.NodeIterator, err = it.t.trie.NodeIterator(startkey)
- if err != nil {
- return err
- }
- return it.NodeIterator.Error()
- })
- return it
-}
-
-func (it *nodeIterator) Next(descend bool) bool {
- var ok bool
- it.do(func() error {
- ok = it.NodeIterator.Next(descend)
- return it.NodeIterator.Error()
- })
- return ok
-}
-
-// do runs fn and attempts to fill in missing nodes by retrieving.
-func (it *nodeIterator) do(fn func() error) {
- var lasthash common.Hash
- for {
- it.err = fn()
- missing, ok := it.err.(*trie.MissingNodeError)
- if !ok {
- return
- }
- if missing.NodeHash == lasthash {
- it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash)
- return
- }
- lasthash = missing.NodeHash
- r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)}
- if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil {
- return
- }
- }
-}
-
-func (it *nodeIterator) Error() error {
- if it.err != nil {
- return it.err
- }
- return it.NodeIterator.Error()
-}
-
-func nibblesToKey(nib []byte) []byte {
- if len(nib) > 0 && nib[len(nib)-1] == 0x10 {
- nib = nib[:len(nib)-1] // drop terminator
- }
- if len(nib)&1 == 1 {
- nib = append(nib, 0) // make even
- }
- key := make([]byte, len(nib)/2)
- for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 {
- key[bi] = nib[ni]<<4 | nib[ni+1]
- }
- return key
-}
diff --git a/light/trie_test.go b/light/trie_test.go
deleted file mode 100644
index fe724e9ee..000000000
--- a/light/trie_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "math/big"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-func TestNodeIterator(t *testing.T) {
- var (
- fulldb = rawdb.NewMemoryDatabase()
- lightdb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- blockchain, _ := core.NewBlockChain(fulldb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- panic(err)
- }
-
- gspec.MustCommit(lightdb, trie.NewDatabase(lightdb, trie.HashDefaults))
- ctx := context.Background()
- odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- head := blockchain.CurrentHeader()
- lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root)
- fullTrie, _ := blockchain.StateCache().OpenTrie(head.Root)
- if err := diffTries(fullTrie, lightTrie); err != nil {
- t.Fatal(err)
- }
-}
-
-func diffTries(t1, t2 state.Trie) error {
- trieIt1, err := t1.NodeIterator(nil)
- if err != nil {
- return err
- }
- trieIt2, err := t2.NodeIterator(nil)
- if err != nil {
- return err
- }
- i1 := trie.NewIterator(trieIt1)
- i2 := trie.NewIterator(trieIt2)
- for i1.Next() && i2.Next() {
- if !bytes.Equal(i1.Key, i2.Key) {
- spew.Dump(i2)
- return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key)
- }
- if !bytes.Equal(i1.Value, i2.Value) {
- return fmt.Errorf("tries differ at key %x", i1.Key)
- }
- }
- switch {
- case i1.Err != nil:
- return fmt.Errorf("full trie iterator error: %v", i1.Err)
- case i2.Err != nil:
- return fmt.Errorf("light trie iterator error: %v", i2.Err)
- case i1.Next():
- return errors.New("full trie iterator has more k/v pairs")
- case i2.Next():
- return errors.New("light trie iterator has more k/v pairs")
- }
- return nil
-}
diff --git a/light/txpool.go b/light/txpool.go
deleted file mode 100644
index b792d70b1..000000000
--- a/light/txpool.go
+++ /dev/null
@@ -1,556 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "fmt"
- "math/big"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
-)
-
-const (
- // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
- chainHeadChanSize = 10
-)
-
-// txPermanent is the number of mined blocks after a mined transaction is
-// considered permanent and no rollback is expected
-var txPermanent = uint64(500)
-
-// TxPool implements the transaction pool for light clients, which keeps track
-// of the status of locally created transactions, detecting if they are included
-// in a block (mined) or rolled back. There are no queued transactions since we
-// always receive all locally signed transactions in the same order as they are
-// created.
-type TxPool struct {
- config *params.ChainConfig
- signer types.Signer
- quit chan bool
- txFeed event.Feed
- scope event.SubscriptionScope
- chainHeadCh chan core.ChainHeadEvent
- chainHeadSub event.Subscription
- mu sync.RWMutex
- chain *LightChain
- odr OdrBackend
- chainDb ethdb.Database
- relay TxRelayBackend
- head common.Hash
- nonce map[common.Address]uint64 // "pending" nonce
- pending map[common.Hash]*types.Transaction // pending transactions by tx hash
- mined map[common.Hash][]*types.Transaction // mined transactions by block hash
- clearIdx uint64 // earliest block nr that can contain mined tx info
-
- istanbul bool // Fork indicator whether we are in the istanbul stage.
- eip2718 bool // Fork indicator whether we are in the eip2718 stage.
- shanghai bool // Fork indicator whether we are in the shanghai stage.
-}
-
-// TxRelayBackend provides an interface to the mechanism that forwards transactions to the
-// ETH network. The implementations of the functions should be non-blocking.
-//
-// Send instructs backend to forward new transactions NewHead notifies backend about a new
-// head after processed by the tx pool, including mined and rolled back transactions since
-// the last event.
-//
-// Discard notifies backend about transactions that should be discarded either because
-// they have been replaced by a re-send or because they have been mined long ago and no
-// rollback is expected.
-type TxRelayBackend interface {
- Send(txs types.Transactions)
- NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash)
- Discard(hashes []common.Hash)
-}
-
-// NewTxPool creates a new light transaction pool
-func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool {
- pool := &TxPool{
- config: config,
- signer: types.LatestSigner(config),
- nonce: make(map[common.Address]uint64),
- pending: make(map[common.Hash]*types.Transaction),
- mined: make(map[common.Hash][]*types.Transaction),
- quit: make(chan bool),
- chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
- chain: chain,
- relay: relay,
- odr: chain.Odr(),
- chainDb: chain.Odr().Database(),
- head: chain.CurrentHeader().Hash(),
- clearIdx: chain.CurrentHeader().Number.Uint64(),
- }
- // Subscribe events from blockchain
- pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
- go pool.eventLoop()
-
- return pool
-}
-
-// currentState returns the light state of the current head header
-func (pool *TxPool) currentState(ctx context.Context) *state.StateDB {
- return NewState(ctx, pool.chain.CurrentHeader(), pool.odr)
-}
-
-// GetNonce returns the "pending" nonce of a given address. It always queries
-// the nonce belonging to the latest header too in order to detect if another
-// client using the same key sent a transaction.
-func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) {
- state := pool.currentState(ctx)
- nonce := state.GetNonce(addr)
- if state.Error() != nil {
- return 0, state.Error()
- }
- sn, ok := pool.nonce[addr]
- if ok && sn > nonce {
- nonce = sn
- }
- if !ok || sn < nonce {
- pool.nonce[addr] = nonce
- }
- return nonce, nil
-}
-
-// txStateChanges stores the recent changes between pending/mined states of
-// transactions. True means mined, false means rolled back, no entry means no change
-type txStateChanges map[common.Hash]bool
-
-// setState sets the status of a tx to either recently mined or recently rolled back
-func (txc txStateChanges) setState(txHash common.Hash, mined bool) {
- val, ent := txc[txHash]
- if ent && (val != mined) {
- delete(txc, txHash)
- } else {
- txc[txHash] = mined
- }
-}
-
-// getLists creates lists of mined and rolled back tx hashes
-func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Hash) {
- for hash, val := range txc {
- if val {
- mined = append(mined, hash)
- } else {
- rollback = append(rollback, hash)
- }
- }
- return
-}
-
-// checkMinedTxs checks newly added blocks for the currently pending transactions
-// and marks them as mined if necessary. It also stores block position in the db
-// and adds them to the received txStateChanges map.
-func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error {
- // If no transactions are pending, we don't care about anything
- if len(pool.pending) == 0 {
- return nil
- }
- block, err := GetBlock(ctx, pool.odr, hash, number)
- if err != nil {
- return err
- }
- // Gather all the local transaction mined in this block
- list := pool.mined[hash]
- for _, tx := range block.Transactions() {
- if _, ok := pool.pending[tx.Hash()]; ok {
- list = append(list, tx)
- }
- }
- // If some transactions have been mined, write the needed data to disk and update
- if list != nil {
- // Retrieve all the receipts belonging to this block and write the lookup table
- if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results
- return err
- }
- rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block)
-
- // Update the transaction pool's state
- for _, tx := range list {
- delete(pool.pending, tx.Hash())
- txc.setState(tx.Hash(), true)
- }
- pool.mined[hash] = list
- }
- return nil
-}
-
-// rollbackTxs marks the transactions contained in recently rolled back blocks
-// as rolled back. It also removes any positional lookup entries.
-func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
- batch := pool.chainDb.NewBatch()
- if list, ok := pool.mined[hash]; ok {
- for _, tx := range list {
- txHash := tx.Hash()
- rawdb.DeleteTxLookupEntry(batch, txHash)
- pool.pending[txHash] = tx
- txc.setState(txHash, false)
- }
- delete(pool.mined, hash)
- }
- batch.Write()
-}
-
-// reorgOnNewHead sets a new head header, processing (and rolling back if necessary)
-// the blocks since the last known head and returns a txStateChanges map containing
-// the recently mined and rolled back transaction hashes. If an error (context
-// timeout) occurs during checking new blocks, it leaves the locally known head
-// at the latest checked block and still returns a valid txStateChanges, making it
-// possible to continue checking the missing blocks at the next chain head event
-func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) {
- txc := make(txStateChanges)
- oldh := pool.chain.GetHeaderByHash(pool.head)
- newh := newHeader
- // find common ancestor, create list of rolled back and new block hashes
- var oldHashes, newHashes []common.Hash
- for oldh.Hash() != newh.Hash() {
- if oldh.Number.Uint64() >= newh.Number.Uint64() {
- oldHashes = append(oldHashes, oldh.Hash())
- oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1)
- }
- if oldh.Number.Uint64() < newh.Number.Uint64() {
- newHashes = append(newHashes, newh.Hash())
- newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1)
- if newh == nil {
- // happens when CHT syncing, nothing to do
- newh = oldh
- }
- }
- }
- if oldh.Number.Uint64() < pool.clearIdx {
- pool.clearIdx = oldh.Number.Uint64()
- }
- // roll back old blocks
- for _, hash := range oldHashes {
- pool.rollbackTxs(hash, txc)
- }
- pool.head = oldh.Hash()
- // check mined txs of new blocks (array is in reversed order)
- for i := len(newHashes) - 1; i >= 0; i-- {
- hash := newHashes[i]
- if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil {
- return txc, err
- }
- pool.head = hash
- }
-
- // clear old mined tx entries of old blocks
- if idx := newHeader.Number.Uint64(); idx > pool.clearIdx+txPermanent {
- idx2 := idx - txPermanent
- if len(pool.mined) > 0 {
- for i := pool.clearIdx; i < idx2; i++ {
- hash := rawdb.ReadCanonicalHash(pool.chainDb, i)
- if list, ok := pool.mined[hash]; ok {
- hashes := make([]common.Hash, len(list))
- for i, tx := range list {
- hashes[i] = tx.Hash()
- }
- pool.relay.Discard(hashes)
- delete(pool.mined, hash)
- }
- }
- }
- pool.clearIdx = idx2
- }
-
- return txc, nil
-}
-
-// blockCheckTimeout is the time limit for checking new blocks for mined
-// transactions. Checking resumes at the next chain head event if timed out.
-const blockCheckTimeout = time.Second * 3
-
-// eventLoop processes chain head events and also notifies the tx relay backend
-// about the new head hash and tx state changes
-func (pool *TxPool) eventLoop() {
- for {
- select {
- case ev := <-pool.chainHeadCh:
- pool.setNewHead(ev.Block.Header())
- // hack in order to avoid hogging the lock; this part will
- // be replaced by a subsequent PR.
- time.Sleep(time.Millisecond)
-
- // System stopped
- case <-pool.chainHeadSub.Err():
- return
- }
- }
-}
-
-func (pool *TxPool) setNewHead(head *types.Header) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout)
- defer cancel()
-
- txc, _ := pool.reorgOnNewHead(ctx, head)
- m, r := txc.getLists()
- pool.relay.NewHead(pool.head, m, r)
-
- // Update fork indicator by next pending block number
- next := new(big.Int).Add(head.Number, big.NewInt(1))
- pool.istanbul = pool.config.IsIstanbul(next)
- pool.eip2718 = pool.config.IsBerlin(next)
- pool.shanghai = pool.config.IsShanghai(next, uint64(time.Now().Unix()))
-}
-
-// Stop stops the light transaction pool
-func (pool *TxPool) Stop() {
- // Unsubscribe all subscriptions registered from txpool
- pool.scope.Close()
- // Unsubscribe subscriptions registered from blockchain
- pool.chainHeadSub.Unsubscribe()
- close(pool.quit)
- log.Info("Transaction pool stopped")
-}
-
-// SubscribeNewTxsEvent registers a subscription of core.NewTxsEvent and
-// starts sending event to the given channel.
-func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return pool.scope.Track(pool.txFeed.Subscribe(ch))
-}
-
-// Stats returns the number of currently pending (locally created) transactions
-func (pool *TxPool) Stats() (pending int) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- pending = len(pool.pending)
- return
-}
-
-// validateTx checks whether a transaction is valid according to the consensus rules.
-func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error {
- // Validate sender
- var (
- from common.Address
- err error
- )
-
- // Validate the transaction sender and it's sig. Throw
- // if the from fields is invalid.
- if from, err = types.Sender(pool.signer, tx); err != nil {
- return txpool.ErrInvalidSender
- }
- // Last but not least check for nonce errors
- currentState := pool.currentState(ctx)
- if n := currentState.GetNonce(from); n > tx.Nonce() {
- return core.ErrNonceTooLow
- }
-
- // Check the transaction doesn't exceed the current
- // block limit gas.
- header := pool.chain.GetHeaderByHash(pool.head)
- if header.GasLimit < tx.Gas() {
- return txpool.ErrGasLimit
- }
-
- // Transactions can't be negative. This may never happen
- // using RLP decoded transactions but may occur if you create
- // a transaction using the RPC for example.
- if tx.Value().Sign() < 0 {
- return txpool.ErrNegativeValue
- }
-
- // Transactor should have enough funds to cover the costs
- // cost == V + GP * GL
- if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 {
- return core.ErrInsufficientFunds
- }
-
- // Should supply enough intrinsic gas
- gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
- if err != nil {
- return err
- }
- if tx.Gas() < gas {
- return core.ErrIntrinsicGas
- }
- return currentState.Error()
-}
-
-// add validates a new transaction and sets its state pending if processable.
-// It also updates the locally stored nonce if necessary.
-func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error {
- hash := tx.Hash()
-
- if pool.pending[hash] != nil {
- return fmt.Errorf("known transaction (%x)", hash[:4])
- }
- err := pool.validateTx(ctx, tx)
- if err != nil {
- return err
- }
-
- if _, ok := pool.pending[hash]; !ok {
- pool.pending[hash] = tx
-
- nonce := tx.Nonce() + 1
-
- addr, _ := types.Sender(pool.signer, tx)
- if nonce > pool.nonce[addr] {
- pool.nonce[addr] = nonce
- }
-
- // Notify the subscribers. This event is posted in a goroutine
- // because it's possible that somewhere during the post "Remove transaction"
- // gets called which will then wait for the global tx pool lock and deadlock.
- go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}})
- }
-
- // Print a log message if low enough level is set
- log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To())
- return nil
-}
-
-// Add adds a transaction to the pool if valid and passes it to the tx relay
-// backend
-func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- data, err := tx.MarshalBinary()
- if err != nil {
- return err
- }
-
- if err := pool.add(ctx, tx); err != nil {
- return err
- }
- //fmt.Println("Send", tx.Hash())
- pool.relay.Send(types.Transactions{tx})
-
- pool.chainDb.Put(tx.Hash().Bytes(), data)
- return nil
-}
-
-// AddBatch adds all valid transactions to the pool and passes them to
-// the tx relay backend
-func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- var sendTx types.Transactions
-
- for _, tx := range txs {
- if err := pool.add(ctx, tx); err == nil {
- sendTx = append(sendTx, tx)
- }
- }
- if len(sendTx) > 0 {
- pool.relay.Send(sendTx)
- }
-}
-
-// GetTransaction returns a transaction if it is contained in the pool
-// and nil otherwise.
-func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction {
- // check the txs first
- if tx, ok := pool.pending[hash]; ok {
- return tx
- }
- return nil
-}
-
-// GetTransactions returns all currently processable transactions.
-// The returned slice may be modified by the caller.
-func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- txs = make(types.Transactions, len(pool.pending))
- i := 0
- for _, tx := range pool.pending {
- txs[i] = tx
- i++
- }
- return txs, nil
-}
-
-// Content retrieves the data content of the transaction pool, returning all the
-// pending as well as queued transactions, grouped by account and nonce.
-func (pool *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- // Retrieve all the pending transactions and sort by account and by nonce
- pending := make(map[common.Address][]*types.Transaction)
- for _, tx := range pool.pending {
- account, _ := types.Sender(pool.signer, tx)
- pending[account] = append(pending[account], tx)
- }
- // There are no queued transactions in a light pool, just return an empty map
- queued := make(map[common.Address][]*types.Transaction)
- return pending, queued
-}
-
-// ContentFrom retrieves the data content of the transaction pool, returning the
-// pending as well as queued transactions of this address, grouped by nonce.
-func (pool *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- // Retrieve the pending transactions and sort by nonce
- var pending []*types.Transaction
- for _, tx := range pool.pending {
- account, _ := types.Sender(pool.signer, tx)
- if account != addr {
- continue
- }
- pending = append(pending, tx)
- }
- // There are no queued transactions in a light pool, just return an empty map
- return pending, []*types.Transaction{}
-}
-
-// RemoveTransactions removes all given transactions from the pool.
-func (pool *TxPool) RemoveTransactions(txs types.Transactions) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- var hashes []common.Hash
- batch := pool.chainDb.NewBatch()
- for _, tx := range txs {
- hash := tx.Hash()
- delete(pool.pending, hash)
- batch.Delete(hash.Bytes())
- hashes = append(hashes, hash)
- }
- batch.Write()
- pool.relay.Discard(hashes)
-}
-
-// RemoveTx removes the transaction with the given hash from the pool.
-func (pool *TxPool) RemoveTx(hash common.Hash) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- // delete from pending pool
- delete(pool.pending, hash)
- pool.chainDb.Delete(hash[:])
- pool.relay.Discard([]common.Hash{hash})
-}
diff --git a/light/txpool_test.go b/light/txpool_test.go
deleted file mode 100644
index 1eec7bc42..000000000
--- a/light/txpool_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package light
-
-import (
- "context"
- "math"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-type testTxRelay struct {
- send, discard, mined chan int
-}
-
-func (r *testTxRelay) Send(txs types.Transactions) {
- r.send <- len(txs)
-}
-
-func (r *testTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
- m := len(mined)
- if m != 0 {
- r.mined <- m
- }
-}
-
-func (r *testTxRelay) Discard(hashes []common.Hash) {
- r.discard <- len(hashes)
-}
-
-const poolTestTxs = 1000
-const poolTestBlocks = 100
-
-// test tx 0..n-1
-var testTx [poolTestTxs]*types.Transaction
-
-// txs sent before block i
-func sentTx(i int) int {
- return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs)
-}
-
-// txs included in block i or before that (minedTx(i) <= sentTx(i))
-func minedTx(i int) int {
- return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs)
-}
-
-func txPoolTestChainGen(i int, block *core.BlockGen) {
- s := minedTx(i)
- e := minedTx(i + 1)
- for i := s; i < e; i++ {
- block.AddTx(testTx[i])
- }
-}
-
-func TestTxPool(t *testing.T) {
- for i := range testTx {
- testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
- }
-
- var (
- sdb = rawdb.NewMemoryDatabase()
- ldb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- // Assemble the test environment
- blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), poolTestBlocks, txPoolTestChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- panic(err)
- }
-
- gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
- odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- relay := &testTxRelay{
- send: make(chan int, 1),
- discard: make(chan int, 1),
- mined: make(chan int, 1),
- }
- lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
- txPermanent = 50
- pool := NewTxPool(params.TestChainConfig, lightchain, relay)
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
- defer cancel()
-
- for ii, block := range gchain {
- i := ii + 1
- s := sentTx(i - 1)
- e := sentTx(i)
- for i := s; i < e; i++ {
- pool.Add(ctx, testTx[i])
- got := <-relay.send
- exp := 1
- if got != exp {
- t.Errorf("relay.Send expected len = %d, got %d", exp, got)
- }
- }
-
- if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}); err != nil {
- panic(err)
- }
-
- got := <-relay.mined
- exp := minedTx(i) - minedTx(i-1)
- if got != exp {
- t.Errorf("relay.NewHead expected len(mined) = %d, got %d", exp, got)
- }
-
- exp = 0
- if i > int(txPermanent)+1 {
- exp = minedTx(i-int(txPermanent)-1) - minedTx(i-int(txPermanent)-2)
- }
- if exp != 0 {
- got = <-relay.discard
- if got != exp {
- t.Errorf("relay.Discard expected len = %d, got %d", exp, got)
- }
- }
- }
-}