mirror of https://github.com/ethereum/go-ethereum
tests/fuzzers/les: add fuzzer for les server handler (#22282)
* les: refactored server handler * tests/fuzzers/les: add fuzzer for les server handler * tests, les: update les fuzzer tests: update les fuzzer tests/fuzzer/les: release resources tests/fuzzer/les: pre-initialize all resources * les: refactored server handler and fuzzer Co-authored-by: rjl493456442 <garyrong0905@gmail.com>pull/22359/head
parent
8647233a8e
commit
8f03e3b107
@ -0,0 +1,569 @@ |
||||
// 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: RequestType{ |
||||
Name: "block header request", |
||||
MaxCount: MaxHeaderFetch, |
||||
InPacketsMeter: miscInHeaderPacketsMeter, |
||||
InTrafficMeter: miscInHeaderTrafficMeter, |
||||
OutPacketsMeter: miscOutHeaderPacketsMeter, |
||||
OutTrafficMeter: miscOutHeaderTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeHeaderTimer, |
||||
Handle: handleGetBlockHeaders, |
||||
}, |
||||
GetBlockBodiesMsg: RequestType{ |
||||
Name: "block bodies request", |
||||
MaxCount: MaxBodyFetch, |
||||
InPacketsMeter: miscInBodyPacketsMeter, |
||||
InTrafficMeter: miscInBodyTrafficMeter, |
||||
OutPacketsMeter: miscOutBodyPacketsMeter, |
||||
OutTrafficMeter: miscOutBodyTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeBodyTimer, |
||||
Handle: handleGetBlockBodies, |
||||
}, |
||||
GetCodeMsg: RequestType{ |
||||
Name: "code request", |
||||
MaxCount: MaxCodeFetch, |
||||
InPacketsMeter: miscInCodePacketsMeter, |
||||
InTrafficMeter: miscInCodeTrafficMeter, |
||||
OutPacketsMeter: miscOutCodePacketsMeter, |
||||
OutTrafficMeter: miscOutCodeTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeCodeTimer, |
||||
Handle: handleGetCode, |
||||
}, |
||||
GetReceiptsMsg: RequestType{ |
||||
Name: "receipts request", |
||||
MaxCount: MaxReceiptFetch, |
||||
InPacketsMeter: miscInReceiptPacketsMeter, |
||||
InTrafficMeter: miscInReceiptTrafficMeter, |
||||
OutPacketsMeter: miscOutReceiptPacketsMeter, |
||||
OutTrafficMeter: miscOutReceiptTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeReceiptTimer, |
||||
Handle: handleGetReceipts, |
||||
}, |
||||
GetProofsV2Msg: RequestType{ |
||||
Name: "les/2 proofs request", |
||||
MaxCount: MaxProofsFetch, |
||||
InPacketsMeter: miscInTrieProofPacketsMeter, |
||||
InTrafficMeter: miscInTrieProofTrafficMeter, |
||||
OutPacketsMeter: miscOutTrieProofPacketsMeter, |
||||
OutTrafficMeter: miscOutTrieProofTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeTrieProofTimer, |
||||
Handle: handleGetProofs, |
||||
}, |
||||
GetHelperTrieProofsMsg: RequestType{ |
||||
Name: "helper trie proof request", |
||||
MaxCount: MaxHelperTrieProofsFetch, |
||||
InPacketsMeter: miscInHelperTriePacketsMeter, |
||||
InTrafficMeter: miscInHelperTrieTrafficMeter, |
||||
OutPacketsMeter: miscOutHelperTriePacketsMeter, |
||||
OutTrafficMeter: miscOutHelperTrieTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeHelperTrieTimer, |
||||
Handle: handleGetHelperTrieProofs, |
||||
}, |
||||
SendTxV2Msg: RequestType{ |
||||
Name: "new transactions", |
||||
MaxCount: MaxTxSend, |
||||
InPacketsMeter: miscInTxsPacketsMeter, |
||||
InTrafficMeter: miscInTxsTrafficMeter, |
||||
OutPacketsMeter: miscOutTxsPacketsMeter, |
||||
OutTrafficMeter: miscOutTxsTrafficMeter, |
||||
ServingTimeMeter: miscServingTimeTxTimer, |
||||
Handle: handleSendTx, |
||||
}, |
||||
GetTxStatusMsg: RequestType{ |
||||
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.MarshalIndent(p.Peer.Info(), "", " ") |
||||
p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", 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 |
||||
} |
@ -0,0 +1,41 @@ |
||||
// Copyright 2020 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 main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
|
||||
"github.com/ethereum/go-ethereum/tests/fuzzers/les" |
||||
) |
||||
|
||||
func main() { |
||||
if len(os.Args) != 2 { |
||||
fmt.Fprintf(os.Stderr, "Usage: debug <file>\n") |
||||
fmt.Fprintf(os.Stderr, "Example\n") |
||||
fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") |
||||
os.Exit(1) |
||||
} |
||||
crasher := os.Args[1] |
||||
data, err := ioutil.ReadFile(crasher) |
||||
if err != nil { |
||||
fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) |
||||
os.Exit(1) |
||||
} |
||||
les.Fuzz(data) |
||||
} |
@ -0,0 +1,407 @@ |
||||
// 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 ( |
||||
"bytes" |
||||
"encoding/binary" |
||||
"io" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/consensus/ethash" |
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/rawdb" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
l "github.com/ethereum/go-ethereum/les" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/ethereum/go-ethereum/trie" |
||||
) |
||||
|
||||
var ( |
||||
bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey) |
||||
bankFunds = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)) |
||||
|
||||
testChainLen = 256 |
||||
testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") |
||||
|
||||
chain *core.BlockChain |
||||
addrHashes []common.Hash |
||||
txHashes []common.Hash |
||||
|
||||
chtTrie *trie.Trie |
||||
bloomTrie *trie.Trie |
||||
chtKeys [][]byte |
||||
bloomKeys [][]byte |
||||
) |
||||
|
||||
func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { |
||||
db := rawdb.NewMemoryDatabase() |
||||
gspec := core.Genesis{ |
||||
Config: params.TestChainConfig, |
||||
Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, |
||||
GasLimit: 100000000, |
||||
} |
||||
genesis := gspec.MustCommit(db) |
||||
signer := types.HomesteadSigner{} |
||||
blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, testChainLen, |
||||
func(i int, gen *core.BlockGen) { |
||||
var ( |
||||
tx *types.Transaction |
||||
addr common.Address |
||||
) |
||||
nonce := uint64(i) |
||||
if i%4 == 0 { |
||||
tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, bankKey) |
||||
addr = crypto.CreateAddress(bankAddr, nonce) |
||||
} else { |
||||
addr = common.BigToAddress(big.NewInt(int64(i))) |
||||
tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey) |
||||
} |
||||
gen.AddTx(tx) |
||||
addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:])) |
||||
txHashes = append(txHashes, tx.Hash()) |
||||
}) |
||||
bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) |
||||
if _, err := bc.InsertChain(blocks); err != nil { |
||||
panic(err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) { |
||||
chtTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) |
||||
bloomTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) |
||||
for i := 0; i < testChainLen; i++ { |
||||
// The element in CHT is <big-endian block number> -> <block hash>
|
||||
key := make([]byte, 8) |
||||
binary.BigEndian.PutUint64(key, uint64(i+1)) |
||||
chtTrie.Update(key, []byte{0x1, 0xf}) |
||||
chtKeys = append(chtKeys, key) |
||||
|
||||
// The element in Bloom trie is <2 byte bit index> + <big-endian block number> -> bloom
|
||||
key2 := make([]byte, 10) |
||||
binary.BigEndian.PutUint64(key2[2:], uint64(i+1)) |
||||
bloomTrie.Update(key2, []byte{0x2, 0xe}) |
||||
bloomKeys = append(bloomKeys, key2) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func init() { |
||||
chain, addrHashes, txHashes = makechain() |
||||
chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries() |
||||
} |
||||
|
||||
type fuzzer struct { |
||||
chain *core.BlockChain |
||||
pool *core.TxPool |
||||
|
||||
chainLen int |
||||
addr, txs []common.Hash |
||||
nonce uint64 |
||||
|
||||
chtKeys [][]byte |
||||
bloomKeys [][]byte |
||||
chtTrie *trie.Trie |
||||
bloomTrie *trie.Trie |
||||
|
||||
input io.Reader |
||||
exhausted bool |
||||
} |
||||
|
||||
func newFuzzer(input []byte) *fuzzer { |
||||
return &fuzzer{ |
||||
chain: chain, |
||||
chainLen: testChainLen, |
||||
addr: addrHashes, |
||||
txs: txHashes, |
||||
chtTrie: chtTrie, |
||||
bloomTrie: bloomTrie, |
||||
chtKeys: chtKeys, |
||||
bloomKeys: bloomKeys, |
||||
nonce: uint64(len(txHashes)), |
||||
pool: core.NewTxPool(core.DefaultTxPoolConfig, params.TestChainConfig, chain), |
||||
input: bytes.NewReader(input), |
||||
} |
||||
} |
||||
|
||||
func (f *fuzzer) read(size int) []byte { |
||||
out := make([]byte, size) |
||||
if _, err := f.input.Read(out); err != nil { |
||||
f.exhausted = true |
||||
} |
||||
return out |
||||
} |
||||
|
||||
func (f *fuzzer) randomByte() byte { |
||||
d := f.read(1) |
||||
return d[0] |
||||
} |
||||
|
||||
func (f *fuzzer) randomBool() bool { |
||||
d := f.read(1) |
||||
return d[0]&1 == 1 |
||||
} |
||||
|
||||
func (f *fuzzer) randomInt(max int) int { |
||||
if max == 0 { |
||||
return 0 |
||||
} |
||||
if max <= 256 { |
||||
return int(f.randomByte()) % max |
||||
} |
||||
var a uint16 |
||||
if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { |
||||
f.exhausted = true |
||||
} |
||||
return int(a % uint16(max)) |
||||
} |
||||
|
||||
func (f *fuzzer) randomX(max int) uint64 { |
||||
var a uint16 |
||||
if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { |
||||
f.exhausted = true |
||||
} |
||||
if a < 0x8000 { |
||||
return uint64(a%uint16(max+1)) - 1 |
||||
} |
||||
return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595) |
||||
} |
||||
|
||||
func (f *fuzzer) randomBlockHash() common.Hash { |
||||
h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen))) |
||||
if h != (common.Hash{}) { |
||||
return h |
||||
} |
||||
return common.BytesToHash(f.read(common.HashLength)) |
||||
} |
||||
|
||||
func (f *fuzzer) randomAddrHash() []byte { |
||||
i := f.randomInt(3 * len(f.addr)) |
||||
if i < len(f.addr) { |
||||
return f.addr[i].Bytes() |
||||
} |
||||
return f.read(common.HashLength) |
||||
} |
||||
|
||||
func (f *fuzzer) randomCHTTrieKey() []byte { |
||||
i := f.randomInt(3 * len(f.chtKeys)) |
||||
if i < len(f.chtKeys) { |
||||
return f.chtKeys[i] |
||||
} |
||||
return f.read(8) |
||||
} |
||||
|
||||
func (f *fuzzer) randomBloomTrieKey() []byte { |
||||
i := f.randomInt(3 * len(f.bloomKeys)) |
||||
if i < len(f.bloomKeys) { |
||||
return f.bloomKeys[i] |
||||
} |
||||
return f.read(10) |
||||
} |
||||
|
||||
func (f *fuzzer) randomTxHash() common.Hash { |
||||
i := f.randomInt(3 * len(f.txs)) |
||||
if i < len(f.txs) { |
||||
return f.txs[i] |
||||
} |
||||
return common.BytesToHash(f.read(common.HashLength)) |
||||
} |
||||
|
||||
func (f *fuzzer) BlockChain() *core.BlockChain { |
||||
return f.chain |
||||
} |
||||
|
||||
func (f *fuzzer) TxPool() *core.TxPool { |
||||
return f.pool |
||||
} |
||||
|
||||
func (f *fuzzer) ArchiveMode() bool { |
||||
return false |
||||
} |
||||
|
||||
func (f *fuzzer) AddTxsSync() bool { |
||||
return false |
||||
} |
||||
|
||||
func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie { |
||||
if typ == 0 { |
||||
return f.chtTrie |
||||
} else if typ == 1 { |
||||
return f.bloomTrie |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type dummyMsg struct { |
||||
data []byte |
||||
} |
||||
|
||||
func (d dummyMsg) Decode(val interface{}) error { |
||||
return rlp.DecodeBytes(d.data, val) |
||||
} |
||||
|
||||
func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { |
||||
version := f.randomInt(3) + 2 // [LES2, LES3, LES4]
|
||||
peer := l.NewFuzzerPeer(version) |
||||
enc, err := rlp.EncodeToBytes(packet) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
fn(f, peer, func() bool { return true }) |
||||
|
||||
} |
||||
|
||||
func Fuzz(input []byte) int { |
||||
// We expect some large inputs
|
||||
if len(input) < 100 { |
||||
return -1 |
||||
} |
||||
f := newFuzzer(input) |
||||
if f.exhausted { |
||||
return -1 |
||||
} |
||||
for !f.exhausted { |
||||
switch f.randomInt(8) { |
||||
case 0: |
||||
req := &l.GetBlockHeadersPacket{ |
||||
Query: l.GetBlockHeadersData{ |
||||
Amount: f.randomX(l.MaxHeaderFetch + 1), |
||||
Skip: f.randomX(10), |
||||
Reverse: f.randomBool(), |
||||
}, |
||||
} |
||||
if f.randomBool() { |
||||
req.Query.Origin.Hash = f.randomBlockHash() |
||||
} else { |
||||
req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2)) |
||||
} |
||||
f.doFuzz(l.GetBlockHeadersMsg, req) |
||||
|
||||
case 1: |
||||
req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))} |
||||
for i := range req.Hashes { |
||||
req.Hashes[i] = f.randomBlockHash() |
||||
} |
||||
f.doFuzz(l.GetBlockBodiesMsg, req) |
||||
|
||||
case 2: |
||||
req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))} |
||||
for i := range req.Reqs { |
||||
req.Reqs[i] = l.CodeReq{ |
||||
BHash: f.randomBlockHash(), |
||||
AccKey: f.randomAddrHash(), |
||||
} |
||||
} |
||||
f.doFuzz(l.GetCodeMsg, req) |
||||
|
||||
case 3: |
||||
req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))} |
||||
for i := range req.Hashes { |
||||
req.Hashes[i] = f.randomBlockHash() |
||||
} |
||||
f.doFuzz(l.GetReceiptsMsg, req) |
||||
|
||||
case 4: |
||||
req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))} |
||||
for i := range req.Reqs { |
||||
if f.randomBool() { |
||||
req.Reqs[i] = l.ProofReq{ |
||||
BHash: f.randomBlockHash(), |
||||
AccKey: f.randomAddrHash(), |
||||
Key: f.randomAddrHash(), |
||||
FromLevel: uint(f.randomX(3)), |
||||
} |
||||
} else { |
||||
req.Reqs[i] = l.ProofReq{ |
||||
BHash: f.randomBlockHash(), |
||||
Key: f.randomAddrHash(), |
||||
FromLevel: uint(f.randomX(3)), |
||||
} |
||||
} |
||||
} |
||||
f.doFuzz(l.GetProofsV2Msg, req) |
||||
|
||||
case 5: |
||||
req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))} |
||||
for i := range req.Reqs { |
||||
switch f.randomInt(3) { |
||||
case 0: |
||||
// Canonical hash trie
|
||||
req.Reqs[i] = l.HelperTrieReq{ |
||||
Type: 0, |
||||
TrieIdx: f.randomX(3), |
||||
Key: f.randomCHTTrieKey(), |
||||
FromLevel: uint(f.randomX(3)), |
||||
AuxReq: uint(2), |
||||
} |
||||
case 1: |
||||
// Bloom trie
|
||||
req.Reqs[i] = l.HelperTrieReq{ |
||||
Type: 1, |
||||
TrieIdx: f.randomX(3), |
||||
Key: f.randomBloomTrieKey(), |
||||
FromLevel: uint(f.randomX(3)), |
||||
AuxReq: 0, |
||||
} |
||||
default: |
||||
// Random trie
|
||||
req.Reqs[i] = l.HelperTrieReq{ |
||||
Type: 2, |
||||
TrieIdx: f.randomX(3), |
||||
Key: f.randomCHTTrieKey(), |
||||
FromLevel: uint(f.randomX(3)), |
||||
AuxReq: 0, |
||||
} |
||||
} |
||||
} |
||||
f.doFuzz(l.GetHelperTrieProofsMsg, req) |
||||
|
||||
case 6: |
||||
req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))} |
||||
signer := types.HomesteadSigner{} |
||||
for i := range req.Txs { |
||||
var nonce uint64 |
||||
if f.randomBool() { |
||||
nonce = uint64(f.randomByte()) |
||||
} else { |
||||
nonce = f.nonce |
||||
f.nonce += 1 |
||||
} |
||||
req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey) |
||||
} |
||||
f.doFuzz(l.SendTxV2Msg, req) |
||||
|
||||
case 7: |
||||
req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))} |
||||
for i := range req.Hashes { |
||||
req.Hashes[i] = f.randomTxHash() |
||||
} |
||||
f.doFuzz(l.GetTxStatusMsg, req) |
||||
} |
||||
} |
||||
return 0 |
||||
} |
Loading…
Reference in new issue