2016-11-08 19:01:56 -06:00
// Copyright 2016 The go-ethereum Authors
2016-10-13 22:47:09 -05:00
// 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 <http://www.gnu.org/licenses/>.
2016-11-08 19:01:56 -06:00
2016-10-13 22:47:09 -05:00
package light
import (
2017-03-22 12:20:33 -05:00
"context"
2018-02-05 10:40:32 -06:00
"errors"
2016-10-13 22:47:09 -05:00
"math/big"
"sync"
"sync/atomic"
2017-03-22 14:44:22 -05:00
"time"
2016-10-13 22:47:09 -05:00
"github.com/ethereum/go-ethereum/common"
2017-04-04 17:16:29 -05:00
"github.com/ethereum/go-ethereum/consensus"
2016-10-13 22:47:09 -05:00
"github.com/ethereum/go-ethereum/core"
2018-05-07 06:35:06 -05:00
"github.com/ethereum/go-ethereum/core/rawdb"
2018-02-05 10:40:32 -06:00
"github.com/ethereum/go-ethereum/core/state"
2016-10-13 22:47:09 -05:00
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
2017-02-22 06:10:07 -06:00
"github.com/ethereum/go-ethereum/log"
2016-10-20 06:36:29 -05:00
"github.com/ethereum/go-ethereum/params"
2016-10-13 22:47:09 -05:00
"github.com/ethereum/go-ethereum/rlp"
"github.com/hashicorp/golang-lru"
)
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 {
2017-08-18 05:58:36 -05:00
hc * core . HeaderChain
2018-08-28 02:08:16 -05:00
indexerConfig * IndexerConfig
2017-08-18 05:58:36 -05:00
chainDb ethdb . Database
odr OdrBackend
chainFeed event . Feed
chainSideFeed event . Feed
chainHeadFeed event . Feed
scope event . SubscriptionScope
genesisBlock * types . Block
2016-10-13 22:47:09 -05:00
mu sync . RWMutex
chainmu sync . RWMutex
bodyCache * lru . Cache // Cache for the most recent block bodies
bodyRLPCache * lru . Cache // Cache for the most recent block bodies in RLP encoded format
blockCache * lru . Cache // Cache for the most recent entire blocks
quit chan struct { }
running int32 // running must be called automically
// procInterrupt must be atomically called
procInterrupt int32 // interrupt signaler for block processing
wg sync . WaitGroup
2017-04-04 17:16:29 -05:00
engine consensus . Engine
2016-10-13 22:47:09 -05:00
}
// NewLightChain returns a fully initialised light chain using information
// available in the database. It initialises the default Ethereum header
// validator.
2017-08-18 05:58:36 -05:00
func NewLightChain ( odr OdrBackend , config * params . ChainConfig , engine consensus . Engine ) ( * LightChain , error ) {
2016-10-13 22:47:09 -05:00
bodyCache , _ := lru . New ( bodyCacheLimit )
bodyRLPCache , _ := lru . New ( bodyCacheLimit )
blockCache , _ := lru . New ( blockCacheLimit )
bc := & LightChain {
2018-08-28 02:08:16 -05:00
chainDb : odr . Database ( ) ,
indexerConfig : odr . IndexerConfig ( ) ,
odr : odr ,
quit : make ( chan struct { } ) ,
bodyCache : bodyCache ,
bodyRLPCache : bodyRLPCache ,
blockCache : blockCache ,
engine : engine ,
2016-10-13 22:47:09 -05:00
}
var err error
2017-04-04 17:16:29 -05:00
bc . hc , err = core . NewHeaderChain ( odr . Database ( ) , config , bc . engine , bc . getProcInterrupt )
2016-10-13 22:47:09 -05:00
if err != nil {
return nil , err
}
bc . genesisBlock , _ = bc . GetBlockByNumber ( NoOdr , 0 )
if bc . genesisBlock == nil {
2017-03-02 07:03:33 -06:00
return nil , core . ErrNoGenesis
2016-10-13 22:47:09 -05:00
}
2017-10-24 08:19:09 -05:00
if cp , ok := trustedCheckpoints [ bc . genesisBlock . Hash ( ) ] ; ok {
bc . addTrustedCheckpoint ( cp )
2017-09-11 16:36:16 -05:00
}
2016-10-13 22:47:09 -05:00
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
2017-01-06 08:52:03 -06:00
for hash := range core . BadHashes {
2016-10-13 22:47:09 -05:00
if header := bc . GetHeaderByHash ( hash ) ; header != nil {
2017-03-03 03:41:52 -06:00
log . Error ( "Found bad hash, rewinding chain" , "number" , header . Number , "hash" , header . ParentHash )
2016-10-13 22:47:09 -05:00
bc . SetHead ( header . Number . Uint64 ( ) - 1 )
2017-03-03 03:41:52 -06:00
log . Error ( "Chain rewind was successful, resuming normal operation" )
2016-10-13 22:47:09 -05:00
}
}
return bc , nil
}
2017-10-24 08:19:09 -05:00
// addTrustedCheckpoint adds a trusted checkpoint to the blockchain
2018-09-20 06:11:14 -05:00
func ( self * LightChain ) addTrustedCheckpoint ( cp * params . TrustedCheckpoint ) {
2017-10-24 08:19:09 -05:00
if self . odr . ChtIndexer ( ) != nil {
2018-09-20 06:11:14 -05:00
StoreChtRoot ( self . chainDb , cp . SectionIndex , cp . SectionHead , cp . CHTRoot )
self . odr . ChtIndexer ( ) . AddCheckpoint ( cp . SectionIndex , cp . SectionHead )
2017-10-24 08:19:09 -05:00
}
if self . odr . BloomTrieIndexer ( ) != nil {
2018-09-20 06:11:14 -05:00
StoreBloomTrieRoot ( self . chainDb , cp . SectionIndex , cp . SectionHead , cp . BloomRoot )
self . odr . BloomTrieIndexer ( ) . AddCheckpoint ( cp . SectionIndex , cp . SectionHead )
2017-10-24 08:19:09 -05:00
}
if self . odr . BloomIndexer ( ) != nil {
2018-09-20 06:11:14 -05:00
self . odr . BloomIndexer ( ) . AddCheckpoint ( cp . SectionIndex , cp . SectionHead )
2017-10-24 08:19:09 -05:00
}
2018-09-20 06:11:14 -05:00
log . Info ( "Added trusted checkpoint" , "chain" , cp . Name , "block" , ( cp . SectionIndex + 1 ) * self . indexerConfig . ChtSize - 1 , "hash" , cp . SectionHead )
2017-10-24 08:19:09 -05:00
}
2016-10-13 22:47:09 -05:00
func ( self * LightChain ) getProcInterrupt ( ) bool {
return atomic . LoadInt32 ( & self . procInterrupt ) == 1
}
// Odr returns the ODR backend of the chain
func ( self * LightChain ) Odr ( ) OdrBackend {
return self . odr
}
// loadLastState loads the last known chain state from the database. This method
// assumes that the chain manager mutex is held.
func ( self * LightChain ) loadLastState ( ) error {
2018-05-07 06:35:06 -05:00
if head := rawdb . ReadHeadHeaderHash ( self . chainDb ) ; head == ( common . Hash { } ) {
2016-10-13 22:47:09 -05:00
// Corrupt or empty database, init from scratch
self . Reset ( )
} else {
if header := self . GetHeaderByHash ( head ) ; header != nil {
self . hc . SetCurrentHeader ( header )
}
}
// Issue a status log and return
header := self . hc . CurrentHeader ( )
headerTd := self . GetTd ( header . Hash ( ) , header . Number . Uint64 ( ) )
2018-09-20 03:41:59 -05:00
log . Info ( "Loaded most recent local header" , "number" , header . Number , "hash" , header . Hash ( ) , "td" , headerTd , "age" , common . PrettyAge ( time . Unix ( header . Time . Int64 ( ) , 0 ) ) )
2016-10-13 22:47:09 -05:00
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 ( bc * LightChain ) SetHead ( head uint64 ) {
bc . mu . Lock ( )
defer bc . mu . Unlock ( )
bc . hc . SetHead ( head , nil )
bc . loadLastState ( )
}
// GasLimit returns the gas limit of the current HEAD block.
2017-11-13 05:47:27 -06:00
func ( self * LightChain ) GasLimit ( ) uint64 {
2016-10-13 22:47:09 -05:00
return self . hc . CurrentHeader ( ) . GasLimit
}
// Reset purges the entire blockchain, restoring it to its genesis state.
func ( bc * LightChain ) Reset ( ) {
bc . ResetWithGenesisBlock ( bc . genesisBlock )
}
// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
// specified genesis state.
func ( bc * LightChain ) ResetWithGenesisBlock ( genesis * types . Block ) {
// Dump the entire block chain and purge the caches
bc . SetHead ( 0 )
bc . mu . Lock ( )
defer bc . mu . Unlock ( )
// Prepare the genesis block and reinitialise the chain
2018-05-07 06:35:06 -05:00
rawdb . WriteTd ( bc . chainDb , genesis . Hash ( ) , genesis . NumberU64 ( ) , genesis . Difficulty ( ) )
rawdb . WriteBlock ( bc . chainDb , genesis )
2016-10-13 22:47:09 -05:00
bc . genesisBlock = genesis
bc . hc . SetGenesis ( bc . genesisBlock . Header ( ) )
bc . hc . SetCurrentHeader ( bc . genesisBlock . Header ( ) )
}
// Accessors
2017-04-12 08:38:31 -05:00
// Engine retrieves the light chain's consensus engine.
func ( bc * LightChain ) Engine ( ) consensus . Engine { return bc . engine }
2016-10-13 22:47:09 -05:00
// Genesis returns the genesis block
func ( bc * LightChain ) Genesis ( ) * types . Block {
return bc . genesisBlock
}
2018-02-05 10:40:32 -06:00
// State returns a new mutable state based on the current HEAD block.
func ( bc * LightChain ) State ( ) ( * state . StateDB , error ) {
return nil , errors . New ( "not implemented, needs client/server interface split" )
}
2016-10-13 22:47:09 -05:00
// GetBody retrieves a block body (transactions and uncles) from the database
// or ODR service by hash, caching it if found.
func ( self * 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 := self . bodyCache . Get ( hash ) ; ok {
body := cached . ( * types . Body )
return body , nil
}
2018-05-07 06:35:06 -05:00
number := self . hc . GetBlockNumber ( hash )
if number == nil {
return nil , errors . New ( "unknown block" )
}
body , err := GetBody ( ctx , self . odr , hash , * number )
2016-10-13 22:47:09 -05:00
if err != nil {
return nil , err
}
// Cache the found body for next time and return
self . 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 ( self * 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 := self . bodyRLPCache . Get ( hash ) ; ok {
return cached . ( rlp . RawValue ) , nil
}
2018-05-07 06:35:06 -05:00
number := self . hc . GetBlockNumber ( hash )
if number == nil {
return nil , errors . New ( "unknown block" )
}
body , err := GetBodyRLP ( ctx , self . odr , hash , * number )
2016-10-13 22:47:09 -05:00
if err != nil {
return nil , err
}
// Cache the found body for next time and return
self . bodyRLPCache . Add ( hash , body )
return body , nil
}
// HasBlock checks if a block is fully present in the database or not, caching
// it if present.
2017-09-09 11:03:07 -05:00
func ( bc * LightChain ) HasBlock ( hash common . Hash , number uint64 ) bool {
blk , _ := bc . GetBlock ( NoOdr , hash , number )
2016-10-13 22:47:09 -05:00
return blk != nil
}
// GetBlock retrieves a block from the database or ODR service by hash and number,
// caching it if found.
func ( self * 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 := self . blockCache . Get ( hash ) ; ok {
return block . ( * types . Block ) , nil
}
block , err := GetBlock ( ctx , self . odr , hash , number )
if err != nil {
return nil , err
}
// Cache the found block for next time and return
self . 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 ( self * LightChain ) GetBlockByHash ( ctx context . Context , hash common . Hash ) ( * types . Block , error ) {
2018-05-07 06:35:06 -05:00
number := self . hc . GetBlockNumber ( hash )
if number == nil {
return nil , errors . New ( "unknown block" )
}
return self . GetBlock ( ctx , hash , * number )
2016-10-13 22:47:09 -05:00
}
// GetBlockByNumber retrieves a block from the database or ODR service by
// number, caching it (associated with its hash) if found.
func ( self * LightChain ) GetBlockByNumber ( ctx context . Context , number uint64 ) ( * types . Block , error ) {
hash , err := GetCanonicalHash ( ctx , self . odr , number )
if hash == ( common . Hash { } ) || err != nil {
return nil , err
}
return self . GetBlock ( ctx , hash , number )
}
// Stop stops the blockchain service. If any imports are currently in progress
// it will abort them using the procInterrupt.
func ( bc * LightChain ) Stop ( ) {
if ! atomic . CompareAndSwapInt32 ( & bc . running , 0 , 1 ) {
return
}
close ( bc . quit )
atomic . StoreInt32 ( & bc . procInterrupt , 1 )
bc . wg . Wait ( )
2017-03-03 03:41:52 -06:00
log . Info ( "Blockchain manager stopped" )
2016-10-13 22:47:09 -05:00
}
// Rollback is designed to remove a chain of links from the database that aren't
// certain enough to be valid.
func ( self * LightChain ) Rollback ( chain [ ] common . Hash ) {
self . mu . Lock ( )
defer self . mu . Unlock ( )
for i := len ( chain ) - 1 ; i >= 0 ; i -- {
hash := chain [ i ]
if head := self . hc . CurrentHeader ( ) ; head . Hash ( ) == hash {
self . hc . SetCurrentHeader ( self . GetHeader ( head . ParentHash , head . Number . Uint64 ( ) - 1 ) )
}
}
}
// postChainEvents iterates over the events generated by a chain insertion and
2017-08-18 05:58:36 -05:00
// posts them into the event feed.
2016-10-13 22:47:09 -05:00
func ( self * LightChain ) postChainEvents ( events [ ] interface { } ) {
for _ , event := range events {
2017-08-18 05:58:36 -05:00
switch ev := event . ( type ) {
case core . ChainEvent :
2018-01-30 10:39:32 -06:00
if self . CurrentHeader ( ) . Hash ( ) == ev . Hash {
2017-08-18 05:58:36 -05:00
self . chainHeadFeed . Send ( core . ChainHeadEvent { Block : ev . Block } )
2016-10-13 22:47:09 -05:00
}
2017-08-18 05:58:36 -05:00
self . chainFeed . Send ( ev )
case core . ChainSideEvent :
self . chainSideFeed . Send ( ev )
2016-10-13 22:47:09 -05:00
}
}
}
// 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.
//
// The verify parameter can be used to fine tune whether nonce verification
// should be done or not. The reason behind the optional check is because some
// of the header retrieval mechanisms already need to verfy nonces, as well as
// because nonces can be verified sparsely, not needing to check each.
//
// In the case of a light chain, InsertHeaderChain also creates and posts light
// chain events when necessary.
func ( self * LightChain ) InsertHeaderChain ( chain [ ] * types . Header , checkFreq int ) ( int , error ) {
2017-03-22 14:44:22 -05:00
start := time . Now ( )
if i , err := self . hc . ValidateHeaderChain ( chain , checkFreq ) ; err != nil {
return i , err
}
2016-10-13 22:47:09 -05:00
// Make sure only one thread manipulates the chain at once
self . chainmu . Lock ( )
2017-03-22 14:44:22 -05:00
defer func ( ) {
self . chainmu . Unlock ( )
time . Sleep ( time . Millisecond * 10 ) // ugly hack; do not hog chain lock in case syncing is CPU-limited by validation
} ( )
2016-10-13 22:47:09 -05:00
self . wg . Add ( 1 )
defer self . wg . Done ( )
var events [ ] interface { }
whFunc := func ( header * types . Header ) error {
self . mu . Lock ( )
defer self . mu . Unlock ( )
status , err := self . hc . WriteHeader ( header )
switch status {
case core . CanonStatTy :
2017-03-03 03:41:52 -06:00
log . Debug ( "Inserted new header" , "number" , header . Number , "hash" , header . Hash ( ) )
2016-10-13 22:47:09 -05:00
events = append ( events , core . ChainEvent { Block : types . NewBlockWithHeader ( header ) , Hash : header . Hash ( ) } )
case core . SideStatTy :
2017-03-03 03:41:52 -06:00
log . Debug ( "Inserted forked header" , "number" , header . Number , "hash" , header . Hash ( ) )
2016-10-13 22:47:09 -05:00
events = append ( events , core . ChainSideEvent { Block : types . NewBlockWithHeader ( header ) } )
}
return err
}
2017-03-22 14:44:22 -05:00
i , err := self . hc . InsertHeaderChain ( chain , whFunc , start )
2018-01-23 05:10:49 -06:00
self . postChainEvents ( events )
2016-10-13 22:47:09 -05:00
return i , err
}
// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func ( self * LightChain ) CurrentHeader ( ) * types . Header {
return self . 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 ( self * LightChain ) GetTd ( hash common . Hash , number uint64 ) * big . Int {
return self . hc . GetTd ( hash , number )
}
// GetTdByHash retrieves a block's total difficulty in the canonical chain from the
// database by hash, caching it if found.
func ( self * LightChain ) GetTdByHash ( hash common . Hash ) * big . Int {
return self . hc . GetTdByHash ( hash )
}
// GetHeader retrieves a block header from the database by hash and number,
// caching it if found.
func ( self * LightChain ) GetHeader ( hash common . Hash , number uint64 ) * types . Header {
return self . hc . GetHeader ( hash , number )
}
// GetHeaderByHash retrieves a block header from the database by hash, caching it if
// found.
func ( self * LightChain ) GetHeaderByHash ( hash common . Hash ) * types . Header {
return self . hc . GetHeaderByHash ( hash )
}
// HasHeader checks if a block header is present in the database or not, caching
// it if present.
2017-09-09 11:03:07 -05:00
func ( bc * LightChain ) HasHeader ( hash common . Hash , number uint64 ) bool {
return bc . hc . HasHeader ( hash , number )
2016-10-13 22:47:09 -05:00
}
// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
// hash, fetching towards the genesis block.
func ( self * LightChain ) GetBlockHashesFromHash ( hash common . Hash , max uint64 ) [ ] common . Hash {
return self . hc . GetBlockHashesFromHash ( hash , max )
}
2018-06-12 08:52:54 -05:00
// 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 ( bc * LightChain ) GetAncestor ( hash common . Hash , number , ancestor uint64 , maxNonCanonical * uint64 ) ( common . Hash , uint64 ) {
bc . chainmu . Lock ( )
defer bc . chainmu . Unlock ( )
return bc . hc . GetAncestor ( hash , number , ancestor , maxNonCanonical )
}
2016-10-13 22:47:09 -05:00
// GetHeaderByNumber retrieves a block header from the database by number,
// caching it (associated with its hash) if found.
func ( self * LightChain ) GetHeaderByNumber ( number uint64 ) * types . Header {
return self . hc . GetHeaderByNumber ( number )
}
// GetHeaderByNumberOdr retrieves a block header from the database or network
// by number, caching it (associated with its hash) if found.
func ( self * LightChain ) GetHeaderByNumberOdr ( ctx context . Context , number uint64 ) ( * types . Header , error ) {
if header := self . hc . GetHeaderByNumber ( number ) ; header != nil {
return header , nil
}
return GetHeaderByNumber ( ctx , self . odr , number )
}
2017-12-28 07:18:34 -06:00
// Config retrieves the header chain's chain configuration.
func ( self * LightChain ) Config ( ) * params . ChainConfig { return self . hc . Config ( ) }
2016-10-13 22:47:09 -05:00
func ( self * LightChain ) SyncCht ( ctx context . Context ) bool {
2018-08-21 06:39:28 -05:00
// If we don't have a CHT indexer, abort
2017-10-24 08:19:09 -05:00
if self . odr . ChtIndexer ( ) == nil {
return false
}
2018-08-21 06:39:28 -05:00
// Ensure the remote CHT head is ahead of us
head := self . CurrentHeader ( ) . Number . Uint64 ( )
sections , _ , _ := self . odr . ChtIndexer ( ) . Sections ( )
2018-08-28 02:08:16 -05:00
latest := sections * self . indexerConfig . ChtSize - 1
2018-08-21 06:39:28 -05:00
if clique := self . hc . Config ( ) . Clique ; clique != nil {
latest -= latest % clique . Epoch // epoch snapshot for clique
}
if head >= latest {
return false
}
// Retrieve the latest useful header and update to it
if header , err := GetHeaderByNumber ( ctx , self . odr , latest ) ; header != nil && err == nil {
self . mu . Lock ( )
defer self . mu . Unlock ( )
// Ensure the chain didn't move past the latest block while retrieving it
if self . hc . CurrentHeader ( ) . Number . Uint64 ( ) < header . Number . Uint64 ( ) {
2018-09-20 03:41:59 -05:00
log . Info ( "Updated latest header based on CHT" , "number" , header . Number , "hash" , header . Hash ( ) , "age" , common . PrettyAge ( time . Unix ( header . Time . Int64 ( ) , 0 ) ) )
2018-08-21 06:39:28 -05:00
self . hc . SetCurrentHeader ( header )
2016-10-13 22:47:09 -05:00
}
2018-08-21 06:39:28 -05:00
return true
2016-10-13 22:47:09 -05:00
}
return false
}
2016-11-29 23:02:08 -06:00
// 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 ( self * LightChain ) LockChain ( ) {
self . chainmu . RLock ( )
}
// UnlockChain unlocks the chain mutex
func ( self * LightChain ) UnlockChain ( ) {
self . chainmu . RUnlock ( )
}
2017-08-18 05:58:36 -05:00
// SubscribeChainEvent registers a subscription of ChainEvent.
func ( self * LightChain ) SubscribeChainEvent ( ch chan <- core . ChainEvent ) event . Subscription {
return self . scope . Track ( self . chainFeed . Subscribe ( ch ) )
}
// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent.
func ( self * LightChain ) SubscribeChainHeadEvent ( ch chan <- core . ChainHeadEvent ) event . Subscription {
return self . scope . Track ( self . chainHeadFeed . Subscribe ( ch ) )
}
// SubscribeChainSideEvent registers a subscription of ChainSideEvent.
func ( self * LightChain ) SubscribeChainSideEvent ( ch chan <- core . ChainSideEvent ) event . Subscription {
return self . scope . Track ( self . chainSideFeed . Subscribe ( ch ) )
}
// SubscribeLogsEvent implements the interface of filters.Backend
// LightChain does not send logs events, so return an empty subscription.
func ( self * LightChain ) SubscribeLogsEvent ( ch chan <- [ ] * types . Log ) event . Subscription {
return self . 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 ( self * LightChain ) SubscribeRemovedLogsEvent ( ch chan <- core . RemovedLogsEvent ) event . Subscription {
return self . scope . Track ( new ( event . Feed ) . Subscribe ( ch ) )
}