// 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 . package snap import ( "fmt" "time" "github.com/ethereum/go-ethereum/core" "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" ) const ( // softResponseLimit is the target maximum size of replies to data retrievals. softResponseLimit = 2 * 1024 * 1024 // maxCodeLookups is the maximum number of bytecodes to serve. This number is // there to limit the number of disk lookups. maxCodeLookups = 1024 // stateLookupSlack defines the ratio by how much a state response can exceed // the requested limit in order to try and avoid breaking up contracts into // multiple packages and proving them. stateLookupSlack = 0.1 // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This // number is there to limit the number of disk lookups. maxTrieNodeLookups = 1024 // maxAccessListLookups is the maximum number of BALs to server. This number // is there to limit the number of disk lookups. maxAccessListLookups = 1024 // maxTrieNodeTimeSpent is the maximum time we should spend on looking up trie nodes. // If we spend too much time, then it's a fairly high chance of timing out // at the remote side, which means all the work is in vain. maxTrieNodeTimeSpent = 5 * time.Second ) // 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 // 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 `snap` 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 } // MakeProtocols constructs the P2P protocol definitions for `snap`. func MakeProtocols(backend Backend) []p2p.Protocol { protocols := make([]p2p.Protocol, len(ProtocolVersions)) for i, version := range ProtocolVersions { protocols[i] = p2p.Protocol{ Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error { defer peer.Close() return Handle(backend, peer) }) }, NodeInfo: func() interface{} { return nodeInfo(backend.Chain()) }, PeerInfo: func(id enode.ID) interface{} { return backend.PeerInfo(id) }, Attributes: []enr.Entry{&enrEntry{}}, } } return protocols } // Handle is the callback invoked to manage the life cycle of a `snap` peer. // When this function terminates, the peer is disconnected. func Handle(backend Backend, peer *Peer) error { for { if err := HandleMessage(backend, peer); err != nil { peer.Log().Debug("Message handling failed in `snap`", "err", err) return err } } } type msgHandler func(backend Backend, msg Decoder, peer *Peer) error type Decoder interface { Decode(val interface{}) error } var snap1 = map[uint64]msgHandler{ GetAccountRangeMsg: handleGetAccountRange, AccountRangeMsg: handleAccountRange, GetStorageRangesMsg: handleGetStorageRanges, StorageRangesMsg: handleStorageRanges, GetByteCodesMsg: handleGetByteCodes, ByteCodesMsg: handleByteCodes, GetTrieNodesMsg: handleGetTrienodes, TrieNodesMsg: handleTrieNodes, } // nolint:unused var snap2 = map[uint64]msgHandler{ GetAccountRangeMsg: handleGetAccountRange, AccountRangeMsg: handleAccountRange, GetStorageRangesMsg: handleGetStorageRanges, StorageRangesMsg: handleStorageRanges, GetByteCodesMsg: handleGetByteCodes, ByteCodesMsg: handleByteCodes, GetAccessListsMsg: handleGetAccessLists, // AccessListsMsg: TODO } // HandleMessage is invoked whenever an inbound message is received from a // remote peer on the `snap` protocol. 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 map[uint64]msgHandler switch peer.version { case SNAP1: handlers = snap1 //case SNAP2: // handlers = snap2 default: return fmt.Errorf("unknown eth protocol version: %v", peer.version) } // Track the amount of time it takes to serve the request and run the handler start := time.Now() 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()) }(start) } if handler := handlers[msg.Code]; handler != nil { return handler(backend, msg, peer) } return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } // NodeInfo represents a short summary of the `snap` sub-protocol metadata // known about the host peer. type NodeInfo struct{} // nodeInfo retrieves some `snap` protocol metadata about the running host node. func nodeInfo(chain *core.BlockChain) *NodeInfo { return &NodeInfo{} }