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