mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
This changes the p2p protocol handlers to delay message decoding. It's the first part of a larger change that will delay decoding all the way through message processing. For responses, we delay the decoding until it is confirmed that the response matches an active request and does not exceed its limits. In order to make this work, all messages have been changed to use rlp.RawList instead of a slice of the decoded item type. For block bodies specifically, the decoding has been delayed all the way until after verification of the response hash. The role of p2p/tracker.Tracker changes significantly in this PR. The Tracker's original purpose was to maintain metrics about requests and responses in the peer-to-peer protocols. Each protocol maintained a single global Tracker instance. As of this change, the Tracker is now always active (regardless of metrics collection), and there is a separate instance of it for each peer. Whenever a response arrives, it is first verified that a request exists for it in the tracker. The tracker is also the place where limits are kept.
240 lines
9.3 KiB
Go
240 lines
9.3 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 snap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
)
|
|
|
|
// Constants to match up protocol versions and messages
|
|
const (
|
|
SNAP1 = 1
|
|
)
|
|
|
|
// ProtocolName is the official short name of the `snap` protocol used during
|
|
// devp2p capability negotiation.
|
|
const ProtocolName = "snap"
|
|
|
|
// ProtocolVersions are the supported versions of the `snap` protocol (first
|
|
// is primary).
|
|
var ProtocolVersions = []uint{SNAP1}
|
|
|
|
// protocolLengths are the number of implemented message corresponding to
|
|
// different protocol versions.
|
|
var protocolLengths = map[uint]uint64{SNAP1: 8}
|
|
|
|
// maxMessageSize is the maximum cap on the size of a protocol message.
|
|
const maxMessageSize = 10 * 1024 * 1024
|
|
|
|
const (
|
|
GetAccountRangeMsg = 0x00
|
|
AccountRangeMsg = 0x01
|
|
GetStorageRangesMsg = 0x02
|
|
StorageRangesMsg = 0x03
|
|
GetByteCodesMsg = 0x04
|
|
ByteCodesMsg = 0x05
|
|
GetTrieNodesMsg = 0x06
|
|
TrieNodesMsg = 0x07
|
|
)
|
|
|
|
var (
|
|
errMsgTooLarge = errors.New("message too long")
|
|
errDecode = errors.New("invalid message")
|
|
errInvalidMsgCode = errors.New("invalid message code")
|
|
errBadRequest = errors.New("bad request")
|
|
)
|
|
|
|
// Packet represents a p2p message in the `snap` protocol.
|
|
type Packet interface {
|
|
Name() string // Name returns a string corresponding to the message type.
|
|
Kind() byte // Kind returns the message type.
|
|
}
|
|
|
|
// GetAccountRangePacket represents an account query.
|
|
type GetAccountRangePacket struct {
|
|
ID uint64 // Request ID to match up responses with
|
|
Root common.Hash // Root hash of the account trie to serve
|
|
Origin common.Hash // Hash of the first account to retrieve
|
|
Limit common.Hash // Hash of the last account to retrieve
|
|
Bytes uint64 // Soft limit at which to stop returning data
|
|
}
|
|
|
|
type accountRangeInput struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Accounts rlp.RawList[*AccountData] // List of consecutive accounts from the trie
|
|
Proof rlp.RawList[[]byte] // List of trie nodes proving the account range
|
|
}
|
|
|
|
// AccountRangePacket represents an account query response.
|
|
type AccountRangePacket struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Accounts []*AccountData // List of consecutive accounts from the trie
|
|
Proof [][]byte // List of trie nodes proving the account range
|
|
}
|
|
|
|
// AccountData represents a single account in a query response.
|
|
type AccountData struct {
|
|
Hash common.Hash // Hash of the account
|
|
Body rlp.RawValue // Account body in slim format
|
|
}
|
|
|
|
// Unpack retrieves the accounts from the range packet and converts from slim
|
|
// wire representation to consensus format. The returned data is RLP encoded
|
|
// since it's expected to be serialized to disk without further interpretation.
|
|
//
|
|
// Note, this method does a round of RLP decoding and reencoding, so only use it
|
|
// once and cache the results if need be. Ideally discard the packet afterwards
|
|
// to not double the memory use.
|
|
func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) {
|
|
var (
|
|
hashes = make([]common.Hash, len(p.Accounts))
|
|
accounts = make([][]byte, len(p.Accounts))
|
|
)
|
|
for i, acc := range p.Accounts {
|
|
val, err := types.FullAccountRLP(acc.Body)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err)
|
|
}
|
|
hashes[i], accounts[i] = acc.Hash, val
|
|
}
|
|
return hashes, accounts, nil
|
|
}
|
|
|
|
// GetStorageRangesPacket represents an storage slot query.
|
|
type GetStorageRangesPacket struct {
|
|
ID uint64 // Request ID to match up responses with
|
|
Root common.Hash // Root hash of the account trie to serve
|
|
Accounts []common.Hash // Account hashes of the storage tries to serve
|
|
Origin []byte // Hash of the first storage slot to retrieve (large contract mode)
|
|
Limit []byte // Hash of the last storage slot to retrieve (large contract mode)
|
|
Bytes uint64 // Soft limit at which to stop returning data
|
|
}
|
|
|
|
type storageRangesInput struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Slots rlp.RawList[[]*StorageData] // Lists of consecutive storage slots for the requested accounts
|
|
Proof rlp.RawList[[]byte] // Merkle proofs for the *last* slot range, if it's incomplete
|
|
}
|
|
|
|
// StorageRangesPacket represents a storage slot query response.
|
|
type StorageRangesPacket struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts
|
|
Proof [][]byte // Merkle proofs for the *last* slot range, if it's incomplete
|
|
}
|
|
|
|
// StorageData represents a single storage slot in a query response.
|
|
type StorageData struct {
|
|
Hash common.Hash // Hash of the storage slot
|
|
Body []byte // Data content of the slot
|
|
}
|
|
|
|
// Unpack retrieves the storage slots from the range packet and returns them in
|
|
// a split flat format that's more consistent with the internal data structures.
|
|
func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) {
|
|
var (
|
|
hashset = make([][]common.Hash, len(p.Slots))
|
|
slotset = make([][][]byte, len(p.Slots))
|
|
)
|
|
for i, slots := range p.Slots {
|
|
hashset[i] = make([]common.Hash, len(slots))
|
|
slotset[i] = make([][]byte, len(slots))
|
|
for j, slot := range slots {
|
|
hashset[i][j] = slot.Hash
|
|
slotset[i][j] = slot.Body
|
|
}
|
|
}
|
|
return hashset, slotset
|
|
}
|
|
|
|
// GetByteCodesPacket represents a contract bytecode query.
|
|
type GetByteCodesPacket struct {
|
|
ID uint64 // Request ID to match up responses with
|
|
Hashes []common.Hash // Code hashes to retrieve the code for
|
|
Bytes uint64 // Soft limit at which to stop returning data
|
|
}
|
|
|
|
type byteCodesInput struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Codes rlp.RawList[[]byte] // Requested contract bytecodes
|
|
}
|
|
|
|
// ByteCodesPacket represents a contract bytecode query response.
|
|
type ByteCodesPacket struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Codes [][]byte // Requested contract bytecodes
|
|
}
|
|
|
|
// GetTrieNodesPacket represents a state trie node query.
|
|
type GetTrieNodesPacket struct {
|
|
ID uint64 // Request ID to match up responses with
|
|
Root common.Hash // Root hash of the account trie to serve
|
|
Paths rlp.RawList[TrieNodePathSet] // Trie node hashes to retrieve the nodes for
|
|
Bytes uint64 // Soft limit at which to stop returning data
|
|
}
|
|
|
|
// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to
|
|
// represent trie nodes would be a simple list of `account || storage` path
|
|
// segments concatenated, but that would be very wasteful on the network.
|
|
//
|
|
// Instead, this array special cases the first element as the path in the
|
|
// account trie and the remaining elements as paths in the storage trie. To
|
|
// address an account node, the slice should have a length of 1 consisting
|
|
// of only the account path. There's no need to be able to address both an
|
|
// account node and a storage node in the same request as it cannot happen
|
|
// that a slot is accessed before the account path is fully expanded.
|
|
type TrieNodePathSet [][]byte
|
|
|
|
type trieNodesInput struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Nodes rlp.RawList[[]byte] // Requested state trie nodes
|
|
}
|
|
|
|
// TrieNodesPacket represents a state trie node query response.
|
|
type TrieNodesPacket struct {
|
|
ID uint64 // ID of the request this is a response for
|
|
Nodes [][]byte // Requested state trie nodes
|
|
}
|
|
|
|
func (*GetAccountRangePacket) Name() string { return "GetAccountRange" }
|
|
func (*GetAccountRangePacket) Kind() byte { return GetAccountRangeMsg }
|
|
|
|
func (*AccountRangePacket) Name() string { return "AccountRange" }
|
|
func (*AccountRangePacket) Kind() byte { return AccountRangeMsg }
|
|
|
|
func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" }
|
|
func (*GetStorageRangesPacket) Kind() byte { return GetStorageRangesMsg }
|
|
|
|
func (*StorageRangesPacket) Name() string { return "StorageRanges" }
|
|
func (*StorageRangesPacket) Kind() byte { return StorageRangesMsg }
|
|
|
|
func (*GetByteCodesPacket) Name() string { return "GetByteCodes" }
|
|
func (*GetByteCodesPacket) Kind() byte { return GetByteCodesMsg }
|
|
|
|
func (*ByteCodesPacket) Name() string { return "ByteCodes" }
|
|
func (*ByteCodesPacket) Kind() byte { return ByteCodesMsg }
|
|
|
|
func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" }
|
|
func (*GetTrieNodesPacket) Kind() byte { return GetTrieNodesMsg }
|
|
|
|
func (*TrieNodesPacket) Name() string { return "TrieNodes" }
|
|
func (*TrieNodesPacket) Kind() byte { return TrieNodesMsg }
|