github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/les/server_requests.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package les
    18  
    19  import (
    20  	"encoding/binary"
    21  	"encoding/json"
    22  
    23  	"github.com/ethw3/go-ethereuma/common"
    24  	"github.com/ethw3/go-ethereuma/core"
    25  	"github.com/ethw3/go-ethereuma/core/state"
    26  	"github.com/ethw3/go-ethereuma/core/types"
    27  	"github.com/ethw3/go-ethereuma/light"
    28  	"github.com/ethw3/go-ethereuma/log"
    29  	"github.com/ethw3/go-ethereuma/metrics"
    30  	"github.com/ethw3/go-ethereuma/rlp"
    31  	"github.com/ethw3/go-ethereuma/trie"
    32  )
    33  
    34  // serverBackend defines the backend functions needed for serving LES requests
    35  type serverBackend interface {
    36  	ArchiveMode() bool
    37  	AddTxsSync() bool
    38  	BlockChain() *core.BlockChain
    39  	TxPool() *core.TxPool
    40  	GetHelperTrie(typ uint, index uint64) *trie.Trie
    41  }
    42  
    43  // Decoder is implemented by the messages passed to the handler functions
    44  type Decoder interface {
    45  	Decode(val interface{}) error
    46  }
    47  
    48  // RequestType is a static struct that describes an LES request type and references
    49  // its handler function.
    50  type RequestType struct {
    51  	Name                                                             string
    52  	MaxCount                                                         uint64
    53  	InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter
    54  	ServingTimeMeter                                                 metrics.Timer
    55  	Handle                                                           func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error)
    56  }
    57  
    58  // serveRequestFn is returned by the request handler functions after decoding the request.
    59  // This function does the actual request serving using the supplied backend. waitOrStop is
    60  // called between serving individual request items and may block if the serving process
    61  // needs to be throttled. If it returns false then the process is terminated.
    62  // The reply is not sent by this function yet. The flow control feedback value is supplied
    63  // by the protocol handler when calling the send function of the returned reply struct.
    64  type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply
    65  
    66  // Les3 contains the request types supported by les/2 and les/3
    67  var Les3 = map[uint64]RequestType{
    68  	GetBlockHeadersMsg: {
    69  		Name:             "block header request",
    70  		MaxCount:         MaxHeaderFetch,
    71  		InPacketsMeter:   miscInHeaderPacketsMeter,
    72  		InTrafficMeter:   miscInHeaderTrafficMeter,
    73  		OutPacketsMeter:  miscOutHeaderPacketsMeter,
    74  		OutTrafficMeter:  miscOutHeaderTrafficMeter,
    75  		ServingTimeMeter: miscServingTimeHeaderTimer,
    76  		Handle:           handleGetBlockHeaders,
    77  	},
    78  	GetBlockBodiesMsg: {
    79  		Name:             "block bodies request",
    80  		MaxCount:         MaxBodyFetch,
    81  		InPacketsMeter:   miscInBodyPacketsMeter,
    82  		InTrafficMeter:   miscInBodyTrafficMeter,
    83  		OutPacketsMeter:  miscOutBodyPacketsMeter,
    84  		OutTrafficMeter:  miscOutBodyTrafficMeter,
    85  		ServingTimeMeter: miscServingTimeBodyTimer,
    86  		Handle:           handleGetBlockBodies,
    87  	},
    88  	GetCodeMsg: {
    89  		Name:             "code request",
    90  		MaxCount:         MaxCodeFetch,
    91  		InPacketsMeter:   miscInCodePacketsMeter,
    92  		InTrafficMeter:   miscInCodeTrafficMeter,
    93  		OutPacketsMeter:  miscOutCodePacketsMeter,
    94  		OutTrafficMeter:  miscOutCodeTrafficMeter,
    95  		ServingTimeMeter: miscServingTimeCodeTimer,
    96  		Handle:           handleGetCode,
    97  	},
    98  	GetReceiptsMsg: {
    99  		Name:             "receipts request",
   100  		MaxCount:         MaxReceiptFetch,
   101  		InPacketsMeter:   miscInReceiptPacketsMeter,
   102  		InTrafficMeter:   miscInReceiptTrafficMeter,
   103  		OutPacketsMeter:  miscOutReceiptPacketsMeter,
   104  		OutTrafficMeter:  miscOutReceiptTrafficMeter,
   105  		ServingTimeMeter: miscServingTimeReceiptTimer,
   106  		Handle:           handleGetReceipts,
   107  	},
   108  	GetProofsV2Msg: {
   109  		Name:             "les/2 proofs request",
   110  		MaxCount:         MaxProofsFetch,
   111  		InPacketsMeter:   miscInTrieProofPacketsMeter,
   112  		InTrafficMeter:   miscInTrieProofTrafficMeter,
   113  		OutPacketsMeter:  miscOutTrieProofPacketsMeter,
   114  		OutTrafficMeter:  miscOutTrieProofTrafficMeter,
   115  		ServingTimeMeter: miscServingTimeTrieProofTimer,
   116  		Handle:           handleGetProofs,
   117  	},
   118  	GetHelperTrieProofsMsg: {
   119  		Name:             "helper trie proof request",
   120  		MaxCount:         MaxHelperTrieProofsFetch,
   121  		InPacketsMeter:   miscInHelperTriePacketsMeter,
   122  		InTrafficMeter:   miscInHelperTrieTrafficMeter,
   123  		OutPacketsMeter:  miscOutHelperTriePacketsMeter,
   124  		OutTrafficMeter:  miscOutHelperTrieTrafficMeter,
   125  		ServingTimeMeter: miscServingTimeHelperTrieTimer,
   126  		Handle:           handleGetHelperTrieProofs,
   127  	},
   128  	SendTxV2Msg: {
   129  		Name:             "new transactions",
   130  		MaxCount:         MaxTxSend,
   131  		InPacketsMeter:   miscInTxsPacketsMeter,
   132  		InTrafficMeter:   miscInTxsTrafficMeter,
   133  		OutPacketsMeter:  miscOutTxsPacketsMeter,
   134  		OutTrafficMeter:  miscOutTxsTrafficMeter,
   135  		ServingTimeMeter: miscServingTimeTxTimer,
   136  		Handle:           handleSendTx,
   137  	},
   138  	GetTxStatusMsg: {
   139  		Name:             "transaction status query request",
   140  		MaxCount:         MaxTxStatus,
   141  		InPacketsMeter:   miscInTxStatusPacketsMeter,
   142  		InTrafficMeter:   miscInTxStatusTrafficMeter,
   143  		OutPacketsMeter:  miscOutTxStatusPacketsMeter,
   144  		OutTrafficMeter:  miscOutTxStatusTrafficMeter,
   145  		ServingTimeMeter: miscServingTimeTxStatusTimer,
   146  		Handle:           handleGetTxStatus,
   147  	},
   148  }
   149  
   150  // handleGetBlockHeaders handles a block header request
   151  func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   152  	var r GetBlockHeadersPacket
   153  	if err := msg.Decode(&r); err != nil {
   154  		return nil, 0, 0, err
   155  	}
   156  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   157  		// Gather headers until the fetch or network limits is reached
   158  		var (
   159  			bc              = backend.BlockChain()
   160  			hashMode        = r.Query.Origin.Hash != (common.Hash{})
   161  			first           = true
   162  			maxNonCanonical = uint64(100)
   163  			bytes           common.StorageSize
   164  			headers         []*types.Header
   165  			unknown         bool
   166  		)
   167  		for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit {
   168  			if !first && !waitOrStop() {
   169  				return nil
   170  			}
   171  			// Retrieve the next header satisfying the r
   172  			var origin *types.Header
   173  			if hashMode {
   174  				if first {
   175  					origin = bc.GetHeaderByHash(r.Query.Origin.Hash)
   176  					if origin != nil {
   177  						r.Query.Origin.Number = origin.Number.Uint64()
   178  					}
   179  				} else {
   180  					origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number)
   181  				}
   182  			} else {
   183  				origin = bc.GetHeaderByNumber(r.Query.Origin.Number)
   184  			}
   185  			if origin == nil {
   186  				break
   187  			}
   188  			headers = append(headers, origin)
   189  			bytes += estHeaderRlpSize
   190  
   191  			// Advance to the next header of the r
   192  			switch {
   193  			case hashMode && r.Query.Reverse:
   194  				// Hash based traversal towards the genesis block
   195  				ancestor := r.Query.Skip + 1
   196  				if ancestor == 0 {
   197  					unknown = true
   198  				} else {
   199  					r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical)
   200  					unknown = r.Query.Origin.Hash == common.Hash{}
   201  				}
   202  			case hashMode && !r.Query.Reverse:
   203  				// Hash based traversal towards the leaf block
   204  				var (
   205  					current = origin.Number.Uint64()
   206  					next    = current + r.Query.Skip + 1
   207  				)
   208  				if next <= current {
   209  					infos, _ := json.Marshal(p.Peer.Info())
   210  					p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos))
   211  					unknown = true
   212  				} else {
   213  					if header := bc.GetHeaderByNumber(next); header != nil {
   214  						nextHash := header.Hash()
   215  						expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical)
   216  						if expOldHash == r.Query.Origin.Hash {
   217  							r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next
   218  						} else {
   219  							unknown = true
   220  						}
   221  					} else {
   222  						unknown = true
   223  					}
   224  				}
   225  			case r.Query.Reverse:
   226  				// Number based traversal towards the genesis block
   227  				if r.Query.Origin.Number >= r.Query.Skip+1 {
   228  					r.Query.Origin.Number -= r.Query.Skip + 1
   229  				} else {
   230  					unknown = true
   231  				}
   232  
   233  			case !r.Query.Reverse:
   234  				// Number based traversal towards the leaf block
   235  				r.Query.Origin.Number += r.Query.Skip + 1
   236  			}
   237  			first = false
   238  		}
   239  		return p.replyBlockHeaders(r.ReqID, headers)
   240  	}, r.ReqID, r.Query.Amount, nil
   241  }
   242  
   243  // handleGetBlockBodies handles a block body request
   244  func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   245  	var r GetBlockBodiesPacket
   246  	if err := msg.Decode(&r); err != nil {
   247  		return nil, 0, 0, err
   248  	}
   249  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   250  		var (
   251  			bytes  int
   252  			bodies []rlp.RawValue
   253  		)
   254  		bc := backend.BlockChain()
   255  		for i, hash := range r.Hashes {
   256  			if i != 0 && !waitOrStop() {
   257  				return nil
   258  			}
   259  			if bytes >= softResponseLimit {
   260  				break
   261  			}
   262  			body := bc.GetBodyRLP(hash)
   263  			if body == nil {
   264  				p.bumpInvalid()
   265  				continue
   266  			}
   267  			bodies = append(bodies, body)
   268  			bytes += len(body)
   269  		}
   270  		return p.replyBlockBodiesRLP(r.ReqID, bodies)
   271  	}, r.ReqID, uint64(len(r.Hashes)), nil
   272  }
   273  
   274  // handleGetCode handles a contract code request
   275  func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   276  	var r GetCodePacket
   277  	if err := msg.Decode(&r); err != nil {
   278  		return nil, 0, 0, err
   279  	}
   280  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   281  		var (
   282  			bytes int
   283  			data  [][]byte
   284  		)
   285  		bc := backend.BlockChain()
   286  		for i, request := range r.Reqs {
   287  			if i != 0 && !waitOrStop() {
   288  				return nil
   289  			}
   290  			// Look up the root hash belonging to the request
   291  			header := bc.GetHeaderByHash(request.BHash)
   292  			if header == nil {
   293  				p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash)
   294  				p.bumpInvalid()
   295  				continue
   296  			}
   297  			// Refuse to search stale state data in the database since looking for
   298  			// a non-exist key is kind of expensive.
   299  			local := bc.CurrentHeader().Number.Uint64()
   300  			if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
   301  				p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local)
   302  				p.bumpInvalid()
   303  				continue
   304  			}
   305  			triedb := bc.StateCache().TrieDB()
   306  
   307  			account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey))
   308  			if err != nil {
   309  				p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
   310  				p.bumpInvalid()
   311  				continue
   312  			}
   313  			code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash))
   314  			if err != nil {
   315  				p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err)
   316  				continue
   317  			}
   318  			// Accumulate the code and abort if enough data was retrieved
   319  			data = append(data, code)
   320  			if bytes += len(code); bytes >= softResponseLimit {
   321  				break
   322  			}
   323  		}
   324  		return p.replyCode(r.ReqID, data)
   325  	}, r.ReqID, uint64(len(r.Reqs)), nil
   326  }
   327  
   328  // handleGetReceipts handles a block receipts request
   329  func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   330  	var r GetReceiptsPacket
   331  	if err := msg.Decode(&r); err != nil {
   332  		return nil, 0, 0, err
   333  	}
   334  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   335  		var (
   336  			bytes    int
   337  			receipts []rlp.RawValue
   338  		)
   339  		bc := backend.BlockChain()
   340  		for i, hash := range r.Hashes {
   341  			if i != 0 && !waitOrStop() {
   342  				return nil
   343  			}
   344  			if bytes >= softResponseLimit {
   345  				break
   346  			}
   347  			// Retrieve the requested block's receipts, skipping if unknown to us
   348  			results := bc.GetReceiptsByHash(hash)
   349  			if results == nil {
   350  				if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
   351  					p.bumpInvalid()
   352  					continue
   353  				}
   354  			}
   355  			// If known, encode and queue for response packet
   356  			if encoded, err := rlp.EncodeToBytes(results); err != nil {
   357  				log.Error("Failed to encode receipt", "err", err)
   358  			} else {
   359  				receipts = append(receipts, encoded)
   360  				bytes += len(encoded)
   361  			}
   362  		}
   363  		return p.replyReceiptsRLP(r.ReqID, receipts)
   364  	}, r.ReqID, uint64(len(r.Hashes)), nil
   365  }
   366  
   367  // handleGetProofs handles a proof request
   368  func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   369  	var r GetProofsPacket
   370  	if err := msg.Decode(&r); err != nil {
   371  		return nil, 0, 0, err
   372  	}
   373  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   374  		var (
   375  			lastBHash common.Hash
   376  			root      common.Hash
   377  			header    *types.Header
   378  			err       error
   379  		)
   380  		bc := backend.BlockChain()
   381  		nodes := light.NewNodeSet()
   382  
   383  		for i, request := range r.Reqs {
   384  			if i != 0 && !waitOrStop() {
   385  				return nil
   386  			}
   387  			// Look up the root hash belonging to the request
   388  			if request.BHash != lastBHash {
   389  				root, lastBHash = common.Hash{}, request.BHash
   390  
   391  				if header = bc.GetHeaderByHash(request.BHash); header == nil {
   392  					p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash)
   393  					p.bumpInvalid()
   394  					continue
   395  				}
   396  				// Refuse to search stale state data in the database since looking for
   397  				// a non-exist key is kind of expensive.
   398  				local := bc.CurrentHeader().Number.Uint64()
   399  				if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
   400  					p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
   401  					p.bumpInvalid()
   402  					continue
   403  				}
   404  				root = header.Root
   405  			}
   406  			// If a header lookup failed (non existent), ignore subsequent requests for the same header
   407  			if root == (common.Hash{}) {
   408  				p.bumpInvalid()
   409  				continue
   410  			}
   411  			// Open the account or storage trie for the request
   412  			statedb := bc.StateCache()
   413  
   414  			var trie state.Trie
   415  			switch len(request.AccKey) {
   416  			case 0:
   417  				// No account key specified, open an account trie
   418  				trie, err = statedb.OpenTrie(root)
   419  				if trie == nil || err != nil {
   420  					p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
   421  					continue
   422  				}
   423  			default:
   424  				// Account key specified, open a storage trie
   425  				account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey))
   426  				if err != nil {
   427  					p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
   428  					p.bumpInvalid()
   429  					continue
   430  				}
   431  				trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root)
   432  				if trie == nil || err != nil {
   433  					p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err)
   434  					continue
   435  				}
   436  			}
   437  			// Prove the user's request from the account or stroage trie
   438  			if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil {
   439  				p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
   440  				continue
   441  			}
   442  			if nodes.DataSize() >= softResponseLimit {
   443  				break
   444  			}
   445  		}
   446  		return p.replyProofsV2(r.ReqID, nodes.NodeList())
   447  	}, r.ReqID, uint64(len(r.Reqs)), nil
   448  }
   449  
   450  // handleGetHelperTrieProofs handles a helper trie proof request
   451  func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   452  	var r GetHelperTrieProofsPacket
   453  	if err := msg.Decode(&r); err != nil {
   454  		return nil, 0, 0, err
   455  	}
   456  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   457  		var (
   458  			lastIdx  uint64
   459  			lastType uint
   460  			auxTrie  *trie.Trie
   461  			auxBytes int
   462  			auxData  [][]byte
   463  		)
   464  		bc := backend.BlockChain()
   465  		nodes := light.NewNodeSet()
   466  		for i, request := range r.Reqs {
   467  			if i != 0 && !waitOrStop() {
   468  				return nil
   469  			}
   470  			if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
   471  				lastType, lastIdx = request.Type, request.TrieIdx
   472  				auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx)
   473  			}
   474  			if auxTrie == nil {
   475  				return nil
   476  			}
   477  			// TODO(rjl493456442) short circuit if the proving is failed.
   478  			// The original client side code has a dirty hack to retrieve
   479  			// the headers with no valid proof. Keep the compatibility for
   480  			// legacy les protocol and drop this hack when the les2/3 are
   481  			// not supported.
   482  			err := auxTrie.Prove(request.Key, request.FromLevel, nodes)
   483  			if p.version >= lpv4 && err != nil {
   484  				return nil
   485  			}
   486  			if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 {
   487  				header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key))
   488  				data, err := rlp.EncodeToBytes(header)
   489  				if err != nil {
   490  					log.Error("Failed to encode header", "err", err)
   491  					return nil
   492  				}
   493  				auxData = append(auxData, data)
   494  				auxBytes += len(data)
   495  			}
   496  			if nodes.DataSize()+auxBytes >= softResponseLimit {
   497  				break
   498  			}
   499  		}
   500  		return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData})
   501  	}, r.ReqID, uint64(len(r.Reqs)), nil
   502  }
   503  
   504  // handleSendTx handles a transaction propagation request
   505  func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   506  	var r SendTxPacket
   507  	if err := msg.Decode(&r); err != nil {
   508  		return nil, 0, 0, err
   509  	}
   510  	amount := uint64(len(r.Txs))
   511  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   512  		stats := make([]light.TxStatus, len(r.Txs))
   513  		for i, tx := range r.Txs {
   514  			if i != 0 && !waitOrStop() {
   515  				return nil
   516  			}
   517  			hash := tx.Hash()
   518  			stats[i] = txStatus(backend, hash)
   519  			if stats[i].Status == core.TxStatusUnknown {
   520  				addFn := backend.TxPool().AddRemotes
   521  				// Add txs synchronously for testing purpose
   522  				if backend.AddTxsSync() {
   523  					addFn = backend.TxPool().AddRemotesSync
   524  				}
   525  				if errs := addFn([]*types.Transaction{tx}); errs[0] != nil {
   526  					stats[i].Error = errs[0].Error()
   527  					continue
   528  				}
   529  				stats[i] = txStatus(backend, hash)
   530  			}
   531  		}
   532  		return p.replyTxStatus(r.ReqID, stats)
   533  	}, r.ReqID, amount, nil
   534  }
   535  
   536  // handleGetTxStatus handles a transaction status query
   537  func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) {
   538  	var r GetTxStatusPacket
   539  	if err := msg.Decode(&r); err != nil {
   540  		return nil, 0, 0, err
   541  	}
   542  	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
   543  		stats := make([]light.TxStatus, len(r.Hashes))
   544  		for i, hash := range r.Hashes {
   545  			if i != 0 && !waitOrStop() {
   546  				return nil
   547  			}
   548  			stats[i] = txStatus(backend, hash)
   549  		}
   550  		return p.replyTxStatus(r.ReqID, stats)
   551  	}, r.ReqID, uint64(len(r.Hashes)), nil
   552  }
   553  
   554  // txStatus returns the status of a specified transaction.
   555  func txStatus(b serverBackend, hash common.Hash) light.TxStatus {
   556  	var stat light.TxStatus
   557  	// Looking the transaction in txpool first.
   558  	stat.Status = b.TxPool().Status([]common.Hash{hash})[0]
   559  
   560  	// If the transaction is unknown to the pool, try looking it up locally.
   561  	if stat.Status == core.TxStatusUnknown {
   562  		lookup := b.BlockChain().GetTransactionLookup(hash)
   563  		if lookup != nil {
   564  			stat.Status = core.TxStatusIncluded
   565  			stat.Lookup = lookup
   566  		}
   567  	}
   568  	return stat
   569  }