db03faa10d
This PR reduces the amount of work we do when answering header queries, e.g. when a peer is syncing from us. For some items, e.g block bodies, when we read the rlp-data from database, we plug it directly into the response package. We didn't do that for headers, but instead read headers-rlp, decode to types.Header, and re-encode to rlp. This PR changes that to keep it in RLP-form as much as possible. When a node is syncing from us, it typically requests 192 contiguous headers. On master it has the following effect: - For headers not in ancient: 2 db lookups. One for translating hash->number (even though the request is by number), and another for reading by hash (this latter one is sometimes cached). - For headers in ancient: 1 file lookup/syscall for translating hash->number (even though the request is by number), and another for reading the header itself. After this, it also performes a hashing of the header, to ensure that the hash is what it expected. In this PR, I instead move the logic for "give me a sequence of blocks" into the lower layers, where the database can determine how and what to read from leveldb and/or ancients. There are basically four types of requests; three of them are improved this way. The fourth, by hash going backwards, is more tricky to optimize. However, since we know that the gap is 0, we can look up by the parentHash, and stlil shave off all the number->hash lookups. The gapped collection can be optimized similarly, as a follow-up, at least in three out of four cases. Co-authored-by: Felix Lange <fjl@twurst.com>
219 lines
8.0 KiB
Go
219 lines
8.0 KiB
Go
// 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 eth
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/metrics"
|
|
"github.com/ethereum/go-ethereum/p2p"
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
const (
|
|
// softResponseLimit is the target maximum size of replies to data retrievals.
|
|
softResponseLimit = 2 * 1024 * 1024
|
|
|
|
// maxHeadersServe is the maximum number of block headers to serve. This number
|
|
// is there to limit the number of disk lookups.
|
|
maxHeadersServe = 1024
|
|
|
|
// maxBodiesServe is the maximum number of block bodies to serve. This number
|
|
// is mostly there to limit the number of disk lookups. With 24KB block sizes
|
|
// nowadays, the practical limit will always be softResponseLimit.
|
|
maxBodiesServe = 1024
|
|
|
|
// maxNodeDataServe is the maximum number of state trie nodes to serve. This
|
|
// number is there to limit the number of disk lookups.
|
|
maxNodeDataServe = 1024
|
|
|
|
// maxReceiptsServe is the maximum number of block receipts to serve. This
|
|
// number is mostly there to limit the number of disk lookups. With block
|
|
// containing 200+ transactions nowadays, the practical limit will always
|
|
// be softResponseLimit.
|
|
maxReceiptsServe = 1024
|
|
)
|
|
|
|
// Handler is a callback to invoke from an outside runner after the boilerplate
|
|
// exchanges have passed.
|
|
type Handler func(peer *Peer) error
|
|
|
|
// Backend defines the data retrieval methods to serve remote requests and the
|
|
// callback methods to invoke on remote deliveries.
|
|
type Backend interface {
|
|
// Chain retrieves the blockchain object to serve data.
|
|
Chain() *core.BlockChain
|
|
|
|
// TxPool retrieves the transaction pool object to serve data.
|
|
TxPool() TxPool
|
|
|
|
// AcceptTxs retrieves whether transaction processing is enabled on the node
|
|
// or if inbound transactions should simply be dropped.
|
|
AcceptTxs() bool
|
|
|
|
// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
|
|
// should do any peer maintenance work, handshakes and validations. If all
|
|
// is passed, control should be given back to the `handler` to process the
|
|
// inbound messages going forward.
|
|
RunPeer(peer *Peer, handler Handler) error
|
|
|
|
// PeerInfo retrieves all known `eth` information about a peer.
|
|
PeerInfo(id enode.ID) interface{}
|
|
|
|
// Handle is a callback to be invoked when a data packet is received from
|
|
// the remote peer. Only packets not consumed by the protocol handler will
|
|
// be forwarded to the backend.
|
|
Handle(peer *Peer, packet Packet) error
|
|
}
|
|
|
|
// TxPool defines the methods needed by the protocol handler to serve transactions.
|
|
type TxPool interface {
|
|
// Get retrieves the transaction from the local txpool with the given hash.
|
|
Get(hash common.Hash) *types.Transaction
|
|
}
|
|
|
|
// MakeProtocols constructs the P2P protocol definitions for `eth`.
|
|
func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
|
|
protocols := make([]p2p.Protocol, len(ProtocolVersions))
|
|
for i, version := range ProtocolVersions {
|
|
version := version // Closure
|
|
|
|
protocols[i] = p2p.Protocol{
|
|
Name: ProtocolName,
|
|
Version: version,
|
|
Length: protocolLengths[version],
|
|
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
|
peer := NewPeer(version, p, rw, backend.TxPool())
|
|
defer peer.Close()
|
|
|
|
return backend.RunPeer(peer, func(peer *Peer) error {
|
|
return Handle(backend, peer)
|
|
})
|
|
},
|
|
NodeInfo: func() interface{} {
|
|
return nodeInfo(backend.Chain(), network)
|
|
},
|
|
PeerInfo: func(id enode.ID) interface{} {
|
|
return backend.PeerInfo(id)
|
|
},
|
|
Attributes: []enr.Entry{currentENREntry(backend.Chain())},
|
|
DialCandidates: dnsdisc,
|
|
}
|
|
}
|
|
return protocols
|
|
}
|
|
|
|
// NodeInfo represents a short summary of the `eth` sub-protocol metadata
|
|
// known about the host peer.
|
|
type NodeInfo struct {
|
|
Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
|
|
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
|
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
|
Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
|
|
Head common.Hash `json:"head"` // Hex hash of the host's best owned block
|
|
}
|
|
|
|
// nodeInfo retrieves some `eth` protocol metadata about the running host node.
|
|
func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo {
|
|
head := chain.CurrentBlock()
|
|
return &NodeInfo{
|
|
Network: network,
|
|
Difficulty: chain.GetTd(head.Hash(), head.NumberU64()),
|
|
Genesis: chain.Genesis().Hash(),
|
|
Config: chain.Config(),
|
|
Head: head.Hash(),
|
|
}
|
|
}
|
|
|
|
// Handle is invoked whenever an `eth` connection is made that successfully passes
|
|
// the protocol handshake. This method will keep processing messages until the
|
|
// connection is torn down.
|
|
func Handle(backend Backend, peer *Peer) error {
|
|
for {
|
|
if err := handleMessage(backend, peer); err != nil {
|
|
peer.Log().Debug("Message handling failed in `eth`", "err", err)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
type msgHandler func(backend Backend, msg Decoder, peer *Peer) error
|
|
type Decoder interface {
|
|
Decode(val interface{}) error
|
|
Time() time.Time
|
|
}
|
|
|
|
var eth66 = map[uint64]msgHandler{
|
|
NewBlockHashesMsg: handleNewBlockhashes,
|
|
NewBlockMsg: handleNewBlock,
|
|
TransactionsMsg: handleTransactions,
|
|
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
|
|
GetBlockHeadersMsg: handleGetBlockHeaders66,
|
|
BlockHeadersMsg: handleBlockHeaders66,
|
|
GetBlockBodiesMsg: handleGetBlockBodies66,
|
|
BlockBodiesMsg: handleBlockBodies66,
|
|
GetNodeDataMsg: handleGetNodeData66,
|
|
NodeDataMsg: handleNodeData66,
|
|
GetReceiptsMsg: handleGetReceipts66,
|
|
ReceiptsMsg: handleReceipts66,
|
|
GetPooledTransactionsMsg: handleGetPooledTransactions66,
|
|
PooledTransactionsMsg: handlePooledTransactions66,
|
|
}
|
|
|
|
// handleMessage is invoked whenever an inbound message is received from a remote
|
|
// peer. The remote connection is torn down upon returning any error.
|
|
func handleMessage(backend Backend, peer *Peer) error {
|
|
// Read the next message from the remote peer, and ensure it's fully consumed
|
|
msg, err := peer.rw.ReadMsg()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if msg.Size > maxMessageSize {
|
|
return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
|
|
}
|
|
defer msg.Discard()
|
|
|
|
var handlers = eth66
|
|
//if peer.Version() >= ETH67 { // Left in as a sample when new protocol is added
|
|
// handlers = eth67
|
|
//}
|
|
|
|
// Track the amount of time it takes to serve the request and run the handler
|
|
if metrics.Enabled {
|
|
h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code)
|
|
defer func(start time.Time) {
|
|
sampler := func() metrics.Sample {
|
|
return metrics.ResettingSample(
|
|
metrics.NewExpDecaySample(1028, 0.015),
|
|
)
|
|
}
|
|
metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds())
|
|
}(time.Now())
|
|
}
|
|
if handler := handlers[msg.Code]; handler != nil {
|
|
return handler(backend, msg, peer)
|
|
}
|
|
return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
|
|
}
|