diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go
deleted file mode 100644
index 3045303f22..0000000000
--- a/eth/protocols/eth/broadcast.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// 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 eth
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-const (
- // This is the target size for the packs of transactions or announcements. A
- // pack can get larger than this if a single transactions exceeds this size.
- maxTxPacketSize = 100 * 1024
-)
-
-// blockPropagation is a block propagation event, waiting for its turn in the
-// broadcast queue.
-type blockPropagation struct {
- block *types.Block
- td *big.Int
-}
-
-// broadcastBlocks is a write loop that multiplexes blocks and block announcements
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *Peer) broadcastBlocks() {
- for {
- select {
- case prop := <-p.queuedBlocks:
- if err := p.SendNewBlock(prop.block, prop.td); err != nil {
- return
- }
- p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
-
- case block := <-p.queuedBlockAnns:
- if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
- return
- }
- p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
-
- case <-p.term:
- return
- }
- }
-}
-
-// broadcastTransactions is a write loop that schedules transaction broadcasts
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *Peer) broadcastTransactions() {
- var (
- queue []common.Hash // Queue of hashes to broadcast as full transactions
- done chan struct{} // Non-nil if background broadcaster is running
- fail = make(chan error, 1) // Channel used to receive network error
- failed bool // Flag whether a send failed, discard everything onward
- )
- for {
- // If there's no in-flight broadcast running, check if a new one is needed
- if done == nil && len(queue) > 0 {
- // Pile transaction until we reach our allowed network limit
- var (
- hashesCount uint64
- txs []*types.Transaction
- size common.StorageSize
- )
- for i := 0; i < len(queue) && size < maxTxPacketSize; i++ {
- if tx := p.txpool.Get(queue[i]); tx != nil {
- txs = append(txs, tx)
- size += common.StorageSize(tx.Size())
- }
- hashesCount++
- }
- queue = queue[:copy(queue, queue[hashesCount:])]
-
- // If there's anything available to transfer, fire up an async writer
- if len(txs) > 0 {
- done = make(chan struct{})
- go func() {
- if err := p.SendTransactions(txs); err != nil {
- fail <- err
- return
- }
- close(done)
- p.Log().Trace("Sent transactions", "count", len(txs))
- }()
- }
- }
- // Transfer goroutine may or may not have been started, listen for events
- select {
- case hashes := <-p.txBroadcast:
- // If the connection failed, discard all transaction events
- if failed {
- continue
- }
- // New batch of transactions to be broadcast, queue them (with cap)
- queue = append(queue, hashes...)
- if len(queue) > maxQueuedTxs {
- // Fancy copy and resize to ensure buffer doesn't grow indefinitely
- queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
- }
-
- case <-done:
- done = nil
-
- case <-fail:
- failed = true
-
- case <-p.term:
- return
- }
- }
-}
-
-// announceTransactions is a write loop that schedules transaction broadcasts
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *Peer) announceTransactions() {
- var (
- queue []common.Hash // Queue of hashes to announce as transaction stubs
- done chan struct{} // Non-nil if background announcer is running
- fail = make(chan error, 1) // Channel used to receive network error
- failed bool // Flag whether a send failed, discard everything onward
- )
- for {
- // If there's no in-flight announce running, check if a new one is needed
- if done == nil && len(queue) > 0 {
- // Pile transaction hashes until we reach our allowed network limit
- var (
- count int
- pending []common.Hash
- pendingTypes []byte
- pendingSizes []uint32
- size common.StorageSize
- )
- for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
- if tx := p.txpool.Get(queue[count]); tx != nil {
- pending = append(pending, queue[count])
- pendingTypes = append(pendingTypes, tx.Type())
- pendingSizes = append(pendingSizes, uint32(tx.Size()))
- size += common.HashLength
- }
- }
- // Shift and trim queue
- queue = queue[:copy(queue, queue[count:])]
-
- // If there's anything available to transfer, fire up an async writer
- if len(pending) > 0 {
- done = make(chan struct{})
- go func() {
- if p.version >= ETH68 {
- if err := p.sendPooledTransactionHashes68(pending, pendingTypes, pendingSizes); err != nil {
- fail <- err
- return
- }
- } else {
- if err := p.sendPooledTransactionHashes66(pending); err != nil {
- fail <- err
- return
- }
- }
- close(done)
- p.Log().Trace("Sent transaction announcements", "count", len(pending))
- }()
- }
- }
- // Transfer goroutine may or may not have been started, listen for events
- select {
- case hashes := <-p.txAnnounce:
- // If the connection failed, discard all transaction events
- if failed {
- continue
- }
- // New batch of transactions to be broadcast, queue them (with cap)
- queue = append(queue, hashes...)
- if len(queue) > maxQueuedTxAnns {
- // Fancy copy and resize to ensure buffer doesn't grow indefinitely
- queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])]
- }
-
- case <-done:
- done = nil
-
- case <-fail:
- failed = true
-
- case <-p.term:
- return
- }
- }
-}
diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go
deleted file mode 100644
index a7bdd47daf..0000000000
--- a/eth/protocols/eth/discovery.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// 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 eth
-
-import (
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// enrEntry is the ENR entry which advertises `eth` protocol on the discovery.
-type enrEntry struct {
- ForkID forkid.ID // Fork identifier per EIP-2124
-
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-// ENRKey implements enr.Entry.
-func (e enrEntry) ENRKey() string {
- return "eth"
-}
-
-// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain
-// head events and updates the requested node record whenever a fork is passed.
-func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) {
- var newHead = make(chan core.ChainHeadEvent, 10)
- sub := chain.SubscribeChainHeadEvent(newHead)
-
- go func() {
- defer sub.Unsubscribe()
- for {
- select {
- case <-newHead:
- ln.Set(currentENREntry(chain))
- case <-sub.Err():
- // Would be nice to sync with Stop, but there is no
- // good way to do that.
- return
- }
- }
- }()
-}
-
-// currentENREntry constructs an `eth` ENR entry based on the current state of the chain.
-func currentENREntry(chain *core.BlockChain) *enrEntry {
- head := chain.CurrentHeader()
- return &enrEntry{
- ForkID: forkid.NewID(chain.Config(), chain.Genesis(), head.Number.Uint64(), head.Time),
- }
-}
diff --git a/eth/protocols/eth/dispatcher.go b/eth/protocols/eth/dispatcher.go
deleted file mode 100644
index ae98820cd6..0000000000
--- a/eth/protocols/eth/dispatcher.go
+++ /dev/null
@@ -1,253 +0,0 @@
-// 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 .
-
-package eth
-
-import (
- "errors"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-var (
- // errDisconnected is returned if a request is attempted to be made to a peer
- // that was already closed.
- errDisconnected = errors.New("disconnected")
-
- // errDanglingResponse is returned if a response arrives with a request id
- // which does not match to any existing pending requests.
- errDanglingResponse = errors.New("response to non-existent request")
-
- // errMismatchingResponseType is returned if the remote peer sent a different
- // packet type as a response to a request than what the local node expected.
- errMismatchingResponseType = errors.New("mismatching response type")
-)
-
-// Request is a pending request to allow tracking it and delivering a response
-// back to the requester on their chosen channel.
-type Request struct {
- peer *Peer // Peer to which this request belongs for untracking
- id uint64 // Request ID to match up replies to
-
- sink chan *Response // Channel to deliver the response on
- cancel chan struct{} // Channel to cancel requests ahead of time
-
- code uint64 // Message code of the request packet
- want uint64 // Message code of the response packet
- data interface{} // Data content of the request packet
-
- Peer string // Demultiplexer if cross-peer requests are batched together
- Sent time.Time // Timestamp when the request was sent
-}
-
-// Close aborts an in-flight request. Although there's no way to notify the
-// remote peer about the cancellation, this method notifies the dispatcher to
-// discard any late responses.
-func (r *Request) Close() error {
- if r.peer == nil { // Tests mock out the dispatcher, skip internal cancellation
- return nil
- }
- cancelOp := &cancel{
- id: r.id,
- fail: make(chan error),
- }
- select {
- case r.peer.reqCancel <- cancelOp:
- if err := <-cancelOp.fail; err != nil {
- return err
- }
- close(r.cancel)
- return nil
- case <-r.peer.term:
- return errDisconnected
- }
-}
-
-// request is a wrapper around a client Request that has an error channel to
-// signal on if sending the request already failed on a network level.
-type request struct {
- req *Request
- fail chan error
-}
-
-// cancel is a maintenance type on the dispatcher to stop tracking a pending
-// request.
-type cancel struct {
- id uint64 // Request ID to stop tracking
- fail chan error
-}
-
-// Response is a reply packet to a previously created request. It is delivered
-// on the channel assigned by the requester subsystem and contains the original
-// request embedded to allow uniquely matching it caller side.
-type Response struct {
- id uint64 // Request ID to match up this reply to
- recv time.Time // Timestamp when the request was received
- code uint64 // Response packet type to cross validate with request
-
- Req *Request // Original request to cross-reference with
- Res interface{} // Remote response for the request query
- Meta interface{} // Metadata generated locally on the receiver thread
- Time time.Duration // Time it took for the request to be served
- Done chan error // Channel to signal message handling to the reader
-}
-
-// response is a wrapper around a remote Response that has an error channel to
-// signal on if processing the response failed.
-type response struct {
- res *Response
- fail chan error
-}
-
-// dispatchRequest schedules the request to the dispatcher for tracking and
-// network serialization, blocking until it's successfully sent.
-//
-// The returned Request must either be closed before discarding it, or the reply
-// must be waited for and the Response's Done channel signalled.
-func (p *Peer) dispatchRequest(req *Request) error {
- reqOp := &request{
- req: req,
- fail: make(chan error),
- }
- req.cancel = make(chan struct{})
- req.peer = p
- req.Peer = p.id
-
- select {
- case p.reqDispatch <- reqOp:
- return <-reqOp.fail
- case <-p.term:
- return errDisconnected
- }
-}
-
-// dispatchRequest fulfils a pending request and delivers it to the requested
-// sink.
-func (p *Peer) dispatchResponse(res *Response, metadata func() interface{}) error {
- resOp := &response{
- res: res,
- fail: make(chan error),
- }
- res.recv = time.Now()
- res.Done = make(chan error)
-
- select {
- case p.resDispatch <- resOp:
- // Ensure the response is accepted by the dispatcher
- if err := <-resOp.fail; err != nil {
- return nil
- }
- // Request was accepted, run any postprocessing step to generate metadata
- // on the receiver thread, not the sink thread
- if metadata != nil {
- res.Meta = metadata()
- }
- // Deliver the filled out response and wait until it's handled. This
- // path is a bit funky as Go's select has no order, so if a response
- // arrives to an already cancelled request, there's a 50-50% changes
- // of picking on channel or the other. To avoid such cases delivering
- // the packet upstream, check for cancellation first and only after
- // block on delivery.
- select {
- case <-res.Req.cancel:
- return nil // Request cancelled, silently discard response
- default:
- // Request not yet cancelled, attempt to deliver it, but do watch
- // for fresh cancellations too
- select {
- case res.Req.sink <- res:
- return <-res.Done // Response delivered, return any errors
- case <-res.Req.cancel:
- return nil // Request cancelled, silently discard response
- }
- }
-
- case <-p.term:
- return errDisconnected
- }
-}
-
-// dispatcher is a loop that accepts requests from higher layer packages, pushes
-// it to the network and tracks and dispatches the responses back to the original
-// requester.
-func (p *Peer) dispatcher() {
- pending := make(map[uint64]*Request)
-
- for {
- select {
- case reqOp := <-p.reqDispatch:
- req := reqOp.req
- req.Sent = time.Now()
-
- requestTracker.Track(p.id, p.version, req.code, req.want, req.id)
- err := p2p.Send(p.rw, req.code, req.data)
- reqOp.fail <- err
-
- if err == nil {
- pending[req.id] = req
- }
-
- case cancelOp := <-p.reqCancel:
- // Retrieve the pending request to cancel and short circuit if it
- // has already been serviced and is not available anymore
- req := pending[cancelOp.id]
- if req == nil {
- cancelOp.fail <- nil
- continue
- }
- // Stop tracking the request
- delete(pending, cancelOp.id)
- cancelOp.fail <- nil
-
- case resOp := <-p.resDispatch:
- res := resOp.res
- res.Req = pending[res.id]
-
- // Independent if the request exists or not, track this packet
- requestTracker.Fulfil(p.id, p.version, res.code, res.id)
-
- switch {
- case res.Req == nil:
- // Response arrived with an untracked ID. Since even cancelled
- // requests are tracked until fulfillment, a dangling response
- // means the remote peer implements the protocol badly.
- resOp.fail <- errDanglingResponse
-
- case res.Req.want != res.code:
- // Response arrived, but it's a different packet type than the
- // one expected by the requester. Either the local code is bad,
- // or the remote peer send junk. In neither cases can we handle
- // the packet.
- resOp.fail <- fmt.Errorf("%w: have %d, want %d", errMismatchingResponseType, res.code, res.Req.want)
-
- default:
- // All dispatcher checks passed and the response was initialized
- // with the matching request. Signal to the delivery routine that
- // it can wait for a handler response and dispatch the data.
- res.Time = res.recv.Sub(res.Req.Sent)
- resOp.fail <- nil
-
- // Stop tracking the request, the response dispatcher will deliver
- delete(pending, res.id)
- }
-
- case <-p.term:
- return
- }
- }
-}
diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go
deleted file mode 100644
index 42d0412a12..0000000000
--- a/eth/protocols/eth/handler.go
+++ /dev/null
@@ -1,232 +0,0 @@
-// 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 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
-
- // 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, 0, len(ProtocolVersions))
- for _, version := range ProtocolVersions {
- // Blob transactions require eth/68 announcements, disable everything else
- if version <= ETH67 && backend.Chain().Config().CancunTime != nil {
- continue
- }
- version := version // Closure
-
- protocols = append(protocols, 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=Mainnet, Goerli=5)
- 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()
- hash := head.Hash()
-
- return &NodeInfo{
- Network: network,
- Difficulty: chain.GetTd(hash, head.Number.Uint64()),
- Genesis: chain.Genesis().Hash(),
- Config: chain.Config(),
- 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 eth67 = map[uint64]msgHandler{
- NewBlockHashesMsg: handleNewBlockhashes,
- NewBlockMsg: handleNewBlock,
- TransactionsMsg: handleTransactions,
- NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes67,
- GetBlockHeadersMsg: handleGetBlockHeaders,
- BlockHeadersMsg: handleBlockHeaders,
- GetBlockBodiesMsg: handleGetBlockBodies,
- BlockBodiesMsg: handleBlockBodies,
- GetReceiptsMsg: handleGetReceipts,
- ReceiptsMsg: handleReceipts,
- GetPooledTransactionsMsg: handleGetPooledTransactions,
- PooledTransactionsMsg: handlePooledTransactions,
-}
-
-var eth68 = map[uint64]msgHandler{
- NewBlockHashesMsg: handleNewBlockhashes,
- NewBlockMsg: handleNewBlock,
- TransactionsMsg: handleTransactions,
- NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes68,
- GetBlockHeadersMsg: handleGetBlockHeaders,
- BlockHeadersMsg: handleBlockHeaders,
- GetBlockBodiesMsg: handleGetBlockBodies,
- BlockBodiesMsg: handleBlockBodies,
- GetReceiptsMsg: handleGetReceipts,
- ReceiptsMsg: handleReceipts,
- GetPooledTransactionsMsg: handleGetPooledTransactions,
- PooledTransactionsMsg: handlePooledTransactions,
-}
-
-// 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 = eth67
- if peer.Version() >= ETH68 {
- handlers = eth68
- }
- // 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)
-}
diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go
deleted file mode 100644
index 41e18bfb3e..0000000000
--- a/eth/protocols/eth/handler_test.go
+++ /dev/null
@@ -1,504 +0,0 @@
-// 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 eth
-
-import (
- "math"
- "math/big"
- "math/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/beacon"
- "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/txpool"
- "github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
-)
-
-var (
- // testKey is a private key to use for funding a tester account.
- testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-
- // testAddr is the Ethereum address of the tester account.
- testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
-)
-
-func u64(val uint64) *uint64 { return &val }
-
-// testBackend is a mock implementation of the live Ethereum message handler. Its
-// purpose is to allow testing the request/reply workflows and wire serialization
-// in the `eth` protocol without actually doing any data processing.
-type testBackend struct {
- db ethdb.Database
- chain *core.BlockChain
- txpool *txpool.TxPool
-}
-
-// newTestBackend creates an empty chain and wraps it into a mock backend.
-func newTestBackend(blocks int) *testBackend {
- return newTestBackendWithGenerator(blocks, false, nil)
-}
-
-// newTestBackend creates a chain with a number of explicitly defined blocks and
-// wraps it into a mock backend.
-func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, *core.BlockGen)) *testBackend {
- var (
- // Create a database pre-initialize with a genesis block
- db = rawdb.NewMemoryDatabase()
- config = params.TestChainConfig
- engine consensus.Engine = ethash.NewFaker()
- )
-
- if shanghai {
- config = ¶ms.ChainConfig{
- ChainID: big.NewInt(1),
- HomesteadBlock: big.NewInt(0),
- DAOForkBlock: nil,
- DAOForkSupport: true,
- EIP150Block: big.NewInt(0),
- EIP155Block: big.NewInt(0),
- EIP158Block: big.NewInt(0),
- ByzantiumBlock: big.NewInt(0),
- ConstantinopleBlock: big.NewInt(0),
- PetersburgBlock: big.NewInt(0),
- IstanbulBlock: big.NewInt(0),
- MuirGlacierBlock: big.NewInt(0),
- BerlinBlock: big.NewInt(0),
- LondonBlock: big.NewInt(0),
- ArrowGlacierBlock: big.NewInt(0),
- GrayGlacierBlock: big.NewInt(0),
- MergeNetsplitBlock: big.NewInt(0),
- ShanghaiTime: u64(0),
- TerminalTotalDifficulty: big.NewInt(0),
- TerminalTotalDifficultyPassed: true,
- Ethash: new(params.EthashConfig),
- }
- engine = beacon.NewFaker()
- }
-
- gspec := &core.Genesis{
- Config: config,
- Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}},
- }
- chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil)
-
- _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, blocks, generator)
- if _, err := chain.InsertChain(bs); err != nil {
- panic(err)
- }
- for _, block := range bs {
- chain.TrieDB().Commit(block.Root(), false)
- }
- txconfig := legacypool.DefaultConfig
- txconfig.Journal = "" // Don't litter the disk with test journals
-
- pool := legacypool.New(txconfig, chain)
- txpool, _ := txpool.New(new(big.Int).SetUint64(txconfig.PriceLimit), chain, []txpool.SubPool{pool})
-
- return &testBackend{
- db: db,
- chain: chain,
- txpool: txpool,
- }
-}
-
-// close tears down the transaction pool and chain behind the mock backend.
-func (b *testBackend) close() {
- b.txpool.Close()
- b.chain.Stop()
-}
-
-func (b *testBackend) Chain() *core.BlockChain { return b.chain }
-func (b *testBackend) TxPool() TxPool { return b.txpool }
-
-func (b *testBackend) RunPeer(peer *Peer, handler Handler) error {
- // Normally the backend would do peer maintenance and handshakes. All that
- // is omitted and we will just give control back to the handler.
- return handler(peer)
-}
-func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") }
-
-func (b *testBackend) AcceptTxs() bool {
- panic("data processing tests should be done in the handler package")
-}
-func (b *testBackend) Handle(*Peer, Packet) error {
- panic("data processing tests should be done in the handler package")
-}
-
-// Tests that block headers can be retrieved from a remote chain based on user queries.
-func TestGetBlockHeaders67(t *testing.T) { testGetBlockHeaders(t, ETH67) }
-func TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) }
-
-func testGetBlockHeaders(t *testing.T, protocol uint) {
- t.Parallel()
-
- backend := newTestBackend(maxHeadersServe + 15)
- defer backend.close()
-
- peer, _ := newTestPeer("peer", protocol, backend)
- defer peer.close()
-
- // Create a "random" unknown hash for testing
- var unknown common.Hash
- for i := range unknown {
- unknown[i] = byte(i)
- }
- getHashes := func(from, limit uint64) (hashes []common.Hash) {
- for i := uint64(0); i < limit; i++ {
- hashes = append(hashes, backend.chain.GetCanonicalHash(from-1-i))
- }
- return hashes
- }
- // Create a batch of tests for various scenarios
- limit := uint64(maxHeadersServe)
- tests := []struct {
- query *GetBlockHeadersRequest // The query to execute for header retrieval
- expect []common.Hash // The hashes of the block whose headers are expected
- }{
- // A single random block should be retrievable by hash
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
- []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
- },
- // A single random block should be retrievable by number
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 1},
- []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
- },
- // Multiple headers should be retrievable in both directions
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 3},
- []common.Hash{
- backend.chain.GetBlockByNumber(limit / 2).Hash(),
- backend.chain.GetBlockByNumber(limit/2 + 1).Hash(),
- backend.chain.GetBlockByNumber(limit/2 + 2).Hash(),
- },
- }, {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
- []common.Hash{
- backend.chain.GetBlockByNumber(limit / 2).Hash(),
- backend.chain.GetBlockByNumber(limit/2 - 1).Hash(),
- backend.chain.GetBlockByNumber(limit/2 - 2).Hash(),
- },
- },
- // Multiple headers with skip lists should be retrievable
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
- []common.Hash{
- backend.chain.GetBlockByNumber(limit / 2).Hash(),
- backend.chain.GetBlockByNumber(limit/2 + 4).Hash(),
- backend.chain.GetBlockByNumber(limit/2 + 8).Hash(),
- },
- }, {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
- []common.Hash{
- backend.chain.GetBlockByNumber(limit / 2).Hash(),
- backend.chain.GetBlockByNumber(limit/2 - 4).Hash(),
- backend.chain.GetBlockByNumber(limit/2 - 8).Hash(),
- },
- },
- // The chain endpoints should be retrievable
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 0}, Amount: 1},
- []common.Hash{backend.chain.GetBlockByNumber(0).Hash()},
- },
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 1},
- []common.Hash{backend.chain.CurrentBlock().Hash()},
- },
- { // If the peer requests a bit into the future, we deliver what we have
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 10},
- []common.Hash{backend.chain.CurrentBlock().Hash()},
- },
- // Ensure protocol limits are honored
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 1}, Amount: limit + 10, Reverse: true},
- getHashes(backend.chain.CurrentBlock().Number.Uint64(), limit),
- },
- // Check that requesting more than available is handled gracefully
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 3, Amount: 3},
- []common.Hash{
- backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 4).Hash(),
- backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()).Hash(),
- },
- }, {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
- []common.Hash{
- backend.chain.GetBlockByNumber(4).Hash(),
- backend.chain.GetBlockByNumber(0).Hash(),
- },
- },
- // Check that requesting more than available is handled gracefully, even if mid skip
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 2, Amount: 3},
- []common.Hash{
- backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 4).Hash(),
- backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 1).Hash(),
- },
- }, {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
- []common.Hash{
- backend.chain.GetBlockByNumber(4).Hash(),
- backend.chain.GetBlockByNumber(1).Hash(),
- },
- },
- // Check a corner case where requesting more can iterate past the endpoints
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true},
- []common.Hash{
- backend.chain.GetBlockByNumber(2).Hash(),
- backend.chain.GetBlockByNumber(1).Hash(),
- backend.chain.GetBlockByNumber(0).Hash(),
- },
- },
- // Check a corner case where skipping overflow loops back into the chain start
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1},
- []common.Hash{
- backend.chain.GetBlockByNumber(3).Hash(),
- },
- },
- // Check a corner case where skipping overflow loops back to the same header
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64},
- []common.Hash{
- backend.chain.GetBlockByNumber(1).Hash(),
- },
- },
- // Check that non existing headers aren't returned
- {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: unknown}, Amount: 1},
- []common.Hash{},
- }, {
- &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() + 1}, Amount: 1},
- []common.Hash{},
- },
- }
- // Run each of the tests and verify the results against the chain
- for i, tt := range tests {
- // Collect the headers to expect in the response
- var headers []*types.Header
- for _, hash := range tt.expect {
- headers = append(headers, backend.chain.GetBlockByHash(hash).Header())
- }
- // Send the hash request and verify the response
- p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket{
- RequestId: 123,
- GetBlockHeadersRequest: tt.query,
- })
- if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, &BlockHeadersPacket{
- RequestId: 123,
- BlockHeadersRequest: headers,
- }); err != nil {
- t.Errorf("test %d: headers mismatch: %v", i, err)
- }
- // If the test used number origins, repeat with hashes as the too
- if tt.query.Origin.Hash == (common.Hash{}) {
- if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
- tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
-
- p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket{
- RequestId: 456,
- GetBlockHeadersRequest: tt.query,
- })
- expected := &BlockHeadersPacket{RequestId: 456, BlockHeadersRequest: headers}
- if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, expected); err != nil {
- t.Errorf("test %d by hash: headers mismatch: %v", i, err)
- }
- }
- }
- }
-}
-
-// Tests that block contents can be retrieved from a remote chain based on their hashes.
-func TestGetBlockBodies67(t *testing.T) { testGetBlockBodies(t, ETH67) }
-func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) }
-
-func testGetBlockBodies(t *testing.T, protocol uint) {
- t.Parallel()
-
- gen := func(n int, g *core.BlockGen) {
- if n%2 == 0 {
- w := &types.Withdrawal{
- Address: common.Address{0xaa},
- Amount: 42,
- }
- g.AddWithdrawal(w)
- }
- }
-
- backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen)
- defer backend.close()
-
- peer, _ := newTestPeer("peer", protocol, backend)
- defer peer.close()
-
- // Create a batch of tests for various scenarios
- limit := maxBodiesServe
- tests := []struct {
- random int // Number of blocks to fetch randomly from the chain
- explicit []common.Hash // Explicitly requested blocks
- available []bool // Availability of explicitly requested blocks
- expected int // Total number of existing blocks to expect
- }{
- {1, nil, nil, 1}, // A single random block should be retrievable
- {10, nil, nil, 10}, // Multiple random blocks should be retrievable
- {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
- {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
- {0, []common.Hash{backend.chain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
- {0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
- {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
-
- // Existing and non-existing blocks interleaved should not cause problems
- {0, []common.Hash{
- {},
- backend.chain.GetBlockByNumber(1).Hash(),
- {},
- backend.chain.GetBlockByNumber(10).Hash(),
- {},
- backend.chain.GetBlockByNumber(100).Hash(),
- {},
- }, []bool{false, true, false, true, false, true, false}, 3},
- }
- // Run each of the tests and verify the results against the chain
- for i, tt := range tests {
- // Collect the hashes to request, and the response to expect
- var (
- hashes []common.Hash
- bodies []*BlockBody
- seen = make(map[int64]bool)
- )
- for j := 0; j < tt.random; j++ {
- for {
- num := rand.Int63n(int64(backend.chain.CurrentBlock().Number.Uint64()))
- if !seen[num] {
- seen[num] = true
-
- block := backend.chain.GetBlockByNumber(uint64(num))
- hashes = append(hashes, block.Hash())
- if len(bodies) < tt.expected {
- bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()})
- }
- break
- }
- }
- }
- for j, hash := range tt.explicit {
- hashes = append(hashes, hash)
- if tt.available[j] && len(bodies) < tt.expected {
- block := backend.chain.GetBlockByHash(hash)
- bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()})
- }
- }
-
- // Send the hash request and verify the response
- p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket{
- RequestId: 123,
- GetBlockBodiesRequest: hashes,
- })
- if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, &BlockBodiesPacket{
- RequestId: 123,
- BlockBodiesResponse: bodies,
- }); err != nil {
- t.Fatalf("test %d: bodies mismatch: %v", i, err)
- }
- }
-}
-
-// Tests that the transaction receipts can be retrieved based on hashes.
-func TestGetBlockReceipts67(t *testing.T) { testGetBlockReceipts(t, ETH67) }
-func TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) }
-
-func testGetBlockReceipts(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Define three accounts to simulate transactions with
- acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
- acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
- acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
- acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
-
- signer := types.HomesteadSigner{}
- // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
- generator := func(i int, block *core.BlockGen) {
- switch i {
- case 0:
- // In block 1, the test bank sends account #1 some ether.
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testKey)
- block.AddTx(tx)
- case 1:
- // In block 2, the test bank sends some more ether to account #1.
- // acc1Addr passes it on to account #2.
- tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testKey)
- tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key)
- block.AddTx(tx1)
- block.AddTx(tx2)
- case 2:
- // Block 3 is empty but was mined by account #2.
- block.SetCoinbase(acc2Addr)
- block.SetExtra([]byte("yeehaw"))
- case 3:
- // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
- b2 := block.PrevBlock(1).Header()
- b2.Extra = []byte("foo")
- block.AddUncle(b2)
- b3 := block.PrevBlock(2).Header()
- b3.Extra = []byte("foo")
- block.AddUncle(b3)
- }
- }
- // Assemble the test environment
- backend := newTestBackendWithGenerator(4, false, generator)
- defer backend.close()
-
- peer, _ := newTestPeer("peer", protocol, backend)
- defer peer.close()
-
- // Collect the hashes to request, and the response to expect
- var (
- hashes []common.Hash
- receipts [][]*types.Receipt
- )
- for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ {
- block := backend.chain.GetBlockByNumber(i)
-
- hashes = append(hashes, block.Hash())
- receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash()))
- }
- // Send the hash request and verify the response
- p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket{
- RequestId: 123,
- GetReceiptsRequest: hashes,
- })
- if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket{
- RequestId: 123,
- ReceiptsResponse: receipts,
- }); err != nil {
- t.Errorf("receipts mismatch: %v", err)
- }
-}
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
deleted file mode 100644
index 069e92dadf..0000000000
--- a/eth/protocols/eth/handlers.go
+++ /dev/null
@@ -1,501 +0,0 @@
-// 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 .
-
-package eth
-
-import (
- "encoding/json"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
- // Decode the complex header query
- var query GetBlockHeadersPacket
- if err := msg.Decode(&query); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersRequest, peer)
- return peer.ReplyBlockHeadersRLP(query.RequestId, response)
-}
-
-// ServiceGetBlockHeadersQuery assembles the response to a header query. It is
-// exposed to allow external packages to test protocol behavior.
-func ServiceGetBlockHeadersQuery(chain *core.BlockChain, query *GetBlockHeadersRequest, peer *Peer) []rlp.RawValue {
- if query.Skip == 0 {
- // The fast path: when the request is for a contiguous segment of headers.
- return serviceContiguousBlockHeaderQuery(chain, query)
- } else {
- return serviceNonContiguousBlockHeaderQuery(chain, query, peer)
- }
-}
-
-func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest, peer *Peer) []rlp.RawValue {
- hashMode := query.Origin.Hash != (common.Hash{})
- first := true
- maxNonCanonical := uint64(100)
-
- // Gather headers until the fetch or network limits is reached
- var (
- bytes common.StorageSize
- headers []rlp.RawValue
- unknown bool
- lookups int
- )
- for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit &&
- len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe {
- lookups++
- // Retrieve the next header satisfying the query
- var origin *types.Header
- if hashMode {
- if first {
- first = false
- origin = chain.GetHeaderByHash(query.Origin.Hash)
- if origin != nil {
- query.Origin.Number = origin.Number.Uint64()
- }
- } else {
- origin = chain.GetHeader(query.Origin.Hash, query.Origin.Number)
- }
- } else {
- origin = chain.GetHeaderByNumber(query.Origin.Number)
- }
- if origin == nil {
- break
- }
- if rlpData, err := rlp.EncodeToBytes(origin); err != nil {
- log.Crit("Unable to encode our own headers", "err", err)
- } else {
- headers = append(headers, rlp.RawValue(rlpData))
- bytes += common.StorageSize(len(rlpData))
- }
- // Advance to the next header of the query
- switch {
- case hashMode && query.Reverse:
- // Hash based traversal towards the genesis block
- ancestor := query.Skip + 1
- if ancestor == 0 {
- unknown = true
- } else {
- query.Origin.Hash, query.Origin.Number = chain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
- unknown = (query.Origin.Hash == common.Hash{})
- }
- case hashMode && !query.Reverse:
- // Hash based traversal towards the leaf block
- var (
- current = origin.Number.Uint64()
- next = current + query.Skip + 1
- )
- if next <= current {
- infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ")
- peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
- unknown = true
- } else {
- if header := chain.GetHeaderByNumber(next); header != nil {
- nextHash := header.Hash()
- expOldHash, _ := chain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
- if expOldHash == query.Origin.Hash {
- query.Origin.Hash, query.Origin.Number = nextHash, next
- } else {
- unknown = true
- }
- } else {
- unknown = true
- }
- }
- case query.Reverse:
- // Number based traversal towards the genesis block
- if query.Origin.Number >= query.Skip+1 {
- query.Origin.Number -= query.Skip + 1
- } else {
- unknown = true
- }
-
- case !query.Reverse:
- // Number based traversal towards the leaf block
- query.Origin.Number += query.Skip + 1
- }
- }
- return headers
-}
-
-func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest) []rlp.RawValue {
- count := query.Amount
- if count > maxHeadersServe {
- count = maxHeadersServe
- }
- if query.Origin.Hash == (common.Hash{}) {
- // Number mode, just return the canon chain segment. The backend
- // delivers in [N, N-1, N-2..] descending order, so we need to
- // accommodate for that.
- from := query.Origin.Number
- if !query.Reverse {
- from = from + count - 1
- }
- headers := chain.GetHeadersFrom(from, count)
- if !query.Reverse {
- for i, j := 0, len(headers)-1; i < j; i, j = i+1, j-1 {
- headers[i], headers[j] = headers[j], headers[i]
- }
- }
- return headers
- }
- // Hash mode.
- var (
- headers []rlp.RawValue
- hash = query.Origin.Hash
- header = chain.GetHeaderByHash(hash)
- )
- if header != nil {
- rlpData, _ := rlp.EncodeToBytes(header)
- headers = append(headers, rlpData)
- } else {
- // We don't even have the origin header
- return headers
- }
- num := header.Number.Uint64()
- if !query.Reverse {
- // Theoretically, we are tasked to deliver header by hash H, and onwards.
- // However, if H is not canon, we will be unable to deliver any descendants of
- // H.
- if canonHash := chain.GetCanonicalHash(num); canonHash != hash {
- // Not canon, we can't deliver descendants
- return headers
- }
- descendants := chain.GetHeadersFrom(num+count-1, count-1)
- for i, j := 0, len(descendants)-1; i < j; i, j = i+1, j-1 {
- descendants[i], descendants[j] = descendants[j], descendants[i]
- }
- headers = append(headers, descendants...)
- return headers
- }
- { // Last mode: deliver ancestors of H
- for i := uint64(1); header != nil && i < count; i++ {
- header = chain.GetHeaderByHash(header.ParentHash)
- if header == nil {
- break
- }
- rlpData, _ := rlp.EncodeToBytes(header)
- headers = append(headers, rlpData)
- }
- return headers
- }
-}
-
-func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
- // Decode the block body retrieval message
- var query GetBlockBodiesPacket
- if err := msg.Decode(&query); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesRequest)
- return peer.ReplyBlockBodiesRLP(query.RequestId, response)
-}
-
-// ServiceGetBlockBodiesQuery assembles the response to a body query. It is
-// exposed to allow external packages to test protocol behavior.
-func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequest) []rlp.RawValue {
- // Gather blocks until the fetch or network limits is reached
- var (
- bytes int
- bodies []rlp.RawValue
- )
- for lookups, hash := range query {
- if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe ||
- lookups >= 2*maxBodiesServe {
- break
- }
- if data := chain.GetBodyRLP(hash); len(data) != 0 {
- bodies = append(bodies, data)
- bytes += len(data)
- }
- }
- return bodies
-}
-
-func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error {
- // Decode the block receipts retrieval message
- var query GetReceiptsPacket
- if err := msg.Decode(&query); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsRequest)
- return peer.ReplyReceiptsRLP(query.RequestId, response)
-}
-
-// ServiceGetReceiptsQuery assembles the response to a receipt query. It is
-// exposed to allow external packages to test protocol behavior.
-func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue {
- // Gather state data until the fetch or network limits is reached
- var (
- bytes int
- receipts []rlp.RawValue
- )
- for lookups, hash := range query {
- if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe ||
- lookups >= 2*maxReceiptsServe {
- break
- }
- // Retrieve the requested block's receipts
- results := chain.GetReceiptsByHash(hash)
- if results == nil {
- if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
- 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 receipts
-}
-
-func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error {
- // A batch of new block announcements just arrived
- ann := new(NewBlockHashesPacket)
- if err := msg.Decode(ann); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Mark the hashes as present at the remote node
- for _, block := range *ann {
- peer.markBlock(block.Hash)
- }
- // Deliver them all to the backend for queuing
- return backend.Handle(peer, ann)
-}
-
-func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
- // Retrieve and decode the propagated block
- ann := new(NewBlockPacket)
- if err := msg.Decode(ann); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- if err := ann.sanityCheck(); err != nil {
- return err
- }
- if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() {
- log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash())
- return nil // TODO(karalabe): return error eventually, but wait a few releases
- }
- if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() {
- log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash())
- return nil // TODO(karalabe): return error eventually, but wait a few releases
- }
- ann.Block.ReceivedAt = msg.Time()
- ann.Block.ReceivedFrom = peer
-
- // Mark the peer as owning the block
- peer.markBlock(ann.Block.Hash())
-
- return backend.Handle(peer, ann)
-}
-
-func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
- // A batch of headers arrived to one of our previous requests
- res := new(BlockHeadersPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- metadata := func() interface{} {
- hashes := make([]common.Hash, len(res.BlockHeadersRequest))
- for i, header := range res.BlockHeadersRequest {
- hashes[i] = header.Hash()
- }
- return hashes
- }
- return peer.dispatchResponse(&Response{
- id: res.RequestId,
- code: BlockHeadersMsg,
- Res: &res.BlockHeadersRequest,
- }, metadata)
-}
-
-func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
- // A batch of block bodies arrived to one of our previous requests
- res := new(BlockBodiesPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- metadata := func() interface{} {
- var (
- txsHashes = make([]common.Hash, len(res.BlockBodiesResponse))
- uncleHashes = make([]common.Hash, len(res.BlockBodiesResponse))
- withdrawalHashes = make([]common.Hash, len(res.BlockBodiesResponse))
- )
- hasher := trie.NewStackTrie(nil)
- for i, body := range res.BlockBodiesResponse {
- txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher)
- uncleHashes[i] = types.CalcUncleHash(body.Uncles)
- if body.Withdrawals != nil {
- withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher)
- }
- }
- return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}
- }
- return peer.dispatchResponse(&Response{
- id: res.RequestId,
- code: BlockBodiesMsg,
- Res: &res.BlockBodiesResponse,
- }, metadata)
-}
-
-func handleReceipts(backend Backend, msg Decoder, peer *Peer) error {
- // A batch of receipts arrived to one of our previous requests
- res := new(ReceiptsPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- metadata := func() interface{} {
- hasher := trie.NewStackTrie(nil)
- hashes := make([]common.Hash, len(res.ReceiptsResponse))
- for i, receipt := range res.ReceiptsResponse {
- hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
- }
- return hashes
- }
- return peer.dispatchResponse(&Response{
- id: res.RequestId,
- code: ReceiptsMsg,
- Res: &res.ReceiptsResponse,
- }, metadata)
-}
-
-func handleNewPooledTransactionHashes67(backend Backend, msg Decoder, peer *Peer) error {
- // New transaction announcement arrived, make sure we have
- // a valid and fresh chain to handle them
- if !backend.AcceptTxs() {
- return nil
- }
- ann := new(NewPooledTransactionHashesPacket67)
- if err := msg.Decode(ann); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Schedule all the unknown hashes for retrieval
- for _, hash := range *ann {
- peer.markTransaction(hash)
- }
- return backend.Handle(peer, ann)
-}
-
-func handleNewPooledTransactionHashes68(backend Backend, msg Decoder, peer *Peer) error {
- // New transaction announcement arrived, make sure we have
- // a valid and fresh chain to handle them
- if !backend.AcceptTxs() {
- return nil
- }
- ann := new(NewPooledTransactionHashesPacket68)
- if err := msg.Decode(ann); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- if len(ann.Hashes) != len(ann.Types) || len(ann.Hashes) != len(ann.Sizes) {
- return fmt.Errorf("%w: message %v: invalid len of fields: %v %v %v", errDecode, msg, len(ann.Hashes), len(ann.Types), len(ann.Sizes))
- }
- // Schedule all the unknown hashes for retrieval
- for _, hash := range ann.Hashes {
- peer.markTransaction(hash)
- }
- return backend.Handle(peer, ann)
-}
-
-func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
- // Decode the pooled transactions retrieval message
- var query GetPooledTransactionsPacket
- if err := msg.Decode(&query); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsRequest)
- return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs)
-}
-
-func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsRequest) ([]common.Hash, []rlp.RawValue) {
- // Gather transactions until the fetch or network limits is reached
- var (
- bytes int
- hashes []common.Hash
- txs []rlp.RawValue
- )
- for _, hash := range query {
- if bytes >= softResponseLimit {
- break
- }
- // Retrieve the requested transaction, skipping if unknown to us
- tx := backend.TxPool().Get(hash)
- if tx == nil {
- continue
- }
- // If known, encode and queue for response packet
- if encoded, err := rlp.EncodeToBytes(tx); err != nil {
- log.Error("Failed to encode transaction", "err", err)
- } else {
- hashes = append(hashes, hash)
- txs = append(txs, encoded)
- bytes += len(encoded)
- }
- }
- return hashes, txs
-}
-
-func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
- // Transactions arrived, make sure we have a valid and fresh chain to handle them
- if !backend.AcceptTxs() {
- return nil
- }
- // Transactions can be processed, parse all of them and deliver to the pool
- var txs TransactionsPacket
- if err := msg.Decode(&txs); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- for i, tx := range txs {
- // Validate and mark the remote transaction
- if tx == nil {
- return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
- }
- peer.markTransaction(tx.Hash())
- }
- return backend.Handle(peer, &txs)
-}
-
-func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
- // Transactions arrived, make sure we have a valid and fresh chain to handle them
- if !backend.AcceptTxs() {
- return nil
- }
- // Transactions can be processed, parse all of them and deliver to the pool
- var txs PooledTransactionsPacket
- if err := msg.Decode(&txs); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- for i, tx := range txs.PooledTransactionsResponse {
- // Validate and mark the remote transaction
- if tx == nil {
- return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
- }
- peer.markTransaction(tx.Hash())
- }
- requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)
-
- return backend.Handle(peer, &txs.PooledTransactionsResponse)
-}
diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go
deleted file mode 100644
index ea16a85b1e..0000000000
--- a/eth/protocols/eth/handshake.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// 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 eth
-
-import (
- "errors"
- "fmt"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-const (
- // handshakeTimeout is the maximum allowed time for the `eth` handshake to
- // complete before dropping the connection.= as malicious.
- handshakeTimeout = 5 * time.Second
-)
-
-// Handshake executes the eth protocol handshake, negotiating version number,
-// network IDs, difficulties, head and genesis blocks.
-func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
- // Send out own handshake in a new thread
- errc := make(chan error, 2)
-
- var status StatusPacket // safe to read after two values have been received from errc
-
- go func() {
- errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{
- ProtocolVersion: uint32(p.version),
- NetworkID: network,
- TD: td,
- Head: head,
- Genesis: genesis,
- ForkID: forkID,
- })
- }()
- go func() {
- errc <- p.readStatus(network, &status, genesis, forkFilter)
- }()
- timeout := time.NewTimer(handshakeTimeout)
- defer timeout.Stop()
- for i := 0; i < 2; i++ {
- select {
- case err := <-errc:
- if err != nil {
- markError(p, err)
- return err
- }
- case <-timeout.C:
- markError(p, p2p.DiscReadTimeout)
- return p2p.DiscReadTimeout
- }
- }
- p.td, p.head = status.TD, status.Head
-
- // TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
- // larger, it will still fit within 100 bits
- if tdlen := p.td.BitLen(); tdlen > 100 {
- return fmt.Errorf("too large total difficulty: bitlen %d", tdlen)
- }
- return nil
-}
-
-// readStatus reads the remote handshake message.
-func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error {
- msg, err := p.rw.ReadMsg()
- if err != nil {
- return err
- }
- if msg.Code != StatusMsg {
- return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg)
- }
- if msg.Size > maxMessageSize {
- return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
- }
- // Decode the handshake and make sure everything matches
- if err := msg.Decode(&status); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- if status.NetworkID != network {
- return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network)
- }
- if uint(status.ProtocolVersion) != p.version {
- return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
- }
- if status.Genesis != genesis {
- return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
- }
- if err := forkFilter(status.ForkID); err != nil {
- return fmt.Errorf("%w: %v", errForkIDRejected, err)
- }
- return nil
-}
-
-// markError registers the error with the corresponding metric.
-func markError(p *Peer, err error) {
- if !metrics.Enabled {
- return
- }
- m := meters.get(p.Inbound())
- switch errors.Unwrap(err) {
- case errNetworkIDMismatch:
- m.networkIDMismatch.Mark(1)
- case errProtocolVersionMismatch:
- m.protocolVersionMismatch.Mark(1)
- case errGenesisMismatch:
- m.genesisMismatch.Mark(1)
- case errForkIDRejected:
- m.forkidRejected.Mark(1)
- case p2p.DiscReadTimeout:
- m.timeoutError.Mark(1)
- default:
- m.peerError.Mark(1)
- }
-}
diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go
deleted file mode 100644
index d96cfc8165..0000000000
--- a/eth/protocols/eth/handshake_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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 eth
-
-import (
- "errors"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// Tests that handshake failures are detected and reported correctly.
-func TestHandshake67(t *testing.T) { testHandshake(t, ETH67) }
-func TestHandshake68(t *testing.T) { testHandshake(t, ETH68) }
-
-func testHandshake(t *testing.T, protocol uint) {
- t.Parallel()
-
- // Create a test backend only to have some valid genesis chain
- backend := newTestBackend(3)
- defer backend.close()
-
- var (
- genesis = backend.chain.Genesis()
- head = backend.chain.CurrentBlock()
- td = backend.chain.GetTd(head.Hash(), head.Number.Uint64())
- forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time)
- )
- tests := []struct {
- code uint64
- data interface{}
- want error
- }{
- {
- code: TransactionsMsg, data: []interface{}{},
- want: errNoStatusMsg,
- },
- {
- code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID},
- want: errProtocolVersionMismatch,
- },
- {
- code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID},
- want: errNetworkIDMismatch,
- },
- {
- code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID},
- want: errGenesisMismatch,
- },
- {
- code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
- want: errForkIDRejected,
- },
- }
- for i, test := range tests {
- // Create the two peers to shake with each other
- app, net := p2p.MsgPipe()
- defer app.Close()
- defer net.Close()
-
- peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil)
- defer peer.Close()
-
- // Send the junk test with one peer, check the handshake failure
- go p2p.Send(app, test.code, test.data)
-
- err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain))
- if err == nil {
- t.Errorf("test %d: protocol returned nil error, want %q", i, test.want)
- } else if !errors.Is(err, test.want) {
- t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want)
- }
- }
-}
diff --git a/eth/protocols/eth/metrics.go b/eth/protocols/eth/metrics.go
deleted file mode 100644
index 5e0aee39f8..0000000000
--- a/eth/protocols/eth/metrics.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2023 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 eth
-
-import "github.com/ethereum/go-ethereum/metrics"
-
-// meters stores ingress and egress handshake meters.
-var meters bidirectionalMeters
-
-// bidirectionalMeters stores ingress and egress handshake meters.
-type bidirectionalMeters struct {
- ingress *hsMeters
- egress *hsMeters
-}
-
-// get returns the corresponding meter depending if ingress or egress is
-// desired.
-func (h *bidirectionalMeters) get(ingress bool) *hsMeters {
- if ingress {
- return h.ingress
- }
- return h.egress
-}
-
-// hsMeters is a collection of meters which track metrics related to the
-// eth subprotocol handshake.
-type hsMeters struct {
- // peerError measures the number of errors related to incorrect peer
- // behaviour, such as invalid message code, size, encoding, etc.
- peerError metrics.Meter
-
- // timeoutError measures the number of timeouts.
- timeoutError metrics.Meter
-
- // networkIDMismatch measures the number of network id mismatch errors.
- networkIDMismatch metrics.Meter
-
- // protocolVersionMismatch measures the number of differing protocol
- // versions.
- protocolVersionMismatch metrics.Meter
-
- // genesisMismatch measures the number of differing genesises.
- genesisMismatch metrics.Meter
-
- // forkidRejected measures the number of differing forkids.
- forkidRejected metrics.Meter
-}
-
-// newHandshakeMeters registers and returns handshake meters for the given
-// base.
-func newHandshakeMeters(base string) *hsMeters {
- return &hsMeters{
- peerError: metrics.NewRegisteredMeter(base+"error/peer", nil),
- timeoutError: metrics.NewRegisteredMeter(base+"error/timeout", nil),
- networkIDMismatch: metrics.NewRegisteredMeter(base+"error/network", nil),
- protocolVersionMismatch: metrics.NewRegisteredMeter(base+"error/version", nil),
- genesisMismatch: metrics.NewRegisteredMeter(base+"error/genesis", nil),
- forkidRejected: metrics.NewRegisteredMeter(base+"error/forkid", nil),
- }
-}
-
-func init() {
- meters = bidirectionalMeters{
- ingress: newHandshakeMeters("eth/protocols/eth/ingress/handshake/"),
- egress: newHandshakeMeters("eth/protocols/eth/egress/handshake/"),
- }
-}
diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go
deleted file mode 100644
index 98ad22a8cf..0000000000
--- a/eth/protocols/eth/peer.go
+++ /dev/null
@@ -1,505 +0,0 @@
-// 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 eth
-
-import (
- "math/big"
- "math/rand"
- "sync"
-
- mapset "github.com/deckarep/golang-set/v2"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- // maxKnownTxs is the maximum transactions hashes to keep in the known list
- // before starting to randomly evict them.
- maxKnownTxs = 32768
-
- // maxKnownBlocks is the maximum block hashes to keep in the known list
- // before starting to randomly evict them.
- maxKnownBlocks = 1024
-
- // maxQueuedTxs is the maximum number of transactions to queue up before dropping
- // older broadcasts.
- maxQueuedTxs = 4096
-
- // maxQueuedTxAnns is the maximum number of transaction announcements to queue up
- // before dropping older announcements.
- maxQueuedTxAnns = 4096
-
- // maxQueuedBlocks is the maximum number of block propagations to queue up before
- // dropping broadcasts. There's not much point in queueing stale blocks, so a few
- // that might cover uncles should be enough.
- maxQueuedBlocks = 4
-
- // maxQueuedBlockAnns is the maximum number of block announcements to queue up before
- // dropping broadcasts. Similarly to block propagations, there's no point to queue
- // above some healthy uncle limit, so use that.
- maxQueuedBlockAnns = 4
-)
-
-// max is a helper function which returns the larger of the two given integers.
-func max(a, b int) int {
- if a > b {
- return a
- }
- return b
-}
-
-// Peer is a collection of relevant information we have about a `eth` peer.
-type Peer struct {
- id string // Unique ID for the peer, cached
-
- *p2p.Peer // The embedded P2P package peer
- rw p2p.MsgReadWriter // Input/output streams for snap
- version uint // Protocol version negotiated
-
- head common.Hash // Latest advertised head block hash
- td *big.Int // Latest advertised head block total difficulty
-
- knownBlocks *knownCache // Set of block hashes known to be known by this peer
- queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer
- queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
-
- txpool TxPool // Transaction pool used by the broadcasters for liveness checks
- knownTxs *knownCache // Set of transaction hashes known to be known by this peer
- txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
- txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
-
- reqDispatch chan *request // Dispatch channel to send requests and track then until fulfillment
- reqCancel chan *cancel // Dispatch channel to cancel pending requests and untrack them
- resDispatch chan *response // Dispatch channel to fulfil pending requests and untrack them
-
- term chan struct{} // Termination channel to stop the broadcasters
- lock sync.RWMutex // Mutex protecting the internal fields
-}
-
-// NewPeer create a wrapper for a network connection and negotiated protocol
-// version.
-func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer {
- peer := &Peer{
- id: p.ID().String(),
- Peer: p,
- rw: rw,
- version: version,
- knownTxs: newKnownCache(maxKnownTxs),
- knownBlocks: newKnownCache(maxKnownBlocks),
- queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks),
- queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
- txBroadcast: make(chan []common.Hash),
- txAnnounce: make(chan []common.Hash),
- reqDispatch: make(chan *request),
- reqCancel: make(chan *cancel),
- resDispatch: make(chan *response),
- txpool: txpool,
- term: make(chan struct{}),
- }
- // Start up all the broadcasters
- go peer.broadcastBlocks()
- go peer.broadcastTransactions()
- go peer.announceTransactions()
- go peer.dispatcher()
-
- return peer
-}
-
-// Close signals the broadcast goroutine to terminate. Only ever call this if
-// you created the peer yourself via NewPeer. Otherwise let whoever created it
-// clean it up!
-func (p *Peer) Close() {
- close(p.term)
-}
-
-// ID retrieves the peer's unique identifier.
-func (p *Peer) ID() string {
- return p.id
-}
-
-// Version retrieves the peer's negotiated `eth` protocol version.
-func (p *Peer) Version() uint {
- return p.version
-}
-
-// Head retrieves the current head hash and total difficulty of the peer.
-func (p *Peer) Head() (hash common.Hash, td *big.Int) {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- copy(hash[:], p.head[:])
- return hash, new(big.Int).Set(p.td)
-}
-
-// SetHead updates the head hash and total difficulty of the peer.
-func (p *Peer) SetHead(hash common.Hash, td *big.Int) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- copy(p.head[:], hash[:])
- p.td.Set(td)
-}
-
-// KnownBlock returns whether peer is known to already have a block.
-func (p *Peer) KnownBlock(hash common.Hash) bool {
- return p.knownBlocks.Contains(hash)
-}
-
-// KnownTransaction returns whether peer is known to already have a transaction.
-func (p *Peer) KnownTransaction(hash common.Hash) bool {
- return p.knownTxs.Contains(hash)
-}
-
-// markBlock marks a block as known for the peer, ensuring that the block will
-// never be propagated to this particular peer.
-func (p *Peer) markBlock(hash common.Hash) {
- // If we reached the memory allowance, drop a previously known block hash
- p.knownBlocks.Add(hash)
-}
-
-// markTransaction marks a transaction as known for the peer, ensuring that it
-// will never be propagated to this particular peer.
-func (p *Peer) markTransaction(hash common.Hash) {
- // If we reached the memory allowance, drop a previously known transaction hash
- p.knownTxs.Add(hash)
-}
-
-// SendTransactions sends transactions to the peer and includes the hashes
-// in its transaction hash set for future reference.
-//
-// This method is a helper used by the async transaction sender. Don't call it
-// directly as the queueing (memory) and transmission (bandwidth) costs should
-// not be managed directly.
-//
-// The reasons this is public is to allow packages using this protocol to write
-// tests that directly send messages without having to do the async queueing.
-func (p *Peer) SendTransactions(txs types.Transactions) error {
- // Mark all the transactions as known, but ensure we don't overflow our limits
- for _, tx := range txs {
- p.knownTxs.Add(tx.Hash())
- }
- return p2p.Send(p.rw, TransactionsMsg, txs)
-}
-
-// AsyncSendTransactions queues a list of transactions (by hash) to eventually
-// propagate to a remote peer. The number of pending sends are capped (new ones
-// will force old sends to be dropped)
-func (p *Peer) AsyncSendTransactions(hashes []common.Hash) {
- select {
- case p.txBroadcast <- hashes:
- // Mark all the transactions as known, but ensure we don't overflow our limits
- p.knownTxs.Add(hashes...)
- case <-p.term:
- p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
- }
-}
-
-// sendPooledTransactionHashes66 sends transaction hashes to the peer and includes
-// them in its transaction hash set for future reference.
-//
-// This method is a helper used by the async transaction announcer. Don't call it
-// directly as the queueing (memory) and transmission (bandwidth) costs should
-// not be managed directly.
-func (p *Peer) sendPooledTransactionHashes66(hashes []common.Hash) error {
- // Mark all the transactions as known, but ensure we don't overflow our limits
- p.knownTxs.Add(hashes...)
- return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket67(hashes))
-}
-
-// sendPooledTransactionHashes68 sends transaction hashes (tagged with their type
-// and size) to the peer and includes them in its transaction hash set for future
-// reference.
-//
-// This method is a helper used by the async transaction announcer. Don't call it
-// directly as the queueing (memory) and transmission (bandwidth) costs should
-// not be managed directly.
-func (p *Peer) sendPooledTransactionHashes68(hashes []common.Hash, types []byte, sizes []uint32) error {
- // Mark all the transactions as known, but ensure we don't overflow our limits
- p.knownTxs.Add(hashes...)
- return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket68{Types: types, Sizes: sizes, Hashes: hashes})
-}
-
-// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
-// announce to a remote peer. The number of pending sends are capped (new ones
-// will force old sends to be dropped)
-func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
- select {
- case p.txAnnounce <- hashes:
- // Mark all the transactions as known, but ensure we don't overflow our limits
- p.knownTxs.Add(hashes...)
- case <-p.term:
- p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
- }
-}
-
-// ReplyPooledTransactionsRLP is the response to RequestTxs.
-func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error {
- // Mark all the transactions as known, but ensure we don't overflow our limits
- p.knownTxs.Add(hashes...)
-
- // Not packed into PooledTransactionsResponse to avoid RLP decoding
- return p2p.Send(p.rw, PooledTransactionsMsg, &PooledTransactionsRLPPacket{
- RequestId: id,
- PooledTransactionsRLPResponse: txs,
- })
-}
-
-// SendNewBlockHashes announces the availability of a number of blocks through
-// a hash notification.
-func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
- // Mark all the block hashes as known, but ensure we don't overflow our limits
- p.knownBlocks.Add(hashes...)
-
- request := make(NewBlockHashesPacket, len(hashes))
- for i := 0; i < len(hashes); i++ {
- request[i].Hash = hashes[i]
- request[i].Number = numbers[i]
- }
- return p2p.Send(p.rw, NewBlockHashesMsg, request)
-}
-
-// AsyncSendNewBlockHash queues the availability of a block for propagation to a
-// remote peer. If the peer's broadcast queue is full, the event is silently
-// dropped.
-func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
- select {
- case p.queuedBlockAnns <- block:
- // Mark all the block hash as known, but ensure we don't overflow our limits
- p.knownBlocks.Add(block.Hash())
- default:
- p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
- }
-}
-
-// SendNewBlock propagates an entire block to a remote peer.
-func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
- // Mark all the block hash as known, but ensure we don't overflow our limits
- p.knownBlocks.Add(block.Hash())
- return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
- Block: block,
- TD: td,
- })
-}
-
-// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
-// the peer's broadcast queue is full, the event is silently dropped.
-func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
- select {
- case p.queuedBlocks <- &blockPropagation{block: block, td: td}:
- // Mark all the block hash as known, but ensure we don't overflow our limits
- p.knownBlocks.Add(block.Hash())
- default:
- p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
- }
-}
-
-// ReplyBlockHeadersRLP is the response to GetBlockHeaders.
-func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error {
- return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket{
- RequestId: id,
- BlockHeadersRLPResponse: headers,
- })
-}
-
-// ReplyBlockBodiesRLP is the response to GetBlockBodies.
-func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error {
- // Not packed into BlockBodiesResponse to avoid RLP decoding
- return p2p.Send(p.rw, BlockBodiesMsg, &BlockBodiesRLPPacket{
- RequestId: id,
- BlockBodiesRLPResponse: bodies,
- })
-}
-
-// ReplyReceiptsRLP is the response to GetReceipts.
-func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error {
- return p2p.Send(p.rw, ReceiptsMsg, &ReceiptsRLPPacket{
- RequestId: id,
- ReceiptsRLPResponse: receipts,
- })
-}
-
-// RequestOneHeader is a wrapper around the header query functions to fetch a
-// single header. It is used solely by the fetcher.
-func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) {
- p.Log().Debug("Fetching single header", "hash", hash)
- id := rand.Uint64()
-
- req := &Request{
- id: id,
- sink: sink,
- code: GetBlockHeadersMsg,
- want: BlockHeadersMsg,
- data: &GetBlockHeadersPacket{
- RequestId: id,
- GetBlockHeadersRequest: &GetBlockHeadersRequest{
- Origin: HashOrNumber{Hash: hash},
- Amount: uint64(1),
- Skip: uint64(0),
- Reverse: false,
- },
- },
- }
- if err := p.dispatchRequest(req); err != nil {
- return nil, err
- }
- return req, nil
-}
-
-// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the hash of an origin block.
-func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) {
- p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
- id := rand.Uint64()
-
- req := &Request{
- id: id,
- sink: sink,
- code: GetBlockHeadersMsg,
- want: BlockHeadersMsg,
- data: &GetBlockHeadersPacket{
- RequestId: id,
- GetBlockHeadersRequest: &GetBlockHeadersRequest{
- Origin: HashOrNumber{Hash: origin},
- Amount: uint64(amount),
- Skip: uint64(skip),
- Reverse: reverse,
- },
- },
- }
- if err := p.dispatchRequest(req); err != nil {
- return nil, err
- }
- return req, nil
-}
-
-// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the number of an origin block.
-func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) {
- p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
- id := rand.Uint64()
-
- req := &Request{
- id: id,
- sink: sink,
- code: GetBlockHeadersMsg,
- want: BlockHeadersMsg,
- data: &GetBlockHeadersPacket{
- RequestId: id,
- GetBlockHeadersRequest: &GetBlockHeadersRequest{
- Origin: HashOrNumber{Number: origin},
- Amount: uint64(amount),
- Skip: uint64(skip),
- Reverse: reverse,
- },
- },
- }
- if err := p.dispatchRequest(req); err != nil {
- return nil, err
- }
- return req, nil
-}
-
-// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
-// specified.
-func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Request, error) {
- p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
- id := rand.Uint64()
-
- req := &Request{
- id: id,
- sink: sink,
- code: GetBlockBodiesMsg,
- want: BlockBodiesMsg,
- data: &GetBlockBodiesPacket{
- RequestId: id,
- GetBlockBodiesRequest: hashes,
- },
- }
- if err := p.dispatchRequest(req); err != nil {
- return nil, err
- }
- return req, nil
-}
-
-// RequestReceipts fetches a batch of transaction receipts from a remote node.
-func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Request, error) {
- p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
- id := rand.Uint64()
-
- req := &Request{
- id: id,
- sink: sink,
- code: GetReceiptsMsg,
- want: ReceiptsMsg,
- data: &GetReceiptsPacket{
- RequestId: id,
- GetReceiptsRequest: hashes,
- },
- }
- if err := p.dispatchRequest(req); err != nil {
- return nil, err
- }
- return req, nil
-}
-
-// RequestTxs fetches a batch of transactions from a remote node.
-func (p *Peer) RequestTxs(hashes []common.Hash) error {
- p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
- id := rand.Uint64()
-
- requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id)
- return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket{
- RequestId: id,
- GetPooledTransactionsRequest: hashes,
- })
-}
-
-// knownCache is a cache for known hashes.
-type knownCache struct {
- hashes mapset.Set[common.Hash]
- max int
-}
-
-// newKnownCache creates a new knownCache with a max capacity.
-func newKnownCache(max int) *knownCache {
- return &knownCache{
- max: max,
- hashes: mapset.NewSet[common.Hash](),
- }
-}
-
-// Add adds a list of elements to the set.
-func (k *knownCache) Add(hashes ...common.Hash) {
- for k.hashes.Cardinality() > max(0, k.max-len(hashes)) {
- k.hashes.Pop()
- }
- for _, hash := range hashes {
- k.hashes.Add(hash)
- }
-}
-
-// Contains returns whether the given item is in the set.
-func (k *knownCache) Contains(hash common.Hash) bool {
- return k.hashes.Contains(hash)
-}
-
-// Cardinality returns the number of elements in the set.
-func (k *knownCache) Cardinality() int {
- return k.hashes.Cardinality()
-}
diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go
deleted file mode 100644
index efbbbc6fff..0000000000
--- a/eth/protocols/eth/peer_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// 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 .
-
-// This file contains some shares testing functionality, common to multiple
-// different files and modules being tested.
-
-package eth
-
-import (
- "crypto/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// testPeer is a simulated peer to allow testing direct network calls.
-type testPeer struct {
- *Peer
-
- net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
- app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
-}
-
-// newTestPeer creates a new peer registered at the given data backend.
-func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) {
- // Create a message pipe to communicate through
- app, net := p2p.MsgPipe()
-
- // Start the peer on a new thread
- var id enode.ID
- rand.Read(id[:])
-
- peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool())
- errc := make(chan error, 1)
- go func() {
- defer app.Close()
-
- errc <- backend.RunPeer(peer, func(peer *Peer) error {
- return Handle(backend, peer)
- })
- }()
- return &testPeer{app: app, net: net, Peer: peer}, errc
-}
-
-// close terminates the local side of the peer, notifying the remote protocol
-// manager of termination.
-func (p *testPeer) close() {
- p.Peer.Close()
- p.app.Close()
-}
-
-func TestPeerSet(t *testing.T) {
- size := 5
- s := newKnownCache(size)
-
- // add 10 items
- for i := 0; i < size*2; i++ {
- s.Add(common.Hash{byte(i)})
- }
-
- if s.Cardinality() != size {
- t.Fatalf("wrong size, expected %d but found %d", size, s.Cardinality())
- }
-
- vals := []common.Hash{}
- for i := 10; i < 20; i++ {
- vals = append(vals, common.Hash{byte(i)})
- }
-
- // add item in batch
- s.Add(vals...)
- if s.Cardinality() < size {
- t.Fatalf("bad size")
- }
-}
diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go
deleted file mode 100644
index 0f44f83de1..0000000000
--- a/eth/protocols/eth/protocol.go
+++ /dev/null
@@ -1,364 +0,0 @@
-// 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 eth
-
-import (
- "errors"
- "fmt"
- "io"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Constants to match up protocol versions and messages
-const (
- ETH67 = 67
- ETH68 = 68
-)
-
-// ProtocolName is the official short name of the `eth` protocol used during
-// devp2p capability negotiation.
-const ProtocolName = "eth"
-
-// ProtocolVersions are the supported versions of the `eth` protocol (first
-// is primary).
-var ProtocolVersions = []uint{ETH68, ETH67}
-
-// protocolLengths are the number of implemented message corresponding to
-// different protocol versions.
-var protocolLengths = map[uint]uint64{ETH68: 17, ETH67: 17}
-
-// maxMessageSize is the maximum cap on the size of a protocol message.
-const maxMessageSize = 10 * 1024 * 1024
-
-const (
- StatusMsg = 0x00
- NewBlockHashesMsg = 0x01
- TransactionsMsg = 0x02
- GetBlockHeadersMsg = 0x03
- BlockHeadersMsg = 0x04
- GetBlockBodiesMsg = 0x05
- BlockBodiesMsg = 0x06
- NewBlockMsg = 0x07
- NewPooledTransactionHashesMsg = 0x08
- GetPooledTransactionsMsg = 0x09
- PooledTransactionsMsg = 0x0a
- GetReceiptsMsg = 0x0f
- ReceiptsMsg = 0x10
-)
-
-var (
- errNoStatusMsg = errors.New("no status message")
- errMsgTooLarge = errors.New("message too long")
- errDecode = errors.New("invalid message")
- errInvalidMsgCode = errors.New("invalid message code")
- errProtocolVersionMismatch = errors.New("protocol version mismatch")
- errNetworkIDMismatch = errors.New("network ID mismatch")
- errGenesisMismatch = errors.New("genesis mismatch")
- errForkIDRejected = errors.New("fork ID rejected")
-)
-
-// Packet represents a p2p message in the `eth` protocol.
-type Packet interface {
- Name() string // Name returns a string corresponding to the message type.
- Kind() byte // Kind returns the message type.
-}
-
-// StatusPacket is the network packet for the status message.
-type StatusPacket struct {
- ProtocolVersion uint32
- NetworkID uint64
- TD *big.Int
- Head common.Hash
- Genesis common.Hash
- ForkID forkid.ID
-}
-
-// NewBlockHashesPacket is the network packet for the block announcements.
-type NewBlockHashesPacket []struct {
- Hash common.Hash // Hash of one particular block being announced
- Number uint64 // Number of one particular block being announced
-}
-
-// Unpack retrieves the block hashes and numbers from the announcement packet
-// and returns them in a split flat format that's more consistent with the
-// internal data structures.
-func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) {
- var (
- hashes = make([]common.Hash, len(*p))
- numbers = make([]uint64, len(*p))
- )
- for i, body := range *p {
- hashes[i], numbers[i] = body.Hash, body.Number
- }
- return hashes, numbers
-}
-
-// TransactionsPacket is the network packet for broadcasting new transactions.
-type TransactionsPacket []*types.Transaction
-
-// GetBlockHeadersRequest represents a block header query.
-type GetBlockHeadersRequest struct {
- Origin HashOrNumber // Block from which to retrieve headers
- Amount uint64 // Maximum number of headers to retrieve
- Skip uint64 // Blocks to skip between consecutive headers
- Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
-}
-
-// GetBlockHeadersPacket represents a block header query with request ID wrapping.
-type GetBlockHeadersPacket struct {
- RequestId uint64
- *GetBlockHeadersRequest
-}
-
-// HashOrNumber is a combined field for specifying an origin block.
-type HashOrNumber struct {
- Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
- Number uint64 // Block hash from which to retrieve headers (excludes Hash)
-}
-
-// EncodeRLP is a specialized encoder for HashOrNumber to encode only one of the
-// two contained union fields.
-func (hn *HashOrNumber) EncodeRLP(w io.Writer) error {
- if hn.Hash == (common.Hash{}) {
- return rlp.Encode(w, hn.Number)
- }
- if hn.Number != 0 {
- return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
- }
- return rlp.Encode(w, hn.Hash)
-}
-
-// DecodeRLP is a specialized decoder for HashOrNumber to decode the contents
-// into either a block hash or a block number.
-func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error {
- _, size, err := s.Kind()
- switch {
- case err != nil:
- return err
- case size == 32:
- hn.Number = 0
- return s.Decode(&hn.Hash)
- case size <= 8:
- hn.Hash = common.Hash{}
- return s.Decode(&hn.Number)
- default:
- return fmt.Errorf("invalid input size %d for origin", size)
- }
-}
-
-// BlockHeadersRequest represents a block header response.
-type BlockHeadersRequest []*types.Header
-
-// BlockHeadersPacket represents a block header response over with request ID wrapping.
-type BlockHeadersPacket struct {
- RequestId uint64
- BlockHeadersRequest
-}
-
-// BlockHeadersRLPResponse represents a block header response, to use when we already
-// have the headers rlp encoded.
-type BlockHeadersRLPResponse []rlp.RawValue
-
-// BlockHeadersRLPPacket represents a block header response with request ID wrapping.
-type BlockHeadersRLPPacket struct {
- RequestId uint64
- BlockHeadersRLPResponse
-}
-
-// NewBlockPacket is the network packet for the block propagation message.
-type NewBlockPacket struct {
- Block *types.Block
- TD *big.Int
-}
-
-// sanityCheck verifies that the values are reasonable, as a DoS protection
-func (request *NewBlockPacket) sanityCheck() error {
- if err := request.Block.SanityCheck(); err != nil {
- return err
- }
- //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
- // larger, it will still fit within 100 bits
- if tdlen := request.TD.BitLen(); tdlen > 100 {
- return fmt.Errorf("too large block TD: bitlen %d", tdlen)
- }
- return nil
-}
-
-// GetBlockBodiesRequest represents a block body query.
-type GetBlockBodiesRequest []common.Hash
-
-// GetBlockBodiesPacket represents a block body query with request ID wrapping.
-type GetBlockBodiesPacket struct {
- RequestId uint64
- GetBlockBodiesRequest
-}
-
-// BlockBodiesResponse is the network packet for block content distribution.
-type BlockBodiesResponse []*BlockBody
-
-// BlockBodiesPacket is the network packet for block content distribution with
-// request ID wrapping.
-type BlockBodiesPacket struct {
- RequestId uint64
- BlockBodiesResponse
-}
-
-// BlockBodiesRLPResponse is used for replying to block body requests, in cases
-// where we already have them RLP-encoded, and thus can avoid the decode-encode
-// roundtrip.
-type BlockBodiesRLPResponse []rlp.RawValue
-
-// BlockBodiesRLPPacket is the BlockBodiesRLPResponse with request ID wrapping.
-type BlockBodiesRLPPacket struct {
- RequestId uint64
- BlockBodiesRLPResponse
-}
-
-// BlockBody represents the data content of a single block.
-type BlockBody struct {
- Transactions []*types.Transaction // Transactions contained within a block
- Uncles []*types.Header // Uncles contained within a block
- Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block
-}
-
-// Unpack retrieves the transactions and uncles from the range packet and returns
-// them in a split flat format that's more consistent with the internal data structures.
-func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) {
- // TODO(matt): add support for withdrawals to fetchers
- var (
- txset = make([][]*types.Transaction, len(*p))
- uncleset = make([][]*types.Header, len(*p))
- withdrawalset = make([][]*types.Withdrawal, len(*p))
- )
- for i, body := range *p {
- txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals
- }
- return txset, uncleset, withdrawalset
-}
-
-// GetReceiptsRequest represents a block receipts query.
-type GetReceiptsRequest []common.Hash
-
-// GetReceiptsPacket represents a block receipts query with request ID wrapping.
-type GetReceiptsPacket struct {
- RequestId uint64
- GetReceiptsRequest
-}
-
-// ReceiptsResponse is the network packet for block receipts distribution.
-type ReceiptsResponse [][]*types.Receipt
-
-// ReceiptsPacket is the network packet for block receipts distribution with
-// request ID wrapping.
-type ReceiptsPacket struct {
- RequestId uint64
- ReceiptsResponse
-}
-
-// ReceiptsRLPResponse is used for receipts, when we already have it encoded
-type ReceiptsRLPResponse []rlp.RawValue
-
-// ReceiptsRLPPacket is ReceiptsRLPResponse with request ID wrapping.
-type ReceiptsRLPPacket struct {
- RequestId uint64
- ReceiptsRLPResponse
-}
-
-// NewPooledTransactionHashesPacket67 represents a transaction announcement packet on eth/67.
-type NewPooledTransactionHashesPacket67 []common.Hash
-
-// NewPooledTransactionHashesPacket68 represents a transaction announcement packet on eth/68 and newer.
-type NewPooledTransactionHashesPacket68 struct {
- Types []byte
- Sizes []uint32
- Hashes []common.Hash
-}
-
-// GetPooledTransactionsRequest represents a transaction query.
-type GetPooledTransactionsRequest []common.Hash
-
-// GetPooledTransactionsPacket represents a transaction query with request ID wrapping.
-type GetPooledTransactionsPacket struct {
- RequestId uint64
- GetPooledTransactionsRequest
-}
-
-// PooledTransactionsResponse is the network packet for transaction distribution.
-type PooledTransactionsResponse []*types.Transaction
-
-// PooledTransactionsPacket is the network packet for transaction distribution
-// with request ID wrapping.
-type PooledTransactionsPacket struct {
- RequestId uint64
- PooledTransactionsResponse
-}
-
-// PooledTransactionsRLPResponse is the network packet for transaction distribution, used
-// in the cases we already have them in rlp-encoded form
-type PooledTransactionsRLPResponse []rlp.RawValue
-
-// PooledTransactionsRLPPacket is PooledTransactionsRLPResponse with request ID wrapping.
-type PooledTransactionsRLPPacket struct {
- RequestId uint64
- PooledTransactionsRLPResponse
-}
-
-func (*StatusPacket) Name() string { return "Status" }
-func (*StatusPacket) Kind() byte { return StatusMsg }
-
-func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" }
-func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg }
-
-func (*TransactionsPacket) Name() string { return "Transactions" }
-func (*TransactionsPacket) Kind() byte { return TransactionsMsg }
-
-func (*GetBlockHeadersRequest) Name() string { return "GetBlockHeaders" }
-func (*GetBlockHeadersRequest) Kind() byte { return GetBlockHeadersMsg }
-
-func (*BlockHeadersRequest) Name() string { return "BlockHeaders" }
-func (*BlockHeadersRequest) Kind() byte { return BlockHeadersMsg }
-
-func (*GetBlockBodiesRequest) Name() string { return "GetBlockBodies" }
-func (*GetBlockBodiesRequest) Kind() byte { return GetBlockBodiesMsg }
-
-func (*BlockBodiesResponse) Name() string { return "BlockBodies" }
-func (*BlockBodiesResponse) Kind() byte { return BlockBodiesMsg }
-
-func (*NewBlockPacket) Name() string { return "NewBlock" }
-func (*NewBlockPacket) Kind() byte { return NewBlockMsg }
-
-func (*NewPooledTransactionHashesPacket67) Name() string { return "NewPooledTransactionHashes" }
-func (*NewPooledTransactionHashesPacket67) Kind() byte { return NewPooledTransactionHashesMsg }
-func (*NewPooledTransactionHashesPacket68) Name() string { return "NewPooledTransactionHashes" }
-func (*NewPooledTransactionHashesPacket68) Kind() byte { return NewPooledTransactionHashesMsg }
-
-func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" }
-func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg }
-
-func (*PooledTransactionsResponse) Name() string { return "PooledTransactions" }
-func (*PooledTransactionsResponse) Kind() byte { return PooledTransactionsMsg }
-
-func (*GetReceiptsRequest) Name() string { return "GetReceipts" }
-func (*GetReceiptsRequest) Kind() byte { return GetReceiptsMsg }
-
-func (*ReceiptsResponse) Name() string { return "Receipts" }
-func (*ReceiptsResponse) Kind() byte { return ReceiptsMsg }
diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go
deleted file mode 100644
index bc2545dea2..0000000000
--- a/eth/protocols/eth/protocol_test.go
+++ /dev/null
@@ -1,248 +0,0 @@
-// 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 eth
-
-import (
- "bytes"
- "math/big"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Tests that the custom union field encoder and decoder works correctly.
-func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
- // Create a "random" hash for testing
- var hash common.Hash
- for i := range hash {
- hash[i] = byte(i)
- }
- // Assemble some table driven tests
- tests := []struct {
- packet *GetBlockHeadersRequest
- fail bool
- }{
- // Providing the origin as either a hash or a number should both work
- {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 314}}},
- {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash}}},
-
- // Providing arbitrary query field should also work
- {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
- {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
-
- // Providing both the origin hash and origin number must fail
- {fail: true, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash, Number: 314}}},
- }
- // Iterate over each of the tests and try to encode and then decode
- for i, tt := range tests {
- bytes, err := rlp.EncodeToBytes(tt.packet)
- if err != nil && !tt.fail {
- t.Fatalf("test %d: failed to encode packet: %v", i, err)
- } else if err == nil && tt.fail {
- t.Fatalf("test %d: encode should have failed", i)
- }
- if !tt.fail {
- packet := new(GetBlockHeadersRequest)
- if err := rlp.DecodeBytes(bytes, packet); err != nil {
- t.Fatalf("test %d: failed to decode packet: %v", i, err)
- }
- if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
- packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
- t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
- }
- }
- }
-}
-
-// TestEmptyMessages tests encoding of empty messages.
-func TestEmptyMessages(t *testing.T) {
- // All empty messages encodes to the same format
- want := common.FromHex("c4820457c0")
-
- for i, msg := range []interface{}{
- // Headers
- GetBlockHeadersPacket{1111, nil},
- BlockHeadersPacket{1111, nil},
- // Bodies
- GetBlockBodiesPacket{1111, nil},
- BlockBodiesPacket{1111, nil},
- BlockBodiesRLPPacket{1111, nil},
- // Receipts
- GetReceiptsPacket{1111, nil},
- ReceiptsPacket{1111, nil},
- // Transactions
- GetPooledTransactionsPacket{1111, nil},
- PooledTransactionsPacket{1111, nil},
- PooledTransactionsRLPPacket{1111, nil},
-
- // Headers
- BlockHeadersPacket{1111, BlockHeadersRequest([]*types.Header{})},
- // Bodies
- GetBlockBodiesPacket{1111, GetBlockBodiesRequest([]common.Hash{})},
- BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{})},
- BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{})},
- // Receipts
- GetReceiptsPacket{1111, GetReceiptsRequest([]common.Hash{})},
- ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{})},
- // Transactions
- GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest([]common.Hash{})},
- PooledTransactionsPacket{1111, PooledTransactionsResponse([]*types.Transaction{})},
- PooledTransactionsRLPPacket{1111, PooledTransactionsRLPResponse([]rlp.RawValue{})},
- } {
- if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) {
- t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want)
- }
- }
-}
-
-// TestMessages tests the encoding of all messages.
-func TestMessages(t *testing.T) {
- // Some basic structs used during testing
- var (
- header *types.Header
- blockBody *BlockBody
- blockBodyRlp rlp.RawValue
- txs []*types.Transaction
- txRlps []rlp.RawValue
- hashes []common.Hash
- receipts []*types.Receipt
- receiptsRlp rlp.RawValue
-
- err error
- )
- header = &types.Header{
- Difficulty: big.NewInt(2222),
- Number: big.NewInt(3333),
- GasLimit: 4444,
- GasUsed: 5555,
- Time: 6666,
- Extra: []byte{0x77, 0x88},
- }
- // Init the transactions, taken from a different test
- {
- for _, hexrlp := range []string{
- "f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
- "f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
- } {
- var tx *types.Transaction
- rlpdata := common.FromHex(hexrlp)
- if err := rlp.DecodeBytes(rlpdata, &tx); err != nil {
- t.Fatal(err)
- }
- txs = append(txs, tx)
- txRlps = append(txRlps, rlpdata)
- }
- }
- // init the block body data, both object and rlp form
- blockBody = &BlockBody{
- Transactions: txs,
- Uncles: []*types.Header{header},
- }
- blockBodyRlp, err = rlp.EncodeToBytes(blockBody)
- if err != nil {
- t.Fatal(err)
- }
-
- hashes = []common.Hash{
- common.HexToHash("deadc0de"),
- common.HexToHash("feedbeef"),
- }
- // init the receipts
- {
- receipts = []*types.Receipt{
- {
- Status: types.ReceiptStatusFailed,
- CumulativeGasUsed: 1,
- Logs: []*types.Log{
- {
- Address: common.BytesToAddress([]byte{0x11}),
- Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
- Data: []byte{0x01, 0x00, 0xff},
- },
- },
- TxHash: hashes[0],
- ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
- GasUsed: 111111,
- },
- }
- rlpData, err := rlp.EncodeToBytes(receipts)
- if err != nil {
- t.Fatal(err)
- }
- receiptsRlp = rlpData
- }
-
- for i, tc := range []struct {
- message interface{}
- want []byte
- }{
- {
- GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{hashes[0], 0}, 5, 5, false}},
- common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"),
- },
- {
- GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}},
- common.FromHex("ca820457c682270f050580"),
- },
- {
- BlockHeadersPacket{1111, BlockHeadersRequest{header}},
- common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
- },
- {
- GetBlockBodiesPacket{1111, GetBlockBodiesRequest(hashes)},
- common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
- },
- {
- BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{blockBody})},
- common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
- },
- { // Identical to non-rlp-shortcut version
- BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{blockBodyRlp})},
- common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
- },
- {
- GetReceiptsPacket{1111, GetReceiptsRequest(hashes)},
- common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
- },
- {
- ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{receipts})},
- common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
- },
- {
- ReceiptsRLPPacket{1111, ReceiptsRLPResponse([]rlp.RawValue{receiptsRlp})},
- common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
- },
- {
- GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest(hashes)},
- common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
- },
- {
- PooledTransactionsPacket{1111, PooledTransactionsResponse(txs)},
- common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"),
- },
- {
- PooledTransactionsRLPPacket{1111, PooledTransactionsRLPResponse(txRlps)},
- common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"),
- },
- } {
- if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) {
- t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want)
- }
- }
-}
diff --git a/eth/protocols/eth/tracker.go b/eth/protocols/eth/tracker.go
deleted file mode 100644
index 324fd22839..0000000000
--- a/eth/protocols/eth/tracker.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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 .
-
-package eth
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/tracker"
-)
-
-// requestTracker is a singleton tracker for eth/66 and newer request times.
-var requestTracker = tracker.New(ProtocolName, 5*time.Minute)
diff --git a/eth/protocols/snap/discovery.go b/eth/protocols/snap/discovery.go
deleted file mode 100644
index 684ec7e632..0000000000
--- a/eth/protocols/snap/discovery.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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 (
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// enrEntry is the ENR entry which advertises `snap` protocol on the discovery.
-type enrEntry struct {
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-// ENRKey implements enr.Entry.
-func (e enrEntry) ENRKey() string {
- return "snap"
-}
diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go
deleted file mode 100644
index bd7ce9e715..0000000000
--- a/eth/protocols/snap/handler.go
+++ /dev/null
@@ -1,577 +0,0 @@
-// 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 (
- "bytes"
- "fmt"
- "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/log"
- "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/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-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
-
- // 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, dnsdisc enode.Iterator) []p2p.Protocol {
- // Filter the discovery iterator for nodes advertising snap support.
- dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool {
- var snap enrEntry
- return n.Load(&snap) == nil
- })
-
- 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 {
- return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error {
- 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{}},
- DialCandidates: dnsdisc,
- }
- }
- 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
- }
- }
-}
-
-// 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()
- start := time.Now()
- // 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())
- }(start)
- }
- // Handle the message depending on its contents
- switch {
- case msg.Code == GetAccountRangeMsg:
- // Decode the account retrieval request
- var req GetAccountRangePacket
- if err := msg.Decode(&req); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Service the request, potentially returning nothing in case of errors
- accounts, proofs := ServiceGetAccountRangeQuery(backend.Chain(), &req)
-
- // Send back anything accumulated (or empty in case of errors)
- return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{
- ID: req.ID,
- Accounts: accounts,
- Proof: proofs,
- })
-
- case msg.Code == AccountRangeMsg:
- // A range of accounts arrived to one of our previous requests
- res := new(AccountRangePacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Ensure the range is monotonically increasing
- for i := 1; i < len(res.Accounts); i++ {
- if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 {
- return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:])
- }
- }
- requestTracker.Fulfil(peer.id, peer.version, AccountRangeMsg, res.ID)
-
- return backend.Handle(peer, res)
-
- case msg.Code == GetStorageRangesMsg:
- // Decode the storage retrieval request
- var req GetStorageRangesPacket
- if err := msg.Decode(&req); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Service the request, potentially returning nothing in case of errors
- slots, proofs := ServiceGetStorageRangesQuery(backend.Chain(), &req)
-
- // Send back anything accumulated (or empty in case of errors)
- return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{
- ID: req.ID,
- Slots: slots,
- Proof: proofs,
- })
-
- case msg.Code == StorageRangesMsg:
- // A range of storage slots arrived to one of our previous requests
- res := new(StorageRangesPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Ensure the ranges are monotonically increasing
- for i, slots := range res.Slots {
- for j := 1; j < len(slots); j++ {
- if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 {
- return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:])
- }
- }
- }
- requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID)
-
- return backend.Handle(peer, res)
-
- case msg.Code == GetByteCodesMsg:
- // Decode bytecode retrieval request
- var req GetByteCodesPacket
- if err := msg.Decode(&req); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Service the request, potentially returning nothing in case of errors
- codes := ServiceGetByteCodesQuery(backend.Chain(), &req)
-
- // Send back anything accumulated (or empty in case of errors)
- return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{
- ID: req.ID,
- Codes: codes,
- })
-
- case msg.Code == ByteCodesMsg:
- // A batch of byte codes arrived to one of our previous requests
- res := new(ByteCodesPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID)
-
- return backend.Handle(peer, res)
-
- case msg.Code == GetTrieNodesMsg:
- // Decode trie node retrieval request
- var req GetTrieNodesPacket
- if err := msg.Decode(&req); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- // Service the request, potentially returning nothing in case of errors
- nodes, err := ServiceGetTrieNodesQuery(backend.Chain(), &req, start)
- if err != nil {
- return err
- }
- // Send back anything accumulated (or empty in case of errors)
- return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{
- ID: req.ID,
- Nodes: nodes,
- })
-
- case msg.Code == TrieNodesMsg:
- // A batch of trie nodes arrived to one of our previous requests
- res := new(TrieNodesPacket)
- if err := msg.Decode(res); err != nil {
- return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
- }
- requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID)
-
- return backend.Handle(peer, res)
-
- default:
- return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
- }
-}
-
-// ServiceGetAccountRangeQuery assembles the response to an account range query.
-// It is exposed to allow external packages to test protocol behavior.
-func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePacket) ([]*AccountData, [][]byte) {
- if req.Bytes > softResponseLimit {
- req.Bytes = softResponseLimit
- }
- // Retrieve the requested state and bail out if non existent
- tr, err := trie.New(trie.StateTrieID(req.Root), chain.TrieDB())
- if err != nil {
- return nil, nil
- }
- it, err := chain.Snapshots().AccountIterator(req.Root, req.Origin)
- if err != nil {
- return nil, nil
- }
- // Iterate over the requested range and pile accounts up
- var (
- accounts []*AccountData
- size uint64
- last common.Hash
- )
- for it.Next() {
- hash, account := it.Hash(), common.CopyBytes(it.Account())
-
- // Track the returned interval for the Merkle proofs
- last = hash
-
- // Assemble the reply item
- size += uint64(common.HashLength + len(account))
- accounts = append(accounts, &AccountData{
- Hash: hash,
- Body: account,
- })
- // If we've exceeded the request threshold, abort
- if bytes.Compare(hash[:], req.Limit[:]) >= 0 {
- break
- }
- if size > req.Bytes {
- break
- }
- }
- it.Release()
-
- // Generate the Merkle proofs for the first and last account
- proof := trienode.NewProofSet()
- if err := tr.Prove(req.Origin[:], proof); err != nil {
- log.Warn("Failed to prove account range", "origin", req.Origin, "err", err)
- return nil, nil
- }
- if last != (common.Hash{}) {
- if err := tr.Prove(last[:], proof); err != nil {
- log.Warn("Failed to prove account range", "last", last, "err", err)
- return nil, nil
- }
- }
- var proofs [][]byte
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- return accounts, proofs
-}
-
-func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesPacket) ([][]*StorageData, [][]byte) {
- if req.Bytes > softResponseLimit {
- req.Bytes = softResponseLimit
- }
- // TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set?
- // TODO(karalabe): - Logging locally is not ideal as remote faults annoy the local user
- // TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional)
-
- // Calculate the hard limit at which to abort, even if mid storage trie
- hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack))
-
- // Retrieve storage ranges until the packet limit is reached
- var (
- slots [][]*StorageData
- proofs [][]byte
- size uint64
- )
- for _, account := range req.Accounts {
- // If we've exceeded the requested data limit, abort without opening
- // a new storage range (that we'd need to prove due to exceeded size)
- if size >= req.Bytes {
- break
- }
- // The first account might start from a different origin and end sooner
- var origin common.Hash
- if len(req.Origin) > 0 {
- origin, req.Origin = common.BytesToHash(req.Origin), nil
- }
- var limit = common.MaxHash
- if len(req.Limit) > 0 {
- limit, req.Limit = common.BytesToHash(req.Limit), nil
- }
- // Retrieve the requested state and bail out if non existent
- it, err := chain.Snapshots().StorageIterator(req.Root, account, origin)
- if err != nil {
- return nil, nil
- }
- // Iterate over the requested range and pile slots up
- var (
- storage []*StorageData
- last common.Hash
- abort bool
- )
- for it.Next() {
- if size >= hardLimit {
- abort = true
- break
- }
- hash, slot := it.Hash(), common.CopyBytes(it.Slot())
-
- // Track the returned interval for the Merkle proofs
- last = hash
-
- // Assemble the reply item
- size += uint64(common.HashLength + len(slot))
- storage = append(storage, &StorageData{
- Hash: hash,
- Body: slot,
- })
- // If we've exceeded the request threshold, abort
- if bytes.Compare(hash[:], limit[:]) >= 0 {
- break
- }
- }
- if len(storage) > 0 {
- slots = append(slots, storage)
- }
- it.Release()
-
- // Generate the Merkle proofs for the first and last storage slot, but
- // only if the response was capped. If the entire storage trie included
- // in the response, no need for any proofs.
- if origin != (common.Hash{}) || (abort && len(storage) > 0) {
- // Request started at a non-zero hash or was capped prematurely, add
- // the endpoint Merkle proofs
- accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), chain.TrieDB())
- if err != nil {
- return nil, nil
- }
- acc, err := accTrie.GetAccountByHash(account)
- if err != nil || acc == nil {
- return nil, nil
- }
- id := trie.StorageTrieID(req.Root, account, acc.Root)
- stTrie, err := trie.NewStateTrie(id, chain.TrieDB())
- if err != nil {
- return nil, nil
- }
- proof := trienode.NewProofSet()
- if err := stTrie.Prove(origin[:], proof); err != nil {
- log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err)
- return nil, nil
- }
- if last != (common.Hash{}) {
- if err := stTrie.Prove(last[:], proof); err != nil {
- log.Warn("Failed to prove storage range", "last", last, "err", err)
- return nil, nil
- }
- }
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- // Proof terminates the reply as proofs are only added if a node
- // refuses to serve more data (exception when a contract fetch is
- // finishing, but that's that).
- break
- }
- }
- return slots, proofs
-}
-
-// ServiceGetByteCodesQuery assembles the response to a byte codes query.
-// It is exposed to allow external packages to test protocol behavior.
-func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [][]byte {
- if req.Bytes > softResponseLimit {
- req.Bytes = softResponseLimit
- }
- if len(req.Hashes) > maxCodeLookups {
- req.Hashes = req.Hashes[:maxCodeLookups]
- }
- // Retrieve bytecodes until the packet size limit is reached
- var (
- codes [][]byte
- bytes uint64
- )
- for _, hash := range req.Hashes {
- if hash == types.EmptyCodeHash {
- // Peers should not request the empty code, but if they do, at
- // least sent them back a correct response without db lookups
- codes = append(codes, []byte{})
- } else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil {
- codes = append(codes, blob)
- bytes += uint64(len(blob))
- }
- if bytes > req.Bytes {
- break
- }
- }
- return codes
-}
-
-// ServiceGetTrieNodesQuery assembles the response to a trie nodes query.
-// It is exposed to allow external packages to test protocol behavior.
-func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, start time.Time) ([][]byte, error) {
- if req.Bytes > softResponseLimit {
- req.Bytes = softResponseLimit
- }
- // Make sure we have the state associated with the request
- triedb := chain.TrieDB()
-
- accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), triedb)
- if err != nil {
- // We don't have the requested state available, bail out
- return nil, nil
- }
- // The 'snap' might be nil, in which case we cannot serve storage slots.
- snap := chain.Snapshots().Snapshot(req.Root)
- // Retrieve trie nodes until the packet size limit is reached
- var (
- nodes [][]byte
- bytes uint64
- loads int // Trie hash expansions to count database reads
- )
- for _, pathset := range req.Paths {
- switch len(pathset) {
- case 0:
- // Ensure we penalize invalid requests
- return nil, fmt.Errorf("%w: zero-item pathset requested", errBadRequest)
-
- case 1:
- // If we're only retrieving an account trie node, fetch it directly
- blob, resolved, err := accTrie.GetNode(pathset[0])
- loads += resolved // always account database reads, even for failures
- if err != nil {
- break
- }
- nodes = append(nodes, blob)
- bytes += uint64(len(blob))
-
- default:
- var stRoot common.Hash
- // Storage slots requested, open the storage trie and retrieve from there
- if snap == nil {
- // We don't have the requested state snapshotted yet (or it is stale),
- // but can look up the account via the trie instead.
- account, err := accTrie.GetAccountByHash(common.BytesToHash(pathset[0]))
- loads += 8 // We don't know the exact cost of lookup, this is an estimate
- if err != nil || account == nil {
- break
- }
- stRoot = account.Root
- } else {
- account, err := snap.Account(common.BytesToHash(pathset[0]))
- loads++ // always account database reads, even for failures
- if err != nil || account == nil {
- break
- }
- stRoot = common.BytesToHash(account.Root)
- }
- id := trie.StorageTrieID(req.Root, common.BytesToHash(pathset[0]), stRoot)
- stTrie, err := trie.NewStateTrie(id, triedb)
- loads++ // always account database reads, even for failures
- if err != nil {
- break
- }
- for _, path := range pathset[1:] {
- blob, resolved, err := stTrie.GetNode(path)
- loads += resolved // always account database reads, even for failures
- if err != nil {
- break
- }
- nodes = append(nodes, blob)
- bytes += uint64(len(blob))
-
- // Sanity check limits to avoid DoS on the store trie loads
- if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent {
- break
- }
- }
- }
- // Abort request processing if we've exceeded our limits
- if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent {
- break
- }
- }
- return nodes, nil
-}
-
-// 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{}
-}
diff --git a/eth/protocols/snap/handler_fuzzing_test.go b/eth/protocols/snap/handler_fuzzing_test.go
deleted file mode 100644
index daed7ed44a..0000000000
--- a/eth/protocols/snap/handler_fuzzing_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "math/big"
- "testing"
- "time"
-
- "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/vm"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- fuzz "github.com/google/gofuzz"
-)
-
-func FuzzARange(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &GetAccountRangePacket{}, GetAccountRangeMsg)
- })
-}
-
-func FuzzSRange(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &GetStorageRangesPacket{}, GetStorageRangesMsg)
- })
-}
-
-func FuzzByteCodes(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &GetByteCodesPacket{}, GetByteCodesMsg)
- })
-}
-
-func FuzzTrieNodes(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &GetTrieNodesPacket{}, GetTrieNodesMsg)
- })
-}
-
-func doFuzz(input []byte, obj interface{}, code int) {
- bc := getChain()
- defer bc.Stop()
- fuzz.NewFromGoFuzz(input).Fuzz(obj)
- var data []byte
- switch p := obj.(type) {
- case *GetTrieNodesPacket:
- p.Root = trieRoot
- data, _ = rlp.EncodeToBytes(obj)
- default:
- data, _ = rlp.EncodeToBytes(obj)
- }
- cli := &dummyRW{
- code: uint64(code),
- data: data,
- }
- peer := NewFakePeer(65, "gazonk01", cli)
- err := HandleMessage(&dummyBackend{bc}, peer)
- switch {
- case err == nil && cli.writeCount != 1:
- panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
- case err != nil && cli.writeCount != 0:
- panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
- }
-}
-
-var trieRoot common.Hash
-
-func getChain() *core.BlockChain {
- ga := make(core.GenesisAlloc, 1000)
- var a = make([]byte, 20)
- var mkStorage = func(k, v int) (common.Hash, common.Hash) {
- var kB = make([]byte, 32)
- var vB = make([]byte, 32)
- binary.LittleEndian.PutUint64(kB, uint64(k))
- binary.LittleEndian.PutUint64(vB, uint64(v))
- return common.BytesToHash(kB), common.BytesToHash(vB)
- }
- storage := make(map[common.Hash]common.Hash)
- for i := 0; i < 10; i++ {
- k, v := mkStorage(i, i)
- storage[k] = v
- }
- for i := 0; i < 1000; i++ {
- binary.LittleEndian.PutUint64(a, uint64(i+0xff))
- acc := core.GenesisAccount{Balance: big.NewInt(int64(i))}
- if i%2 == 1 {
- acc.Storage = storage
- }
- ga[common.BytesToAddress(a)] = acc
- }
- gspec := &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: ga,
- }
- _, blocks, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *core.BlockGen) {})
- cacheConf := &core.CacheConfig{
- TrieCleanLimit: 0,
- TrieDirtyLimit: 0,
- TrieTimeLimit: 5 * time.Minute,
- TrieCleanNoPrefetch: true,
- SnapshotLimit: 100,
- SnapshotWait: true,
- }
- trieRoot = blocks[len(blocks)-1].Root()
- bc, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), cacheConf, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
- if _, err := bc.InsertChain(blocks); err != nil {
- panic(err)
- }
- return bc
-}
-
-type dummyBackend struct {
- chain *core.BlockChain
-}
-
-func (d *dummyBackend) Chain() *core.BlockChain { return d.chain }
-func (d *dummyBackend) RunPeer(*Peer, Handler) error { return nil }
-func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" }
-func (d *dummyBackend) Handle(*Peer, Packet) error { return nil }
-
-type dummyRW struct {
- code uint64
- data []byte
- writeCount int
-}
-
-func (d *dummyRW) ReadMsg() (p2p.Msg, error) {
- return p2p.Msg{
- Code: d.code,
- Payload: bytes.NewReader(d.data),
- ReceivedAt: time.Now(),
- Size: uint32(len(d.data)),
- }, nil
-}
-
-func (d *dummyRW) WriteMsg(msg p2p.Msg) error {
- d.writeCount++
- return nil
-}
diff --git a/eth/protocols/snap/metrics.go b/eth/protocols/snap/metrics.go
deleted file mode 100644
index a7d071953f..0000000000
--- a/eth/protocols/snap/metrics.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2023 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 (
- metrics "github.com/ethereum/go-ethereum/metrics"
-)
-
-var (
- ingressRegistrationErrorName = "eth/protocols/snap/ingress/registration/error"
- egressRegistrationErrorName = "eth/protocols/snap/egress/registration/error"
-
- IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil)
- EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil)
-
- // deletionGauge is the metric to track how many trie node deletions
- // are performed in total during the sync process.
- deletionGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete", nil)
-
- // lookupGauge is the metric to track how many trie node lookups are
- // performed to determine if node needs to be deleted.
- lookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/lookup", nil)
-
- // boundaryAccountNodesGauge is the metric to track how many boundary trie
- // nodes in account trie are met.
- boundaryAccountNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/account", nil)
-
- // boundaryAccountNodesGauge is the metric to track how many boundary trie
- // nodes in storage tries are met.
- boundaryStorageNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/storage", nil)
-
- // smallStorageGauge is the metric to track how many storages are small enough
- // to retrieved in one or two request.
- smallStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/small", nil)
-
- // largeStorageGauge is the metric to track how many storages are large enough
- // to retrieved concurrently.
- largeStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/large", nil)
-
- // skipStorageHealingGauge is the metric to track how many storages are retrieved
- // in multiple requests but healing is not necessary.
- skipStorageHealingGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/noheal", nil)
-)
diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go
deleted file mode 100644
index 3db6e22cbd..0000000000
--- a/eth/protocols/snap/peer.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// 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 (
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-// Peer is a collection of relevant information we have about a `snap` peer.
-type Peer struct {
- id string // Unique ID for the peer, cached
-
- *p2p.Peer // The embedded P2P package peer
- rw p2p.MsgReadWriter // Input/output streams for snap
- version uint // Protocol version negotiated
-
- logger log.Logger // Contextual logger with the peer id injected
-}
-
-// NewPeer create a wrapper for a network connection and negotiated protocol
-// version.
-func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
- id := p.ID().String()
- return &Peer{
- id: id,
- Peer: p,
- rw: rw,
- version: version,
- logger: log.New("peer", id[:8]),
- }
-}
-
-// NewFakePeer create a fake snap peer without a backing p2p peer, for testing purposes.
-func NewFakePeer(version uint, id string, rw p2p.MsgReadWriter) *Peer {
- return &Peer{
- id: id,
- rw: rw,
- version: version,
- logger: log.New("peer", id[:8]),
- }
-}
-
-// ID retrieves the peer's unique identifier.
-func (p *Peer) ID() string {
- return p.id
-}
-
-// Version retrieves the peer's negotiated `snap` protocol version.
-func (p *Peer) Version() uint {
- return p.version
-}
-
-// Log overrides the P2P logger with the higher level one containing only the id.
-func (p *Peer) Log() log.Logger {
- return p.logger
-}
-
-// RequestAccountRange fetches a batch of accounts rooted in a specific account
-// trie, starting with the origin.
-func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error {
- p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
-
- requestTracker.Track(p.id, p.version, GetAccountRangeMsg, AccountRangeMsg, id)
- return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{
- ID: id,
- Root: root,
- Origin: origin,
- Limit: limit,
- Bytes: bytes,
- })
-}
-
-// RequestStorageRanges fetches a batch of storage slots belonging to one or more
-// accounts. If slots from only one account is requested, an origin marker may also
-// be used to retrieve from there.
-func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
- if len(accounts) == 1 && origin != nil {
- p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
- } else {
- p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
- }
- requestTracker.Track(p.id, p.version, GetStorageRangesMsg, StorageRangesMsg, id)
- return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{
- ID: id,
- Root: root,
- Accounts: accounts,
- Origin: origin,
- Limit: limit,
- Bytes: bytes,
- })
-}
-
-// RequestByteCodes fetches a batch of bytecodes by hash.
-func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
- p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
-
- requestTracker.Track(p.id, p.version, GetByteCodesMsg, ByteCodesMsg, id)
- return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{
- ID: id,
- Hashes: hashes,
- Bytes: bytes,
- })
-}
-
-// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
-// a specific state trie.
-func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
- p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
-
- requestTracker.Track(p.id, p.version, GetTrieNodesMsg, TrieNodesMsg, id)
- return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{
- ID: id,
- Root: root,
- Paths: paths,
- Bytes: bytes,
- })
-}
diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go
deleted file mode 100644
index 0db206b081..0000000000
--- a/eth/protocols/snap/protocol.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// 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 (
- "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
-}
-
-// 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
-}
-
-// 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
-}
-
-// 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 []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
-
-// 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 }
diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go
deleted file mode 100644
index 8c98c71d50..0000000000
--- a/eth/protocols/snap/range.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/holiman/uint256"
-)
-
-// hashRange is a utility to handle ranges of hashes, Split up the
-// hash-space into sections, and 'walk' over the sections
-type hashRange struct {
- current *uint256.Int
- step *uint256.Int
-}
-
-// newHashRange creates a new hashRange, initiated at the start position,
-// and with the step set to fill the desired 'num' chunks
-func newHashRange(start common.Hash, num uint64) *hashRange {
- left := new(big.Int).Sub(hashSpace, start.Big())
- step := new(big.Int).Div(
- new(big.Int).Add(left, new(big.Int).SetUint64(num-1)),
- new(big.Int).SetUint64(num),
- )
- step256 := new(uint256.Int)
- step256.SetFromBig(step)
-
- return &hashRange{
- current: new(uint256.Int).SetBytes32(start[:]),
- step: step256,
- }
-}
-
-// Next pushes the hash range to the next interval.
-func (r *hashRange) Next() bool {
- next, overflow := new(uint256.Int).AddOverflow(r.current, r.step)
- if overflow {
- return false
- }
- r.current = next
- return true
-}
-
-// Start returns the first hash in the current interval.
-func (r *hashRange) Start() common.Hash {
- return r.current.Bytes32()
-}
-
-// End returns the last hash in the current interval.
-func (r *hashRange) End() common.Hash {
- // If the end overflows (non divisible range), return a shorter interval
- next, overflow := new(uint256.Int).AddOverflow(r.current, r.step)
- if overflow {
- return common.MaxHash
- }
- return next.SubUint64(next, 1).Bytes32()
-}
-
-// incHash returns the next hash, in lexicographical order (a.k.a plus one)
-func incHash(h common.Hash) common.Hash {
- var a uint256.Int
- a.SetBytes32(h[:])
- a.AddUint64(&a, 1)
- return common.Hash(a.Bytes32())
-}
diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go
deleted file mode 100644
index ea643f1361..0000000000
--- a/eth/protocols/snap/range_test.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-// Tests that given a starting hash and a density, the hash ranger can correctly
-// split up the remaining hash space into a fixed number of chunks.
-func TestHashRanges(t *testing.T) {
- tests := []struct {
- head common.Hash
- chunks uint64
- starts []common.Hash
- ends []common.Hash
- }{
- // Simple test case to split the entire hash range into 4 chunks
- {
- head: common.Hash{},
- chunks: 4,
- starts: []common.Hash{
- {},
- common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000"),
- common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000"),
- common.HexToHash("0xc000000000000000000000000000000000000000000000000000000000000000"),
- },
- ends: []common.Hash{
- common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- common.MaxHash,
- },
- },
- // Split a divisible part of the hash range up into 2 chunks
- {
- head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
- chunks: 2,
- starts: []common.Hash{
- {},
- common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"),
- },
- ends: []common.Hash{
- common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- common.MaxHash,
- },
- },
- // Split the entire hash range into a non divisible 3 chunks
- {
- head: common.Hash{},
- chunks: 3,
- starts: []common.Hash{
- {},
- common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555556"),
- common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
- },
- ends: []common.Hash{
- common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"),
- common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
- common.MaxHash,
- },
- },
- // Split a part of hash range into a non divisible 3 chunks
- {
- head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
- chunks: 3,
- starts: []common.Hash{
- {},
- common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
- common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555556"),
- },
- ends: []common.Hash{
- common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
- common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"),
- common.MaxHash,
- },
- },
- // Split a part of hash range into a non divisible 3 chunks, but with a
- // meaningful space size for manual verification.
- // - The head being 0xff...f0, we have 14 hashes left in the space
- // - Chunking up 14 into 3 pieces is 4.(6), but we need the ceil of 5 to avoid a micro-last-chunk
- // - Since the range is not divisible, the last interval will be shorter, capped at 0xff...f
- // - The chunk ranges thus needs to be [..0, ..5], [..6, ..b], [..c, ..f]
- {
- head: common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
- chunks: 3,
- starts: []common.Hash{
- {},
- common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
- common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
- },
- ends: []common.Hash{
- common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
- common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"),
- common.MaxHash,
- },
- },
- }
- for i, tt := range tests {
- r := newHashRange(tt.head, tt.chunks)
-
- var (
- starts = []common.Hash{{}}
- ends = []common.Hash{r.End()}
- )
- for r.Next() {
- starts = append(starts, r.Start())
- ends = append(ends, r.End())
- }
- if len(starts) != len(tt.starts) {
- t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts))
- }
- for j := 0; j < len(starts) && j < len(tt.starts); j++ {
- if starts[j] != tt.starts[j] {
- t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j])
- }
- }
- if len(ends) != len(tt.ends) {
- t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends))
- }
- for j := 0; j < len(ends) && j < len(tt.ends); j++ {
- if ends[j] != tt.ends[j] {
- t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j])
- }
- }
- }
-}
diff --git a/eth/protocols/snap/sort_test.go b/eth/protocols/snap/sort_test.go
deleted file mode 100644
index be0a8c5706..0000000000
--- a/eth/protocols/snap/sort_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2022 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 (
- "bytes"
- "fmt"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-func hexToNibbles(s string) []byte {
- if len(s) >= 2 && s[0] == '0' && s[1] == 'x' {
- s = s[2:]
- }
- var s2 []byte
- for _, ch := range []byte(s) {
- s2 = append(s2, '0')
- s2 = append(s2, ch)
- }
- return common.Hex2Bytes(string(s2))
-}
-
-func TestRequestSorting(t *testing.T) {
- // - Path 0x9 -> {0x19}
- // - Path 0x99 -> {0x0099}
- // - Path 0x01234567890123456789012345678901012345678901234567890123456789019 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19}
- // - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099}
- var f = func(path string) string {
- data := hexToNibbles(path)
- return string(data)
- }
- var (
- hashes []common.Hash
- paths []string
- )
- for _, x := range []string{
- "0x9",
- "0x012345678901234567890123456789010123456789012345678901234567890195",
- "0x012345678901234567890123456789010123456789012345678901234567890197",
- "0x012345678901234567890123456789010123456789012345678901234567890196",
- "0x99",
- "0x012345678901234567890123456789010123456789012345678901234567890199",
- "0x01234567890123456789012345678901012345678901234567890123456789019",
- "0x0123456789012345678901234567890101234567890123456789012345678901",
- "0x01234567890123456789012345678901012345678901234567890123456789010",
- "0x01234567890123456789012345678901012345678901234567890123456789011",
- } {
- paths = append(paths, f(x))
- hashes = append(hashes, common.Hash{})
- }
- _, _, syncPaths, pathsets := sortByAccountPath(paths, hashes)
- {
- var b = new(bytes.Buffer)
- for i := 0; i < len(syncPaths); i++ {
- fmt.Fprintf(b, "\n%d. paths %x", i, syncPaths[i])
- }
- want := `
-0. paths [0099]
-1. paths [0123456789012345678901234567890101234567890123456789012345678901 00]
-2. paths [0123456789012345678901234567890101234567890123456789012345678901 0095]
-3. paths [0123456789012345678901234567890101234567890123456789012345678901 0096]
-4. paths [0123456789012345678901234567890101234567890123456789012345678901 0097]
-5. paths [0123456789012345678901234567890101234567890123456789012345678901 0099]
-6. paths [0123456789012345678901234567890101234567890123456789012345678901 10]
-7. paths [0123456789012345678901234567890101234567890123456789012345678901 11]
-8. paths [0123456789012345678901234567890101234567890123456789012345678901 19]
-9. paths [19]`
- if have := b.String(); have != want {
- t.Errorf("have:%v\nwant:%v\n", have, want)
- }
- }
- {
- var b = new(bytes.Buffer)
- for i := 0; i < len(pathsets); i++ {
- fmt.Fprintf(b, "\n%d. pathset %x", i, pathsets[i])
- }
- want := `
-0. pathset [0099]
-1. pathset [0123456789012345678901234567890101234567890123456789012345678901 00 0095 0096 0097 0099 10 11 19]
-2. pathset [19]`
- if have := b.String(); have != want {
- t.Errorf("have:%v\nwant:%v\n", have, want)
- }
- }
-}
diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go
deleted file mode 100644
index 887a50775d..0000000000
--- a/eth/protocols/snap/sync.go
+++ /dev/null
@@ -1,3210 +0,0 @@
-// 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 (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- gomath "math"
- "math/big"
- "math/rand"
- "sort"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/msgrate"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "golang.org/x/crypto/sha3"
-)
-
-const (
- // minRequestSize is the minimum number of bytes to request from a remote peer.
- // This number is used as the low cap for account and storage range requests.
- // Bytecode and trienode are limited inherently by item count (1).
- minRequestSize = 64 * 1024
-
- // maxRequestSize is the maximum number of bytes to request from a remote peer.
- // This number is used as the high cap for account and storage range requests.
- // Bytecode and trienode are limited more explicitly by the caps below.
- maxRequestSize = 512 * 1024
-
- // maxCodeRequestCount is the maximum number of bytecode blobs to request in a
- // single query. If this number is too low, we're not filling responses fully
- // and waste round trip times. If it's too high, we're capping responses and
- // waste bandwidth.
- //
- // Deployed bytecodes are currently capped at 24KB, so the minimum request
- // size should be maxRequestSize / 24K. Assuming that most contracts do not
- // come close to that, requesting 4x should be a good approximation.
- maxCodeRequestCount = maxRequestSize / (24 * 1024) * 4
-
- // maxTrieRequestCount is the maximum number of trie node blobs to request in
- // a single query. If this number is too low, we're not filling responses fully
- // and waste round trip times. If it's too high, we're capping responses and
- // waste bandwidth.
- maxTrieRequestCount = maxRequestSize / 512
-
- // trienodeHealRateMeasurementImpact is the impact a single measurement has on
- // the local node's trienode processing capacity. A value closer to 0 reacts
- // slower to sudden changes, but it is also more stable against temporary hiccups.
- trienodeHealRateMeasurementImpact = 0.005
-
- // minTrienodeHealThrottle is the minimum divisor for throttling trie node
- // heal requests to avoid overloading the local node and excessively expanding
- // the state trie breadth wise.
- minTrienodeHealThrottle = 1
-
- // maxTrienodeHealThrottle is the maximum divisor for throttling trie node
- // heal requests to avoid overloading the local node and exessively expanding
- // the state trie bedth wise.
- maxTrienodeHealThrottle = maxTrieRequestCount
-
- // trienodeHealThrottleIncrease is the multiplier for the throttle when the
- // rate of arriving data is higher than the rate of processing it.
- trienodeHealThrottleIncrease = 1.33
-
- // trienodeHealThrottleDecrease is the divisor for the throttle when the
- // rate of arriving data is lower than the rate of processing it.
- trienodeHealThrottleDecrease = 1.25
-)
-
-var (
- // accountConcurrency is the number of chunks to split the account trie into
- // to allow concurrent retrievals.
- accountConcurrency = 16
-
- // storageConcurrency is the number of chunks to split the a large contract
- // storage trie into to allow concurrent retrievals.
- storageConcurrency = 16
-)
-
-// ErrCancelled is returned from snap syncing if the operation was prematurely
-// terminated.
-var ErrCancelled = errors.New("sync cancelled")
-
-// accountRequest tracks a pending account range request to ensure responses are
-// to actual requests and to validate any security constraints.
-//
-// Concurrency note: account requests and responses are handled concurrently from
-// the main runloop to allow Merkle proof verifications on the peer's thread and
-// to drop on invalid response. The request struct must contain all the data to
-// construct the response without accessing runloop internals (i.e. task). That
-// is only included to allow the runloop to match a response to the task being
-// synced without having yet another set of maps.
-type accountRequest struct {
- peer string // Peer to which this request is assigned
- id uint64 // Request ID of this request
- time time.Time // Timestamp when the request was sent
-
- deliver chan *accountResponse // Channel to deliver successful response on
- revert chan *accountRequest // Channel to deliver request failure on
- cancel chan struct{} // Channel to track sync cancellation
- timeout *time.Timer // Timer to track delivery timeout
- stale chan struct{} // Channel to signal the request was dropped
-
- origin common.Hash // First account requested to allow continuation checks
- limit common.Hash // Last account requested to allow non-overlapping chunking
-
- task *accountTask // Task which this request is filling (only access fields through the runloop!!)
-}
-
-// accountResponse is an already Merkle-verified remote response to an account
-// range request. It contains the subtrie for the requested account range and
-// the database that's going to be filled with the internal nodes on commit.
-type accountResponse struct {
- task *accountTask // Task which this request is filling
-
- hashes []common.Hash // Account hashes in the returned range
- accounts []*types.StateAccount // Expanded accounts in the returned range
-
- cont bool // Whether the account range has a continuation
-}
-
-// bytecodeRequest tracks a pending bytecode request to ensure responses are to
-// actual requests and to validate any security constraints.
-//
-// Concurrency note: bytecode requests and responses are handled concurrently from
-// the main runloop to allow Keccak256 hash verifications on the peer's thread and
-// to drop on invalid response. The request struct must contain all the data to
-// construct the response without accessing runloop internals (i.e. task). That
-// is only included to allow the runloop to match a response to the task being
-// synced without having yet another set of maps.
-type bytecodeRequest struct {
- peer string // Peer to which this request is assigned
- id uint64 // Request ID of this request
- time time.Time // Timestamp when the request was sent
-
- deliver chan *bytecodeResponse // Channel to deliver successful response on
- revert chan *bytecodeRequest // Channel to deliver request failure on
- cancel chan struct{} // Channel to track sync cancellation
- timeout *time.Timer // Timer to track delivery timeout
- stale chan struct{} // Channel to signal the request was dropped
-
- hashes []common.Hash // Bytecode hashes to validate responses
- task *accountTask // Task which this request is filling (only access fields through the runloop!!)
-}
-
-// bytecodeResponse is an already verified remote response to a bytecode request.
-type bytecodeResponse struct {
- task *accountTask // Task which this request is filling
-
- hashes []common.Hash // Hashes of the bytecode to avoid double hashing
- codes [][]byte // Actual bytecodes to store into the database (nil = missing)
-}
-
-// storageRequest tracks a pending storage ranges request to ensure responses are
-// to actual requests and to validate any security constraints.
-//
-// Concurrency note: storage requests and responses are handled concurrently from
-// the main runloop to allow Merkle proof verifications on the peer's thread and
-// to drop on invalid response. The request struct must contain all the data to
-// construct the response without accessing runloop internals (i.e. tasks). That
-// is only included to allow the runloop to match a response to the task being
-// synced without having yet another set of maps.
-type storageRequest struct {
- peer string // Peer to which this request is assigned
- id uint64 // Request ID of this request
- time time.Time // Timestamp when the request was sent
-
- deliver chan *storageResponse // Channel to deliver successful response on
- revert chan *storageRequest // Channel to deliver request failure on
- cancel chan struct{} // Channel to track sync cancellation
- timeout *time.Timer // Timer to track delivery timeout
- stale chan struct{} // Channel to signal the request was dropped
-
- accounts []common.Hash // Account hashes to validate responses
- roots []common.Hash // Storage roots to validate responses
-
- origin common.Hash // First storage slot requested to allow continuation checks
- limit common.Hash // Last storage slot requested to allow non-overlapping chunking
-
- mainTask *accountTask // Task which this response belongs to (only access fields through the runloop!!)
- subTask *storageTask // Task which this response is filling (only access fields through the runloop!!)
-}
-
-// storageResponse is an already Merkle-verified remote response to a storage
-// range request. It contains the subtries for the requested storage ranges and
-// the databases that's going to be filled with the internal nodes on commit.
-type storageResponse struct {
- mainTask *accountTask // Task which this response belongs to
- subTask *storageTask // Task which this response is filling
-
- accounts []common.Hash // Account hashes requested, may be only partially filled
- roots []common.Hash // Storage roots requested, may be only partially filled
-
- hashes [][]common.Hash // Storage slot hashes in the returned range
- slots [][][]byte // Storage slot values in the returned range
-
- cont bool // Whether the last storage range has a continuation
-}
-
-// trienodeHealRequest tracks a pending state trie request to ensure responses
-// are to actual requests and to validate any security constraints.
-//
-// Concurrency note: trie node requests and responses are handled concurrently from
-// the main runloop to allow Keccak256 hash verifications on the peer's thread and
-// to drop on invalid response. The request struct must contain all the data to
-// construct the response without accessing runloop internals (i.e. task). That
-// is only included to allow the runloop to match a response to the task being
-// synced without having yet another set of maps.
-type trienodeHealRequest struct {
- peer string // Peer to which this request is assigned
- id uint64 // Request ID of this request
- time time.Time // Timestamp when the request was sent
-
- deliver chan *trienodeHealResponse // Channel to deliver successful response on
- revert chan *trienodeHealRequest // Channel to deliver request failure on
- cancel chan struct{} // Channel to track sync cancellation
- timeout *time.Timer // Timer to track delivery timeout
- stale chan struct{} // Channel to signal the request was dropped
-
- paths []string // Trie node paths for identifying trie node
- hashes []common.Hash // Trie node hashes to validate responses
-
- task *healTask // Task which this request is filling (only access fields through the runloop!!)
-}
-
-// trienodeHealResponse is an already verified remote response to a trie node request.
-type trienodeHealResponse struct {
- task *healTask // Task which this request is filling
-
- paths []string // Paths of the trie nodes
- hashes []common.Hash // Hashes of the trie nodes to avoid double hashing
- nodes [][]byte // Actual trie nodes to store into the database (nil = missing)
-}
-
-// bytecodeHealRequest tracks a pending bytecode request to ensure responses are to
-// actual requests and to validate any security constraints.
-//
-// Concurrency note: bytecode requests and responses are handled concurrently from
-// the main runloop to allow Keccak256 hash verifications on the peer's thread and
-// to drop on invalid response. The request struct must contain all the data to
-// construct the response without accessing runloop internals (i.e. task). That
-// is only included to allow the runloop to match a response to the task being
-// synced without having yet another set of maps.
-type bytecodeHealRequest struct {
- peer string // Peer to which this request is assigned
- id uint64 // Request ID of this request
- time time.Time // Timestamp when the request was sent
-
- deliver chan *bytecodeHealResponse // Channel to deliver successful response on
- revert chan *bytecodeHealRequest // Channel to deliver request failure on
- cancel chan struct{} // Channel to track sync cancellation
- timeout *time.Timer // Timer to track delivery timeout
- stale chan struct{} // Channel to signal the request was dropped
-
- hashes []common.Hash // Bytecode hashes to validate responses
- task *healTask // Task which this request is filling (only access fields through the runloop!!)
-}
-
-// bytecodeHealResponse is an already verified remote response to a bytecode request.
-type bytecodeHealResponse struct {
- task *healTask // Task which this request is filling
-
- hashes []common.Hash // Hashes of the bytecode to avoid double hashing
- codes [][]byte // Actual bytecodes to store into the database (nil = missing)
-}
-
-// accountTask represents the sync task for a chunk of the account snapshot.
-type accountTask struct {
- // These fields get serialized to leveldb on shutdown
- Next common.Hash // Next account to sync in this interval
- Last common.Hash // Last account to sync in this interval
- SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts
-
- // These fields are internals used during runtime
- req *accountRequest // Pending request to fill this task
- res *accountResponse // Validate response filling this task
- pend int // Number of pending subtasks for this round
-
- needCode []bool // Flags whether the filling accounts need code retrieval
- needState []bool // Flags whether the filling accounts need storage retrieval
- needHeal []bool // Flags whether the filling accounts's state was chunked and need healing
-
- codeTasks map[common.Hash]struct{} // Code hashes that need retrieval
- stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval
-
- genBatch ethdb.Batch // Batch used by the node generator
- genTrie *trie.StackTrie // Node generator from storage slots
-
- done bool // Flag whether the task can be removed
-}
-
-// storageTask represents the sync task for a chunk of the storage snapshot.
-type storageTask struct {
- Next common.Hash // Next account to sync in this interval
- Last common.Hash // Last account to sync in this interval
-
- // These fields are internals used during runtime
- root common.Hash // Storage root hash for this instance
- req *storageRequest // Pending request to fill this task
-
- genBatch ethdb.Batch // Batch used by the node generator
- genTrie *trie.StackTrie // Node generator from storage slots
-
- done bool // Flag whether the task can be removed
-}
-
-// healTask represents the sync task for healing the snap-synced chunk boundaries.
-type healTask struct {
- scheduler *trie.Sync // State trie sync scheduler defining the tasks
-
- trieTasks map[string]common.Hash // Set of trie node tasks currently queued for retrieval, indexed by node path
- codeTasks map[common.Hash]struct{} // Set of byte code tasks currently queued for retrieval, indexed by code hash
-}
-
-// SyncProgress is a database entry to allow suspending and resuming a snapshot state
-// sync. Opposed to full and fast sync, there is no way to restart a suspended
-// snap sync without prior knowledge of the suspension point.
-type SyncProgress struct {
- Tasks []*accountTask // The suspended account tasks (contract tasks within)
-
- // Status report during syncing phase
- AccountSynced uint64 // Number of accounts downloaded
- AccountBytes common.StorageSize // Number of account trie bytes persisted to disk
- BytecodeSynced uint64 // Number of bytecodes downloaded
- BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded
- StorageSynced uint64 // Number of storage slots downloaded
- StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk
-
- // Status report during healing phase
- TrienodeHealSynced uint64 // Number of state trie nodes downloaded
- TrienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk
- BytecodeHealSynced uint64 // Number of bytecodes downloaded
- BytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk
-}
-
-// SyncPending is analogous to SyncProgress, but it's used to report on pending
-// ephemeral sync progress that doesn't get persisted into the database.
-type SyncPending struct {
- TrienodeHeal uint64 // Number of state trie nodes pending
- BytecodeHeal uint64 // Number of bytecodes pending
-}
-
-// SyncPeer abstracts out the methods required for a peer to be synced against
-// with the goal of allowing the construction of mock peers without the full
-// blown networking.
-type SyncPeer interface {
- // ID retrieves the peer's unique identifier.
- ID() string
-
- // RequestAccountRange fetches a batch of accounts rooted in a specific account
- // trie, starting with the origin.
- RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error
-
- // RequestStorageRanges fetches a batch of storage slots belonging to one or
- // more accounts. If slots from only one account is requested, an origin marker
- // may also be used to retrieve from there.
- RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error
-
- // RequestByteCodes fetches a batch of bytecodes by hash.
- RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error
-
- // RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
- // a specific state trie.
- RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error
-
- // Log retrieves the peer's own contextual logger.
- Log() log.Logger
-}
-
-// Syncer is an Ethereum account and storage trie syncer based on snapshots and
-// the snap protocol. It's purpose is to download all the accounts and storage
-// slots from remote peers and reassemble chunks of the state trie, on top of
-// which a state sync can be run to fix any gaps / overlaps.
-//
-// Every network request has a variety of failure events:
-// - The peer disconnects after task assignment, failing to send the request
-// - The peer disconnects after sending the request, before delivering on it
-// - The peer remains connected, but does not deliver a response in time
-// - The peer delivers a stale response after a previous timeout
-// - The peer delivers a refusal to serve the requested state
-type Syncer struct {
- db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup)
- scheme string // Node scheme used in node database
-
- root common.Hash // Current state trie root being synced
- tasks []*accountTask // Current account task set being synced
- snapped bool // Flag to signal that snap phase is done
- healer *healTask // Current state healing task being executed
- update chan struct{} // Notification channel for possible sync progression
-
- peers map[string]SyncPeer // Currently active peers to download from
- peerJoin *event.Feed // Event feed to react to peers joining
- peerDrop *event.Feed // Event feed to react to peers dropping
- rates *msgrate.Trackers // Message throughput rates for peers
-
- // Request tracking during syncing phase
- statelessPeers map[string]struct{} // Peers that failed to deliver state data
- accountIdlers map[string]struct{} // Peers that aren't serving account requests
- bytecodeIdlers map[string]struct{} // Peers that aren't serving bytecode requests
- storageIdlers map[string]struct{} // Peers that aren't serving storage requests
-
- accountReqs map[uint64]*accountRequest // Account requests currently running
- bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running
- storageReqs map[uint64]*storageRequest // Storage requests currently running
-
- accountSynced uint64 // Number of accounts downloaded
- accountBytes common.StorageSize // Number of account trie bytes persisted to disk
- bytecodeSynced uint64 // Number of bytecodes downloaded
- bytecodeBytes common.StorageSize // Number of bytecode bytes downloaded
- storageSynced uint64 // Number of storage slots downloaded
- storageBytes common.StorageSize // Number of storage trie bytes persisted to disk
-
- extProgress *SyncProgress // progress that can be exposed to external caller.
-
- // Request tracking during healing phase
- trienodeHealIdlers map[string]struct{} // Peers that aren't serving trie node requests
- bytecodeHealIdlers map[string]struct{} // Peers that aren't serving bytecode requests
-
- trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running
- bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running
-
- trienodeHealRate float64 // Average heal rate for processing trie node data
- trienodeHealPend atomic.Uint64 // Number of trie nodes currently pending for processing
- trienodeHealThrottle float64 // Divisor for throttling the amount of trienode heal data requested
- trienodeHealThrottled time.Time // Timestamp the last time the throttle was updated
-
- trienodeHealSynced uint64 // Number of state trie nodes downloaded
- trienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk
- trienodeHealDups uint64 // Number of state trie nodes already processed
- trienodeHealNops uint64 // Number of state trie nodes not requested
- bytecodeHealSynced uint64 // Number of bytecodes downloaded
- bytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk
- bytecodeHealDups uint64 // Number of bytecodes already processed
- bytecodeHealNops uint64 // Number of bytecodes not requested
-
- stateWriter ethdb.Batch // Shared batch writer used for persisting raw states
- accountHealed uint64 // Number of accounts downloaded during the healing stage
- accountHealedBytes common.StorageSize // Number of raw account bytes persisted to disk during the healing stage
- storageHealed uint64 // Number of storage slots downloaded during the healing stage
- storageHealedBytes common.StorageSize // Number of raw storage bytes persisted to disk during the healing stage
-
- startTime time.Time // Time instance when snapshot sync started
- logTime time.Time // Time instance when status was last reported
-
- pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown
- lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root)
-}
-
-// NewSyncer creates a new snapshot syncer to download the Ethereum state over the
-// snap protocol.
-func NewSyncer(db ethdb.KeyValueStore, scheme string) *Syncer {
- return &Syncer{
- db: db,
- scheme: scheme,
-
- peers: make(map[string]SyncPeer),
- peerJoin: new(event.Feed),
- peerDrop: new(event.Feed),
- rates: msgrate.NewTrackers(log.New("proto", "snap")),
- update: make(chan struct{}, 1),
-
- accountIdlers: make(map[string]struct{}),
- storageIdlers: make(map[string]struct{}),
- bytecodeIdlers: make(map[string]struct{}),
-
- accountReqs: make(map[uint64]*accountRequest),
- storageReqs: make(map[uint64]*storageRequest),
- bytecodeReqs: make(map[uint64]*bytecodeRequest),
-
- trienodeHealIdlers: make(map[string]struct{}),
- bytecodeHealIdlers: make(map[string]struct{}),
-
- trienodeHealReqs: make(map[uint64]*trienodeHealRequest),
- bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest),
- trienodeHealThrottle: maxTrienodeHealThrottle, // Tune downward instead of insta-filling with junk
- stateWriter: db.NewBatch(),
-
- extProgress: new(SyncProgress),
- }
-}
-
-// Register injects a new data source into the syncer's peerset.
-func (s *Syncer) Register(peer SyncPeer) error {
- // Make sure the peer is not registered yet
- id := peer.ID()
-
- s.lock.Lock()
- if _, ok := s.peers[id]; ok {
- log.Error("Snap peer already registered", "id", id)
-
- s.lock.Unlock()
- return errors.New("already registered")
- }
- s.peers[id] = peer
- s.rates.Track(id, msgrate.NewTracker(s.rates.MeanCapacities(), s.rates.MedianRoundTrip()))
-
- // Mark the peer as idle, even if no sync is running
- s.accountIdlers[id] = struct{}{}
- s.storageIdlers[id] = struct{}{}
- s.bytecodeIdlers[id] = struct{}{}
- s.trienodeHealIdlers[id] = struct{}{}
- s.bytecodeHealIdlers[id] = struct{}{}
- s.lock.Unlock()
-
- // Notify any active syncs that a new peer can be assigned data
- s.peerJoin.Send(id)
- return nil
-}
-
-// Unregister injects a new data source into the syncer's peerset.
-func (s *Syncer) Unregister(id string) error {
- // Remove all traces of the peer from the registry
- s.lock.Lock()
- if _, ok := s.peers[id]; !ok {
- log.Error("Snap peer not registered", "id", id)
-
- s.lock.Unlock()
- return errors.New("not registered")
- }
- delete(s.peers, id)
- s.rates.Untrack(id)
-
- // Remove status markers, even if no sync is running
- delete(s.statelessPeers, id)
-
- delete(s.accountIdlers, id)
- delete(s.storageIdlers, id)
- delete(s.bytecodeIdlers, id)
- delete(s.trienodeHealIdlers, id)
- delete(s.bytecodeHealIdlers, id)
- s.lock.Unlock()
-
- // Notify any active syncs that pending requests need to be reverted
- s.peerDrop.Send(id)
- return nil
-}
-
-// Sync starts (or resumes a previous) sync cycle to iterate over a state trie
-// with the given root and reconstruct the nodes based on the snapshot leaves.
-// Previously downloaded segments will not be redownloaded of fixed, rather any
-// errors will be healed after the leaves are fully accumulated.
-func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error {
- // Move the trie root from any previous value, revert stateless markers for
- // any peers and initialize the syncer if it was not yet run
- s.lock.Lock()
- s.root = root
- s.healer = &healTask{
- scheduler: state.NewStateSync(root, s.db, s.onHealState, s.scheme),
- trieTasks: make(map[string]common.Hash),
- codeTasks: make(map[common.Hash]struct{}),
- }
- s.statelessPeers = make(map[string]struct{})
- s.lock.Unlock()
-
- if s.startTime == (time.Time{}) {
- s.startTime = time.Now()
- }
- // Retrieve the previous sync status from LevelDB and abort if already synced
- s.loadSyncStatus()
- if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 {
- log.Debug("Snapshot sync already completed")
- return nil
- }
- defer func() { // Persist any progress, independent of failure
- for _, task := range s.tasks {
- s.forwardAccountTask(task)
- }
- s.cleanAccountTasks()
- s.saveSyncStatus()
- }()
-
- log.Debug("Starting snapshot sync cycle", "root", root)
-
- // Flush out the last committed raw states
- defer func() {
- if s.stateWriter.ValueSize() > 0 {
- s.stateWriter.Write()
- s.stateWriter.Reset()
- }
- }()
- defer s.report(true)
- // commit any trie- and bytecode-healing data.
- defer s.commitHealer(true)
-
- // Whether sync completed or not, disregard any future packets
- defer func() {
- log.Debug("Terminating snapshot sync cycle", "root", root)
- s.lock.Lock()
- s.accountReqs = make(map[uint64]*accountRequest)
- s.storageReqs = make(map[uint64]*storageRequest)
- s.bytecodeReqs = make(map[uint64]*bytecodeRequest)
- s.trienodeHealReqs = make(map[uint64]*trienodeHealRequest)
- s.bytecodeHealReqs = make(map[uint64]*bytecodeHealRequest)
- s.lock.Unlock()
- }()
- // Keep scheduling sync tasks
- peerJoin := make(chan string, 16)
- peerJoinSub := s.peerJoin.Subscribe(peerJoin)
- defer peerJoinSub.Unsubscribe()
-
- peerDrop := make(chan string, 16)
- peerDropSub := s.peerDrop.Subscribe(peerDrop)
- defer peerDropSub.Unsubscribe()
-
- // Create a set of unique channels for this sync cycle. We need these to be
- // ephemeral so a data race doesn't accidentally deliver something stale on
- // a persistent channel across syncs (yup, this happened)
- var (
- accountReqFails = make(chan *accountRequest)
- storageReqFails = make(chan *storageRequest)
- bytecodeReqFails = make(chan *bytecodeRequest)
- accountResps = make(chan *accountResponse)
- storageResps = make(chan *storageResponse)
- bytecodeResps = make(chan *bytecodeResponse)
- trienodeHealReqFails = make(chan *trienodeHealRequest)
- bytecodeHealReqFails = make(chan *bytecodeHealRequest)
- trienodeHealResps = make(chan *trienodeHealResponse)
- bytecodeHealResps = make(chan *bytecodeHealResponse)
- )
- for {
- // Remove all completed tasks and terminate sync if everything's done
- s.cleanStorageTasks()
- s.cleanAccountTasks()
- if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 {
- return nil
- }
- // Assign all the data retrieval tasks to any free peers
- s.assignAccountTasks(accountResps, accountReqFails, cancel)
- s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel)
- s.assignStorageTasks(storageResps, storageReqFails, cancel)
-
- if len(s.tasks) == 0 {
- // Sync phase done, run heal phase
- s.assignTrienodeHealTasks(trienodeHealResps, trienodeHealReqFails, cancel)
- s.assignBytecodeHealTasks(bytecodeHealResps, bytecodeHealReqFails, cancel)
- }
- // Update sync progress
- s.lock.Lock()
- s.extProgress = &SyncProgress{
- AccountSynced: s.accountSynced,
- AccountBytes: s.accountBytes,
- BytecodeSynced: s.bytecodeSynced,
- BytecodeBytes: s.bytecodeBytes,
- StorageSynced: s.storageSynced,
- StorageBytes: s.storageBytes,
- TrienodeHealSynced: s.trienodeHealSynced,
- TrienodeHealBytes: s.trienodeHealBytes,
- BytecodeHealSynced: s.bytecodeHealSynced,
- BytecodeHealBytes: s.bytecodeHealBytes,
- }
- s.lock.Unlock()
- // Wait for something to happen
- select {
- case <-s.update:
- // Something happened (new peer, delivery, timeout), recheck tasks
- case <-peerJoin:
- // A new peer joined, try to schedule it new tasks
- case id := <-peerDrop:
- s.revertRequests(id)
- case <-cancel:
- return ErrCancelled
-
- case req := <-accountReqFails:
- s.revertAccountRequest(req)
- case req := <-bytecodeReqFails:
- s.revertBytecodeRequest(req)
- case req := <-storageReqFails:
- s.revertStorageRequest(req)
- case req := <-trienodeHealReqFails:
- s.revertTrienodeHealRequest(req)
- case req := <-bytecodeHealReqFails:
- s.revertBytecodeHealRequest(req)
-
- case res := <-accountResps:
- s.processAccountResponse(res)
- case res := <-bytecodeResps:
- s.processBytecodeResponse(res)
- case res := <-storageResps:
- s.processStorageResponse(res)
- case res := <-trienodeHealResps:
- s.processTrienodeHealResponse(res)
- case res := <-bytecodeHealResps:
- s.processBytecodeHealResponse(res)
- }
- // Report stats if something meaningful happened
- s.report(false)
- }
-}
-
-// cleanPath is used to remove the dangling nodes in the stackTrie.
-func (s *Syncer) cleanPath(batch ethdb.Batch, owner common.Hash, path []byte) {
- if owner == (common.Hash{}) && rawdb.ExistsAccountTrieNode(s.db, path) {
- rawdb.DeleteAccountTrieNode(batch, path)
- deletionGauge.Inc(1)
- }
- if owner != (common.Hash{}) && rawdb.ExistsStorageTrieNode(s.db, owner, path) {
- rawdb.DeleteStorageTrieNode(batch, owner, path)
- deletionGauge.Inc(1)
- }
- lookupGauge.Inc(1)
-}
-
-// loadSyncStatus retrieves a previously aborted sync status from the database,
-// or generates a fresh one if none is available.
-func (s *Syncer) loadSyncStatus() {
- var progress SyncProgress
-
- if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil {
- if err := json.Unmarshal(status, &progress); err != nil {
- log.Error("Failed to decode snap sync status", "err", err)
- } else {
- for _, task := range progress.Tasks {
- log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last)
- }
- s.tasks = progress.Tasks
- for _, task := range s.tasks {
- task := task // closure for task.genBatch in the stacktrie writer callback
-
- task.genBatch = ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.accountBytes += common.StorageSize(len(key) + len(value))
- },
- }
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(task.genBatch, common.Hash{}, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- // Configure the dangling node cleaner and also filter out boundary nodes
- // only in the context of the path scheme. Deletion is forbidden in the
- // hash scheme, as it can disrupt state completeness.
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(task.genBatch, common.Hash{}, path)
- })
- // Skip the left boundary if it's not the first range.
- // Skip the right boundary if it's not the last range.
- options = options.WithSkipBoundary(task.Next != (common.Hash{}), task.Last != common.MaxHash, boundaryAccountNodesGauge)
- }
- task.genTrie = trie.NewStackTrie(options)
- for accountHash, subtasks := range task.SubTasks {
- for _, subtask := range subtasks {
- subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback
-
- subtask.genBatch = ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.storageBytes += common.StorageSize(len(key) + len(value))
- },
- }
- owner := accountHash // local assignment for stacktrie writer closure
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- // Configure the dangling node cleaner and also filter out boundary nodes
- // only in the context of the path scheme. Deletion is forbidden in the
- // hash scheme, as it can disrupt state completeness.
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(subtask.genBatch, owner, path)
- })
- // Skip the left boundary if it's not the first range.
- // Skip the right boundary if it's not the last range.
- options = options.WithSkipBoundary(subtask.Next != common.Hash{}, subtask.Last != common.MaxHash, boundaryStorageNodesGauge)
- }
- subtask.genTrie = trie.NewStackTrie(options)
- }
- }
- }
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.snapped = len(s.tasks) == 0
-
- s.accountSynced = progress.AccountSynced
- s.accountBytes = progress.AccountBytes
- s.bytecodeSynced = progress.BytecodeSynced
- s.bytecodeBytes = progress.BytecodeBytes
- s.storageSynced = progress.StorageSynced
- s.storageBytes = progress.StorageBytes
-
- s.trienodeHealSynced = progress.TrienodeHealSynced
- s.trienodeHealBytes = progress.TrienodeHealBytes
- s.bytecodeHealSynced = progress.BytecodeHealSynced
- s.bytecodeHealBytes = progress.BytecodeHealBytes
- return
- }
- }
- // Either we've failed to decode the previous state, or there was none.
- // Start a fresh sync by chunking up the account range and scheduling
- // them for retrieval.
- s.tasks = nil
- s.accountSynced, s.accountBytes = 0, 0
- s.bytecodeSynced, s.bytecodeBytes = 0, 0
- s.storageSynced, s.storageBytes = 0, 0
- s.trienodeHealSynced, s.trienodeHealBytes = 0, 0
- s.bytecodeHealSynced, s.bytecodeHealBytes = 0, 0
-
- var next common.Hash
- step := new(big.Int).Sub(
- new(big.Int).Div(
- new(big.Int).Exp(common.Big2, common.Big256, nil),
- big.NewInt(int64(accountConcurrency)),
- ), common.Big1,
- )
- for i := 0; i < accountConcurrency; i++ {
- last := common.BigToHash(new(big.Int).Add(next.Big(), step))
- if i == accountConcurrency-1 {
- // Make sure we don't overflow if the step is not a proper divisor
- last = common.MaxHash
- }
- batch := ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.accountBytes += common.StorageSize(len(key) + len(value))
- },
- }
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- // Configure the dangling node cleaner and also filter out boundary nodes
- // only in the context of the path scheme. Deletion is forbidden in the
- // hash scheme, as it can disrupt state completeness.
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(batch, common.Hash{}, path)
- })
- // Skip the left boundary if it's not the first range.
- // Skip the right boundary if it's not the last range.
- options = options.WithSkipBoundary(next != common.Hash{}, last != common.MaxHash, boundaryAccountNodesGauge)
- }
- s.tasks = append(s.tasks, &accountTask{
- Next: next,
- Last: last,
- SubTasks: make(map[common.Hash][]*storageTask),
- genBatch: batch,
- genTrie: trie.NewStackTrie(options),
- })
- log.Debug("Created account sync task", "from", next, "last", last)
- next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
- }
-}
-
-// saveSyncStatus marshals the remaining sync tasks into leveldb.
-func (s *Syncer) saveSyncStatus() {
- // Serialize any partial progress to disk before spinning down
- for _, task := range s.tasks {
- if err := task.genBatch.Write(); err != nil {
- log.Error("Failed to persist account slots", "err", err)
- }
- for _, subtasks := range task.SubTasks {
- for _, subtask := range subtasks {
- if err := subtask.genBatch.Write(); err != nil {
- log.Error("Failed to persist storage slots", "err", err)
- }
- }
- }
- }
- // Store the actual progress markers
- progress := &SyncProgress{
- Tasks: s.tasks,
- AccountSynced: s.accountSynced,
- AccountBytes: s.accountBytes,
- BytecodeSynced: s.bytecodeSynced,
- BytecodeBytes: s.bytecodeBytes,
- StorageSynced: s.storageSynced,
- StorageBytes: s.storageBytes,
- TrienodeHealSynced: s.trienodeHealSynced,
- TrienodeHealBytes: s.trienodeHealBytes,
- BytecodeHealSynced: s.bytecodeHealSynced,
- BytecodeHealBytes: s.bytecodeHealBytes,
- }
- status, err := json.Marshal(progress)
- if err != nil {
- panic(err) // This can only fail during implementation
- }
- rawdb.WriteSnapshotSyncStatus(s.db, status)
-}
-
-// Progress returns the snap sync status statistics.
-func (s *Syncer) Progress() (*SyncProgress, *SyncPending) {
- s.lock.Lock()
- defer s.lock.Unlock()
- pending := new(SyncPending)
- if s.healer != nil {
- pending.TrienodeHeal = uint64(len(s.healer.trieTasks))
- pending.BytecodeHeal = uint64(len(s.healer.codeTasks))
- }
- return s.extProgress, pending
-}
-
-// cleanAccountTasks removes account range retrieval tasks that have already been
-// completed.
-func (s *Syncer) cleanAccountTasks() {
- // If the sync was already done before, don't even bother
- if len(s.tasks) == 0 {
- return
- }
- // Sync wasn't finished previously, check for any task that can be finalized
- for i := 0; i < len(s.tasks); i++ {
- if s.tasks[i].done {
- s.tasks = append(s.tasks[:i], s.tasks[i+1:]...)
- i--
- }
- }
- // If everything was just finalized just, generate the account trie and start heal
- if len(s.tasks) == 0 {
- s.lock.Lock()
- s.snapped = true
- s.lock.Unlock()
-
- // Push the final sync report
- s.reportSyncProgress(true)
- }
-}
-
-// cleanStorageTasks iterates over all the account tasks and storage sub-tasks
-// within, cleaning any that have been completed.
-func (s *Syncer) cleanStorageTasks() {
- for _, task := range s.tasks {
- for account, subtasks := range task.SubTasks {
- // Remove storage range retrieval tasks that completed
- for j := 0; j < len(subtasks); j++ {
- if subtasks[j].done {
- subtasks = append(subtasks[:j], subtasks[j+1:]...)
- j--
- }
- }
- if len(subtasks) > 0 {
- task.SubTasks[account] = subtasks
- continue
- }
- // If all storage chunks are done, mark the account as done too
- for j, hash := range task.res.hashes {
- if hash == account {
- task.needState[j] = false
- }
- }
- delete(task.SubTasks, account)
- task.pend--
-
- // If this was the last pending task, forward the account task
- if task.pend == 0 {
- s.forwardAccountTask(task)
- }
- }
- }
-}
-
-// assignAccountTasks attempts to match idle peers to pending account range
-// retrievals.
-func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *accountRequest, cancel chan struct{}) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- // Sort the peers by download capacity to use faster ones if many available
- idlers := &capacitySort{
- ids: make([]string, 0, len(s.accountIdlers)),
- caps: make([]int, 0, len(s.accountIdlers)),
- }
- targetTTL := s.rates.TargetTimeout()
- for id := range s.accountIdlers {
- if _, ok := s.statelessPeers[id]; ok {
- continue
- }
- idlers.ids = append(idlers.ids, id)
- idlers.caps = append(idlers.caps, s.rates.Capacity(id, AccountRangeMsg, targetTTL))
- }
- if len(idlers.ids) == 0 {
- return
- }
- sort.Sort(sort.Reverse(idlers))
-
- // Iterate over all the tasks and try to find a pending one
- for _, task := range s.tasks {
- // Skip any tasks already filling
- if task.req != nil || task.res != nil {
- continue
- }
- // Task pending retrieval, try to find an idle peer. If no such peer
- // exists, we probably assigned tasks for all (or they are stateless).
- // Abort the entire assignment mechanism.
- if len(idlers.ids) == 0 {
- return
- }
- var (
- idle = idlers.ids[0]
- peer = s.peers[idle]
- cap = idlers.caps[0]
- )
- idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
-
- // Matched a pending task to an idle peer, allocate a unique request id
- var reqid uint64
- for {
- reqid = uint64(rand.Int63())
- if reqid == 0 {
- continue
- }
- if _, ok := s.accountReqs[reqid]; ok {
- continue
- }
- break
- }
- // Generate the network query and send it to the peer
- req := &accountRequest{
- peer: idle,
- id: reqid,
- time: time.Now(),
- deliver: success,
- revert: fail,
- cancel: cancel,
- stale: make(chan struct{}),
- origin: task.Next,
- limit: task.Last,
- task: task,
- }
- req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() {
- peer.Log().Debug("Account range request timed out", "reqid", reqid)
- s.rates.Update(idle, AccountRangeMsg, 0, 0)
- s.scheduleRevertAccountRequest(req)
- })
- s.accountReqs[reqid] = req
- delete(s.accountIdlers, idle)
-
- s.pend.Add(1)
- go func(root common.Hash) {
- defer s.pend.Done()
-
- // Attempt to send the remote request and revert if it fails
- if cap > maxRequestSize {
- cap = maxRequestSize
- }
- if cap < minRequestSize { // Don't bother with peers below a bare minimum performance
- cap = minRequestSize
- }
- if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, uint64(cap)); err != nil {
- peer.Log().Debug("Failed to request account range", "err", err)
- s.scheduleRevertAccountRequest(req)
- }
- }(s.root)
-
- // Inject the request into the task to block further assignments
- task.req = req
- }
-}
-
-// assignBytecodeTasks attempts to match idle peers to pending code retrievals.
-func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan *bytecodeRequest, cancel chan struct{}) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- // Sort the peers by download capacity to use faster ones if many available
- idlers := &capacitySort{
- ids: make([]string, 0, len(s.bytecodeIdlers)),
- caps: make([]int, 0, len(s.bytecodeIdlers)),
- }
- targetTTL := s.rates.TargetTimeout()
- for id := range s.bytecodeIdlers {
- if _, ok := s.statelessPeers[id]; ok {
- continue
- }
- idlers.ids = append(idlers.ids, id)
- idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL))
- }
- if len(idlers.ids) == 0 {
- return
- }
- sort.Sort(sort.Reverse(idlers))
-
- // Iterate over all the tasks and try to find a pending one
- for _, task := range s.tasks {
- // Skip any tasks not in the bytecode retrieval phase
- if task.res == nil {
- continue
- }
- // Skip tasks that are already retrieving (or done with) all codes
- if len(task.codeTasks) == 0 {
- continue
- }
- // Task pending retrieval, try to find an idle peer. If no such peer
- // exists, we probably assigned tasks for all (or they are stateless).
- // Abort the entire assignment mechanism.
- if len(idlers.ids) == 0 {
- return
- }
- var (
- idle = idlers.ids[0]
- peer = s.peers[idle]
- cap = idlers.caps[0]
- )
- idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
-
- // Matched a pending task to an idle peer, allocate a unique request id
- var reqid uint64
- for {
- reqid = uint64(rand.Int63())
- if reqid == 0 {
- continue
- }
- if _, ok := s.bytecodeReqs[reqid]; ok {
- continue
- }
- break
- }
- // Generate the network query and send it to the peer
- if cap > maxCodeRequestCount {
- cap = maxCodeRequestCount
- }
- hashes := make([]common.Hash, 0, cap)
- for hash := range task.codeTasks {
- delete(task.codeTasks, hash)
- hashes = append(hashes, hash)
- if len(hashes) >= cap {
- break
- }
- }
- req := &bytecodeRequest{
- peer: idle,
- id: reqid,
- time: time.Now(),
- deliver: success,
- revert: fail,
- cancel: cancel,
- stale: make(chan struct{}),
- hashes: hashes,
- task: task,
- }
- req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() {
- peer.Log().Debug("Bytecode request timed out", "reqid", reqid)
- s.rates.Update(idle, ByteCodesMsg, 0, 0)
- s.scheduleRevertBytecodeRequest(req)
- })
- s.bytecodeReqs[reqid] = req
- delete(s.bytecodeIdlers, idle)
-
- s.pend.Add(1)
- go func() {
- defer s.pend.Done()
-
- // Attempt to send the remote request and revert if it fails
- if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil {
- log.Debug("Failed to request bytecodes", "err", err)
- s.scheduleRevertBytecodeRequest(req)
- }
- }()
- }
-}
-
-// assignStorageTasks attempts to match idle peers to pending storage range
-// retrievals.
-func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *storageRequest, cancel chan struct{}) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- // Sort the peers by download capacity to use faster ones if many available
- idlers := &capacitySort{
- ids: make([]string, 0, len(s.storageIdlers)),
- caps: make([]int, 0, len(s.storageIdlers)),
- }
- targetTTL := s.rates.TargetTimeout()
- for id := range s.storageIdlers {
- if _, ok := s.statelessPeers[id]; ok {
- continue
- }
- idlers.ids = append(idlers.ids, id)
- idlers.caps = append(idlers.caps, s.rates.Capacity(id, StorageRangesMsg, targetTTL))
- }
- if len(idlers.ids) == 0 {
- return
- }
- sort.Sort(sort.Reverse(idlers))
-
- // Iterate over all the tasks and try to find a pending one
- for _, task := range s.tasks {
- // Skip any tasks not in the storage retrieval phase
- if task.res == nil {
- continue
- }
- // Skip tasks that are already retrieving (or done with) all small states
- if len(task.SubTasks) == 0 && len(task.stateTasks) == 0 {
- continue
- }
- // Task pending retrieval, try to find an idle peer. If no such peer
- // exists, we probably assigned tasks for all (or they are stateless).
- // Abort the entire assignment mechanism.
- if len(idlers.ids) == 0 {
- return
- }
- var (
- idle = idlers.ids[0]
- peer = s.peers[idle]
- cap = idlers.caps[0]
- )
- idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
-
- // Matched a pending task to an idle peer, allocate a unique request id
- var reqid uint64
- for {
- reqid = uint64(rand.Int63())
- if reqid == 0 {
- continue
- }
- if _, ok := s.storageReqs[reqid]; ok {
- continue
- }
- break
- }
- // Generate the network query and send it to the peer. If there are
- // large contract tasks pending, complete those before diving into
- // even more new contracts.
- if cap > maxRequestSize {
- cap = maxRequestSize
- }
- if cap < minRequestSize { // Don't bother with peers below a bare minimum performance
- cap = minRequestSize
- }
- storageSets := cap / 1024
-
- var (
- accounts = make([]common.Hash, 0, storageSets)
- roots = make([]common.Hash, 0, storageSets)
- subtask *storageTask
- )
- for account, subtasks := range task.SubTasks {
- for _, st := range subtasks {
- // Skip any subtasks already filling
- if st.req != nil {
- continue
- }
- // Found an incomplete storage chunk, schedule it
- accounts = append(accounts, account)
- roots = append(roots, st.root)
- subtask = st
- break // Large contract chunks are downloaded individually
- }
- if subtask != nil {
- break // Large contract chunks are downloaded individually
- }
- }
- if subtask == nil {
- // No large contract required retrieval, but small ones available
- for account, root := range task.stateTasks {
- delete(task.stateTasks, account)
-
- accounts = append(accounts, account)
- roots = append(roots, root)
-
- if len(accounts) >= storageSets {
- break
- }
- }
- }
- // If nothing was found, it means this task is actually already fully
- // retrieving, but large contracts are hard to detect. Skip to the next.
- if len(accounts) == 0 {
- continue
- }
- req := &storageRequest{
- peer: idle,
- id: reqid,
- time: time.Now(),
- deliver: success,
- revert: fail,
- cancel: cancel,
- stale: make(chan struct{}),
- accounts: accounts,
- roots: roots,
- mainTask: task,
- subTask: subtask,
- }
- if subtask != nil {
- req.origin = subtask.Next
- req.limit = subtask.Last
- }
- req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() {
- peer.Log().Debug("Storage request timed out", "reqid", reqid)
- s.rates.Update(idle, StorageRangesMsg, 0, 0)
- s.scheduleRevertStorageRequest(req)
- })
- s.storageReqs[reqid] = req
- delete(s.storageIdlers, idle)
-
- s.pend.Add(1)
- go func(root common.Hash) {
- defer s.pend.Done()
-
- // Attempt to send the remote request and revert if it fails
- var origin, limit []byte
- if subtask != nil {
- origin, limit = req.origin[:], req.limit[:]
- }
- if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, uint64(cap)); err != nil {
- log.Debug("Failed to request storage", "err", err)
- s.scheduleRevertStorageRequest(req)
- }
- }(s.root)
-
- // Inject the request into the subtask to block further assignments
- if subtask != nil {
- subtask.req = req
- }
- }
-}
-
-// assignTrienodeHealTasks attempts to match idle peers to trie node requests to
-// heal any trie errors caused by the snap sync's chunked retrieval model.
-func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fail chan *trienodeHealRequest, cancel chan struct{}) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- // Sort the peers by download capacity to use faster ones if many available
- idlers := &capacitySort{
- ids: make([]string, 0, len(s.trienodeHealIdlers)),
- caps: make([]int, 0, len(s.trienodeHealIdlers)),
- }
- targetTTL := s.rates.TargetTimeout()
- for id := range s.trienodeHealIdlers {
- if _, ok := s.statelessPeers[id]; ok {
- continue
- }
- idlers.ids = append(idlers.ids, id)
- idlers.caps = append(idlers.caps, s.rates.Capacity(id, TrieNodesMsg, targetTTL))
- }
- if len(idlers.ids) == 0 {
- return
- }
- sort.Sort(sort.Reverse(idlers))
-
- // Iterate over pending tasks and try to find a peer to retrieve with
- for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 {
- // If there are not enough trie tasks queued to fully assign, fill the
- // queue from the state sync scheduler. The trie synced schedules these
- // together with bytecodes, so we need to queue them combined.
- var (
- have = len(s.healer.trieTasks) + len(s.healer.codeTasks)
- want = maxTrieRequestCount + maxCodeRequestCount
- )
- if have < want {
- paths, hashes, codes := s.healer.scheduler.Missing(want - have)
- for i, path := range paths {
- s.healer.trieTasks[path] = hashes[i]
- }
- for _, hash := range codes {
- s.healer.codeTasks[hash] = struct{}{}
- }
- }
- // If all the heal tasks are bytecodes or already downloading, bail
- if len(s.healer.trieTasks) == 0 {
- return
- }
- // Task pending retrieval, try to find an idle peer. If no such peer
- // exists, we probably assigned tasks for all (or they are stateless).
- // Abort the entire assignment mechanism.
- if len(idlers.ids) == 0 {
- return
- }
- var (
- idle = idlers.ids[0]
- peer = s.peers[idle]
- cap = idlers.caps[0]
- )
- idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
-
- // Matched a pending task to an idle peer, allocate a unique request id
- var reqid uint64
- for {
- reqid = uint64(rand.Int63())
- if reqid == 0 {
- continue
- }
- if _, ok := s.trienodeHealReqs[reqid]; ok {
- continue
- }
- break
- }
- // Generate the network query and send it to the peer
- if cap > maxTrieRequestCount {
- cap = maxTrieRequestCount
- }
- cap = int(float64(cap) / s.trienodeHealThrottle)
- if cap <= 0 {
- cap = 1
- }
- var (
- hashes = make([]common.Hash, 0, cap)
- paths = make([]string, 0, cap)
- pathsets = make([]TrieNodePathSet, 0, cap)
- )
- for path, hash := range s.healer.trieTasks {
- delete(s.healer.trieTasks, path)
-
- paths = append(paths, path)
- hashes = append(hashes, hash)
- if len(paths) >= cap {
- break
- }
- }
- // Group requests by account hash
- paths, hashes, _, pathsets = sortByAccountPath(paths, hashes)
- req := &trienodeHealRequest{
- peer: idle,
- id: reqid,
- time: time.Now(),
- deliver: success,
- revert: fail,
- cancel: cancel,
- stale: make(chan struct{}),
- paths: paths,
- hashes: hashes,
- task: s.healer,
- }
- req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() {
- peer.Log().Debug("Trienode heal request timed out", "reqid", reqid)
- s.rates.Update(idle, TrieNodesMsg, 0, 0)
- s.scheduleRevertTrienodeHealRequest(req)
- })
- s.trienodeHealReqs[reqid] = req
- delete(s.trienodeHealIdlers, idle)
-
- s.pend.Add(1)
- go func(root common.Hash) {
- defer s.pend.Done()
-
- // Attempt to send the remote request and revert if it fails
- if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil {
- log.Debug("Failed to request trienode healers", "err", err)
- s.scheduleRevertTrienodeHealRequest(req)
- }
- }(s.root)
- }
-}
-
-// assignBytecodeHealTasks attempts to match idle peers to bytecode requests to
-// heal any trie errors caused by the snap sync's chunked retrieval model.
-func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fail chan *bytecodeHealRequest, cancel chan struct{}) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- // Sort the peers by download capacity to use faster ones if many available
- idlers := &capacitySort{
- ids: make([]string, 0, len(s.bytecodeHealIdlers)),
- caps: make([]int, 0, len(s.bytecodeHealIdlers)),
- }
- targetTTL := s.rates.TargetTimeout()
- for id := range s.bytecodeHealIdlers {
- if _, ok := s.statelessPeers[id]; ok {
- continue
- }
- idlers.ids = append(idlers.ids, id)
- idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL))
- }
- if len(idlers.ids) == 0 {
- return
- }
- sort.Sort(sort.Reverse(idlers))
-
- // Iterate over pending tasks and try to find a peer to retrieve with
- for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 {
- // If there are not enough trie tasks queued to fully assign, fill the
- // queue from the state sync scheduler. The trie synced schedules these
- // together with trie nodes, so we need to queue them combined.
- var (
- have = len(s.healer.trieTasks) + len(s.healer.codeTasks)
- want = maxTrieRequestCount + maxCodeRequestCount
- )
- if have < want {
- paths, hashes, codes := s.healer.scheduler.Missing(want - have)
- for i, path := range paths {
- s.healer.trieTasks[path] = hashes[i]
- }
- for _, hash := range codes {
- s.healer.codeTasks[hash] = struct{}{}
- }
- }
- // If all the heal tasks are trienodes or already downloading, bail
- if len(s.healer.codeTasks) == 0 {
- return
- }
- // Task pending retrieval, try to find an idle peer. If no such peer
- // exists, we probably assigned tasks for all (or they are stateless).
- // Abort the entire assignment mechanism.
- if len(idlers.ids) == 0 {
- return
- }
- var (
- idle = idlers.ids[0]
- peer = s.peers[idle]
- cap = idlers.caps[0]
- )
- idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:]
-
- // Matched a pending task to an idle peer, allocate a unique request id
- var reqid uint64
- for {
- reqid = uint64(rand.Int63())
- if reqid == 0 {
- continue
- }
- if _, ok := s.bytecodeHealReqs[reqid]; ok {
- continue
- }
- break
- }
- // Generate the network query and send it to the peer
- if cap > maxCodeRequestCount {
- cap = maxCodeRequestCount
- }
- hashes := make([]common.Hash, 0, cap)
- for hash := range s.healer.codeTasks {
- delete(s.healer.codeTasks, hash)
-
- hashes = append(hashes, hash)
- if len(hashes) >= cap {
- break
- }
- }
- req := &bytecodeHealRequest{
- peer: idle,
- id: reqid,
- time: time.Now(),
- deliver: success,
- revert: fail,
- cancel: cancel,
- stale: make(chan struct{}),
- hashes: hashes,
- task: s.healer,
- }
- req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() {
- peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid)
- s.rates.Update(idle, ByteCodesMsg, 0, 0)
- s.scheduleRevertBytecodeHealRequest(req)
- })
- s.bytecodeHealReqs[reqid] = req
- delete(s.bytecodeHealIdlers, idle)
-
- s.pend.Add(1)
- go func() {
- defer s.pend.Done()
-
- // Attempt to send the remote request and revert if it fails
- if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil {
- log.Debug("Failed to request bytecode healers", "err", err)
- s.scheduleRevertBytecodeHealRequest(req)
- }
- }()
- }
-}
-
-// revertRequests locates all the currently pending requests from a particular
-// peer and reverts them, rescheduling for others to fulfill.
-func (s *Syncer) revertRequests(peer string) {
- // Gather the requests first, revertals need the lock too
- s.lock.Lock()
- var accountReqs []*accountRequest
- for _, req := range s.accountReqs {
- if req.peer == peer {
- accountReqs = append(accountReqs, req)
- }
- }
- var bytecodeReqs []*bytecodeRequest
- for _, req := range s.bytecodeReqs {
- if req.peer == peer {
- bytecodeReqs = append(bytecodeReqs, req)
- }
- }
- var storageReqs []*storageRequest
- for _, req := range s.storageReqs {
- if req.peer == peer {
- storageReqs = append(storageReqs, req)
- }
- }
- var trienodeHealReqs []*trienodeHealRequest
- for _, req := range s.trienodeHealReqs {
- if req.peer == peer {
- trienodeHealReqs = append(trienodeHealReqs, req)
- }
- }
- var bytecodeHealReqs []*bytecodeHealRequest
- for _, req := range s.bytecodeHealReqs {
- if req.peer == peer {
- bytecodeHealReqs = append(bytecodeHealReqs, req)
- }
- }
- s.lock.Unlock()
-
- // Revert all the requests matching the peer
- for _, req := range accountReqs {
- s.revertAccountRequest(req)
- }
- for _, req := range bytecodeReqs {
- s.revertBytecodeRequest(req)
- }
- for _, req := range storageReqs {
- s.revertStorageRequest(req)
- }
- for _, req := range trienodeHealReqs {
- s.revertTrienodeHealRequest(req)
- }
- for _, req := range bytecodeHealReqs {
- s.revertBytecodeHealRequest(req)
- }
-}
-
-// scheduleRevertAccountRequest asks the event loop to clean up an account range
-// request and return all failed retrieval tasks to the scheduler for reassignment.
-func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) {
- select {
- case req.revert <- req:
- // Sync event loop notified
- case <-req.cancel:
- // Sync cycle got cancelled
- case <-req.stale:
- // Request already reverted
- }
-}
-
-// revertAccountRequest cleans up an account range request and returns all failed
-// retrieval tasks to the scheduler for reassignment.
-//
-// Note, this needs to run on the event runloop thread to reschedule to idle peers.
-// On peer threads, use scheduleRevertAccountRequest.
-func (s *Syncer) revertAccountRequest(req *accountRequest) {
- log.Debug("Reverting account request", "peer", req.peer, "reqid", req.id)
- select {
- case <-req.stale:
- log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id)
- return
- default:
- }
- close(req.stale)
-
- // Remove the request from the tracked set
- s.lock.Lock()
- delete(s.accountReqs, req.id)
- s.lock.Unlock()
-
- // If there's a timeout timer still running, abort it and mark the account
- // task as not-pending, ready for rescheduling
- req.timeout.Stop()
- if req.task.req == req {
- req.task.req = nil
- }
-}
-
-// scheduleRevertBytecodeRequest asks the event loop to clean up a bytecode request
-// and return all failed retrieval tasks to the scheduler for reassignment.
-func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) {
- select {
- case req.revert <- req:
- // Sync event loop notified
- case <-req.cancel:
- // Sync cycle got cancelled
- case <-req.stale:
- // Request already reverted
- }
-}
-
-// revertBytecodeRequest cleans up a bytecode request and returns all failed
-// retrieval tasks to the scheduler for reassignment.
-//
-// Note, this needs to run on the event runloop thread to reschedule to idle peers.
-// On peer threads, use scheduleRevertBytecodeRequest.
-func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) {
- log.Debug("Reverting bytecode request", "peer", req.peer)
- select {
- case <-req.stale:
- log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id)
- return
- default:
- }
- close(req.stale)
-
- // Remove the request from the tracked set
- s.lock.Lock()
- delete(s.bytecodeReqs, req.id)
- s.lock.Unlock()
-
- // If there's a timeout timer still running, abort it and mark the code
- // retrievals as not-pending, ready for rescheduling
- req.timeout.Stop()
- for _, hash := range req.hashes {
- req.task.codeTasks[hash] = struct{}{}
- }
-}
-
-// scheduleRevertStorageRequest asks the event loop to clean up a storage range
-// request and return all failed retrieval tasks to the scheduler for reassignment.
-func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) {
- select {
- case req.revert <- req:
- // Sync event loop notified
- case <-req.cancel:
- // Sync cycle got cancelled
- case <-req.stale:
- // Request already reverted
- }
-}
-
-// revertStorageRequest cleans up a storage range request and returns all failed
-// retrieval tasks to the scheduler for reassignment.
-//
-// Note, this needs to run on the event runloop thread to reschedule to idle peers.
-// On peer threads, use scheduleRevertStorageRequest.
-func (s *Syncer) revertStorageRequest(req *storageRequest) {
- log.Debug("Reverting storage request", "peer", req.peer)
- select {
- case <-req.stale:
- log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id)
- return
- default:
- }
- close(req.stale)
-
- // Remove the request from the tracked set
- s.lock.Lock()
- delete(s.storageReqs, req.id)
- s.lock.Unlock()
-
- // If there's a timeout timer still running, abort it and mark the storage
- // task as not-pending, ready for rescheduling
- req.timeout.Stop()
- if req.subTask != nil {
- req.subTask.req = nil
- } else {
- for i, account := range req.accounts {
- req.mainTask.stateTasks[account] = req.roots[i]
- }
- }
-}
-
-// scheduleRevertTrienodeHealRequest asks the event loop to clean up a trienode heal
-// request and return all failed retrieval tasks to the scheduler for reassignment.
-func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) {
- select {
- case req.revert <- req:
- // Sync event loop notified
- case <-req.cancel:
- // Sync cycle got cancelled
- case <-req.stale:
- // Request already reverted
- }
-}
-
-// revertTrienodeHealRequest cleans up a trienode heal request and returns all
-// failed retrieval tasks to the scheduler for reassignment.
-//
-// Note, this needs to run on the event runloop thread to reschedule to idle peers.
-// On peer threads, use scheduleRevertTrienodeHealRequest.
-func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) {
- log.Debug("Reverting trienode heal request", "peer", req.peer)
- select {
- case <-req.stale:
- log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id)
- return
- default:
- }
- close(req.stale)
-
- // Remove the request from the tracked set
- s.lock.Lock()
- delete(s.trienodeHealReqs, req.id)
- s.lock.Unlock()
-
- // If there's a timeout timer still running, abort it and mark the trie node
- // retrievals as not-pending, ready for rescheduling
- req.timeout.Stop()
- for i, path := range req.paths {
- req.task.trieTasks[path] = req.hashes[i]
- }
-}
-
-// scheduleRevertBytecodeHealRequest asks the event loop to clean up a bytecode heal
-// request and return all failed retrieval tasks to the scheduler for reassignment.
-func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) {
- select {
- case req.revert <- req:
- // Sync event loop notified
- case <-req.cancel:
- // Sync cycle got cancelled
- case <-req.stale:
- // Request already reverted
- }
-}
-
-// revertBytecodeHealRequest cleans up a bytecode heal request and returns all
-// failed retrieval tasks to the scheduler for reassignment.
-//
-// Note, this needs to run on the event runloop thread to reschedule to idle peers.
-// On peer threads, use scheduleRevertBytecodeHealRequest.
-func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) {
- log.Debug("Reverting bytecode heal request", "peer", req.peer)
- select {
- case <-req.stale:
- log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id)
- return
- default:
- }
- close(req.stale)
-
- // Remove the request from the tracked set
- s.lock.Lock()
- delete(s.bytecodeHealReqs, req.id)
- s.lock.Unlock()
-
- // If there's a timeout timer still running, abort it and mark the code
- // retrievals as not-pending, ready for rescheduling
- req.timeout.Stop()
- for _, hash := range req.hashes {
- req.task.codeTasks[hash] = struct{}{}
- }
-}
-
-// processAccountResponse integrates an already validated account range response
-// into the account tasks.
-func (s *Syncer) processAccountResponse(res *accountResponse) {
- // Switch the task from pending to filling
- res.task.req = nil
- res.task.res = res
-
- // Ensure that the response doesn't overflow into the subsequent task
- last := res.task.Last.Big()
- for i, hash := range res.hashes {
- // Mark the range complete if the last is already included.
- // Keep iteration to delete the extra states if exists.
- cmp := hash.Big().Cmp(last)
- if cmp == 0 {
- res.cont = false
- continue
- }
- if cmp > 0 {
- // Chunk overflown, cut off excess
- res.hashes = res.hashes[:i]
- res.accounts = res.accounts[:i]
- res.cont = false // Mark range completed
- break
- }
- }
- // Iterate over all the accounts and assemble which ones need further sub-
- // filling before the entire account range can be persisted.
- res.task.needCode = make([]bool, len(res.accounts))
- res.task.needState = make([]bool, len(res.accounts))
- res.task.needHeal = make([]bool, len(res.accounts))
-
- res.task.codeTasks = make(map[common.Hash]struct{})
- res.task.stateTasks = make(map[common.Hash]common.Hash)
-
- resumed := make(map[common.Hash]struct{})
-
- res.task.pend = 0
- for i, account := range res.accounts {
- // Check if the account is a contract with an unknown code
- if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
- if !rawdb.HasCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)) {
- res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{}
- res.task.needCode[i] = true
- res.task.pend++
- }
- }
- // Check if the account is a contract with an unknown storage trie
- if account.Root != types.EmptyRootHash {
- if !rawdb.HasTrieNode(s.db, res.hashes[i], nil, account.Root, s.scheme) {
- // If there was a previous large state retrieval in progress,
- // don't restart it from scratch. This happens if a sync cycle
- // is interrupted and resumed later. However, *do* update the
- // previous root hash.
- if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok {
- log.Debug("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root)
- for _, subtask := range subtasks {
- subtask.root = account.Root
- }
- res.task.needHeal[i] = true
- resumed[res.hashes[i]] = struct{}{}
- } else {
- res.task.stateTasks[res.hashes[i]] = account.Root
- }
- res.task.needState[i] = true
- res.task.pend++
- }
- }
- }
- // Delete any subtasks that have been aborted but not resumed. This may undo
- // some progress if a new peer gives us less accounts than an old one, but for
- // now we have to live with that.
- for hash := range res.task.SubTasks {
- if _, ok := resumed[hash]; !ok {
- log.Debug("Aborting suspended storage retrieval", "account", hash)
- delete(res.task.SubTasks, hash)
- }
- }
- // If the account range contained no contracts, or all have been fully filled
- // beforehand, short circuit storage filling and forward to the next task
- if res.task.pend == 0 {
- s.forwardAccountTask(res.task)
- return
- }
- // Some accounts are incomplete, leave as is for the storage and contract
- // task assigners to pick up and fill
-}
-
-// processBytecodeResponse integrates an already validated bytecode response
-// into the account tasks.
-func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) {
- batch := s.db.NewBatch()
-
- var (
- codes uint64
- )
- for i, hash := range res.hashes {
- code := res.codes[i]
-
- // If the bytecode was not delivered, reschedule it
- if code == nil {
- res.task.codeTasks[hash] = struct{}{}
- continue
- }
- // Code was delivered, mark it not needed any more
- for j, account := range res.task.res.accounts {
- if res.task.needCode[j] && hash == common.BytesToHash(account.CodeHash) {
- res.task.needCode[j] = false
- res.task.pend--
- }
- }
- // Push the bytecode into a database batch
- codes++
- rawdb.WriteCode(batch, hash, code)
- }
- bytes := common.StorageSize(batch.ValueSize())
- if err := batch.Write(); err != nil {
- log.Crit("Failed to persist bytecodes", "err", err)
- }
- s.bytecodeSynced += codes
- s.bytecodeBytes += bytes
-
- log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes)
-
- // If this delivery completed the last pending task, forward the account task
- // to the next chunk
- if res.task.pend == 0 {
- s.forwardAccountTask(res.task)
- return
- }
- // Some accounts are still incomplete, leave as is for the storage and contract
- // task assigners to pick up and fill.
-}
-
-// processStorageResponse integrates an already validated storage response
-// into the account tasks.
-func (s *Syncer) processStorageResponse(res *storageResponse) {
- // Switch the subtask from pending to idle
- if res.subTask != nil {
- res.subTask.req = nil
- }
- batch := ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.storageBytes += common.StorageSize(len(key) + len(value))
- },
- }
- var (
- slots int
- oldStorageBytes = s.storageBytes
- )
- // Iterate over all the accounts and reconstruct their storage tries from the
- // delivered slots
- for i, account := range res.accounts {
- // If the account was not delivered, reschedule it
- if i >= len(res.hashes) {
- res.mainTask.stateTasks[account] = res.roots[i]
- continue
- }
- // State was delivered, if complete mark as not needed any more, otherwise
- // mark the account as needing healing
- for j, hash := range res.mainTask.res.hashes {
- if account != hash {
- continue
- }
- acc := res.mainTask.res.accounts[j]
-
- // If the packet contains multiple contract storage slots, all
- // but the last are surely complete. The last contract may be
- // chunked, so check it's continuation flag.
- if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) {
- res.mainTask.needState[j] = false
- res.mainTask.pend--
- smallStorageGauge.Inc(1)
- }
- // If the last contract was chunked, mark it as needing healing
- // to avoid writing it out to disk prematurely.
- if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont {
- res.mainTask.needHeal[j] = true
- }
- // If the last contract was chunked, we need to switch to large
- // contract handling mode
- if res.subTask == nil && i == len(res.hashes)-1 && res.cont {
- // If we haven't yet started a large-contract retrieval, create
- // the subtasks for it within the main account task
- if tasks, ok := res.mainTask.SubTasks[account]; !ok {
- var (
- keys = res.hashes[i]
- chunks = uint64(storageConcurrency)
- lastKey common.Hash
- )
- if len(keys) > 0 {
- lastKey = keys[len(keys)-1]
- }
- // If the number of slots remaining is low, decrease the
- // number of chunks. Somewhere on the order of 10-15K slots
- // fit into a packet of 500KB. A key/slot pair is maximum 64
- // bytes, so pessimistically maxRequestSize/64 = 8K.
- //
- // Chunk so that at least 2 packets are needed to fill a task.
- if estimate, err := estimateRemainingSlots(len(keys), lastKey); err == nil {
- if n := estimate / (2 * (maxRequestSize / 64)); n+1 < chunks {
- chunks = n + 1
- }
- log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "remaining", estimate, "chunks", chunks)
- } else {
- log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks)
- }
- r := newHashRange(lastKey, chunks)
- if chunks == 1 {
- smallStorageGauge.Inc(1)
- } else {
- largeStorageGauge.Inc(1)
- }
- // Our first task is the one that was just filled by this response.
- batch := ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.storageBytes += common.StorageSize(len(key) + len(value))
- },
- }
- owner := account // local assignment for stacktrie writer closure
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(batch, owner, path)
- })
- // Keep the left boundary as it's the first range.
- // Skip the right boundary if it's not the last range.
- options = options.WithSkipBoundary(false, r.End() != common.MaxHash, boundaryStorageNodesGauge)
- }
- tasks = append(tasks, &storageTask{
- Next: common.Hash{},
- Last: r.End(),
- root: acc.Root,
- genBatch: batch,
- genTrie: trie.NewStackTrie(options),
- })
- for r.Next() {
- batch := ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.storageBytes += common.StorageSize(len(key) + len(value))
- },
- }
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- // Configure the dangling node cleaner and also filter out boundary nodes
- // only in the context of the path scheme. Deletion is forbidden in the
- // hash scheme, as it can disrupt state completeness.
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(batch, owner, path)
- })
- // Skip the left boundary as it's not the first range
- // Skip the right boundary if it's not the last range.
- options = options.WithSkipBoundary(true, r.End() != common.MaxHash, boundaryStorageNodesGauge)
- }
- tasks = append(tasks, &storageTask{
- Next: r.Start(),
- Last: r.End(),
- root: acc.Root,
- genBatch: batch,
- genTrie: trie.NewStackTrie(options),
- })
- }
- for _, task := range tasks {
- log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", task.Next, "last", task.Last)
- }
- res.mainTask.SubTasks[account] = tasks
-
- // Since we've just created the sub-tasks, this response
- // is surely for the first one (zero origin)
- res.subTask = tasks[0]
- }
- }
- // If we're in large contract delivery mode, forward the subtask
- if res.subTask != nil {
- // Ensure the response doesn't overflow into the subsequent task
- last := res.subTask.Last.Big()
- // Find the first overflowing key. While at it, mark res as complete
- // if we find the range to include or pass the 'last'
- index := sort.Search(len(res.hashes[i]), func(k int) bool {
- cmp := res.hashes[i][k].Big().Cmp(last)
- if cmp >= 0 {
- res.cont = false
- }
- return cmp > 0
- })
- if index >= 0 {
- // cut off excess
- res.hashes[i] = res.hashes[i][:index]
- res.slots[i] = res.slots[i][:index]
- }
- // Forward the relevant storage chunk (even if created just now)
- if res.cont {
- res.subTask.Next = incHash(res.hashes[i][len(res.hashes[i])-1])
- } else {
- res.subTask.done = true
- }
- }
- }
- // Iterate over all the complete contracts, reconstruct the trie nodes and
- // push them to disk. If the contract is chunked, the trie nodes will be
- // reconstructed later.
- slots += len(res.hashes[i])
-
- if i < len(res.hashes)-1 || res.subTask == nil {
- // no need to make local reassignment of account: this closure does not outlive the loop
- options := trie.NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(batch, account, path, hash, blob, s.scheme)
- })
- if s.scheme == rawdb.PathScheme {
- // Configure the dangling node cleaner only in the context of the
- // path scheme. Deletion is forbidden in the hash scheme, as it can
- // disrupt state completeness.
- //
- // Notably, boundary nodes can be also kept because the whole storage
- // trie is complete.
- options = options.WithCleaner(func(path []byte) {
- s.cleanPath(batch, account, path)
- })
- }
- tr := trie.NewStackTrie(options)
- for j := 0; j < len(res.hashes[i]); j++ {
- tr.Update(res.hashes[i][j][:], res.slots[i][j])
- }
- tr.Commit()
- }
- // Persist the received storage segments. These flat state maybe
- // outdated during the sync, but it can be fixed later during the
- // snapshot generation.
- for j := 0; j < len(res.hashes[i]); j++ {
- rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j])
-
- // If we're storing large contracts, generate the trie nodes
- // on the fly to not trash the gluing points
- if i == len(res.hashes)-1 && res.subTask != nil {
- res.subTask.genTrie.Update(res.hashes[i][j][:], res.slots[i][j])
- }
- }
- }
- // Large contracts could have generated new trie nodes, flush them to disk
- if res.subTask != nil {
- if res.subTask.done {
- root := res.subTask.genTrie.Commit()
- if err := res.subTask.genBatch.Write(); err != nil {
- log.Error("Failed to persist stack slots", "err", err)
- }
- res.subTask.genBatch.Reset()
-
- // If the chunk's root is an overflown but full delivery,
- // clear the heal request.
- accountHash := res.accounts[len(res.accounts)-1]
- if root == res.subTask.root && rawdb.HasStorageTrieNode(s.db, accountHash, nil, root) {
- for i, account := range res.mainTask.res.hashes {
- if account == accountHash {
- res.mainTask.needHeal[i] = false
- skipStorageHealingGauge.Inc(1)
- }
- }
- }
- }
- if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize {
- if err := res.subTask.genBatch.Write(); err != nil {
- log.Error("Failed to persist stack slots", "err", err)
- }
- res.subTask.genBatch.Reset()
- }
- }
- // Flush anything written just now and update the stats
- if err := batch.Write(); err != nil {
- log.Crit("Failed to persist storage slots", "err", err)
- }
- s.storageSynced += uint64(slots)
-
- log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "bytes", s.storageBytes-oldStorageBytes)
-
- // If this delivery completed the last pending task, forward the account task
- // to the next chunk
- if res.mainTask.pend == 0 {
- s.forwardAccountTask(res.mainTask)
- return
- }
- // Some accounts are still incomplete, leave as is for the storage and contract
- // task assigners to pick up and fill.
-}
-
-// processTrienodeHealResponse integrates an already validated trienode response
-// into the healer tasks.
-func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) {
- var (
- start = time.Now()
- fills int
- )
- for i, hash := range res.hashes {
- node := res.nodes[i]
-
- // If the trie node was not delivered, reschedule it
- if node == nil {
- res.task.trieTasks[res.paths[i]] = res.hashes[i]
- continue
- }
- fills++
-
- // Push the trie node into the state syncer
- s.trienodeHealSynced++
- s.trienodeHealBytes += common.StorageSize(len(node))
-
- err := s.healer.scheduler.ProcessNode(trie.NodeSyncResult{Path: res.paths[i], Data: node})
- switch err {
- case nil:
- case trie.ErrAlreadyProcessed:
- s.trienodeHealDups++
- case trie.ErrNotRequested:
- s.trienodeHealNops++
- default:
- log.Error("Invalid trienode processed", "hash", hash, "err", err)
- }
- }
- s.commitHealer(false)
-
- // Calculate the processing rate of one filled trie node
- rate := float64(fills) / (float64(time.Since(start)) / float64(time.Second))
-
- // Update the currently measured trienode queueing and processing throughput.
- //
- // The processing rate needs to be updated uniformly independent if we've
- // processed 1x100 trie nodes or 100x1 to keep the rate consistent even in
- // the face of varying network packets. As such, we cannot just measure the
- // time it took to process N trie nodes and update once, we need one update
- // per trie node.
- //
- // Naively, that would be:
- //
- // for i:=0; i time.Second {
- // Periodically adjust the trie node throttler
- if float64(pending) > 2*s.trienodeHealRate {
- s.trienodeHealThrottle *= trienodeHealThrottleIncrease
- } else {
- s.trienodeHealThrottle /= trienodeHealThrottleDecrease
- }
- if s.trienodeHealThrottle > maxTrienodeHealThrottle {
- s.trienodeHealThrottle = maxTrienodeHealThrottle
- } else if s.trienodeHealThrottle < minTrienodeHealThrottle {
- s.trienodeHealThrottle = minTrienodeHealThrottle
- }
- s.trienodeHealThrottled = time.Now()
-
- log.Debug("Updated trie node heal throttler", "rate", s.trienodeHealRate, "pending", pending, "throttle", s.trienodeHealThrottle)
- }
-}
-
-func (s *Syncer) commitHealer(force bool) {
- if !force && s.healer.scheduler.MemSize() < ethdb.IdealBatchSize {
- return
- }
- batch := s.db.NewBatch()
- if err := s.healer.scheduler.Commit(batch); err != nil {
- log.Error("Failed to commit healing data", "err", err)
- }
- if err := batch.Write(); err != nil {
- log.Crit("Failed to persist healing data", "err", err)
- }
- log.Debug("Persisted set of healing data", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize()))
-}
-
-// processBytecodeHealResponse integrates an already validated bytecode response
-// into the healer tasks.
-func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) {
- for i, hash := range res.hashes {
- node := res.codes[i]
-
- // If the trie node was not delivered, reschedule it
- if node == nil {
- res.task.codeTasks[hash] = struct{}{}
- continue
- }
- // Push the trie node into the state syncer
- s.bytecodeHealSynced++
- s.bytecodeHealBytes += common.StorageSize(len(node))
-
- err := s.healer.scheduler.ProcessCode(trie.CodeSyncResult{Hash: hash, Data: node})
- switch err {
- case nil:
- case trie.ErrAlreadyProcessed:
- s.bytecodeHealDups++
- case trie.ErrNotRequested:
- s.bytecodeHealNops++
- default:
- log.Error("Invalid bytecode processed", "hash", hash, "err", err)
- }
- }
- s.commitHealer(false)
-}
-
-// forwardAccountTask takes a filled account task and persists anything available
-// into the database, after which it forwards the next account marker so that the
-// task's next chunk may be filled.
-func (s *Syncer) forwardAccountTask(task *accountTask) {
- // Remove any pending delivery
- res := task.res
- if res == nil {
- return // nothing to forward
- }
- task.res = nil
-
- // Persist the received account segments. These flat state maybe
- // outdated during the sync, but it can be fixed later during the
- // snapshot generation.
- oldAccountBytes := s.accountBytes
-
- batch := ethdb.HookedBatch{
- Batch: s.db.NewBatch(),
- OnPut: func(key []byte, value []byte) {
- s.accountBytes += common.StorageSize(len(key) + len(value))
- },
- }
- for i, hash := range res.hashes {
- if task.needCode[i] || task.needState[i] {
- break
- }
- slim := types.SlimAccountRLP(*res.accounts[i])
- rawdb.WriteAccountSnapshot(batch, hash, slim)
-
- // If the task is complete, drop it into the stack trie to generate
- // account trie nodes for it
- if !task.needHeal[i] {
- full, err := types.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted
- if err != nil {
- panic(err) // Really shouldn't ever happen
- }
- task.genTrie.Update(hash[:], full)
- }
- }
- // Flush anything written just now and update the stats
- if err := batch.Write(); err != nil {
- log.Crit("Failed to persist accounts", "err", err)
- }
- s.accountSynced += uint64(len(res.accounts))
-
- // Task filling persisted, push it the chunk marker forward to the first
- // account still missing data.
- for i, hash := range res.hashes {
- if task.needCode[i] || task.needState[i] {
- return
- }
- task.Next = incHash(hash)
- }
- // All accounts marked as complete, track if the entire task is done
- task.done = !res.cont
-
- // Stack trie could have generated trie nodes, push them to disk (we need to
- // flush after finalizing task.done. It's fine even if we crash and lose this
- // write as it will only cause more data to be downloaded during heal.
- if task.done {
- task.genTrie.Commit()
- }
- if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done {
- if err := task.genBatch.Write(); err != nil {
- log.Error("Failed to persist stack account", "err", err)
- }
- task.genBatch.Reset()
- }
- log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "bytes", s.accountBytes-oldAccountBytes)
-}
-
-// OnAccounts is a callback method to invoke when a range of accounts are
-// received from a remote peer.
-func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error {
- size := common.StorageSize(len(hashes) * common.HashLength)
- for _, account := range accounts {
- size += common.StorageSize(len(account))
- }
- for _, node := range proof {
- size += common.StorageSize(len(node))
- }
- logger := peer.Log().New("reqid", id)
- logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size)
-
- // Whether or not the response is valid, we can mark the peer as idle and
- // notify the scheduler to assign a new task. If the response is invalid,
- // we'll drop the peer in a bit.
- defer func() {
- s.lock.Lock()
- defer s.lock.Unlock()
- if _, ok := s.peers[peer.ID()]; ok {
- s.accountIdlers[peer.ID()] = struct{}{}
- }
- select {
- case s.update <- struct{}{}:
- default:
- }
- }()
- s.lock.Lock()
- // Ensure the response is for a valid request
- req, ok := s.accountReqs[id]
- if !ok {
- // Request stale, perhaps the peer timed out but came through in the end
- logger.Warn("Unexpected account range packet")
- s.lock.Unlock()
- return nil
- }
- delete(s.accountReqs, id)
- s.rates.Update(peer.ID(), AccountRangeMsg, time.Since(req.time), int(size))
-
- // Clean up the request timeout timer, we'll see how to proceed further based
- // on the actual delivered content
- if !req.timeout.Stop() {
- // The timeout is already triggered, and this request will be reverted+rescheduled
- s.lock.Unlock()
- return nil
- }
- // Response is valid, but check if peer is signalling that it does not have
- // the requested data. For account range queries that means the state being
- // retrieved was either already pruned remotely, or the peer is not yet
- // synced to our head.
- if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 {
- logger.Debug("Peer rejected account range request", "root", s.root)
- s.statelessPeers[peer.ID()] = struct{}{}
- s.lock.Unlock()
-
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertAccountRequest(req)
- return nil
- }
- root := s.root
- s.lock.Unlock()
-
- // Reconstruct a partial trie from the response and verify it
- keys := make([][]byte, len(hashes))
- for i, key := range hashes {
- keys[i] = common.CopyBytes(key[:])
- }
- nodes := make(trienode.ProofList, len(proof))
- for i, node := range proof {
- nodes[i] = node
- }
- cont, err := trie.VerifyRangeProof(root, req.origin[:], keys, accounts, nodes.Set())
- if err != nil {
- logger.Warn("Account range failed proof", "err", err)
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertAccountRequest(req)
- return err
- }
- accs := make([]*types.StateAccount, len(accounts))
- for i, account := range accounts {
- acc := new(types.StateAccount)
- if err := rlp.DecodeBytes(account, acc); err != nil {
- panic(err) // We created these blobs, we must be able to decode them
- }
- accs[i] = acc
- }
- response := &accountResponse{
- task: req.task,
- hashes: hashes,
- accounts: accs,
- cont: cont,
- }
- select {
- case req.deliver <- response:
- case <-req.cancel:
- case <-req.stale:
- }
- return nil
-}
-
-// OnByteCodes is a callback method to invoke when a batch of contract
-// bytes codes are received from a remote peer.
-func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
- s.lock.RLock()
- syncing := !s.snapped
- s.lock.RUnlock()
-
- if syncing {
- return s.onByteCodes(peer, id, bytecodes)
- }
- return s.onHealByteCodes(peer, id, bytecodes)
-}
-
-// onByteCodes is a callback method to invoke when a batch of contract
-// bytes codes are received from a remote peer in the syncing phase.
-func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
- var size common.StorageSize
- for _, code := range bytecodes {
- size += common.StorageSize(len(code))
- }
- logger := peer.Log().New("reqid", id)
- logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size)
-
- // Whether or not the response is valid, we can mark the peer as idle and
- // notify the scheduler to assign a new task. If the response is invalid,
- // we'll drop the peer in a bit.
- defer func() {
- s.lock.Lock()
- defer s.lock.Unlock()
- if _, ok := s.peers[peer.ID()]; ok {
- s.bytecodeIdlers[peer.ID()] = struct{}{}
- }
- select {
- case s.update <- struct{}{}:
- default:
- }
- }()
- s.lock.Lock()
- // Ensure the response is for a valid request
- req, ok := s.bytecodeReqs[id]
- if !ok {
- // Request stale, perhaps the peer timed out but came through in the end
- logger.Warn("Unexpected bytecode packet")
- s.lock.Unlock()
- return nil
- }
- delete(s.bytecodeReqs, id)
- s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes))
-
- // Clean up the request timeout timer, we'll see how to proceed further based
- // on the actual delivered content
- if !req.timeout.Stop() {
- // The timeout is already triggered, and this request will be reverted+rescheduled
- s.lock.Unlock()
- return nil
- }
-
- // Response is valid, but check if peer is signalling that it does not have
- // the requested data. For bytecode range queries that means the peer is not
- // yet synced.
- if len(bytecodes) == 0 {
- logger.Debug("Peer rejected bytecode request")
- s.statelessPeers[peer.ID()] = struct{}{}
- s.lock.Unlock()
-
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertBytecodeRequest(req)
- return nil
- }
- s.lock.Unlock()
-
- // Cross reference the requested bytecodes with the response to find gaps
- // that the serving node is missing
- hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
- hash := make([]byte, 32)
-
- codes := make([][]byte, len(req.hashes))
- for i, j := 0, 0; i < len(bytecodes); i++ {
- // Find the next hash that we've been served, leaving misses with nils
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hasher.Read(hash)
-
- for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
- j++
- }
- if j < len(req.hashes) {
- codes[j] = bytecodes[i]
- j++
- continue
- }
- // We've either ran out of hashes, or got unrequested data
- logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i)
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertBytecodeRequest(req)
- return errors.New("unexpected bytecode")
- }
- // Response validated, send it to the scheduler for filling
- response := &bytecodeResponse{
- task: req.task,
- hashes: req.hashes,
- codes: codes,
- }
- select {
- case req.deliver <- response:
- case <-req.cancel:
- case <-req.stale:
- }
- return nil
-}
-
-// OnStorage is a callback method to invoke when ranges of storage slots
-// are received from a remote peer.
-func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error {
- // Gather some trace stats to aid in debugging issues
- var (
- hashCount int
- slotCount int
- size common.StorageSize
- )
- for _, hashset := range hashes {
- size += common.StorageSize(common.HashLength * len(hashset))
- hashCount += len(hashset)
- }
- for _, slotset := range slots {
- for _, slot := range slotset {
- size += common.StorageSize(len(slot))
- }
- slotCount += len(slotset)
- }
- for _, node := range proof {
- size += common.StorageSize(len(node))
- }
- logger := peer.Log().New("reqid", id)
- logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size)
-
- // Whether or not the response is valid, we can mark the peer as idle and
- // notify the scheduler to assign a new task. If the response is invalid,
- // we'll drop the peer in a bit.
- defer func() {
- s.lock.Lock()
- defer s.lock.Unlock()
- if _, ok := s.peers[peer.ID()]; ok {
- s.storageIdlers[peer.ID()] = struct{}{}
- }
- select {
- case s.update <- struct{}{}:
- default:
- }
- }()
- s.lock.Lock()
- // Ensure the response is for a valid request
- req, ok := s.storageReqs[id]
- if !ok {
- // Request stale, perhaps the peer timed out but came through in the end
- logger.Warn("Unexpected storage ranges packet")
- s.lock.Unlock()
- return nil
- }
- delete(s.storageReqs, id)
- s.rates.Update(peer.ID(), StorageRangesMsg, time.Since(req.time), int(size))
-
- // Clean up the request timeout timer, we'll see how to proceed further based
- // on the actual delivered content
- if !req.timeout.Stop() {
- // The timeout is already triggered, and this request will be reverted+rescheduled
- s.lock.Unlock()
- return nil
- }
-
- // Reject the response if the hash sets and slot sets don't match, or if the
- // peer sent more data than requested.
- if len(hashes) != len(slots) {
- s.lock.Unlock()
- s.scheduleRevertStorageRequest(req) // reschedule request
- logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots))
- return errors.New("hash and slot set size mismatch")
- }
- if len(hashes) > len(req.accounts) {
- s.lock.Unlock()
- s.scheduleRevertStorageRequest(req) // reschedule request
- logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts))
- return errors.New("hash set larger than requested")
- }
- // Response is valid, but check if peer is signalling that it does not have
- // the requested data. For storage range queries that means the state being
- // retrieved was either already pruned remotely, or the peer is not yet
- // synced to our head.
- if len(hashes) == 0 && len(proof) == 0 {
- logger.Debug("Peer rejected storage request")
- s.statelessPeers[peer.ID()] = struct{}{}
- s.lock.Unlock()
- s.scheduleRevertStorageRequest(req) // reschedule request
- return nil
- }
- s.lock.Unlock()
-
- // Reconstruct the partial tries from the response and verify them
- var cont bool
-
- // If a proof was attached while the response is empty, it indicates that the
- // requested range specified with 'origin' is empty. Construct an empty state
- // response locally to finalize the range.
- if len(hashes) == 0 && len(proof) > 0 {
- hashes = append(hashes, []common.Hash{})
- slots = append(slots, [][]byte{})
- }
- for i := 0; i < len(hashes); i++ {
- // Convert the keys and proofs into an internal format
- keys := make([][]byte, len(hashes[i]))
- for j, key := range hashes[i] {
- keys[j] = common.CopyBytes(key[:])
- }
- nodes := make(trienode.ProofList, 0, len(proof))
- if i == len(hashes)-1 {
- for _, node := range proof {
- nodes = append(nodes, node)
- }
- }
- var err error
- if len(nodes) == 0 {
- // No proof has been attached, the response must cover the entire key
- // space and hash to the origin root.
- _, err = trie.VerifyRangeProof(req.roots[i], nil, keys, slots[i], nil)
- if err != nil {
- s.scheduleRevertStorageRequest(req) // reschedule request
- logger.Warn("Storage slots failed proof", "err", err)
- return err
- }
- } else {
- // A proof was attached, the response is only partial, check that the
- // returned data is indeed part of the storage trie
- proofdb := nodes.Set()
-
- cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], keys, slots[i], proofdb)
- if err != nil {
- s.scheduleRevertStorageRequest(req) // reschedule request
- logger.Warn("Storage range failed proof", "err", err)
- return err
- }
- }
- }
- // Partial tries reconstructed, send them to the scheduler for storage filling
- response := &storageResponse{
- mainTask: req.mainTask,
- subTask: req.subTask,
- accounts: req.accounts,
- roots: req.roots,
- hashes: hashes,
- slots: slots,
- cont: cont,
- }
- select {
- case req.deliver <- response:
- case <-req.cancel:
- case <-req.stale:
- }
- return nil
-}
-
-// OnTrieNodes is a callback method to invoke when a batch of trie nodes
-// are received from a remote peer.
-func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error {
- var size common.StorageSize
- for _, node := range trienodes {
- size += common.StorageSize(len(node))
- }
- logger := peer.Log().New("reqid", id)
- logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size)
-
- // Whether or not the response is valid, we can mark the peer as idle and
- // notify the scheduler to assign a new task. If the response is invalid,
- // we'll drop the peer in a bit.
- defer func() {
- s.lock.Lock()
- defer s.lock.Unlock()
- if _, ok := s.peers[peer.ID()]; ok {
- s.trienodeHealIdlers[peer.ID()] = struct{}{}
- }
- select {
- case s.update <- struct{}{}:
- default:
- }
- }()
- s.lock.Lock()
- // Ensure the response is for a valid request
- req, ok := s.trienodeHealReqs[id]
- if !ok {
- // Request stale, perhaps the peer timed out but came through in the end
- logger.Warn("Unexpected trienode heal packet")
- s.lock.Unlock()
- return nil
- }
- delete(s.trienodeHealReqs, id)
- s.rates.Update(peer.ID(), TrieNodesMsg, time.Since(req.time), len(trienodes))
-
- // Clean up the request timeout timer, we'll see how to proceed further based
- // on the actual delivered content
- if !req.timeout.Stop() {
- // The timeout is already triggered, and this request will be reverted+rescheduled
- s.lock.Unlock()
- return nil
- }
-
- // Response is valid, but check if peer is signalling that it does not have
- // the requested data. For bytecode range queries that means the peer is not
- // yet synced.
- if len(trienodes) == 0 {
- logger.Debug("Peer rejected trienode heal request")
- s.statelessPeers[peer.ID()] = struct{}{}
- s.lock.Unlock()
-
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertTrienodeHealRequest(req)
- return nil
- }
- s.lock.Unlock()
-
- // Cross reference the requested trienodes with the response to find gaps
- // that the serving node is missing
- var (
- hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState)
- hash = make([]byte, 32)
- nodes = make([][]byte, len(req.hashes))
- fills uint64
- )
- for i, j := 0, 0; i < len(trienodes); i++ {
- // Find the next hash that we've been served, leaving misses with nils
- hasher.Reset()
- hasher.Write(trienodes[i])
- hasher.Read(hash)
-
- for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
- j++
- }
- if j < len(req.hashes) {
- nodes[j] = trienodes[i]
- fills++
- j++
- continue
- }
- // We've either ran out of hashes, or got unrequested data
- logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i)
-
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertTrienodeHealRequest(req)
- return errors.New("unexpected healing trienode")
- }
- // Response validated, send it to the scheduler for filling
- s.trienodeHealPend.Add(fills)
- defer func() {
- s.trienodeHealPend.Add(^(fills - 1))
- }()
- response := &trienodeHealResponse{
- paths: req.paths,
- task: req.task,
- hashes: req.hashes,
- nodes: nodes,
- }
- select {
- case req.deliver <- response:
- case <-req.cancel:
- case <-req.stale:
- }
- return nil
-}
-
-// onHealByteCodes is a callback method to invoke when a batch of contract
-// bytes codes are received from a remote peer in the healing phase.
-func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
- var size common.StorageSize
- for _, code := range bytecodes {
- size += common.StorageSize(len(code))
- }
- logger := peer.Log().New("reqid", id)
- logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size)
-
- // Whether or not the response is valid, we can mark the peer as idle and
- // notify the scheduler to assign a new task. If the response is invalid,
- // we'll drop the peer in a bit.
- defer func() {
- s.lock.Lock()
- defer s.lock.Unlock()
- if _, ok := s.peers[peer.ID()]; ok {
- s.bytecodeHealIdlers[peer.ID()] = struct{}{}
- }
- select {
- case s.update <- struct{}{}:
- default:
- }
- }()
- s.lock.Lock()
- // Ensure the response is for a valid request
- req, ok := s.bytecodeHealReqs[id]
- if !ok {
- // Request stale, perhaps the peer timed out but came through in the end
- logger.Warn("Unexpected bytecode heal packet")
- s.lock.Unlock()
- return nil
- }
- delete(s.bytecodeHealReqs, id)
- s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes))
-
- // Clean up the request timeout timer, we'll see how to proceed further based
- // on the actual delivered content
- if !req.timeout.Stop() {
- // The timeout is already triggered, and this request will be reverted+rescheduled
- s.lock.Unlock()
- return nil
- }
-
- // Response is valid, but check if peer is signalling that it does not have
- // the requested data. For bytecode range queries that means the peer is not
- // yet synced.
- if len(bytecodes) == 0 {
- logger.Debug("Peer rejected bytecode heal request")
- s.statelessPeers[peer.ID()] = struct{}{}
- s.lock.Unlock()
-
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertBytecodeHealRequest(req)
- return nil
- }
- s.lock.Unlock()
-
- // Cross reference the requested bytecodes with the response to find gaps
- // that the serving node is missing
- hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
- hash := make([]byte, 32)
-
- codes := make([][]byte, len(req.hashes))
- for i, j := 0, 0; i < len(bytecodes); i++ {
- // Find the next hash that we've been served, leaving misses with nils
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hasher.Read(hash)
-
- for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
- j++
- }
- if j < len(req.hashes) {
- codes[j] = bytecodes[i]
- j++
- continue
- }
- // We've either ran out of hashes, or got unrequested data
- logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i)
- // Signal this request as failed, and ready for rescheduling
- s.scheduleRevertBytecodeHealRequest(req)
- return errors.New("unexpected healing bytecode")
- }
- // Response validated, send it to the scheduler for filling
- response := &bytecodeHealResponse{
- task: req.task,
- hashes: req.hashes,
- codes: codes,
- }
- select {
- case req.deliver <- response:
- case <-req.cancel:
- case <-req.stale:
- }
- return nil
-}
-
-// onHealState is a callback method to invoke when a flat state(account
-// or storage slot) is downloaded during the healing stage. The flat states
-// can be persisted blindly and can be fixed later in the generation stage.
-// Note it's not concurrent safe, please handle the concurrent issue outside.
-func (s *Syncer) onHealState(paths [][]byte, value []byte) error {
- if len(paths) == 1 {
- var account types.StateAccount
- if err := rlp.DecodeBytes(value, &account); err != nil {
- return nil // Returning the error here would drop the remote peer
- }
- blob := types.SlimAccountRLP(account)
- rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob)
- s.accountHealed += 1
- s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob))
- }
- if len(paths) == 2 {
- rawdb.WriteStorageSnapshot(s.stateWriter, common.BytesToHash(paths[0]), common.BytesToHash(paths[1]), value)
- s.storageHealed += 1
- s.storageHealedBytes += common.StorageSize(1 + 2*common.HashLength + len(value))
- }
- if s.stateWriter.ValueSize() > ethdb.IdealBatchSize {
- s.stateWriter.Write() // It's fine to ignore the error here
- s.stateWriter.Reset()
- }
- return nil
-}
-
-// hashSpace is the total size of the 256 bit hash space for accounts.
-var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil)
-
-// report calculates various status reports and provides it to the user.
-func (s *Syncer) report(force bool) {
- if len(s.tasks) > 0 {
- s.reportSyncProgress(force)
- return
- }
- s.reportHealProgress(force)
-}
-
-// reportSyncProgress calculates various status reports and provides it to the user.
-func (s *Syncer) reportSyncProgress(force bool) {
- // Don't report all the events, just occasionally
- if !force && time.Since(s.logTime) < 8*time.Second {
- return
- }
- // Don't report anything until we have a meaningful progress
- synced := s.accountBytes + s.bytecodeBytes + s.storageBytes
- if synced == 0 {
- return
- }
- accountGaps := new(big.Int)
- for _, task := range s.tasks {
- accountGaps.Add(accountGaps, new(big.Int).Sub(task.Last.Big(), task.Next.Big()))
- }
- accountFills := new(big.Int).Sub(hashSpace, accountGaps)
- if accountFills.BitLen() == 0 {
- return
- }
- s.logTime = time.Now()
- estBytes := float64(new(big.Int).Div(
- new(big.Int).Mul(new(big.Int).SetUint64(uint64(synced)), hashSpace),
- accountFills,
- ).Uint64())
- // Don't report anything until we have a meaningful progress
- if estBytes < 1.0 {
- return
- }
- elapsed := time.Since(s.startTime)
- estTime := elapsed / time.Duration(synced) * time.Duration(estBytes)
-
- // Create a mega progress report
- var (
- progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes)
- accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountSynced), s.accountBytes.TerminalString())
- storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageSynced), s.storageBytes.TerminalString())
- bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeSynced), s.bytecodeBytes.TerminalString())
- )
- log.Info("Syncing: state download in progress", "synced", progress, "state", synced,
- "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed))
-}
-
-// reportHealProgress calculates various status reports and provides it to the user.
-func (s *Syncer) reportHealProgress(force bool) {
- // Don't report all the events, just occasionally
- if !force && time.Since(s.logTime) < 8*time.Second {
- return
- }
- s.logTime = time.Now()
-
- // Create a mega progress report
- var (
- trienode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.trienodeHealSynced), s.trienodeHealBytes.TerminalString())
- bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeHealSynced), s.bytecodeHealBytes.TerminalString())
- accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountHealed), s.accountHealedBytes.TerminalString())
- storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageHealed), s.storageHealedBytes.TerminalString())
- )
- log.Info("Syncing: state healing in progress", "accounts", accounts, "slots", storage,
- "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending())
-}
-
-// estimateRemainingSlots tries to determine roughly how many slots are left in
-// a contract storage, based on the number of keys and the last hash. This method
-// assumes that the hashes are lexicographically ordered and evenly distributed.
-func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) {
- if last == (common.Hash{}) {
- return 0, errors.New("last hash empty")
- }
- space := new(big.Int).Mul(math.MaxBig256, big.NewInt(int64(hashes)))
- space.Div(space, last.Big())
- if !space.IsUint64() {
- // Gigantic address space probably due to too few or malicious slots
- return 0, errors.New("too few slots for estimation")
- }
- return space.Uint64() - uint64(hashes), nil
-}
-
-// capacitySort implements the Sort interface, allowing sorting by peer message
-// throughput. Note, callers should use sort.Reverse to get the desired effect
-// of highest capacity being at the front.
-type capacitySort struct {
- ids []string
- caps []int
-}
-
-func (s *capacitySort) Len() int {
- return len(s.ids)
-}
-
-func (s *capacitySort) Less(i, j int) bool {
- return s.caps[i] < s.caps[j]
-}
-
-func (s *capacitySort) Swap(i, j int) {
- s.ids[i], s.ids[j] = s.ids[j], s.ids[i]
- s.caps[i], s.caps[j] = s.caps[j], s.caps[i]
-}
-
-// healRequestSort implements the Sort interface, allowing sorting trienode
-// heal requests, which is a prerequisite for merging storage-requests.
-type healRequestSort struct {
- paths []string
- hashes []common.Hash
- syncPaths []trie.SyncPath
-}
-
-func (t *healRequestSort) Len() int {
- return len(t.hashes)
-}
-
-func (t *healRequestSort) Less(i, j int) bool {
- a := t.syncPaths[i]
- b := t.syncPaths[j]
- switch bytes.Compare(a[0], b[0]) {
- case -1:
- return true
- case 1:
- return false
- }
- // identical first part
- if len(a) < len(b) {
- return true
- }
- if len(b) < len(a) {
- return false
- }
- if len(a) == 2 {
- return bytes.Compare(a[1], b[1]) < 0
- }
- return false
-}
-
-func (t *healRequestSort) Swap(i, j int) {
- t.paths[i], t.paths[j] = t.paths[j], t.paths[i]
- t.hashes[i], t.hashes[j] = t.hashes[j], t.hashes[i]
- t.syncPaths[i], t.syncPaths[j] = t.syncPaths[j], t.syncPaths[i]
-}
-
-// Merge merges the pathsets, so that several storage requests concerning the
-// same account are merged into one, to reduce bandwidth.
-// OBS: This operation is moot if t has not first been sorted.
-func (t *healRequestSort) Merge() []TrieNodePathSet {
- var result []TrieNodePathSet
- for _, path := range t.syncPaths {
- pathset := TrieNodePathSet(path)
- if len(path) == 1 {
- // It's an account reference.
- result = append(result, pathset)
- } else {
- // It's a storage reference.
- end := len(result) - 1
- if len(result) == 0 || !bytes.Equal(pathset[0], result[end][0]) {
- // The account doesn't match last, create a new entry.
- result = append(result, pathset)
- } else {
- // It's the same account as the previous one, add to the storage
- // paths of that request.
- result[end] = append(result[end], pathset[1])
- }
- }
- }
- return result
-}
-
-// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates
-// the TrieNodePaths and merges paths which belongs to the same account path.
-func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common.Hash, []trie.SyncPath, []TrieNodePathSet) {
- var syncPaths []trie.SyncPath
- for _, path := range paths {
- syncPaths = append(syncPaths, trie.NewSyncPath([]byte(path)))
- }
- n := &healRequestSort{paths, hashes, syncPaths}
- sort.Sort(n)
- pathsets := n.Merge()
- return n.paths, n.hashes, n.syncPaths, pathsets
-}
diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go
deleted file mode 100644
index 5d4099a814..0000000000
--- a/eth/protocols/snap/sync_test.go
+++ /dev/null
@@ -1,1974 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "fmt"
- "math/big"
- mrand "math/rand"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/testutil"
- "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "golang.org/x/crypto/sha3"
- "golang.org/x/exp/slices"
-)
-
-func TestHashing(t *testing.T) {
- t.Parallel()
-
- var bytecodes = make([][]byte, 10)
- for i := 0; i < len(bytecodes); i++ {
- buf := make([]byte, 100)
- rand.Read(buf)
- bytecodes[i] = buf
- }
- var want, got string
- var old = func() {
- hasher := sha3.NewLegacyKeccak256()
- for i := 0; i < len(bytecodes); i++ {
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hash := hasher.Sum(nil)
- got = fmt.Sprintf("%v\n%v", got, hash)
- }
- }
- var new = func() {
- hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
- var hash = make([]byte, 32)
- for i := 0; i < len(bytecodes); i++ {
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hasher.Read(hash)
- want = fmt.Sprintf("%v\n%v", want, hash)
- }
- }
- old()
- new()
- if want != got {
- t.Errorf("want\n%v\ngot\n%v\n", want, got)
- }
-}
-
-func BenchmarkHashing(b *testing.B) {
- var bytecodes = make([][]byte, 10000)
- for i := 0; i < len(bytecodes); i++ {
- buf := make([]byte, 100)
- rand.Read(buf)
- bytecodes[i] = buf
- }
- var old = func() {
- hasher := sha3.NewLegacyKeccak256()
- for i := 0; i < len(bytecodes); i++ {
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hasher.Sum(nil)
- }
- }
- var new = func() {
- hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
- var hash = make([]byte, 32)
- for i := 0; i < len(bytecodes); i++ {
- hasher.Reset()
- hasher.Write(bytecodes[i])
- hasher.Read(hash)
- }
- }
- b.Run("old", func(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- old()
- }
- })
- b.Run("new", func(b *testing.B) {
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- new()
- }
- })
-}
-
-type (
- accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error
- storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error
- trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error
- codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error
-)
-
-type testPeer struct {
- id string
- test *testing.T
- remote *Syncer
- logger log.Logger
- accountTrie *trie.Trie
- accountValues []*kv
- storageTries map[common.Hash]*trie.Trie
- storageValues map[common.Hash][]*kv
-
- accountRequestHandler accountHandlerFunc
- storageRequestHandler storageHandlerFunc
- trieRequestHandler trieHandlerFunc
- codeRequestHandler codeHandlerFunc
- term func()
-
- // counters
- nAccountRequests int
- nStorageRequests int
- nBytecodeRequests int
- nTrienodeRequests int
-}
-
-func newTestPeer(id string, t *testing.T, term func()) *testPeer {
- peer := &testPeer{
- id: id,
- test: t,
- logger: log.New("id", id),
- accountRequestHandler: defaultAccountRequestHandler,
- trieRequestHandler: defaultTrieRequestHandler,
- storageRequestHandler: defaultStorageRequestHandler,
- codeRequestHandler: defaultCodeRequestHandler,
- term: term,
- }
- //stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
- //peer.logger.SetHandler(stderrHandler)
- return peer
-}
-
-func (t *testPeer) setStorageTries(tries map[common.Hash]*trie.Trie) {
- t.storageTries = make(map[common.Hash]*trie.Trie)
- for root, trie := range tries {
- t.storageTries[root] = trie.Copy()
- }
-}
-
-func (t *testPeer) ID() string { return t.id }
-func (t *testPeer) Log() log.Logger { return t.logger }
-
-func (t *testPeer) Stats() string {
- return fmt.Sprintf(`Account requests: %d
-Storage requests: %d
-Bytecode requests: %d
-Trienode requests: %d
-`, t.nAccountRequests, t.nStorageRequests, t.nBytecodeRequests, t.nTrienodeRequests)
-}
-
-func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error {
- t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
- t.nAccountRequests++
- go t.accountRequestHandler(t, id, root, origin, limit, bytes)
- return nil
-}
-
-func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
- t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
- t.nTrienodeRequests++
- go t.trieRequestHandler(t, id, root, paths, bytes)
- return nil
-}
-
-func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
- t.nStorageRequests++
- if len(accounts) == 1 && origin != nil {
- t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
- } else {
- t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
- }
- go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes)
- return nil
-}
-
-func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
- t.nBytecodeRequests++
- t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
- go t.codeRequestHandler(t, id, hashes, bytes)
- return nil
-}
-
-// defaultTrieRequestHandler is a well-behaving handler for trie healing requests
-func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
- // Pass the response
- var nodes [][]byte
- for _, pathset := range paths {
- switch len(pathset) {
- case 1:
- blob, _, err := t.accountTrie.GetNode(pathset[0])
- if err != nil {
- t.logger.Info("Error handling req", "error", err)
- break
- }
- nodes = append(nodes, blob)
- default:
- account := t.storageTries[(common.BytesToHash(pathset[0]))]
- for _, path := range pathset[1:] {
- blob, _, err := account.GetNode(path)
- if err != nil {
- t.logger.Info("Error handling req", "error", err)
- break
- }
- nodes = append(nodes, blob)
- }
- }
- }
- t.remote.OnTrieNodes(t, requestId, nodes)
- return nil
-}
-
-// defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests
-func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- keys, vals, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
- if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil {
- t.test.Errorf("Remote side rejected our delivery: %v", err)
- t.term()
- return err
- }
- return nil
-}
-
-func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) {
- var size uint64
- if limit == (common.Hash{}) {
- limit = common.MaxHash
- }
- for _, entry := range t.accountValues {
- if size > cap {
- break
- }
- if bytes.Compare(origin[:], entry.k) <= 0 {
- keys = append(keys, common.BytesToHash(entry.k))
- vals = append(vals, entry.v)
- size += uint64(32 + len(entry.v))
- }
- // If we've exceeded the request threshold, abort
- if bytes.Compare(entry.k, limit[:]) >= 0 {
- break
- }
- }
- // Unless we send the entire trie, we need to supply proofs
- // Actually, we need to supply proofs either way! This seems to be an implementation
- // quirk in go-ethereum
- proof := trienode.NewProofSet()
- if err := t.accountTrie.Prove(origin[:], proof); err != nil {
- t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err)
- }
- if len(keys) > 0 {
- lastK := (keys[len(keys)-1])[:]
- if err := t.accountTrie.Prove(lastK, proof); err != nil {
- t.logger.Error("Could not prove last item", "error", err)
- }
- }
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- return keys, vals, proofs
-}
-
-// defaultStorageRequestHandler is a well-behaving storage request handler
-func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error {
- hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max)
- if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
- t.test.Errorf("Remote side rejected our delivery: %v", err)
- t.term()
- }
- return nil
-}
-
-func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
- var bytecodes [][]byte
- for _, h := range hashes {
- bytecodes = append(bytecodes, getCodeByHash(h))
- }
- if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
- t.test.Errorf("Remote side rejected our delivery: %v", err)
- t.term()
- }
- return nil
-}
-
-func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
- var size uint64
- for _, account := range accounts {
- // The first account might start from a different origin and end sooner
- var originHash common.Hash
- if len(origin) > 0 {
- originHash = common.BytesToHash(origin)
- }
- var limitHash = common.MaxHash
- if len(limit) > 0 {
- limitHash = common.BytesToHash(limit)
- }
- var (
- keys []common.Hash
- vals [][]byte
- abort bool
- )
- for _, entry := range t.storageValues[account] {
- if size >= max {
- abort = true
- break
- }
- if bytes.Compare(entry.k, originHash[:]) < 0 {
- continue
- }
- keys = append(keys, common.BytesToHash(entry.k))
- vals = append(vals, entry.v)
- size += uint64(32 + len(entry.v))
- if bytes.Compare(entry.k, limitHash[:]) >= 0 {
- break
- }
- }
- if len(keys) > 0 {
- hashes = append(hashes, keys)
- slots = append(slots, vals)
- }
- // Generate the Merkle proofs for the first and last storage slot, but
- // only if the response was capped. If the entire storage trie included
- // in the response, no need for any proofs.
- if originHash != (common.Hash{}) || (abort && len(keys) > 0) {
- // If we're aborting, we need to prove the first and last item
- // This terminates the response (and thus the loop)
- proof := trienode.NewProofSet()
- stTrie := t.storageTries[account]
-
- // Here's a potential gotcha: when constructing the proof, we cannot
- // use the 'origin' slice directly, but must use the full 32-byte
- // hash form.
- if err := stTrie.Prove(originHash[:], proof); err != nil {
- t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err)
- }
- if len(keys) > 0 {
- lastK := (keys[len(keys)-1])[:]
- if err := stTrie.Prove(lastK, proof); err != nil {
- t.logger.Error("Could not prove last item", "error", err)
- }
- }
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- break
- }
- }
- return hashes, slots, proofs
-}
-
-// createStorageRequestResponseAlwaysProve tests a cornercase, where the peer always
-// supplies the proof for the last account, even if it is 'complete'.
-func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
- var size uint64
- max = max * 3 / 4
-
- var origin common.Hash
- if len(bOrigin) > 0 {
- origin = common.BytesToHash(bOrigin)
- }
- var exit bool
- for i, account := range accounts {
- var keys []common.Hash
- var vals [][]byte
- for _, entry := range t.storageValues[account] {
- if bytes.Compare(entry.k, origin[:]) < 0 {
- exit = true
- }
- keys = append(keys, common.BytesToHash(entry.k))
- vals = append(vals, entry.v)
- size += uint64(32 + len(entry.v))
- if size > max {
- exit = true
- }
- }
- if i == len(accounts)-1 {
- exit = true
- }
- hashes = append(hashes, keys)
- slots = append(slots, vals)
-
- if exit {
- // If we're aborting, we need to prove the first and last item
- // This terminates the response (and thus the loop)
- proof := trienode.NewProofSet()
- stTrie := t.storageTries[account]
-
- // Here's a potential gotcha: when constructing the proof, we cannot
- // use the 'origin' slice directly, but must use the full 32-byte
- // hash form.
- if err := stTrie.Prove(origin[:], proof); err != nil {
- t.logger.Error("Could not prove inexistence of origin", "origin", origin,
- "error", err)
- }
- if len(keys) > 0 {
- lastK := (keys[len(keys)-1])[:]
- if err := stTrie.Prove(lastK, proof); err != nil {
- t.logger.Error("Could not prove last item", "error", err)
- }
- }
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- break
- }
- }
- return hashes, slots, proofs
-}
-
-// emptyRequestAccountRangeFn is a rejects AccountRangeRequests
-func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- t.remote.OnAccounts(t, requestId, nil, nil, nil)
- return nil
-}
-
-func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- return nil
-}
-
-func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
- t.remote.OnTrieNodes(t, requestId, nil)
- return nil
-}
-
-func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
- return nil
-}
-
-func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- t.remote.OnStorage(t, requestId, nil, nil, nil)
- return nil
-}
-
-func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- return nil
-}
-
-func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- hashes, slots, proofs := createStorageRequestResponseAlwaysProve(t, root, accounts, origin, limit, max)
- if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
- t.test.Errorf("Remote side rejected our delivery: %v", err)
- t.term()
- }
- return nil
-}
-
-//func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
-// var bytecodes [][]byte
-// t.remote.OnByteCodes(t, id, bytecodes)
-// return nil
-//}
-
-func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
- var bytecodes [][]byte
- for _, h := range hashes {
- // Send back the hashes
- bytecodes = append(bytecodes, h[:])
- }
- if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
- t.logger.Info("remote error on delivery (as expected)", "error", err)
- // Mimic the real-life handler, which drops a peer on errors
- t.remote.Unregister(t.id)
- }
- return nil
-}
-
-func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
- var bytecodes [][]byte
- for _, h := range hashes[:1] {
- bytecodes = append(bytecodes, getCodeByHash(h))
- }
- // Missing bytecode can be retrieved again, no error expected
- if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
- t.test.Errorf("Remote side rejected our delivery: %v", err)
- t.term()
- }
- return nil
-}
-
-// starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small
-func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500)
-}
-
-func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- return defaultAccountRequestHandler(t, requestId, root, origin, limit, 500)
-}
-
-//func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error {
-// return defaultAccountRequestHandler(t, requestId-1, root, origin, 500)
-//}
-
-func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
- if len(proofs) > 0 {
- proofs = proofs[1:]
- }
- if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil {
- t.logger.Info("remote error on delivery (as expected)", "error", err)
- // Mimic the real-life handler, which drops a peer on errors
- t.remote.Unregister(t.id)
- }
- return nil
-}
-
-// corruptStorageRequestHandler doesn't provide good proofs
-func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max)
- if len(proofs) > 0 {
- proofs = proofs[1:]
- }
- if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
- t.logger.Info("remote error on delivery (as expected)", "error", err)
- // Mimic the real-life handler, which drops a peer on errors
- t.remote.Unregister(t.id)
- }
- return nil
-}
-
-func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max)
- if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil {
- t.logger.Info("remote error on delivery (as expected)", "error", err)
- // Mimic the real-life handler, which drops a peer on errors
- t.remote.Unregister(t.id)
- }
- return nil
-}
-
-// TestSyncBloatedProof tests a scenario where we provide only _one_ value, but
-// also ship the entire trie inside the proof. If the attack is successful,
-// the remote side does not do any follow-up requests
-func TestSyncBloatedProof(t *testing.T) {
- t.Parallel()
-
- testSyncBloatedProof(t, rawdb.HashScheme)
- testSyncBloatedProof(t, rawdb.PathScheme)
-}
-
-func testSyncBloatedProof(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
- source := newTestPeer("source", t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
-
- source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
- var (
- proofs [][]byte
- keys []common.Hash
- vals [][]byte
- )
- // The values
- for _, entry := range t.accountValues {
- if bytes.Compare(entry.k, origin[:]) < 0 {
- continue
- }
- if bytes.Compare(entry.k, limit[:]) > 0 {
- continue
- }
- keys = append(keys, common.BytesToHash(entry.k))
- vals = append(vals, entry.v)
- }
- // The proofs
- proof := trienode.NewProofSet()
- if err := t.accountTrie.Prove(origin[:], proof); err != nil {
- t.logger.Error("Could not prove origin", "origin", origin, "error", err)
- t.logger.Error("Could not prove origin", "origin", origin, "error", err)
- }
- // The bloat: add proof of every single element
- for _, entry := range t.accountValues {
- if err := t.accountTrie.Prove(entry.k, proof); err != nil {
- t.logger.Error("Could not prove item", "error", err)
- }
- }
- // And remove one item from the elements
- if len(keys) > 2 {
- keys = append(keys[:1], keys[2:]...)
- vals = append(vals[:1], vals[2:]...)
- }
- for _, blob := range proof.List() {
- proofs = append(proofs, blob)
- }
- if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil {
- t.logger.Info("remote error on delivery (as expected)", "error", err)
- t.term()
- // This is actually correct, signal to exit the test successfully
- }
- return nil
- }
- syncer := setupSyncer(nodeScheme, source)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil {
- t.Fatal("No error returned from incomplete/cancelled sync")
- }
-}
-
-func setupSyncer(scheme string, peers ...*testPeer) *Syncer {
- stateDb := rawdb.NewMemoryDatabase()
- syncer := NewSyncer(stateDb, scheme)
- for _, peer := range peers {
- syncer.Register(peer)
- peer.remote = syncer
- }
- return syncer
-}
-
-// TestSync tests a basic sync with one peer
-func TestSync(t *testing.T) {
- t.Parallel()
-
- testSync(t, rawdb.HashScheme)
- testSync(t, rawdb.PathScheme)
-}
-
-func testSync(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- return source
- }
- syncer := setupSyncer(nodeScheme, mkSource("source"))
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a
-// panic within the prover
-func TestSyncTinyTriePanic(t *testing.T) {
- t.Parallel()
-
- testSyncTinyTriePanic(t, rawdb.HashScheme)
- testSyncTinyTriePanic(t, rawdb.PathScheme)
-}
-
-func testSyncTinyTriePanic(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(1, scheme)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- return source
- }
- syncer := setupSyncer(nodeScheme, mkSource("source"))
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestMultiSync tests a basic sync with multiple peers
-func TestMultiSync(t *testing.T) {
- t.Parallel()
-
- testMultiSync(t, rawdb.HashScheme)
- testMultiSync(t, rawdb.PathScheme)
-}
-
-func testMultiSync(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- return source
- }
- syncer := setupSyncer(nodeScheme, mkSource("sourceA"), mkSource("sourceB"))
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncWithStorage tests basic sync using accounts + storage + code
-func TestSyncWithStorage(t *testing.T) {
- t.Parallel()
-
- testSyncWithStorage(t, rawdb.HashScheme)
- testSyncWithStorage(t, rawdb.PathScheme)
-}
-
-func testSyncWithStorage(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false, false)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- return source
- }
- syncer := setupSyncer(scheme, mkSource("sourceA"))
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
-func TestMultiSyncManyUseless(t *testing.T) {
- t.Parallel()
-
- testMultiSyncManyUseless(t, rawdb.HashScheme)
- testMultiSyncManyUseless(t, rawdb.PathScheme)
-}
-
-func testMultiSyncManyUseless(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
-
- mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
-
- if !noAccount {
- source.accountRequestHandler = emptyRequestAccountRangeFn
- }
- if !noStorage {
- source.storageRequestHandler = emptyStorageRequestHandler
- }
- if !noTrieNode {
- source.trieRequestHandler = emptyTrieRequestHandler
- }
- return source
- }
-
- syncer := setupSyncer(
- scheme,
- mkSource("full", true, true, true),
- mkSource("noAccounts", false, true, true),
- mkSource("noStorage", true, false, true),
- mkSource("noTrie", true, true, false),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
-func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) {
- t.Parallel()
-
- testMultiSyncManyUselessWithLowTimeout(t, rawdb.HashScheme)
- testMultiSyncManyUselessWithLowTimeout(t, rawdb.PathScheme)
-}
-
-func testMultiSyncManyUselessWithLowTimeout(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
-
- mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
-
- if !noAccount {
- source.accountRequestHandler = emptyRequestAccountRangeFn
- }
- if !noStorage {
- source.storageRequestHandler = emptyStorageRequestHandler
- }
- if !noTrieNode {
- source.trieRequestHandler = emptyTrieRequestHandler
- }
- return source
- }
-
- syncer := setupSyncer(
- scheme,
- mkSource("full", true, true, true),
- mkSource("noAccounts", false, true, true),
- mkSource("noStorage", true, false, true),
- mkSource("noTrie", true, true, false),
- )
- // We're setting the timeout to very low, to increase the chance of the timeout
- // being triggered. This was previously a cause of panic, when a response
- // arrived simultaneously as a timeout was triggered.
- syncer.rates.OverrideTTLLimit = time.Millisecond
-
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all
-func TestMultiSyncManyUnresponsive(t *testing.T) {
- t.Parallel()
-
- testMultiSyncManyUnresponsive(t, rawdb.HashScheme)
- testMultiSyncManyUnresponsive(t, rawdb.PathScheme)
-}
-
-func testMultiSyncManyUnresponsive(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
-
- mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
-
- if !noAccount {
- source.accountRequestHandler = nonResponsiveRequestAccountRangeFn
- }
- if !noStorage {
- source.storageRequestHandler = nonResponsiveStorageRequestHandler
- }
- if !noTrieNode {
- source.trieRequestHandler = nonResponsiveTrieRequestHandler
- }
- return source
- }
-
- syncer := setupSyncer(
- scheme,
- mkSource("full", true, true, true),
- mkSource("noAccounts", false, true, true),
- mkSource("noStorage", true, false, true),
- mkSource("noTrie", true, true, false),
- )
- // We're setting the timeout to very low, to make the test run a bit faster
- syncer.rates.OverrideTTLLimit = time.Millisecond
-
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-func checkStall(t *testing.T, term func()) chan struct{} {
- testDone := make(chan struct{})
- go func() {
- select {
- case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much
- t.Log("Sync stalled")
- term()
- case <-testDone:
- return
- }
- }()
- return testDone
-}
-
-// TestSyncBoundaryAccountTrie tests sync against a few normal peers, but the
-// account trie has a few boundary elements.
-func TestSyncBoundaryAccountTrie(t *testing.T) {
- t.Parallel()
-
- testSyncBoundaryAccountTrie(t, rawdb.HashScheme)
- testSyncBoundaryAccountTrie(t, rawdb.PathScheme)
-}
-
-func testSyncBoundaryAccountTrie(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeBoundaryAccountTrie(scheme, 3000)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- return source
- }
- syncer := setupSyncer(
- nodeScheme,
- mkSource("peer-a"),
- mkSource("peer-b"),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is
-// consistently returning very small results
-func TestSyncNoStorageAndOneCappedPeer(t *testing.T) {
- t.Parallel()
-
- testSyncNoStorageAndOneCappedPeer(t, rawdb.HashScheme)
- testSyncNoStorageAndOneCappedPeer(t, rawdb.PathScheme)
-}
-
-func testSyncNoStorageAndOneCappedPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme)
-
- mkSource := func(name string, slow bool) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
-
- if slow {
- source.accountRequestHandler = starvingAccountRequestHandler
- }
- return source
- }
-
- syncer := setupSyncer(
- nodeScheme,
- mkSource("nice-a", false),
- mkSource("nice-b", false),
- mkSource("nice-c", false),
- mkSource("capped", true),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver
-// code requests properly.
-func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) {
- t.Parallel()
-
- testSyncNoStorageAndOneCodeCorruptPeer(t, rawdb.HashScheme)
- testSyncNoStorageAndOneCodeCorruptPeer(t, rawdb.PathScheme)
-}
-
-func testSyncNoStorageAndOneCodeCorruptPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme)
-
- mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.codeRequestHandler = codeFn
- return source
- }
- // One is capped, one is corrupt. If we don't use a capped one, there's a 50%
- // chance that the full set of codes requested are sent only to the
- // non-corrupt peer, which delivers everything in one go, and makes the
- // test moot
- syncer := setupSyncer(
- nodeScheme,
- mkSource("capped", cappedCodeRequestHandler),
- mkSource("corrupt", corruptCodeRequestHandler),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) {
- t.Parallel()
-
- testSyncNoStorageAndOneAccountCorruptPeer(t, rawdb.HashScheme)
- testSyncNoStorageAndOneAccountCorruptPeer(t, rawdb.PathScheme)
-}
-
-func testSyncNoStorageAndOneAccountCorruptPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme)
-
- mkSource := func(name string, accFn accountHandlerFunc) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.accountRequestHandler = accFn
- return source
- }
- // One is capped, one is corrupt. If we don't use a capped one, there's a 50%
- // chance that the full set of codes requested are sent only to the
- // non-corrupt peer, which delivers everything in one go, and makes the
- // test moot
- syncer := setupSyncer(
- nodeScheme,
- mkSource("capped", defaultAccountRequestHandler),
- mkSource("corrupt", corruptAccountRequestHandler),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes
-// one by one
-func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) {
- t.Parallel()
-
- testSyncNoStorageAndOneCodeCappedPeer(t, rawdb.HashScheme)
- testSyncNoStorageAndOneCodeCappedPeer(t, rawdb.PathScheme)
-}
-
-func testSyncNoStorageAndOneCodeCappedPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme)
-
- mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.codeRequestHandler = codeFn
- return source
- }
- // Count how many times it's invoked. Remember, there are only 8 unique hashes,
- // so it shouldn't be more than that
- var counter int
- syncer := setupSyncer(
- nodeScheme,
- mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
- counter++
- return cappedCodeRequestHandler(t, id, hashes, max)
- }),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
-
- // There are only 8 unique hashes, and 3K accounts. However, the code
- // deduplication is per request batch. If it were a perfect global dedup,
- // we would expect only 8 requests. If there were no dedup, there would be
- // 3k requests.
- // We expect somewhere below 100 requests for these 8 unique hashes. But
- // the number can be flaky, so don't limit it so strictly.
- if threshold := 100; counter > threshold {
- t.Logf("Error, expected < %d invocations, got %d", threshold, counter)
- }
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the
-// storage trie has a few boundary elements.
-func TestSyncBoundaryStorageTrie(t *testing.T) {
- t.Parallel()
-
- testSyncBoundaryStorageTrie(t, rawdb.HashScheme)
- testSyncBoundaryStorageTrie(t, rawdb.PathScheme)
-}
-
-func testSyncBoundaryStorageTrie(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true, false)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- return source
- }
- syncer := setupSyncer(
- scheme,
- mkSource("peer-a"),
- mkSource("peer-b"),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is
-// consistently returning very small results
-func TestSyncWithStorageAndOneCappedPeer(t *testing.T) {
- t.Parallel()
-
- testSyncWithStorageAndOneCappedPeer(t, rawdb.HashScheme)
- testSyncWithStorageAndOneCappedPeer(t, rawdb.PathScheme)
-}
-
-func testSyncWithStorageAndOneCappedPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false, false)
-
- mkSource := func(name string, slow bool) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
-
- if slow {
- source.storageRequestHandler = starvingStorageRequestHandler
- }
- return source
- }
-
- syncer := setupSyncer(
- scheme,
- mkSource("nice-a", false),
- mkSource("slow", true),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is
-// sometimes sending bad proofs
-func TestSyncWithStorageAndCorruptPeer(t *testing.T) {
- t.Parallel()
-
- testSyncWithStorageAndCorruptPeer(t, rawdb.HashScheme)
- testSyncWithStorageAndCorruptPeer(t, rawdb.PathScheme)
-}
-
-func testSyncWithStorageAndCorruptPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
-
- mkSource := func(name string, handler storageHandlerFunc) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- source.storageRequestHandler = handler
- return source
- }
-
- syncer := setupSyncer(
- scheme,
- mkSource("nice-a", defaultStorageRequestHandler),
- mkSource("nice-b", defaultStorageRequestHandler),
- mkSource("nice-c", defaultStorageRequestHandler),
- mkSource("corrupt", corruptStorageRequestHandler),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-func TestSyncWithStorageAndNonProvingPeer(t *testing.T) {
- t.Parallel()
-
- testSyncWithStorageAndNonProvingPeer(t, rawdb.HashScheme)
- testSyncWithStorageAndNonProvingPeer(t, rawdb.PathScheme)
-}
-
-func testSyncWithStorageAndNonProvingPeer(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
-
- mkSource := func(name string, handler storageHandlerFunc) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- source.storageRequestHandler = handler
- return source
- }
- syncer := setupSyncer(
- scheme,
- mkSource("nice-a", defaultStorageRequestHandler),
- mkSource("nice-b", defaultStorageRequestHandler),
- mkSource("nice-c", defaultStorageRequestHandler),
- mkSource("corrupt", noProofStorageRequestHandler),
- )
- done := checkStall(t, term)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- close(done)
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncWithStorage tests basic sync using accounts + storage + code, against
-// a peer who insists on delivering full storage sets _and_ proofs. This triggered
-// an error, where the recipient erroneously clipped the boundary nodes, but
-// did not mark the account for healing.
-func TestSyncWithStorageMisbehavingProve(t *testing.T) {
- t.Parallel()
-
- testSyncWithStorageMisbehavingProve(t, rawdb.HashScheme)
- testSyncWithStorageMisbehavingProve(t, rawdb.PathScheme)
-}
-
-func testSyncWithStorageMisbehavingProve(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(scheme, 10, 30, false)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- source.storageRequestHandler = proofHappyStorageRequestHandler
- return source
- }
- syncer := setupSyncer(nodeScheme, mkSource("sourceA"))
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
-}
-
-// TestSyncWithUnevenStorage tests sync where the storage trie is not even
-// and with a few empty ranges.
-func TestSyncWithUnevenStorage(t *testing.T) {
- t.Parallel()
-
- testSyncWithUnevenStorage(t, rawdb.HashScheme)
- testSyncWithUnevenStorage(t, rawdb.PathScheme)
-}
-
-func testSyncWithUnevenStorage(t *testing.T, scheme string) {
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- accountTrie, accounts, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 256, false, false, true)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = accountTrie.Copy()
- source.accountValues = accounts
- source.setStorageTries(storageTries)
- source.storageValues = storageElems
- source.storageRequestHandler = func(t *testPeer, reqId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
- return defaultStorageRequestHandler(t, reqId, root, accounts, origin, limit, 128) // retrieve storage in large mode
- }
- return source
- }
- syncer := setupSyncer(scheme, mkSource("source"))
- if err := syncer.Sync(accountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- verifyTrie(scheme, syncer.db, accountTrie.Hash(), t)
-}
-
-type kv struct {
- k, v []byte
-}
-
-func (k *kv) cmp(other *kv) int {
- return bytes.Compare(k.k, other.k)
-}
-
-func key32(i uint64) []byte {
- key := make([]byte, 32)
- binary.LittleEndian.PutUint64(key, i)
- return key
-}
-
-var (
- codehashes = []common.Hash{
- crypto.Keccak256Hash([]byte{0}),
- crypto.Keccak256Hash([]byte{1}),
- crypto.Keccak256Hash([]byte{2}),
- crypto.Keccak256Hash([]byte{3}),
- crypto.Keccak256Hash([]byte{4}),
- crypto.Keccak256Hash([]byte{5}),
- crypto.Keccak256Hash([]byte{6}),
- crypto.Keccak256Hash([]byte{7}),
- }
-)
-
-// getCodeHash returns a pseudo-random code hash
-func getCodeHash(i uint64) []byte {
- h := codehashes[int(i)%len(codehashes)]
- return common.CopyBytes(h[:])
-}
-
-// getCodeByHash convenience function to lookup the code from the code hash
-func getCodeByHash(hash common.Hash) []byte {
- if hash == types.EmptyCodeHash {
- return nil
- }
- for i, h := range codehashes {
- if h == hash {
- return []byte{byte(i)}
- }
- }
- return nil
-}
-
-// makeAccountTrieNoStorage spits out a trie, along with the leafs
-func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv) {
- var (
- db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
- accTrie = trie.NewEmpty(db)
- entries []*kv
- )
- for i := uint64(1); i <= uint64(n); i++ {
- value, _ := rlp.EncodeToBytes(&types.StateAccount{
- Nonce: i,
- Balance: big.NewInt(int64(i)),
- Root: types.EmptyRootHash,
- CodeHash: getCodeHash(i),
- })
- key := key32(i)
- elem := &kv{key, value}
- accTrie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Commit the state changes into db and re-create the trie
- // for accessing later.
- root, nodes, _ := accTrie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- accTrie, _ = trie.New(trie.StateTrieID(root), db)
- return db.Scheme(), accTrie, entries
-}
-
-// makeBoundaryAccountTrie constructs an account trie. Instead of filling
-// accounts normally, this function will fill a few accounts which have
-// boundary hash.
-func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) {
- var (
- entries []*kv
- boundaries []common.Hash
-
- db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
- accTrie = trie.NewEmpty(db)
- )
- // Initialize boundaries
- var next common.Hash
- step := new(big.Int).Sub(
- new(big.Int).Div(
- new(big.Int).Exp(common.Big2, common.Big256, nil),
- big.NewInt(int64(accountConcurrency)),
- ), common.Big1,
- )
- for i := 0; i < accountConcurrency; i++ {
- last := common.BigToHash(new(big.Int).Add(next.Big(), step))
- if i == accountConcurrency-1 {
- last = common.MaxHash
- }
- boundaries = append(boundaries, last)
- next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
- }
- // Fill boundary accounts
- for i := 0; i < len(boundaries); i++ {
- value, _ := rlp.EncodeToBytes(&types.StateAccount{
- Nonce: uint64(0),
- Balance: big.NewInt(int64(i)),
- Root: types.EmptyRootHash,
- CodeHash: getCodeHash(uint64(i)),
- })
- elem := &kv{boundaries[i].Bytes(), value}
- accTrie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- // Fill other accounts if required
- for i := uint64(1); i <= uint64(n); i++ {
- value, _ := rlp.EncodeToBytes(&types.StateAccount{
- Nonce: i,
- Balance: big.NewInt(int64(i)),
- Root: types.EmptyRootHash,
- CodeHash: getCodeHash(i),
- })
- elem := &kv{key32(i), value}
- accTrie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Commit the state changes into db and re-create the trie
- // for accessing later.
- root, nodes, _ := accTrie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- accTrie, _ = trie.New(trie.StateTrieID(root), db)
- return db.Scheme(), accTrie, entries
-}
-
-// makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts
-// has a unique storage set.
-func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots int, code bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
- var (
- db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
- accTrie = trie.NewEmpty(db)
- entries []*kv
- storageRoots = make(map[common.Hash]common.Hash)
- storageTries = make(map[common.Hash]*trie.Trie)
- storageEntries = make(map[common.Hash][]*kv)
- nodes = trienode.NewMergedNodeSet()
- )
- // Create n accounts in the trie
- for i := uint64(1); i <= uint64(accounts); i++ {
- key := key32(i)
- codehash := types.EmptyCodeHash.Bytes()
- if code {
- codehash = getCodeHash(i)
- }
- // Create a storage trie
- stRoot, stNodes, stEntries := makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), i, db)
- nodes.Merge(stNodes)
-
- value, _ := rlp.EncodeToBytes(&types.StateAccount{
- Nonce: i,
- Balance: big.NewInt(int64(i)),
- Root: stRoot,
- CodeHash: codehash,
- })
- elem := &kv{key, value}
- accTrie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
-
- storageRoots[common.BytesToHash(key)] = stRoot
- storageEntries[common.BytesToHash(key)] = stEntries
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Commit account trie
- root, set, _ := accTrie.Commit(true)
- nodes.Merge(set)
-
- // Commit gathered dirty nodes into database
- db.Update(root, types.EmptyRootHash, 0, nodes, nil)
-
- // Re-create tries with new root
- accTrie, _ = trie.New(trie.StateTrieID(root), db)
- for i := uint64(1); i <= uint64(accounts); i++ {
- key := key32(i)
- id := trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)])
- trie, _ := trie.New(id, db)
- storageTries[common.BytesToHash(key)] = trie
- }
- return db.Scheme(), accTrie, entries, storageTries, storageEntries
-}
-
-// makeAccountTrieWithStorage spits out a trie, along with the leafs
-func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool, uneven bool) (*trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
- var (
- db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
- accTrie = trie.NewEmpty(db)
- entries []*kv
- storageRoots = make(map[common.Hash]common.Hash)
- storageTries = make(map[common.Hash]*trie.Trie)
- storageEntries = make(map[common.Hash][]*kv)
- nodes = trienode.NewMergedNodeSet()
- )
- // Create n accounts in the trie
- for i := uint64(1); i <= uint64(accounts); i++ {
- key := key32(i)
- codehash := types.EmptyCodeHash.Bytes()
- if code {
- codehash = getCodeHash(i)
- }
- // Make a storage trie
- var (
- stRoot common.Hash
- stNodes *trienode.NodeSet
- stEntries []*kv
- )
- if boundary {
- stRoot, stNodes, stEntries = makeBoundaryStorageTrie(common.BytesToHash(key), slots, db)
- } else if uneven {
- stRoot, stNodes, stEntries = makeUnevenStorageTrie(common.BytesToHash(key), slots, db)
- } else {
- stRoot, stNodes, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db)
- }
- nodes.Merge(stNodes)
-
- value, _ := rlp.EncodeToBytes(&types.StateAccount{
- Nonce: i,
- Balance: big.NewInt(int64(i)),
- Root: stRoot,
- CodeHash: codehash,
- })
- elem := &kv{key, value}
- accTrie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
-
- // we reuse the same one for all accounts
- storageRoots[common.BytesToHash(key)] = stRoot
- storageEntries[common.BytesToHash(key)] = stEntries
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Commit account trie
- root, set, _ := accTrie.Commit(true)
- nodes.Merge(set)
-
- // Commit gathered dirty nodes into database
- db.Update(root, types.EmptyRootHash, 0, nodes, nil)
-
- // Re-create tries with new root
- accTrie, err := trie.New(trie.StateTrieID(root), db)
- if err != nil {
- panic(err)
- }
- for i := uint64(1); i <= uint64(accounts); i++ {
- key := key32(i)
- id := trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)])
- trie, err := trie.New(id, db)
- if err != nil {
- panic(err)
- }
- storageTries[common.BytesToHash(key)] = trie
- }
- return accTrie, entries, storageTries, storageEntries
-}
-
-// makeStorageTrieWithSeed fills a storage trie with n items, returning the
-// not-yet-committed trie and the sorted entries. The seeds can be used to ensure
-// that tries are unique.
-func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) {
- trie, _ := trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db)
- var entries []*kv
- for i := uint64(1); i <= n; i++ {
- // store 'x' at slot 'x'
- slotValue := key32(i + seed)
- rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
-
- slotKey := key32(i)
- key := crypto.Keccak256Hash(slotKey[:])
-
- elem := &kv{key[:], rlpSlotValue}
- trie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := trie.Commit(false)
- return root, nodes, entries
-}
-
-// makeBoundaryStorageTrie constructs a storage trie. Instead of filling
-// storage slots normally, this function will fill a few slots which have
-// boundary hash.
-func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) {
- var (
- entries []*kv
- boundaries []common.Hash
- trie, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db)
- )
- // Initialize boundaries
- var next common.Hash
- step := new(big.Int).Sub(
- new(big.Int).Div(
- new(big.Int).Exp(common.Big2, common.Big256, nil),
- big.NewInt(int64(accountConcurrency)),
- ), common.Big1,
- )
- for i := 0; i < accountConcurrency; i++ {
- last := common.BigToHash(new(big.Int).Add(next.Big(), step))
- if i == accountConcurrency-1 {
- last = common.MaxHash
- }
- boundaries = append(boundaries, last)
- next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
- }
- // Fill boundary slots
- for i := 0; i < len(boundaries); i++ {
- key := boundaries[i]
- val := []byte{0xde, 0xad, 0xbe, 0xef}
-
- elem := &kv{key[:], val}
- trie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- // Fill other slots if required
- for i := uint64(1); i <= uint64(n); i++ {
- slotKey := key32(i)
- key := crypto.Keccak256Hash(slotKey[:])
-
- slotValue := key32(i)
- rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
-
- elem := &kv{key[:], rlpSlotValue}
- trie.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := trie.Commit(false)
- return root, nodes, entries
-}
-
-// makeUnevenStorageTrie constructs a storage tries will states distributed in
-// different range unevenly.
-func makeUnevenStorageTrie(owner common.Hash, slots int, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) {
- var (
- entries []*kv
- tr, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db)
- chosen = make(map[byte]struct{})
- )
- for i := 0; i < 3; i++ {
- var n int
- for {
- n = mrand.Intn(15) // the last range is set empty deliberately
- if _, ok := chosen[byte(n)]; ok {
- continue
- }
- chosen[byte(n)] = struct{}{}
- break
- }
- for j := 0; j < slots/3; j++ {
- key := append([]byte{byte(n)}, testutil.RandBytes(31)...)
- val, _ := rlp.EncodeToBytes(testutil.RandBytes(32))
-
- elem := &kv{key, val}
- tr.MustUpdate(elem.k, elem.v)
- entries = append(entries, elem)
- }
- }
- slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := tr.Commit(false)
- return root, nodes, entries
-}
-
-func verifyTrie(scheme string, db ethdb.KeyValueStore, root common.Hash, t *testing.T) {
- t.Helper()
- triedb := trie.NewDatabase(rawdb.NewDatabase(db), newDbConfig(scheme))
- accTrie, err := trie.New(trie.StateTrieID(root), triedb)
- if err != nil {
- t.Fatal(err)
- }
- accounts, slots := 0, 0
- accIt := trie.NewIterator(accTrie.MustNodeIterator(nil))
- for accIt.Next() {
- var acc struct {
- Nonce uint64
- Balance *big.Int
- Root common.Hash
- CodeHash []byte
- }
- if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
- log.Crit("Invalid account encountered during snapshot creation", "err", err)
- }
- accounts++
- if acc.Root != types.EmptyRootHash {
- id := trie.StorageTrieID(root, common.BytesToHash(accIt.Key), acc.Root)
- storeTrie, err := trie.NewStateTrie(id, triedb)
- if err != nil {
- t.Fatal(err)
- }
- storeIt := trie.NewIterator(storeTrie.MustNodeIterator(nil))
- for storeIt.Next() {
- slots++
- }
- if err := storeIt.Err; err != nil {
- t.Fatal(err)
- }
- }
- }
- if err := accIt.Err; err != nil {
- t.Fatal(err)
- }
- t.Logf("accounts: %d, slots: %d", accounts, slots)
-}
-
-// TestSyncAccountPerformance tests how efficient the snap algo is at minimizing
-// state healing
-func TestSyncAccountPerformance(t *testing.T) {
- t.Parallel()
-
- testSyncAccountPerformance(t, rawdb.HashScheme)
- testSyncAccountPerformance(t, rawdb.PathScheme)
-}
-
-func testSyncAccountPerformance(t *testing.T, scheme string) {
- // Set the account concurrency to 1. This _should_ result in the
- // range root to become correct, and there should be no healing needed
- defer func(old int) { accountConcurrency = old }(accountConcurrency)
- accountConcurrency = 1
-
- var (
- once sync.Once
- cancel = make(chan struct{})
- term = func() {
- once.Do(func() {
- close(cancel)
- })
- }
- )
- nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme)
-
- mkSource := func(name string) *testPeer {
- source := newTestPeer(name, t, term)
- source.accountTrie = sourceAccountTrie.Copy()
- source.accountValues = elems
- return source
- }
- src := mkSource("source")
- syncer := setupSyncer(nodeScheme, src)
- if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
- t.Fatalf("sync failed: %v", err)
- }
- verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
- // The trie root will always be requested, since it is added when the snap
- // sync cycle starts. When popping the queue, we do not look it up again.
- // Doing so would bring this number down to zero in this artificial testcase,
- // but only add extra IO for no reason in practice.
- if have, want := src.nTrienodeRequests, 1; have != want {
- fmt.Print(src.Stats())
- t.Errorf("trie node heal requests wrong, want %d, have %d", want, have)
- }
-}
-
-func TestSlotEstimation(t *testing.T) {
- for i, tc := range []struct {
- last common.Hash
- count int
- want uint64
- }{
- {
- // Half the space
- common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- 100,
- 100,
- },
- {
- // 1 / 16th
- common.HexToHash("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
- 100,
- 1500,
- },
- {
- // Bit more than 1 / 16th
- common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000000"),
- 100,
- 1499,
- },
- {
- // Almost everything
- common.HexToHash("0xF000000000000000000000000000000000000000000000000000000000000000"),
- 100,
- 6,
- },
- {
- // Almost nothing -- should lead to error
- common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
- 1,
- 0,
- },
- {
- // Nothing -- should lead to error
- common.Hash{},
- 100,
- 0,
- },
- } {
- have, _ := estimateRemainingSlots(tc.count, tc.last)
- if want := tc.want; have != want {
- t.Errorf("test %d: have %d want %d", i, have, want)
- }
- }
-}
-
-func newDbConfig(scheme string) *trie.Config {
- if scheme == rawdb.HashScheme {
- return &trie.Config{}
- }
- return &trie.Config{PathDB: pathdb.Defaults}
-}
diff --git a/eth/protocols/snap/tracker.go b/eth/protocols/snap/tracker.go
deleted file mode 100644
index 2cf59cc23a..0000000000
--- a/eth/protocols/snap/tracker.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/tracker"
-)
-
-// requestTracker is a singleton tracker for request times.
-var requestTracker = tracker.New(ProtocolName, time.Minute)