123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- // Copyright 2021 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 <http://www.gnu.org/licenses/>.
- package les
- import (
- "encoding/binary"
- "encoding/json"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- )
- // serverBackend defines the backend functions needed for serving LES requests
- type serverBackend interface {
- ArchiveMode() bool
- AddTxsSync() bool
- BlockChain() *core.BlockChain
- TxPool() *core.TxPool
- GetHelperTrie(typ uint, index uint64) *trie.Trie
- }
- // Decoder is implemented by the messages passed to the handler functions
- type Decoder interface {
- Decode(val interface{}) error
- }
- // RequestType is a static struct that describes an LES request type and references
- // its handler function.
- type RequestType struct {
- Name string
- MaxCount uint64
- InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter
- ServingTimeMeter metrics.Timer
- Handle func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error)
- }
- // serveRequestFn is returned by the request handler functions after decoding the request.
- // This function does the actual request serving using the supplied backend. waitOrStop is
- // called between serving individual request items and may block if the serving process
- // needs to be throttled. If it returns false then the process is terminated.
- // The reply is not sent by this function yet. The flow control feedback value is supplied
- // by the protocol handler when calling the send function of the returned reply struct.
- type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply
- // Les3 contains the request types supported by les/2 and les/3
- var Les3 = map[uint64]RequestType{
- GetBlockHeadersMsg: {
- Name: "block header request",
- MaxCount: MaxHeaderFetch,
- InPacketsMeter: miscInHeaderPacketsMeter,
- InTrafficMeter: miscInHeaderTrafficMeter,
- OutPacketsMeter: miscOutHeaderPacketsMeter,
- OutTrafficMeter: miscOutHeaderTrafficMeter,
- ServingTimeMeter: miscServingTimeHeaderTimer,
- Handle: handleGetBlockHeaders,
- },
- GetBlockBodiesMsg: {
- Name: "block bodies request",
- MaxCount: MaxBodyFetch,
- InPacketsMeter: miscInBodyPacketsMeter,
- InTrafficMeter: miscInBodyTrafficMeter,
- OutPacketsMeter: miscOutBodyPacketsMeter,
- OutTrafficMeter: miscOutBodyTrafficMeter,
- ServingTimeMeter: miscServingTimeBodyTimer,
- Handle: handleGetBlockBodies,
- },
- GetCodeMsg: {
- Name: "code request",
- MaxCount: MaxCodeFetch,
- InPacketsMeter: miscInCodePacketsMeter,
- InTrafficMeter: miscInCodeTrafficMeter,
- OutPacketsMeter: miscOutCodePacketsMeter,
- OutTrafficMeter: miscOutCodeTrafficMeter,
- ServingTimeMeter: miscServingTimeCodeTimer,
- Handle: handleGetCode,
- },
- GetReceiptsMsg: {
- Name: "receipts request",
- MaxCount: MaxReceiptFetch,
- InPacketsMeter: miscInReceiptPacketsMeter,
- InTrafficMeter: miscInReceiptTrafficMeter,
- OutPacketsMeter: miscOutReceiptPacketsMeter,
- OutTrafficMeter: miscOutReceiptTrafficMeter,
- ServingTimeMeter: miscServingTimeReceiptTimer,
- Handle: handleGetReceipts,
- },
- GetProofsV2Msg: {
- Name: "les/2 proofs request",
- MaxCount: MaxProofsFetch,
- InPacketsMeter: miscInTrieProofPacketsMeter,
- InTrafficMeter: miscInTrieProofTrafficMeter,
- OutPacketsMeter: miscOutTrieProofPacketsMeter,
- OutTrafficMeter: miscOutTrieProofTrafficMeter,
- ServingTimeMeter: miscServingTimeTrieProofTimer,
- Handle: handleGetProofs,
- },
- GetHelperTrieProofsMsg: {
- Name: "helper trie proof request",
- MaxCount: MaxHelperTrieProofsFetch,
- InPacketsMeter: miscInHelperTriePacketsMeter,
- InTrafficMeter: miscInHelperTrieTrafficMeter,
- OutPacketsMeter: miscOutHelperTriePacketsMeter,
- OutTrafficMeter: miscOutHelperTrieTrafficMeter,
- ServingTimeMeter: miscServingTimeHelperTrieTimer,
- Handle: handleGetHelperTrieProofs,
- },
- SendTxV2Msg: {
- Name: "new transactions",
- MaxCount: MaxTxSend,
- InPacketsMeter: miscInTxsPacketsMeter,
- InTrafficMeter: miscInTxsTrafficMeter,
- OutPacketsMeter: miscOutTxsPacketsMeter,
- OutTrafficMeter: miscOutTxsTrafficMeter,
- ServingTimeMeter: miscServingTimeTxTimer,
- Handle: handleSendTx,
- },
- GetTxStatusMsg: {
- Name: "transaction status query request",
- MaxCount: MaxTxStatus,
- InPacketsMeter: miscInTxStatusPacketsMeter,
- InTrafficMeter: miscInTxStatusTrafficMeter,
- OutPacketsMeter: miscOutTxStatusPacketsMeter,
- OutTrafficMeter: miscOutTxStatusTrafficMeter,
- ServingTimeMeter: miscServingTimeTxStatusTimer,
- Handle: handleGetTxStatus,
- },
- }
- // handleGetBlockHeaders handles a block header request
- func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetBlockHeadersPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- // Gather headers until the fetch or network limits is reached
- var (
- bc = backend.BlockChain()
- hashMode = r.Query.Origin.Hash != (common.Hash{})
- first = true
- maxNonCanonical = uint64(100)
- bytes common.StorageSize
- headers []*types.Header
- unknown bool
- )
- for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit {
- if !first && !waitOrStop() {
- return nil
- }
- // Retrieve the next header satisfying the r
- var origin *types.Header
- if hashMode {
- if first {
- origin = bc.GetHeaderByHash(r.Query.Origin.Hash)
- if origin != nil {
- r.Query.Origin.Number = origin.Number.Uint64()
- }
- } else {
- origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number)
- }
- } else {
- origin = bc.GetHeaderByNumber(r.Query.Origin.Number)
- }
- if origin == nil {
- break
- }
- headers = append(headers, origin)
- bytes += estHeaderRlpSize
- // Advance to the next header of the r
- switch {
- case hashMode && r.Query.Reverse:
- // Hash based traversal towards the genesis block
- ancestor := r.Query.Skip + 1
- if ancestor == 0 {
- unknown = true
- } else {
- r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical)
- unknown = r.Query.Origin.Hash == common.Hash{}
- }
- case hashMode && !r.Query.Reverse:
- // Hash based traversal towards the leaf block
- var (
- current = origin.Number.Uint64()
- next = current + r.Query.Skip + 1
- )
- if next <= current {
- infos, _ := json.Marshal(p.Peer.Info())
- p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos))
- unknown = true
- } else {
- if header := bc.GetHeaderByNumber(next); header != nil {
- nextHash := header.Hash()
- expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical)
- if expOldHash == r.Query.Origin.Hash {
- r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next
- } else {
- unknown = true
- }
- } else {
- unknown = true
- }
- }
- case r.Query.Reverse:
- // Number based traversal towards the genesis block
- if r.Query.Origin.Number >= r.Query.Skip+1 {
- r.Query.Origin.Number -= r.Query.Skip + 1
- } else {
- unknown = true
- }
- case !r.Query.Reverse:
- // Number based traversal towards the leaf block
- r.Query.Origin.Number += r.Query.Skip + 1
- }
- first = false
- }
- return p.replyBlockHeaders(r.ReqID, headers)
- }, r.ReqID, r.Query.Amount, nil
- }
- // handleGetBlockBodies handles a block body request
- func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetBlockBodiesPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- bodies []rlp.RawValue
- )
- bc := backend.BlockChain()
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if bytes >= softResponseLimit {
- break
- }
- body := bc.GetBodyRLP(hash)
- if body == nil {
- p.bumpInvalid()
- continue
- }
- bodies = append(bodies, body)
- bytes += len(body)
- }
- return p.replyBlockBodiesRLP(r.ReqID, bodies)
- }, r.ReqID, uint64(len(r.Hashes)), nil
- }
- // handleGetCode handles a contract code request
- func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetCodePacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- data [][]byte
- )
- bc := backend.BlockChain()
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- // Look up the root hash belonging to the request
- header := bc.GetHeaderByHash(request.BHash)
- if header == nil {
- p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash)
- p.bumpInvalid()
- continue
- }
- // Refuse to search stale state data in the database since looking for
- // a non-exist key is kind of expensive.
- local := bc.CurrentHeader().Number.Uint64()
- if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
- p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local)
- p.bumpInvalid()
- continue
- }
- triedb := bc.StateCache().TrieDB()
- account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey))
- if err != nil {
- p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
- p.bumpInvalid()
- continue
- }
- code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash))
- if err != nil {
- 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)
- continue
- }
- // Accumulate the code and abort if enough data was retrieved
- data = append(data, code)
- if bytes += len(code); bytes >= softResponseLimit {
- break
- }
- }
- return p.replyCode(r.ReqID, data)
- }, r.ReqID, uint64(len(r.Reqs)), nil
- }
- // handleGetReceipts handles a block receipts request
- func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetReceiptsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- receipts []rlp.RawValue
- )
- bc := backend.BlockChain()
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if bytes >= softResponseLimit {
- break
- }
- // Retrieve the requested block's receipts, skipping if unknown to us
- results := bc.GetReceiptsByHash(hash)
- if results == nil {
- if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
- p.bumpInvalid()
- continue
- }
- }
- // If known, encode and queue for response packet
- if encoded, err := rlp.EncodeToBytes(results); err != nil {
- log.Error("Failed to encode receipt", "err", err)
- } else {
- receipts = append(receipts, encoded)
- bytes += len(encoded)
- }
- }
- return p.replyReceiptsRLP(r.ReqID, receipts)
- }, r.ReqID, uint64(len(r.Hashes)), nil
- }
- // handleGetProofs handles a proof request
- func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetProofsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- lastBHash common.Hash
- root common.Hash
- header *types.Header
- err error
- )
- bc := backend.BlockChain()
- nodes := light.NewNodeSet()
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- // Look up the root hash belonging to the request
- if request.BHash != lastBHash {
- root, lastBHash = common.Hash{}, request.BHash
- if header = bc.GetHeaderByHash(request.BHash); header == nil {
- p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash)
- p.bumpInvalid()
- continue
- }
- // Refuse to search stale state data in the database since looking for
- // a non-exist key is kind of expensive.
- local := bc.CurrentHeader().Number.Uint64()
- if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
- p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
- p.bumpInvalid()
- continue
- }
- root = header.Root
- }
- // If a header lookup failed (non existent), ignore subsequent requests for the same header
- if root == (common.Hash{}) {
- p.bumpInvalid()
- continue
- }
- // Open the account or storage trie for the request
- statedb := bc.StateCache()
- var trie state.Trie
- switch len(request.AccKey) {
- case 0:
- // No account key specified, open an account trie
- trie, err = statedb.OpenTrie(root)
- if trie == nil || err != nil {
- p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
- continue
- }
- default:
- // Account key specified, open a storage trie
- account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey))
- if err != nil {
- p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
- p.bumpInvalid()
- continue
- }
- trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root)
- if trie == nil || err != nil {
- 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)
- continue
- }
- }
- // Prove the user's request from the account or stroage trie
- if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil {
- p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
- continue
- }
- if nodes.DataSize() >= softResponseLimit {
- break
- }
- }
- return p.replyProofsV2(r.ReqID, nodes.NodeList())
- }, r.ReqID, uint64(len(r.Reqs)), nil
- }
- // handleGetHelperTrieProofs handles a helper trie proof request
- func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetHelperTrieProofsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- lastIdx uint64
- lastType uint
- auxTrie *trie.Trie
- auxBytes int
- auxData [][]byte
- )
- bc := backend.BlockChain()
- nodes := light.NewNodeSet()
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
- lastType, lastIdx = request.Type, request.TrieIdx
- auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx)
- }
- if auxTrie == nil {
- return nil
- }
- // TODO(rjl493456442) short circuit if the proving is failed.
- // The original client side code has a dirty hack to retrieve
- // the headers with no valid proof. Keep the compatibility for
- // legacy les protocol and drop this hack when the les2/3 are
- // not supported.
- err := auxTrie.Prove(request.Key, request.FromLevel, nodes)
- if p.version >= lpv4 && err != nil {
- return nil
- }
- if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 {
- header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key))
- data, err := rlp.EncodeToBytes(header)
- if err != nil {
- log.Error("Failed to encode header", "err", err)
- return nil
- }
- auxData = append(auxData, data)
- auxBytes += len(data)
- }
- if nodes.DataSize()+auxBytes >= softResponseLimit {
- break
- }
- }
- return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData})
- }, r.ReqID, uint64(len(r.Reqs)), nil
- }
- // handleSendTx handles a transaction propagation request
- func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r SendTxPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- amount := uint64(len(r.Txs))
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- stats := make([]light.TxStatus, len(r.Txs))
- for i, tx := range r.Txs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- hash := tx.Hash()
- stats[i] = txStatus(backend, hash)
- if stats[i].Status == core.TxStatusUnknown {
- addFn := backend.TxPool().AddRemotes
- // Add txs synchronously for testing purpose
- if backend.AddTxsSync() {
- addFn = backend.TxPool().AddRemotesSync
- }
- if errs := addFn([]*types.Transaction{tx}); errs[0] != nil {
- stats[i].Error = errs[0].Error()
- continue
- }
- stats[i] = txStatus(backend, hash)
- }
- }
- return p.replyTxStatus(r.ReqID, stats)
- }, r.ReqID, amount, nil
- }
- // handleGetTxStatus handles a transaction status query
- func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetTxStatusPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- stats := make([]light.TxStatus, len(r.Hashes))
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- stats[i] = txStatus(backend, hash)
- }
- return p.replyTxStatus(r.ReqID, stats)
- }, r.ReqID, uint64(len(r.Hashes)), nil
- }
- // txStatus returns the status of a specified transaction.
- func txStatus(b serverBackend, hash common.Hash) light.TxStatus {
- var stat light.TxStatus
- // Looking the transaction in txpool first.
- stat.Status = b.TxPool().Status([]common.Hash{hash})[0]
- // If the transaction is unknown to the pool, try looking it up locally.
- if stat.Status == core.TxStatusUnknown {
- lookup := b.BlockChain().GetTransactionLookup(hash)
- if lookup != nil {
- stat.Status = core.TxStatusIncluded
- stat.Lookup = lookup
- }
- }
- return stat
- }
|