forked from cerc-io/plugeth
Merge tag 'v1.10.14' into develop
This commit is contained in:
+1
-1
@@ -353,7 +353,7 @@ func (b *EthAPIBackend) StartMining(threads int) error {
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) {
|
||||
return b.eth.stateAtBlock(block, reexec, base, checkLive, preferDisk)
|
||||
return b.eth.StateAtBlock(block, reexec, base, checkLive, preferDisk)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
|
||||
|
||||
+43
-23
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||
@@ -46,6 +47,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
@@ -71,6 +73,7 @@ type Ethereum struct {
|
||||
handler *handler
|
||||
ethDialCandidates enode.Iterator
|
||||
snapDialCandidates enode.Iterator
|
||||
merger *consensus.Merger
|
||||
|
||||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
@@ -95,6 +98,8 @@ type Ethereum struct {
|
||||
p2pServer *p2p.Server
|
||||
|
||||
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
|
||||
|
||||
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
||||
}
|
||||
|
||||
// New creates a new Ethereum object (including the
|
||||
@@ -131,7 +136,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideArrowGlacier)
|
||||
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideArrowGlacier, config.OverrideTerminalTotalDifficulty)
|
||||
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
|
||||
return nil, genesisErr
|
||||
}
|
||||
@@ -140,8 +145,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
|
||||
log.Error("Failed to recover state", "error", err)
|
||||
}
|
||||
merger := consensus.NewMerger(chainDb)
|
||||
eth := &Ethereum{
|
||||
config: config,
|
||||
merger: merger,
|
||||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
accountManager: stack.AccountManager(),
|
||||
@@ -153,6 +160,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
||||
p2pServer: stack.Server(),
|
||||
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
|
||||
}
|
||||
|
||||
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
||||
@@ -215,6 +223,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
Database: chainDb,
|
||||
Chain: eth.blockchain,
|
||||
TxPool: eth.txPool,
|
||||
Merger: merger,
|
||||
Network: config.NetworkId,
|
||||
Sync: config.SyncMode,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
@@ -225,7 +234,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
|
||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, merger)
|
||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||
|
||||
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
||||
@@ -256,19 +265,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
stack.RegisterAPIs(eth.APIs())
|
||||
stack.RegisterProtocols(eth.Protocols())
|
||||
stack.RegisterLifecycle(eth)
|
||||
// Check for unclean shutdown
|
||||
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
|
||||
log.Error("Could not update unclean-shutdown-marker list", "error", err)
|
||||
} else {
|
||||
if discards > 0 {
|
||||
log.Warn("Old unclean shutdowns found", "count", discards)
|
||||
}
|
||||
for _, tstamp := range uncleanShutdowns {
|
||||
t := time.Unix(int64(tstamp), 0)
|
||||
log.Warn("Unclean shutdown detected", "booted", t,
|
||||
"age", common.PrettyAge(t))
|
||||
}
|
||||
}
|
||||
|
||||
// Successful startup; push a marker and check previous unclean shutdowns.
|
||||
eth.shutdownTracker.MarkStartup()
|
||||
|
||||
return eth, nil
|
||||
}
|
||||
|
||||
@@ -378,10 +378,10 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
||||
//
|
||||
// We regard two types of accounts as local miner account: etherbase
|
||||
// and accounts specified via `txpool.locals` flag.
|
||||
func (s *Ethereum) isLocalBlock(block *types.Block) bool {
|
||||
author, err := s.engine.Author(block.Header())
|
||||
func (s *Ethereum) isLocalBlock(header *types.Header) bool {
|
||||
author, err := s.engine.Author(header)
|
||||
if err != nil {
|
||||
log.Warn("Failed to retrieve block author", "number", block.NumberU64(), "hash", block.Hash(), "err", err)
|
||||
log.Warn("Failed to retrieve block author", "number", header.Number.Uint64(), "hash", header.Hash(), "err", err)
|
||||
return false
|
||||
}
|
||||
// Check whether the given address is etherbase.
|
||||
@@ -404,7 +404,7 @@ func (s *Ethereum) isLocalBlock(block *types.Block) bool {
|
||||
// shouldPreserve checks whether we should preserve the given block
|
||||
// during the chain reorg depending on whether the author of block
|
||||
// is a local account.
|
||||
func (s *Ethereum) shouldPreserve(block *types.Block) bool {
|
||||
func (s *Ethereum) shouldPreserve(header *types.Header) bool {
|
||||
// The reason we need to disable the self-reorg preserving for clique
|
||||
// is it can be probable to introduce a deadlock.
|
||||
//
|
||||
@@ -424,7 +424,7 @@ func (s *Ethereum) shouldPreserve(block *types.Block) bool {
|
||||
if _, ok := s.engine.(*clique.Clique); ok {
|
||||
return false
|
||||
}
|
||||
return s.isLocalBlock(block)
|
||||
return s.isLocalBlock(header)
|
||||
}
|
||||
|
||||
// SetEtherbase sets the mining reward address.
|
||||
@@ -465,13 +465,21 @@ func (s *Ethereum) StartMining(threads int) error {
|
||||
log.Error("Cannot start mining without etherbase", "err", err)
|
||||
return fmt.Errorf("etherbase missing: %v", err)
|
||||
}
|
||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
var cli *clique.Clique
|
||||
if c, ok := s.engine.(*clique.Clique); ok {
|
||||
cli = c
|
||||
} else if cl, ok := s.engine.(*beacon.Beacon); ok {
|
||||
if c, ok := cl.InnerEngine().(*clique.Clique); ok {
|
||||
cli = c
|
||||
}
|
||||
}
|
||||
if cli != nil {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
clique.Authorize(eb, wallet.SignData)
|
||||
cli.Authorize(eb, wallet.SignData)
|
||||
}
|
||||
// If mining is started, we can disable the transaction rejection mechanism
|
||||
// introduced to speed sync times.
|
||||
@@ -508,8 +516,14 @@ func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
||||
func (s *Ethereum) IsListening() bool { return true } // Always listening
|
||||
func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader }
|
||||
func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 }
|
||||
func (s *Ethereum) SetSynced() { atomic.StoreUint32(&s.handler.acceptTxs, 1) }
|
||||
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
|
||||
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
||||
func (s *Ethereum) Merger() *consensus.Merger { return s.merger }
|
||||
func (s *Ethereum) SyncMode() downloader.SyncMode {
|
||||
mode, _ := s.handler.chainSync.modeAndLocalHead()
|
||||
return mode
|
||||
}
|
||||
|
||||
// Protocols returns all the currently configured
|
||||
// network protocols to start.
|
||||
@@ -529,6 +543,9 @@ func (s *Ethereum) Start() error {
|
||||
// Start the bloom bits servicing goroutines
|
||||
s.startBloomHandlers(params.BloomBitsBlocks)
|
||||
|
||||
// Regularly update shutdown marker
|
||||
s.shutdownTracker.Start()
|
||||
|
||||
// Figure out a max peers count based on the server limits
|
||||
maxPeers := s.p2pServer.MaxPeers
|
||||
if s.config.LightServ > 0 {
|
||||
@@ -557,7 +574,10 @@ func (s *Ethereum) Stop() error {
|
||||
s.miner.Close()
|
||||
s.blockchain.Stop()
|
||||
s.engine.Close()
|
||||
rawdb.PopUncleanShutdownMarker(s.chainDb)
|
||||
|
||||
// Clean shutdown marker as the last thing before closing db
|
||||
s.shutdownTracker.Stop()
|
||||
|
||||
s.chainDb.Close()
|
||||
s.eventMux.Stop()
|
||||
|
||||
|
||||
+310
-84
@@ -18,17 +18,23 @@
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
chainParams "github.com/ethereum/go-ethereum/params"
|
||||
@@ -36,31 +42,81 @@ import (
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// Register adds catalyst APIs to the node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||
chainconfig := backend.BlockChain().Config()
|
||||
if chainconfig.TerminalTotalDifficulty == nil {
|
||||
return errors.New("catalyst started without valid total difficulty")
|
||||
}
|
||||
var (
|
||||
VALID = GenericStringResponse{"VALID"}
|
||||
SUCCESS = GenericStringResponse{"SUCCESS"}
|
||||
INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
|
||||
SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil}
|
||||
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
|
||||
UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"}
|
||||
InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"}
|
||||
InvalidPayloadID = rpc.CustomError{Code: 1, ValidationError: "invalid payload id"}
|
||||
)
|
||||
|
||||
log.Warn("Catalyst mode enabled")
|
||||
// Register adds catalyst APIs to the full node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||
log.Warn("Catalyst mode enabled", "protocol", "eth")
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "consensus",
|
||||
Namespace: "engine",
|
||||
Version: "1.0",
|
||||
Service: newConsensusAPI(backend),
|
||||
Service: NewConsensusAPI(backend, nil),
|
||||
Public: true,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type consensusAPI struct {
|
||||
eth *eth.Ethereum
|
||||
// RegisterLight adds catalyst APIs to the light client.
|
||||
func RegisterLight(stack *node.Node, backend *les.LightEthereum) error {
|
||||
log.Warn("Catalyst mode enabled", "protocol", "les")
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "engine",
|
||||
Version: "1.0",
|
||||
Service: NewConsensusAPI(nil, backend),
|
||||
Public: true,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func newConsensusAPI(eth *eth.Ethereum) *consensusAPI {
|
||||
return &consensusAPI{eth: eth}
|
||||
type ConsensusAPI struct {
|
||||
light bool
|
||||
eth *eth.Ethereum
|
||||
les *les.LightEthereum
|
||||
engine consensus.Engine // engine is the post-merge consensus engine, only for block creation
|
||||
preparedBlocks map[uint64]*ExecutableDataV1
|
||||
}
|
||||
|
||||
func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
|
||||
var engine consensus.Engine
|
||||
if eth == nil {
|
||||
if les.BlockChain().Config().TerminalTotalDifficulty == nil {
|
||||
panic("Catalyst started without valid total difficulty")
|
||||
}
|
||||
if b, ok := les.Engine().(*beacon.Beacon); ok {
|
||||
engine = beacon.New(b.InnerEngine())
|
||||
} else {
|
||||
engine = beacon.New(les.Engine())
|
||||
}
|
||||
} else {
|
||||
if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
|
||||
panic("Catalyst started without valid total difficulty")
|
||||
}
|
||||
if b, ok := eth.Engine().(*beacon.Beacon); ok {
|
||||
engine = beacon.New(b.InnerEngine())
|
||||
} else {
|
||||
engine = beacon.New(eth.Engine())
|
||||
}
|
||||
}
|
||||
return &ConsensusAPI{
|
||||
light: eth == nil,
|
||||
eth: eth,
|
||||
les: les,
|
||||
engine: engine,
|
||||
preparedBlocks: make(map[uint64]*ExecutableDataV1),
|
||||
}
|
||||
}
|
||||
|
||||
// blockExecutionEnv gathers all the data required to execute
|
||||
@@ -89,8 +145,24 @@ func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
|
||||
state, err := api.eth.BlockChain().StateAt(parent.Root())
|
||||
func (api *ConsensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
|
||||
// The parent state might be missing. It can be the special scenario
|
||||
// that consensus layer tries to build a new block based on the very
|
||||
// old side chain block and the relevant state is already pruned. So
|
||||
// try to retrieve the live state from the chain, if it's not existent,
|
||||
// do the necessary recovery work.
|
||||
var (
|
||||
err error
|
||||
state *state.StateDB
|
||||
)
|
||||
if api.eth.BlockChain().HasState(parent.Root()) {
|
||||
state, err = api.eth.BlockChain().StateAt(parent.Root())
|
||||
} else {
|
||||
// The maximum acceptable reorg depth can be limited by the
|
||||
// finalised block somehow. TODO(rjl493456442) fix the hard-
|
||||
// coded number here later.
|
||||
state, err = api.eth.StateAtBlock(parent, 1000, nil, false, false)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,57 +175,160 @@ func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*bl
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) GetPayloadV1(payloadID hexutil.Bytes) (*ExecutableDataV1, error) {
|
||||
hash := []byte(payloadID)
|
||||
if len(hash) < 8 {
|
||||
return nil, &InvalidPayloadID
|
||||
}
|
||||
id := binary.BigEndian.Uint64(hash[:8])
|
||||
data, ok := api.preparedBlocks[id]
|
||||
if !ok {
|
||||
return nil, &UnknownPayload
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, PayloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
|
||||
if heads.HeadBlockHash == (common.Hash{}) {
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
|
||||
}
|
||||
if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
|
||||
if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil {
|
||||
// TODO (MariusVanDerWijden) trigger sync
|
||||
return SYNCING, nil
|
||||
}
|
||||
return INVALID, err
|
||||
}
|
||||
// If the finalized block is set, check if it is in our blockchain
|
||||
if heads.FinalizedBlockHash != (common.Hash{}) {
|
||||
if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil {
|
||||
// TODO (MariusVanDerWijden) trigger sync
|
||||
return SYNCING, nil
|
||||
}
|
||||
}
|
||||
// SetHead
|
||||
if err := api.setHead(heads.HeadBlockHash); err != nil {
|
||||
return INVALID, err
|
||||
}
|
||||
// Assemble block (if needed)
|
||||
if PayloadAttributes != nil {
|
||||
data, err := api.assembleBlock(heads.HeadBlockHash, PayloadAttributes)
|
||||
if err != nil {
|
||||
return INVALID, err
|
||||
}
|
||||
hash := computePayloadId(heads.HeadBlockHash, PayloadAttributes)
|
||||
id := binary.BigEndian.Uint64(hash)
|
||||
api.preparedBlocks[id] = data
|
||||
log.Info("Created payload", "payloadid", id)
|
||||
// TODO (MariusVanDerWijden) do something with the payloadID?
|
||||
hex := hexutil.Bytes(hash)
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &hex}, nil
|
||||
}
|
||||
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
|
||||
}
|
||||
|
||||
func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) []byte {
|
||||
// Hash
|
||||
hasher := sha256.New()
|
||||
hasher.Write(headBlockHash[:])
|
||||
binary.Write(hasher, binary.BigEndian, params.Timestamp)
|
||||
hasher.Write(params.Random[:])
|
||||
hasher.Write(params.SuggestedFeeRecipient[:])
|
||||
return hasher.Sum([]byte{})[:8]
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) invalid() ExecutePayloadResponse {
|
||||
if api.light {
|
||||
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()}
|
||||
}
|
||||
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
|
||||
}
|
||||
|
||||
// ExecutePayload creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) {
|
||||
block, err := ExecutableDataToBlock(params)
|
||||
if err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
if api.light {
|
||||
parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
|
||||
if parent == nil {
|
||||
return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
|
||||
}
|
||||
if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
|
||||
}
|
||||
if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
/*
|
||||
TODO (MariusVanDerWijden) reenable once sync is merged
|
||||
if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil {
|
||||
return SYNCING, err
|
||||
}
|
||||
*/
|
||||
// TODO (MariusVanDerWijden) we should return nil here not empty hash
|
||||
return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil
|
||||
}
|
||||
parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
|
||||
td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
|
||||
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
|
||||
if td.Cmp(ttd) < 0 {
|
||||
return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
|
||||
}
|
||||
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
|
||||
return api.invalid(), err
|
||||
}
|
||||
|
||||
if merger := api.merger(); !merger.TDDReached() {
|
||||
merger.ReachTTD()
|
||||
}
|
||||
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
|
||||
}
|
||||
|
||||
// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
|
||||
// data" required for eth2 clients to process the new block.
|
||||
func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableData, error) {
|
||||
log.Info("Producing block", "parentHash", params.ParentHash)
|
||||
func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) {
|
||||
if api.light {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
log.Info("Producing block", "parentHash", parentHash)
|
||||
|
||||
bc := api.eth.BlockChain()
|
||||
parent := bc.GetBlockByHash(params.ParentHash)
|
||||
parent := bc.GetBlockByHash(parentHash)
|
||||
if parent == nil {
|
||||
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash)
|
||||
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash)
|
||||
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", parentHash)
|
||||
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", parentHash)
|
||||
}
|
||||
|
||||
pool := api.eth.TxPool()
|
||||
|
||||
if parent.Time() >= params.Timestamp {
|
||||
return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
|
||||
if params.Timestamp < parent.Time() {
|
||||
return nil, fmt.Errorf("child timestamp lower than parent's: %d < %d", params.Timestamp, parent.Time())
|
||||
}
|
||||
if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
|
||||
wait := time.Duration(params.Timestamp-now) * time.Second
|
||||
log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
|
||||
pending := pool.Pending(true)
|
||||
|
||||
coinbase, err := api.eth.Etherbase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
diff := time.Duration(params.Timestamp-now) * time.Second
|
||||
log.Warn("Producing block too far in the future", "diff", common.PrettyDuration(diff))
|
||||
}
|
||||
pending := api.eth.TxPool().Pending(true)
|
||||
coinbase := params.SuggestedFeeRecipient
|
||||
num := parent.Number()
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Number: num.Add(num, common.Big1),
|
||||
Coinbase: coinbase,
|
||||
GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
|
||||
Extra: []byte{},
|
||||
Extra: []byte{}, // TODO (MariusVanDerWijden) properly set extra data
|
||||
Time: params.Timestamp,
|
||||
}
|
||||
if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) {
|
||||
header.BaseFee = misc.CalcBaseFee(config, parent.Header())
|
||||
}
|
||||
err = api.eth.Engine().Prepare(bc, header)
|
||||
if err != nil {
|
||||
if err := api.engine.Prepare(bc, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env, err := api.makeEnv(parent, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
signer = types.MakeSigner(bc.Config(), header.Number)
|
||||
txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil)
|
||||
@@ -204,25 +379,12 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD
|
||||
txHeap.Shift()
|
||||
}
|
||||
}
|
||||
|
||||
// Create the block.
|
||||
block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
|
||||
block, err := api.engine.FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &executableData{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
Miner: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
}, nil
|
||||
return BlockToExecutableData(block, params.Random), nil
|
||||
}
|
||||
|
||||
func encodeTransactions(txs []*types.Transaction) [][]byte {
|
||||
@@ -245,66 +407,130 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func insertBlockParamsToBlock(config *chainParams.ChainConfig, parent *types.Header, params executableData) (*types.Block, error) {
|
||||
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
|
||||
txs, err := decodeTransactions(params.Transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(params.ExtraData) > 32 {
|
||||
return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData))
|
||||
}
|
||||
number := big.NewInt(0)
|
||||
number.SetUint64(params.Number)
|
||||
header := &types.Header{
|
||||
ParentHash: params.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: params.Miner,
|
||||
Coinbase: params.FeeRecipient,
|
||||
Root: params.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: params.ReceiptRoot,
|
||||
ReceiptHash: params.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(params.LogsBloom),
|
||||
Difficulty: big.NewInt(1),
|
||||
Difficulty: common.Big0,
|
||||
Number: number,
|
||||
GasLimit: params.GasLimit,
|
||||
GasUsed: params.GasUsed,
|
||||
Time: params.Timestamp,
|
||||
}
|
||||
if config.IsLondon(number) {
|
||||
header.BaseFee = misc.CalcBaseFee(config, parent)
|
||||
BaseFee: params.BaseFeePerGas,
|
||||
Extra: params.ExtraData,
|
||||
// TODO (MariusVanDerWijden) add params.Random to header once required
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
|
||||
if block.Hash() != params.BlockHash {
|
||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// NewBlock creates an Eth1 block, inserts it in the chain, and either returns true,
|
||||
// or false + an error. This is a bit redundant for go, but simplifies things on the
|
||||
// eth2 side.
|
||||
func (api *consensusAPI) NewBlock(params executableData) (*newBlockResponse, error) {
|
||||
parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
|
||||
if parent == nil {
|
||||
return &newBlockResponse{false}, fmt.Errorf("could not find parent %x", params.ParentHash)
|
||||
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableDataV1 {
|
||||
return &ExecutableDataV1{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: random,
|
||||
ExtraData: block.Extra(),
|
||||
}
|
||||
block, err := insertBlockParamsToBlock(api.eth.BlockChain().Config(), parent.Header(), params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = api.eth.BlockChain().InsertChainWithoutSealVerification(block)
|
||||
return &newBlockResponse{err == nil}, err
|
||||
}
|
||||
|
||||
// Used in tests to add a the list of transactions from a block to the tx pool.
|
||||
func (api *consensusAPI) addBlockTxs(block *types.Block) error {
|
||||
for _, tx := range block.Transactions() {
|
||||
func (api *ConsensusAPI) insertTransactions(txs types.Transactions) error {
|
||||
for _, tx := range txs {
|
||||
api.eth.TxPool().AddLocal(tx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinalizeBlock is called to mark a block as synchronized, so
|
||||
// that data that is no longer needed can be removed.
|
||||
func (api *consensusAPI) FinalizeBlock(blockHash common.Hash) (*genericResponse, error) {
|
||||
return &genericResponse{true}, nil
|
||||
func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
|
||||
// shortcut if we entered PoS already
|
||||
if api.merger().PoSFinalized() {
|
||||
return nil
|
||||
}
|
||||
// make sure the parent has enough terminal total difficulty
|
||||
newHeadBlock := api.eth.BlockChain().GetBlockByHash(head)
|
||||
if newHeadBlock == nil {
|
||||
return &GenericServerError
|
||||
}
|
||||
td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64())
|
||||
if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 {
|
||||
return &InvalidTB
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHead is called to perform a force choice.
|
||||
func (api *consensusAPI) SetHead(newHead common.Hash) (*genericResponse, error) {
|
||||
return &genericResponse{true}, nil
|
||||
// setHead is called to perform a force choice.
|
||||
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
|
||||
log.Info("Setting head", "head", newHead)
|
||||
if api.light {
|
||||
headHeader := api.les.BlockChain().CurrentHeader()
|
||||
if headHeader.Hash() == newHead {
|
||||
return nil
|
||||
}
|
||||
newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead)
|
||||
if newHeadHeader == nil {
|
||||
return &GenericServerError
|
||||
}
|
||||
if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
// Trigger the transition if it's the first `NewHead` event.
|
||||
merger := api.merger()
|
||||
if !merger.PoSFinalized() {
|
||||
merger.FinalizePoS()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
headBlock := api.eth.BlockChain().CurrentBlock()
|
||||
if headBlock.Hash() == newHead {
|
||||
return nil
|
||||
}
|
||||
newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead)
|
||||
if newHeadBlock == nil {
|
||||
return &GenericServerError
|
||||
}
|
||||
if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
// Trigger the transition if it's the first `NewHead` event.
|
||||
if merger := api.merger(); !merger.PoSFinalized() {
|
||||
merger.FinalizePoS()
|
||||
}
|
||||
// TODO (MariusVanDerWijden) are we really synced now?
|
||||
api.eth.SetSynced()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function, return the merger instance.
|
||||
func (api *ConsensusAPI) merger() *consensus.Merger {
|
||||
if api.light {
|
||||
return api.les.Merger()
|
||||
}
|
||||
return api.eth.Merger()
|
||||
}
|
||||
|
||||
+298
-129
@@ -19,7 +19,10 @@ package catalyst
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
@@ -38,10 +41,10 @@ var (
|
||||
// testAddr is the Ethereum address of the tester account.
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
|
||||
testBalance = big.NewInt(2e15)
|
||||
testBalance = big.NewInt(2e18)
|
||||
)
|
||||
|
||||
func generateTestChain() (*core.Genesis, []*types.Block) {
|
||||
func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
config := params.AllEthashProtocolChanges
|
||||
genesis := &core.Genesis{
|
||||
@@ -51,177 +54,280 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
|
||||
Timestamp: 9000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
testNonce := uint64(0)
|
||||
generate := func(i int, g *core.BlockGen) {
|
||||
g.OffsetTime(5)
|
||||
g.SetExtra([]byte("test"))
|
||||
}
|
||||
gblock := genesis.ToBlock(db)
|
||||
engine := ethash.NewFaker()
|
||||
blocks, _ := core.GenerateChain(config, gblock, engine, db, 10, generate)
|
||||
blocks = append([]*types.Block{gblock}, blocks...)
|
||||
return genesis, blocks
|
||||
}
|
||||
|
||||
// TODO (MariusVanDerWijden) reenable once engine api is updated to the latest spec
|
||||
/*
|
||||
func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, []*types.Block) {
|
||||
if fork >= n {
|
||||
fork = n - 1
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
config := ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1337),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
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),
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
Ethash: new(params.EthashConfig),
|
||||
}
|
||||
genesis := &core.Genesis{
|
||||
Config: config,
|
||||
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
|
||||
ExtraData: []byte("test genesis"),
|
||||
Timestamp: 9000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
generate := func(i int, g *core.BlockGen) {
|
||||
g.OffsetTime(5)
|
||||
g.SetExtra([]byte("test"))
|
||||
}
|
||||
generateFork := func(i int, g *core.BlockGen) {
|
||||
g.OffsetTime(5)
|
||||
g.SetExtra([]byte("testF"))
|
||||
tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(config), testKey)
|
||||
g.AddTx(tx)
|
||||
testNonce++
|
||||
}
|
||||
gblock := genesis.ToBlock(db)
|
||||
engine := ethash.NewFaker()
|
||||
blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
|
||||
blocks = append([]*types.Block{gblock}, blocks...)
|
||||
forkedBlocks, _ := core.GenerateChain(config, blocks[fork], engine, db, n-fork, generateFork)
|
||||
return genesis, blocks, forkedBlocks
|
||||
totalDifficulty := big.NewInt(0)
|
||||
for _, b := range blocks {
|
||||
totalDifficulty.Add(totalDifficulty, b.Difficulty())
|
||||
}
|
||||
config.TerminalTotalDifficulty = totalDifficulty
|
||||
return genesis, blocks
|
||||
}
|
||||
*/
|
||||
|
||||
func TestEth2AssembleBlock(t *testing.T) {
|
||||
genesis, blocks := generateTestChain()
|
||||
n, ethservice := startEthService(t, genesis, blocks[1:9])
|
||||
genesis, blocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, blocks)
|
||||
defer n.Close()
|
||||
|
||||
api := newConsensusAPI(ethservice)
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
|
||||
tx, err := types.SignTx(types.NewTransaction(0, blocks[8].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
|
||||
tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("error signing transaction, err=%v", err)
|
||||
}
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
blockParams := assembleBlockParams{
|
||||
ParentHash: blocks[8].ParentHash(),
|
||||
Timestamp: blocks[8].Time(),
|
||||
blockParams := PayloadAttributesV1{
|
||||
Timestamp: blocks[9].Time() + 5,
|
||||
}
|
||||
execData, err := api.AssembleBlock(blockParams)
|
||||
|
||||
execData, err := api.assembleBlock(blocks[9].Hash(), &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error producing block, err=%v", err)
|
||||
}
|
||||
|
||||
if len(execData.Transactions) != 1 {
|
||||
t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
|
||||
genesis, blocks := generateTestChain()
|
||||
n, ethservice := startEthService(t, genesis, blocks[1:9])
|
||||
genesis, blocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, blocks[:9])
|
||||
defer n.Close()
|
||||
|
||||
api := newConsensusAPI(ethservice)
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
|
||||
// Put the 10th block's tx in the pool and produce a new block
|
||||
api.addBlockTxs(blocks[9])
|
||||
blockParams := assembleBlockParams{
|
||||
ParentHash: blocks[9].ParentHash(),
|
||||
Timestamp: blocks[9].Time(),
|
||||
api.insertTransactions(blocks[9].Transactions())
|
||||
blockParams := PayloadAttributesV1{
|
||||
Timestamp: blocks[8].Time() + 5,
|
||||
}
|
||||
execData, err := api.AssembleBlock(blockParams)
|
||||
execData, err := api.assembleBlock(blocks[8].Hash(), &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error producing block, err=%v", err)
|
||||
}
|
||||
|
||||
if len(execData.Transactions) != blocks[9].Transactions().Len() {
|
||||
t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (MariusVanDerWijden) reenable once engine api is updated to the latest spec
|
||||
/*
|
||||
func TestEth2NewBlock(t *testing.T) {
|
||||
genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 4)
|
||||
n, ethservice := startEthService(t, genesis, blocks[1:5])
|
||||
func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
|
||||
genesis, blocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, blocks)
|
||||
defer n.Close()
|
||||
|
||||
api := newConsensusAPI(ethservice)
|
||||
for i := 5; i < 10; i++ {
|
||||
p := executableData{
|
||||
ParentHash: ethservice.BlockChain().CurrentBlock().Hash(),
|
||||
Miner: blocks[i].Coinbase(),
|
||||
StateRoot: blocks[i].Root(),
|
||||
GasLimit: blocks[i].GasLimit(),
|
||||
GasUsed: blocks[i].GasUsed(),
|
||||
Transactions: encodeTransactions(blocks[i].Transactions()),
|
||||
ReceiptRoot: blocks[i].ReceiptHash(),
|
||||
LogsBloom: blocks[i].Bloom().Bytes(),
|
||||
BlockHash: blocks[i].Hash(),
|
||||
Timestamp: blocks[i].Time(),
|
||||
Number: uint64(i),
|
||||
}
|
||||
success, err := api.NewBlock(p)
|
||||
if err != nil || !success.Valid {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
fcState := ForkchoiceStateV1{
|
||||
HeadBlockHash: blocks[5].Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
|
||||
exp := ethservice.BlockChain().CurrentBlock().Hash()
|
||||
|
||||
// Introduce the fork point.
|
||||
lastBlockNum := blocks[4].Number()
|
||||
lastBlock := blocks[4]
|
||||
for i := 0; i < 4; i++ {
|
||||
lastBlockNum.Add(lastBlockNum, big.NewInt(1))
|
||||
p := executableData{
|
||||
ParentHash: lastBlock.Hash(),
|
||||
Miner: forkedBlocks[i].Coinbase(),
|
||||
StateRoot: forkedBlocks[i].Root(),
|
||||
Number: lastBlockNum.Uint64(),
|
||||
GasLimit: forkedBlocks[i].GasLimit(),
|
||||
GasUsed: forkedBlocks[i].GasUsed(),
|
||||
Transactions: encodeTransactions(blocks[i].Transactions()),
|
||||
ReceiptRoot: forkedBlocks[i].ReceiptHash(),
|
||||
LogsBloom: forkedBlocks[i].Bloom().Bytes(),
|
||||
BlockHash: forkedBlocks[i].Hash(),
|
||||
Timestamp: forkedBlocks[i].Time(),
|
||||
}
|
||||
success, err := api.NewBlock(p)
|
||||
if err != nil || !success.Valid {
|
||||
t.Fatalf("Failed to insert forked block #%d: %v", i, err)
|
||||
}
|
||||
lastBlock, err = insertBlockParamsToBlock(ethservice.BlockChain().Config(), lastBlock.Header(), p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if ethservice.BlockChain().CurrentBlock().Hash() != exp {
|
||||
t.Fatalf("Wrong head after inserting fork %x != %x", exp, ethservice.BlockChain().CurrentBlock().Hash())
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil {
|
||||
t.Errorf("fork choice updated before total terminal difficulty should fail")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||
genesis, blocks := generatePreMergeChain(10)
|
||||
// We need to properly set the terminal total difficulty
|
||||
genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
|
||||
n, ethservice := startEthService(t, genesis, blocks[:9])
|
||||
defer n.Close()
|
||||
|
||||
api := NewConsensusAPI(ethservice, nil)
|
||||
|
||||
// Put the 10th block's tx in the pool and produce a new block
|
||||
api.insertTransactions(blocks[9].Transactions())
|
||||
blockParams := PayloadAttributesV1{
|
||||
Timestamp: blocks[8].Time() + 5,
|
||||
}
|
||||
fcState := ForkchoiceStateV1{
|
||||
HeadBlockHash: blocks[8].Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
|
||||
execData, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
|
||||
if err != nil {
|
||||
t.Fatalf("error getting payload, err=%v", err)
|
||||
}
|
||||
if len(execData.Transactions) != blocks[9].Transactions().Len() {
|
||||
t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
|
||||
}
|
||||
}
|
||||
|
||||
func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) {
|
||||
t.Helper()
|
||||
|
||||
if len(logsCh) != wantNew {
|
||||
t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
|
||||
}
|
||||
if len(rmLogsCh) != wantRemoved {
|
||||
t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
|
||||
}
|
||||
// Drain events.
|
||||
for i := 0; i < len(logsCh); i++ {
|
||||
<-logsCh
|
||||
}
|
||||
for i := 0; i < len(rmLogsCh); i++ {
|
||||
<-rmLogsCh
|
||||
}
|
||||
}
|
||||
|
||||
func TestEth2NewBlock(t *testing.T) {
|
||||
genesis, preMergeBlocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
ethservice.Merger().ReachTTD()
|
||||
defer n.Close()
|
||||
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice, nil)
|
||||
parent = preMergeBlocks[len(preMergeBlocks)-1]
|
||||
|
||||
// This EVM code generates a log when the contract is created.
|
||||
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||
)
|
||||
// The event channels.
|
||||
newLogCh := make(chan []*types.Log, 10)
|
||||
rmLogsCh := make(chan core.RemovedLogsEvent, 10)
|
||||
ethservice.BlockChain().SubscribeLogsEvent(newLogCh)
|
||||
ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
|
||||
nonce := statedb.GetNonce(testAddr)
|
||||
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
|
||||
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create the executable data %v", err)
|
||||
}
|
||||
block, err := ExecutableDataToBlock(*execData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
newResp, err := api.ExecutePayloadV1(*execData)
|
||||
if err != nil || newResp.Status != "VALID" {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64()-1 {
|
||||
t.Fatalf("Chain head shouldn't be updated")
|
||||
}
|
||||
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
|
||||
fcState := ForkchoiceStateV1{
|
||||
HeadBlockHash: block.Hash(),
|
||||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
|
||||
t.Fatalf("Chain head should be updated")
|
||||
}
|
||||
checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
|
||||
|
||||
parent = block
|
||||
}
|
||||
|
||||
// Introduce fork chain
|
||||
var (
|
||||
head = ethservice.BlockChain().CurrentBlock().NumberU64()
|
||||
)
|
||||
parent = preMergeBlocks[len(preMergeBlocks)-1]
|
||||
for i := 0; i < 10; i++ {
|
||||
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 6,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create the executable data %v", err)
|
||||
}
|
||||
block, err := ExecutableDataToBlock(*execData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
newResp, err := api.ExecutePayloadV1(*execData)
|
||||
if err != nil || newResp.Status != "VALID" {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != head {
|
||||
t.Fatalf("Chain head shouldn't be updated")
|
||||
}
|
||||
|
||||
fcState := ForkchoiceStateV1{
|
||||
HeadBlockHash: block.Hash(),
|
||||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
|
||||
t.Fatalf("Chain head should be updated")
|
||||
}
|
||||
parent, head = block, block.NumberU64()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEth2DeepReorg(t *testing.T) {
|
||||
// TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg
|
||||
// before the totalTerminalDifficulty threshold
|
||||
/*
|
||||
genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
defer n.Close()
|
||||
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice, nil)
|
||||
parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1]
|
||||
head = ethservice.BlockChain().CurrentBlock().NumberU64()
|
||||
)
|
||||
if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) {
|
||||
t.Errorf("Block %d not pruned", parent.NumberU64())
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
execData, err := api.assembleBlock(AssembleBlockParams{
|
||||
ParentHash: parent.Hash(),
|
||||
Timestamp: parent.Time() + 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create the executable data %v", err)
|
||||
}
|
||||
block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
newResp, err := api.ExecutePayload(*execData)
|
||||
if err != nil || newResp.Status != "VALID" {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != head {
|
||||
t.Fatalf("Chain head shouldn't be updated")
|
||||
}
|
||||
if err := api.setHead(block.Hash()); err != nil {
|
||||
t.Fatalf("Failed to set head: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
|
||||
t.Fatalf("Chain head should be updated")
|
||||
}
|
||||
parent, head = block, block.NumberU64()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// startEthService creates a full node instance for testing.
|
||||
func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
|
||||
@@ -232,7 +338,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
|
||||
t.Fatal("can't create node:", err)
|
||||
}
|
||||
|
||||
ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}
|
||||
ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
|
||||
ethservice, err := eth.New(n, ethcfg)
|
||||
if err != nil {
|
||||
t.Fatal("can't create eth service:", err)
|
||||
@@ -245,6 +351,69 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
|
||||
t.Fatal("can't import test blocks:", err)
|
||||
}
|
||||
ethservice.SetEtherbase(testAddr)
|
||||
ethservice.SetSynced()
|
||||
|
||||
return n, ethservice
|
||||
}
|
||||
|
||||
func TestFullAPI(t *testing.T) {
|
||||
genesis, preMergeBlocks := generatePreMergeChain(10)
|
||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||
ethservice.Merger().ReachTTD()
|
||||
defer n.Close()
|
||||
var (
|
||||
api = NewConsensusAPI(ethservice, nil)
|
||||
parent = ethservice.BlockChain().CurrentBlock()
|
||||
// This EVM code generates a log when the contract is created.
|
||||
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||
)
|
||||
for i := 0; i < 10; i++ {
|
||||
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
|
||||
nonce := statedb.GetNonce(testAddr)
|
||||
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||
ethservice.TxPool().AddLocal(tx)
|
||||
|
||||
params := PayloadAttributesV1{
|
||||
Timestamp: parent.Time() + 1,
|
||||
Random: crypto.Keccak256Hash([]byte{byte(i)}),
|
||||
SuggestedFeeRecipient: parent.Coinbase(),
|
||||
}
|
||||
fcState := ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
resp, err := api.ForkchoiceUpdatedV1(fcState, ¶ms)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
if resp.Status != SUCCESS.Status {
|
||||
t.Fatalf("error preparing payload, invalid status: %v", resp.Status)
|
||||
}
|
||||
payloadID := computePayloadId(parent.Hash(), ¶ms)
|
||||
payload, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
|
||||
if err != nil {
|
||||
t.Fatalf("can't get payload: %v", err)
|
||||
}
|
||||
execResp, err := api.ExecutePayloadV1(*payload)
|
||||
if err != nil {
|
||||
t.Fatalf("can't execute payload: %v", err)
|
||||
}
|
||||
if execResp.Status != VALID.Status {
|
||||
t.Fatalf("invalid status: %v", execResp.Status)
|
||||
}
|
||||
fcState = ForkchoiceStateV1{
|
||||
HeadBlockHash: payload.BlockHash,
|
||||
SafeBlockHash: payload.ParentHash,
|
||||
FinalizedBlockHash: payload.ParentHash,
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
|
||||
t.Fatalf("Chain head should be updated")
|
||||
}
|
||||
parent = ethservice.BlockChain().CurrentBlock()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+73
-29
@@ -17,54 +17,98 @@
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type assembleBlockParams -field-override assembleBlockParamsMarshaling -out gen_blockparams.go
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
||||
// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ
|
||||
type assembleBlockParams struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
// Structure described at https://github.com/ethereum/execution-apis/pull/74
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for assembleBlockParams.
|
||||
type assembleBlockParamsMarshaling struct {
|
||||
// JSON type overrides for PayloadAttributesV1.
|
||||
type payloadAttributesMarshaling struct {
|
||||
Timestamp hexutil.Uint64
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type executableData -field-override executableDataMarshaling -out gen_ed.go
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
|
||||
|
||||
// Structure described at https://notes.ethereum.org/@n0ble/rayonism-the-merge-spec#Parameters1
|
||||
type executableData struct {
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Miner common.Address `json:"miner" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
Number uint64 `json:"number" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
// Structure described at https://github.com/ethereum/execution-apis/src/engine/specification.md
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
type executableDataMarshaling struct {
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
BaseFeePerGas *hexutil.Big
|
||||
ExtraData hexutil.Bytes
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
}
|
||||
|
||||
type newBlockResponse struct {
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadResponse -field-override payloadResponseMarshaling -out gen_payload.go
|
||||
|
||||
type PayloadResponse struct {
|
||||
PayloadID uint64 `json:"payloadId"`
|
||||
}
|
||||
|
||||
// JSON type overrides for payloadResponse.
|
||||
type payloadResponseMarshaling struct {
|
||||
PayloadID hexutil.Uint64
|
||||
}
|
||||
|
||||
type NewBlockResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
|
||||
type genericResponse struct {
|
||||
type GenericResponse struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type GenericStringResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ExecutePayloadResponse struct {
|
||||
Status string `json:"status"`
|
||||
LatestValidHash common.Hash `json:"latestValidHash"`
|
||||
}
|
||||
|
||||
type ConsensusValidatedParams struct {
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ForkChoiceResponse struct {
|
||||
Status string `json:"status"`
|
||||
PayloadID *hexutil.Bytes `json:"payloadId"`
|
||||
}
|
||||
|
||||
type ForkchoiceStateV1 struct {
|
||||
HeadBlockHash common.Hash `json:"headBlockHash"`
|
||||
SafeBlockHash common.Hash `json:"safeBlockHash"`
|
||||
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
|
||||
}
|
||||
|
||||
@@ -10,37 +10,44 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*assembleBlockParamsMarshaling)(nil)
|
||||
var _ = (*payloadAttributesMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (a assembleBlockParams) MarshalJSON() ([]byte, error) {
|
||||
type assembleBlockParams struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var enc assembleBlockParams
|
||||
enc.ParentHash = a.ParentHash
|
||||
enc.Timestamp = hexutil.Uint64(a.Timestamp)
|
||||
var enc PayloadAttributesV1
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
enc.Random = p.Random
|
||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (a *assembleBlockParams) UnmarshalJSON(input []byte) error {
|
||||
type assembleBlockParams struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random *common.Hash `json:"random" gencodec:"required"`
|
||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var dec assembleBlockParams
|
||||
var dec PayloadAttributesV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.ParentHash == nil {
|
||||
return errors.New("missing required field 'parentHash' for assembleBlockParams")
|
||||
}
|
||||
a.ParentHash = *dec.ParentHash
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for assembleBlockParams")
|
||||
return errors.New("missing required field 'timestamp' for PayloadAttributesV1")
|
||||
}
|
||||
a.Timestamp = uint64(*dec.Timestamp)
|
||||
p.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'random' for PayloadAttributesV1")
|
||||
}
|
||||
p.Random = *dec.Random
|
||||
if dec.SuggestedFeeRecipient == nil {
|
||||
return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV1")
|
||||
}
|
||||
p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient
|
||||
return nil
|
||||
}
|
||||
|
||||
+74
-52
@@ -5,6 +5,7 @@ package catalyst
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -13,31 +14,37 @@ import (
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (e executableData) MarshalJSON() ([]byte, error) {
|
||||
type executableData struct {
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Miner common.Address `json:"miner" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"random" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var enc executableData
|
||||
enc.BlockHash = e.BlockHash
|
||||
var enc ExecutableDataV1
|
||||
enc.ParentHash = e.ParentHash
|
||||
enc.Miner = e.Miner
|
||||
enc.FeeRecipient = e.FeeRecipient
|
||||
enc.StateRoot = e.StateRoot
|
||||
enc.ReceiptsRoot = e.ReceiptsRoot
|
||||
enc.LogsBloom = e.LogsBloom
|
||||
enc.Random = e.Random
|
||||
enc.Number = hexutil.Uint64(e.Number)
|
||||
enc.GasLimit = hexutil.Uint64(e.GasLimit)
|
||||
enc.GasUsed = hexutil.Uint64(e.GasUsed)
|
||||
enc.Timestamp = hexutil.Uint64(e.Timestamp)
|
||||
enc.ReceiptRoot = e.ReceiptRoot
|
||||
enc.LogsBloom = e.LogsBloom
|
||||
enc.ExtraData = e.ExtraData
|
||||
enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas)
|
||||
enc.BlockHash = e.BlockHash
|
||||
if e.Transactions != nil {
|
||||
enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
|
||||
for k, v := range e.Transactions {
|
||||
@@ -48,66 +55,81 @@ func (e executableData) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *executableData) UnmarshalJSON(input []byte) error {
|
||||
type executableData struct {
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
Miner *common.Address `json:"miner" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ReceiptRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"random" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var dec executableData
|
||||
var dec ExecutableDataV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.BlockHash == nil {
|
||||
return errors.New("missing required field 'blockHash' for executableData")
|
||||
}
|
||||
e.BlockHash = *dec.BlockHash
|
||||
if dec.ParentHash == nil {
|
||||
return errors.New("missing required field 'parentHash' for executableData")
|
||||
return errors.New("missing required field 'parentHash' for ExecutableDataV1")
|
||||
}
|
||||
e.ParentHash = *dec.ParentHash
|
||||
if dec.Miner == nil {
|
||||
return errors.New("missing required field 'miner' for executableData")
|
||||
if dec.FeeRecipient == nil {
|
||||
return errors.New("missing required field 'feeRecipient' for ExecutableDataV1")
|
||||
}
|
||||
e.Miner = *dec.Miner
|
||||
e.FeeRecipient = *dec.FeeRecipient
|
||||
if dec.StateRoot == nil {
|
||||
return errors.New("missing required field 'stateRoot' for executableData")
|
||||
return errors.New("missing required field 'stateRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.StateRoot = *dec.StateRoot
|
||||
if dec.ReceiptsRoot == nil {
|
||||
return errors.New("missing required field 'receiptsRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.ReceiptsRoot = *dec.ReceiptsRoot
|
||||
if dec.LogsBloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for ExecutableDataV1")
|
||||
}
|
||||
e.LogsBloom = *dec.LogsBloom
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'random' for ExecutableDataV1")
|
||||
}
|
||||
e.Random = *dec.Random
|
||||
if dec.Number == nil {
|
||||
return errors.New("missing required field 'number' for executableData")
|
||||
return errors.New("missing required field 'blockNumber' for ExecutableDataV1")
|
||||
}
|
||||
e.Number = uint64(*dec.Number)
|
||||
if dec.GasLimit == nil {
|
||||
return errors.New("missing required field 'gasLimit' for executableData")
|
||||
return errors.New("missing required field 'gasLimit' for ExecutableDataV1")
|
||||
}
|
||||
e.GasLimit = uint64(*dec.GasLimit)
|
||||
if dec.GasUsed == nil {
|
||||
return errors.New("missing required field 'gasUsed' for executableData")
|
||||
return errors.New("missing required field 'gasUsed' for ExecutableDataV1")
|
||||
}
|
||||
e.GasUsed = uint64(*dec.GasUsed)
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for executableData")
|
||||
return errors.New("missing required field 'timestamp' for ExecutableDataV1")
|
||||
}
|
||||
e.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.ReceiptRoot == nil {
|
||||
return errors.New("missing required field 'receiptsRoot' for executableData")
|
||||
if dec.ExtraData == nil {
|
||||
return errors.New("missing required field 'extraData' for ExecutableDataV1")
|
||||
}
|
||||
e.ReceiptRoot = *dec.ReceiptRoot
|
||||
if dec.LogsBloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for executableData")
|
||||
e.ExtraData = *dec.ExtraData
|
||||
if dec.BaseFeePerGas == nil {
|
||||
return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1")
|
||||
}
|
||||
e.LogsBloom = *dec.LogsBloom
|
||||
e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
|
||||
if dec.BlockHash == nil {
|
||||
return errors.New("missing required field 'blockHash' for ExecutableDataV1")
|
||||
}
|
||||
e.BlockHash = *dec.BlockHash
|
||||
if dec.Transactions == nil {
|
||||
return errors.New("missing required field 'transactions' for executableData")
|
||||
return errors.New("missing required field 'transactions' for ExecutableDataV1")
|
||||
}
|
||||
e.Transactions = make([][]byte, len(dec.Transactions))
|
||||
for k, v := range dec.Transactions {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package catalyst
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*payloadResponseMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (p PayloadResponse) MarshalJSON() ([]byte, error) {
|
||||
type PayloadResponse struct {
|
||||
PayloadID hexutil.Uint64 `json:"payloadId"`
|
||||
}
|
||||
var enc PayloadResponse
|
||||
enc.PayloadID = hexutil.Uint64(p.PayloadID)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (p *PayloadResponse) UnmarshalJSON(input []byte) error {
|
||||
type PayloadResponse struct {
|
||||
PayloadID *hexutil.Uint64 `json:"payloadId"`
|
||||
}
|
||||
var dec PayloadResponse
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.PayloadID != nil {
|
||||
p.PayloadID = uint64(*dec.PayloadID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+403
-797
File diff suppressed because it is too large
Load Diff
+437
-689
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
)
|
||||
|
||||
// fetchHeadersByHash is a blocking version of Peer.RequestHeadersByHash which
|
||||
// handles all the cancellation, interruption and timeout mechanisms of a data
|
||||
// retrieval to allow blocking API calls.
|
||||
func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
// Create the response sink and send the network request
|
||||
start := time.Now()
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := p.peer.RequestHeadersByHash(hash, amount, skip, reverse, resCh)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
// Wait until the response arrives, the request is cancelled or times out
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
|
||||
timeoutTimer := time.NewTimer(ttl)
|
||||
defer timeoutTimer.Stop()
|
||||
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return nil, nil, errCanceled
|
||||
|
||||
case <-timeoutTimer.C:
|
||||
// Header retrieval timed out, update the metrics
|
||||
p.log.Debug("Header request timed out", "elapsed", ttl)
|
||||
headerTimeoutMeter.Mark(1)
|
||||
|
||||
return nil, nil, errTimeout
|
||||
|
||||
case res := <-resCh:
|
||||
// Headers successfully retrieved, update the metrics
|
||||
headerReqTimer.Update(time.Since(start))
|
||||
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersPacket))))
|
||||
|
||||
// Don't reject the packet even if it turns out to be bad, downloader will
|
||||
// disconnect the peer on its own terms. Simply delivery the headers to
|
||||
// be processed by the caller
|
||||
res.Done <- nil
|
||||
|
||||
return *res.Res.(*eth.BlockHeadersPacket), res.Meta.([]common.Hash), nil
|
||||
}
|
||||
}
|
||||
|
||||
// fetchHeadersByNumber is a blocking version of Peer.RequestHeadersByNumber which
|
||||
// handles all the cancellation, interruption and timeout mechanisms of a data
|
||||
// retrieval to allow blocking API calls.
|
||||
func (d *Downloader) fetchHeadersByNumber(p *peerConnection, number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
// Create the response sink and send the network request
|
||||
start := time.Now()
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := p.peer.RequestHeadersByNumber(number, amount, skip, reverse, resCh)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
// Wait until the response arrives, the request is cancelled or times out
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
|
||||
timeoutTimer := time.NewTimer(ttl)
|
||||
defer timeoutTimer.Stop()
|
||||
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return nil, nil, errCanceled
|
||||
|
||||
case <-timeoutTimer.C:
|
||||
// Header retrieval timed out, update the metrics
|
||||
p.log.Debug("Header request timed out", "elapsed", ttl)
|
||||
headerTimeoutMeter.Mark(1)
|
||||
|
||||
return nil, nil, errTimeout
|
||||
|
||||
case res := <-resCh:
|
||||
// Headers successfully retrieved, update the metrics
|
||||
headerReqTimer.Update(time.Since(start))
|
||||
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersPacket))))
|
||||
|
||||
// Don't reject the packet even if it turns out to be bad, downloader will
|
||||
// disconnect the peer on its own terms. Simply delivery the headers to
|
||||
// be processed by the caller
|
||||
res.Done <- nil
|
||||
|
||||
return *res.Res.(*eth.BlockHeadersPacket), res.Meta.([]common.Hash), nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// timeoutGracePeriod is the amount of time to allow for a peer to deliver a
|
||||
// response to a locally already timed out request. Timeouts are not penalized
|
||||
// as a peer might be temporarily overloaded, however, they still must reply
|
||||
// to each request. Failing to do so is considered a protocol violation.
|
||||
var timeoutGracePeriod = 2 * time.Minute
|
||||
|
||||
// typedQueue is an interface defining the adaptor needed to translate the type
|
||||
// specific downloader/queue schedulers into the type-agnostic general concurrent
|
||||
// fetcher algorithm calls.
|
||||
type typedQueue interface {
|
||||
// waker returns a notification channel that gets pinged in case more fetches
|
||||
// have been queued up, so the fetcher might assign it to idle peers.
|
||||
waker() chan bool
|
||||
|
||||
// pending returns the number of wrapped items that are currently queued for
|
||||
// fetching by the concurrent downloader.
|
||||
pending() int
|
||||
|
||||
// capacity is responsible for calculating how many items of the abstracted
|
||||
// type a particular peer is estimated to be able to retrieve within the
|
||||
// alloted round trip time.
|
||||
capacity(peer *peerConnection, rtt time.Duration) int
|
||||
|
||||
// updateCapacity is responsible for updating how many items of the abstracted
|
||||
// type a particular peer is estimated to be able to retrieve in a unit time.
|
||||
updateCapacity(peer *peerConnection, items int, elapsed time.Duration)
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending items
|
||||
// from the download queue to the specified peer.
|
||||
reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool)
|
||||
|
||||
// unreserve is resposible for removing the current retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
unreserve(peer string) int
|
||||
|
||||
// request is responsible for converting a generic fetch request into a typed
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the
|
||||
// concurrent fetcher, unpacking the type specific data and delivering
|
||||
// it to the downloader's queue.
|
||||
deliver(peer *peerConnection, packet *eth.Response) (int, error)
|
||||
}
|
||||
|
||||
// concurrentFetch iteratively downloads scheduled block parts, taking available
|
||||
// peers, reserving a chunk of fetch requests for each and waiting for delivery
|
||||
// or timeouts.
|
||||
func (d *Downloader) concurrentFetch(queue typedQueue) error {
|
||||
// Create a delivery channel to accept responses from all peers
|
||||
responses := make(chan *eth.Response)
|
||||
|
||||
// Track the currently active requests and their timeout order
|
||||
pending := make(map[string]*eth.Request)
|
||||
defer func() {
|
||||
// Abort all requests on sync cycle cancellation. The requests may still
|
||||
// be fulfilled by the remote side, but the dispatcher will not wait to
|
||||
// deliver them since nobody's going to be listening.
|
||||
for _, req := range pending {
|
||||
req.Close()
|
||||
}
|
||||
}()
|
||||
ordering := make(map[*eth.Request]int)
|
||||
timeouts := prque.New(func(data interface{}, index int) {
|
||||
ordering[data.(*eth.Request)] = index
|
||||
})
|
||||
|
||||
timeout := time.NewTimer(0)
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
defer timeout.Stop()
|
||||
|
||||
// Track the timed-out but not-yet-answered requests separately. We want to
|
||||
// keep tracking which peers are busy (potentially overloaded), so removing
|
||||
// all trace of a timed out request is not good. We also can't just cancel
|
||||
// the pending request altogether as that would prevent a late response from
|
||||
// being delivered, thus never unblocking the peer.
|
||||
stales := make(map[string]*eth.Request)
|
||||
defer func() {
|
||||
// Abort all requests on sync cycle cancellation. The requests may still
|
||||
// be fulfilled by the remote side, but the dispatcher will not wait to
|
||||
// deliver them since nobody's going to be listening.
|
||||
for _, req := range stales {
|
||||
req.Close()
|
||||
}
|
||||
}()
|
||||
// Subscribe to peer lifecycle events to schedule tasks to new joiners and
|
||||
// reschedule tasks upon disconnections. We don't care which event happened
|
||||
// for simplicity, so just use a single channel.
|
||||
peering := make(chan *peeringEvent, 64) // arbitrary buffer, just some burst protection
|
||||
|
||||
peeringSub := d.peers.SubscribeEvents(peering)
|
||||
defer peeringSub.Unsubscribe()
|
||||
|
||||
// Prepare the queue and fetch block parts until the block header fetcher's done
|
||||
finished := false
|
||||
for {
|
||||
// Short circuit if we lost all our peers
|
||||
if d.peers.Len() == 0 {
|
||||
return errNoPeers
|
||||
}
|
||||
// If there's nothing more to fetch, wait or terminate
|
||||
if queue.pending() == 0 {
|
||||
if len(pending) == 0 && finished {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Send a download request to all idle peers, until throttled
|
||||
var (
|
||||
idles []*peerConnection
|
||||
caps []int
|
||||
)
|
||||
for _, peer := range d.peers.AllPeers() {
|
||||
pending, stale := pending[peer.id], stales[peer.id]
|
||||
if pending == nil && stale == nil {
|
||||
idles = append(idles, peer)
|
||||
caps = append(caps, queue.capacity(peer, time.Second))
|
||||
} else if stale != nil {
|
||||
if waited := time.Since(stale.Sent); waited > timeoutGracePeriod {
|
||||
// Request has been in flight longer than the grace period
|
||||
// permitted it, consider the peer malicious attempting to
|
||||
// stall the sync.
|
||||
peer.log.Warn("Peer stalling, dropping", "waited", common.PrettyDuration(waited))
|
||||
d.dropPeer(peer.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(&peerCapacitySort{idles, caps})
|
||||
|
||||
var (
|
||||
progressed bool
|
||||
throttled bool
|
||||
queued = queue.pending()
|
||||
)
|
||||
for _, peer := range idles {
|
||||
// Short circuit if throttling activated or there are no more
|
||||
// queued tasks to be retrieved
|
||||
if throttled {
|
||||
break
|
||||
}
|
||||
if queued = queue.pending(); queued == 0 {
|
||||
break
|
||||
}
|
||||
// Reserve a chunk of fetches for a peer. A nil can mean either that
|
||||
// no more headers are available, or that the peer is known not to
|
||||
// have them.
|
||||
request, progress, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
|
||||
if progress {
|
||||
progressed = true
|
||||
}
|
||||
if throttle {
|
||||
throttled = true
|
||||
throttleCounter.Inc(1)
|
||||
}
|
||||
if request == nil {
|
||||
continue
|
||||
}
|
||||
// Fetch the chunk and make sure any errors return the hashes to the queue
|
||||
req, err := queue.request(peer, request, responses)
|
||||
if err != nil {
|
||||
// Sending the request failed, which generally means the peer
|
||||
// was diconnected in between assignment and network send.
|
||||
// Although all peer removal operations return allocated tasks
|
||||
// to the queue, that is async, and we can do better here by
|
||||
// immediately pushing the unfulfilled requests.
|
||||
queue.unreserve(peer.id) // TODO(karalabe): This needs a non-expiration method
|
||||
continue
|
||||
}
|
||||
pending[peer.id] = req
|
||||
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
ordering[req] = timeouts.Size()
|
||||
|
||||
timeouts.Push(req, -time.Now().Add(ttl).UnixNano())
|
||||
if timeouts.Size() == 1 {
|
||||
timeout.Reset(ttl)
|
||||
}
|
||||
}
|
||||
// Make sure that we have peers available for fetching. If all peers have been tried
|
||||
// and all failed throw an error
|
||||
if !progressed && !throttled && len(pending) == 0 && len(idles) == d.peers.Len() && queued > 0 {
|
||||
return errPeersUnavailable
|
||||
}
|
||||
}
|
||||
// Wait for something to happen
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
// If sync was cancelled, tear down the parallel retriever. Pending
|
||||
// requests will be cancelled locally, and the remote responses will
|
||||
// be dropped when they arrive
|
||||
return errCanceled
|
||||
|
||||
case event := <-peering:
|
||||
// A peer joined or left, the tasks queue and allocations need to be
|
||||
// checked for potential assignment or reassignment
|
||||
peerid := event.peer.id
|
||||
|
||||
if event.join {
|
||||
// Sanity check the internal state; this can be dropped later
|
||||
if _, ok := pending[peerid]; ok {
|
||||
event.peer.log.Error("Pending request exists for joining peer")
|
||||
}
|
||||
if _, ok := stales[peerid]; ok {
|
||||
event.peer.log.Error("Stale request exists for joining peer")
|
||||
}
|
||||
// Loop back to the entry point for task assignment
|
||||
continue
|
||||
}
|
||||
// A peer left, any existing requests need to be untracked, pending
|
||||
// tasks returned and possible reassignment checked
|
||||
if req, ok := pending[peerid]; ok {
|
||||
queue.unreserve(peerid) // TODO(karalabe): This needs a non-expiration method
|
||||
delete(pending, peerid)
|
||||
req.Close()
|
||||
|
||||
if index, live := ordering[req]; live {
|
||||
timeouts.Remove(index)
|
||||
if index == 0 {
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
}
|
||||
delete(ordering, req)
|
||||
}
|
||||
}
|
||||
if req, ok := stales[peerid]; ok {
|
||||
delete(stales, peerid)
|
||||
req.Close()
|
||||
}
|
||||
|
||||
case <-timeout.C:
|
||||
// Retrieve the next request which should have timed out. The check
|
||||
// below is purely for to catch programming errors, given the correct
|
||||
// code, there's no possible order of events that should result in a
|
||||
// timeout firing for a non-existent event.
|
||||
item, exp := timeouts.Peek()
|
||||
if now, at := time.Now(), time.Unix(0, -exp); now.Before(at) {
|
||||
log.Error("Timeout triggered but not reached", "left", at.Sub(now))
|
||||
timeout.Reset(at.Sub(now))
|
||||
continue
|
||||
}
|
||||
req := item.(*eth.Request)
|
||||
|
||||
// Stop tracking the timed out request from a timing perspective,
|
||||
// cancel it, so it's not considered in-flight anymore, but keep
|
||||
// the peer marked busy to prevent assigning a second request and
|
||||
// overloading it further.
|
||||
delete(pending, req.Peer)
|
||||
stales[req.Peer] = req
|
||||
delete(ordering, req)
|
||||
|
||||
timeouts.Pop()
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
// New timeout potentially set if there are more requests pending,
|
||||
// reschedule the failed one to a free peer
|
||||
fails := queue.unreserve(req.Peer)
|
||||
|
||||
// Finally, update the peer's retrieval capacity, or if it's already
|
||||
// below the minimum allowance, drop the peer. If a lot of retrieval
|
||||
// elements expired, we might have overestimated the remote peer or
|
||||
// perhaps ourselves. Only reset to minimal throughput but don't drop
|
||||
// just yet.
|
||||
//
|
||||
// The reason the minimum threshold is 2 is that the downloader tries
|
||||
// to estimate the bandwidth and latency of a peer separately, which
|
||||
// requires pushing the measured capacity a bit and seeing how response
|
||||
// times reacts, to it always requests one more than the minimum (i.e.
|
||||
// min 2).
|
||||
peer := d.peers.Peer(req.Peer)
|
||||
if peer == nil {
|
||||
// If the peer got disconnected in between, we should really have
|
||||
// short-circuited it already. Just in case there's some strange
|
||||
// codepath, leave this check in not to crash.
|
||||
log.Error("Delivery timeout from unknown peer", "peer", req.Peer)
|
||||
continue
|
||||
}
|
||||
if fails > 2 {
|
||||
queue.updateCapacity(peer, 0, 0)
|
||||
} else {
|
||||
d.dropPeer(peer.id)
|
||||
|
||||
// If this peer was the master peer, abort sync immediately
|
||||
d.cancelLock.RLock()
|
||||
master := peer.id == d.cancelPeer
|
||||
d.cancelLock.RUnlock()
|
||||
|
||||
if master {
|
||||
d.cancel()
|
||||
return errTimeout
|
||||
}
|
||||
}
|
||||
|
||||
case res := <-responses:
|
||||
// Response arrived, it may be for an existing or an already timed
|
||||
// out request. If the former, update the timeout heap and perhaps
|
||||
// reschedule the timeout timer.
|
||||
index, live := ordering[res.Req]
|
||||
if live {
|
||||
timeouts.Remove(index)
|
||||
if index == 0 {
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
}
|
||||
delete(ordering, res.Req)
|
||||
}
|
||||
// Delete the pending request (if it still exists) and mark the peer idle
|
||||
delete(pending, res.Req.Peer)
|
||||
delete(stales, res.Req.Peer)
|
||||
|
||||
// Signal the dispatcher that the round trip is done. We'll drop the
|
||||
// peer if the data turns out to be junk.
|
||||
res.Done <- nil
|
||||
res.Req.Close()
|
||||
|
||||
// If the peer was previously banned and failed to deliver its pack
|
||||
// in a reasonable time frame, ignore its message.
|
||||
if peer := d.peers.Peer(res.Req.Peer); peer != nil {
|
||||
// Deliver the received chunk of data and check chain validity
|
||||
accepted, err := queue.deliver(peer, res)
|
||||
if errors.Is(err, errInvalidChain) {
|
||||
return err
|
||||
}
|
||||
// Unless a peer delivered something completely else than requested (usually
|
||||
// caused by a timed out request which came through in the end), set it to
|
||||
// idle. If the delivery's stale, the peer should have already been idled.
|
||||
if !errors.Is(err, errStaleDelivery) {
|
||||
queue.updateCapacity(peer, accepted, res.Time)
|
||||
}
|
||||
}
|
||||
|
||||
case cont := <-queue.waker():
|
||||
// The header fetcher sent a continuation flag, check if it's done
|
||||
if !cont {
|
||||
finished = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// bodyQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type bodyQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more body
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *bodyQueue) waker() chan bool {
|
||||
return q.queue.blockWakeCh
|
||||
}
|
||||
|
||||
// pending returns the number of bodies that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *bodyQueue) pending() int {
|
||||
return q.queue.PendingBodies()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many bodies a particular peer is
|
||||
// estimated to be able to retrieve within the alloted round trip time.
|
||||
func (q *bodyQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.BodyCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many bodies a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *bodyQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateBodyRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending bodies
|
||||
// from the download queue to the specified peer.
|
||||
func (q *bodyQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveBodies(peer, items)
|
||||
}
|
||||
|
||||
// unreserve is resposible for removing the current body retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *bodyQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireBodies(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Body delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Body delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a body
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of bodies", "count", len(req.Headers), "from", req.Headers[0].Number)
|
||||
if q.bodyFetchHook != nil {
|
||||
q.bodyFetchHook(req.Headers)
|
||||
}
|
||||
|
||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
||||
for _, header := range req.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
return peer.peer.RequestBodies(hashes, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the body data and delivering it to the downloader's queue.
|
||||
func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
txs, uncles := packet.Res.(*eth.BlockBodiesPacket).Unpack()
|
||||
hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes}
|
||||
|
||||
accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1])
|
||||
switch {
|
||||
case err == nil && len(txs) == 0:
|
||||
peer.log.Trace("Requested bodies delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of bodies", "count", len(txs), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved bodies", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// headerQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type headerQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more header
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *headerQueue) waker() chan bool {
|
||||
return q.queue.headerContCh
|
||||
}
|
||||
|
||||
// pending returns the number of headers that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *headerQueue) pending() int {
|
||||
return q.queue.PendingHeaders()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many headers a particular peer is
|
||||
// estimated to be able to retrieve within the alloted round trip time.
|
||||
func (q *headerQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.HeaderCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many headers a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *headerQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateHeaderRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending headers
|
||||
// from the download queue to the specified peer.
|
||||
func (q *headerQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveHeaders(peer, items), false, false
|
||||
}
|
||||
|
||||
// unreserve is resposible for removing the current header retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *headerQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireHeaders(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Header delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Header delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a header
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *headerQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of headers", "from", req.From)
|
||||
return peer.peer.RequestHeadersByNumber(req.From, MaxHeaderFetch, 0, false, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the header data and delivering it to the downloader's queue.
|
||||
func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
headers := *packet.Res.(*eth.BlockHeadersPacket)
|
||||
hashes := packet.Meta.([]common.Hash)
|
||||
|
||||
accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh)
|
||||
switch {
|
||||
case err == nil && len(headers) == 0:
|
||||
peer.log.Trace("Requested headers delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of headers", "count", len(headers), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved headers", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// receiptQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type receiptQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more reecipt
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *receiptQueue) waker() chan bool {
|
||||
return q.queue.receiptWakeCh
|
||||
}
|
||||
|
||||
// pending returns the number of receipt that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *receiptQueue) pending() int {
|
||||
return q.queue.PendingReceipts()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many receipts a particular peer is
|
||||
// estimated to be able to retrieve within the alloted round trip time.
|
||||
func (q *receiptQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.ReceiptCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many receipts a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *receiptQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateReceiptRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending receipts
|
||||
// from the download queue to the specified peer.
|
||||
func (q *receiptQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveReceipts(peer, items)
|
||||
}
|
||||
|
||||
// unreserve is resposible for removing the current receipt retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *receiptQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireReceipts(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Receipt delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Receipt delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a receipt
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of receipts", "count", len(req.Headers), "from", req.Headers[0].Number)
|
||||
if q.receiptFetchHook != nil {
|
||||
q.receiptFetchHook(req.Headers)
|
||||
}
|
||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
||||
for _, header := range req.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
return peer.peer.RequestReceipts(hashes, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the receipt data and delivering it to the downloader's queue.
|
||||
func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
receipts := *packet.Res.(*eth.ReceiptsPacket)
|
||||
hashes := packet.Meta.([]common.Hash) // {receipt hashes}
|
||||
|
||||
accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes)
|
||||
switch {
|
||||
case err == nil && len(receipts) == 0:
|
||||
peer.log.Trace("Requested receipts delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of receipts", "count", len(receipts), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved receipts", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
@@ -38,8 +38,5 @@ var (
|
||||
receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil)
|
||||
receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
|
||||
|
||||
stateInMeter = metrics.NewRegisteredMeter("eth/downloader/states/in", nil)
|
||||
stateDropMeter = metrics.NewRegisteredMeter("eth/downloader/states/drop", nil)
|
||||
|
||||
throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil)
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ type SyncMode uint32
|
||||
|
||||
const (
|
||||
FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks
|
||||
FastSync // Quickly download the headers, full sync only at the chain
|
||||
SnapSync // Download the chain and the state via compact snapshots
|
||||
LightSync // Download only the headers and terminate afterwards
|
||||
)
|
||||
@@ -38,8 +37,6 @@ func (mode SyncMode) String() string {
|
||||
switch mode {
|
||||
case FullSync:
|
||||
return "full"
|
||||
case FastSync:
|
||||
return "fast"
|
||||
case SnapSync:
|
||||
return "snap"
|
||||
case LightSync:
|
||||
@@ -53,8 +50,6 @@ func (mode SyncMode) MarshalText() ([]byte, error) {
|
||||
switch mode {
|
||||
case FullSync:
|
||||
return []byte("full"), nil
|
||||
case FastSync:
|
||||
return []byte("fast"), nil
|
||||
case SnapSync:
|
||||
return []byte("snap"), nil
|
||||
case LightSync:
|
||||
@@ -68,14 +63,12 @@ func (mode *SyncMode) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "full":
|
||||
*mode = FullSync
|
||||
case "fast":
|
||||
*mode = FastSync
|
||||
case "snap":
|
||||
*mode = SnapSync
|
||||
case "light":
|
||||
*mode = LightSync
|
||||
default:
|
||||
return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text)
|
||||
return fmt.Errorf(`unknown sync mode %q, want "full", "snap" or "light"`, text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+40
-229
@@ -22,9 +22,7 @@ package downloader
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -39,7 +37,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyFetching = errors.New("already fetching blocks from peer")
|
||||
errAlreadyRegistered = errors.New("peer is already registered")
|
||||
errNotRegistered = errors.New("peer is not registered")
|
||||
)
|
||||
@@ -48,16 +45,6 @@ var (
|
||||
type peerConnection struct {
|
||||
id string // Unique identifier of the peer
|
||||
|
||||
headerIdle int32 // Current header activity state of the peer (idle = 0, active = 1)
|
||||
blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1)
|
||||
receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1)
|
||||
stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1)
|
||||
|
||||
headerStarted time.Time // Time instance when the last header fetch was started
|
||||
blockStarted time.Time // Time instance when the last block (body) fetch was started
|
||||
receiptStarted time.Time // Time instance when the last receipt fetch was started
|
||||
stateStarted time.Time // Time instance when the last node data fetch was started
|
||||
|
||||
rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second
|
||||
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
|
||||
|
||||
@@ -71,16 +58,15 @@ type peerConnection struct {
|
||||
// LightPeer encapsulates the methods required to synchronise with a remote light peer.
|
||||
type LightPeer interface {
|
||||
Head() (common.Hash, *big.Int)
|
||||
RequestHeadersByHash(common.Hash, int, int, bool) error
|
||||
RequestHeadersByNumber(uint64, int, int, bool) error
|
||||
RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
||||
RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
||||
}
|
||||
|
||||
// Peer encapsulates the methods required to synchronise with a remote full peer.
|
||||
type Peer interface {
|
||||
LightPeer
|
||||
RequestBodies([]common.Hash) error
|
||||
RequestReceipts([]common.Hash) error
|
||||
RequestNodeData([]common.Hash) error
|
||||
RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
}
|
||||
|
||||
// lightPeerWrapper wraps a LightPeer struct, stubbing out the Peer-only methods.
|
||||
@@ -89,21 +75,18 @@ type lightPeerWrapper struct {
|
||||
}
|
||||
|
||||
func (w *lightPeerWrapper) Head() (common.Hash, *big.Int) { return w.peer.Head() }
|
||||
func (w *lightPeerWrapper) RequestHeadersByHash(h common.Hash, amount int, skip int, reverse bool) error {
|
||||
return w.peer.RequestHeadersByHash(h, amount, skip, reverse)
|
||||
func (w *lightPeerWrapper) RequestHeadersByHash(h common.Hash, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) {
|
||||
return w.peer.RequestHeadersByHash(h, amount, skip, reverse, sink)
|
||||
}
|
||||
func (w *lightPeerWrapper) RequestHeadersByNumber(i uint64, amount int, skip int, reverse bool) error {
|
||||
return w.peer.RequestHeadersByNumber(i, amount, skip, reverse)
|
||||
func (w *lightPeerWrapper) RequestHeadersByNumber(i uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) {
|
||||
return w.peer.RequestHeadersByNumber(i, amount, skip, reverse, sink)
|
||||
}
|
||||
func (w *lightPeerWrapper) RequestBodies([]common.Hash) error {
|
||||
func (w *lightPeerWrapper) RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) {
|
||||
panic("RequestBodies not supported in light client mode sync")
|
||||
}
|
||||
func (w *lightPeerWrapper) RequestReceipts([]common.Hash) error {
|
||||
func (w *lightPeerWrapper) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) {
|
||||
panic("RequestReceipts not supported in light client mode sync")
|
||||
}
|
||||
func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error {
|
||||
panic("RequestNodeData not supported in light client mode sync")
|
||||
}
|
||||
|
||||
// newPeerConnection creates a new downloader peer.
|
||||
func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection {
|
||||
@@ -121,114 +104,28 @@ func (p *peerConnection) Reset() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
atomic.StoreInt32(&p.headerIdle, 0)
|
||||
atomic.StoreInt32(&p.blockIdle, 0)
|
||||
atomic.StoreInt32(&p.receiptIdle, 0)
|
||||
atomic.StoreInt32(&p.stateIdle, 0)
|
||||
|
||||
p.lacking = make(map[common.Hash]struct{})
|
||||
}
|
||||
|
||||
// FetchHeaders sends a header retrieval request to the remote peer.
|
||||
func (p *peerConnection) FetchHeaders(from uint64, count int) error {
|
||||
// Short circuit if the peer is already fetching
|
||||
if !atomic.CompareAndSwapInt32(&p.headerIdle, 0, 1) {
|
||||
return errAlreadyFetching
|
||||
}
|
||||
p.headerStarted = time.Now()
|
||||
|
||||
// Issue the header retrieval request (absolute upwards without gaps)
|
||||
go p.peer.RequestHeadersByNumber(from, count, 0, false)
|
||||
|
||||
return nil
|
||||
// UpdateHeaderRate updates the peer's estimated header retrieval throughput with
|
||||
// the current measurement.
|
||||
func (p *peerConnection) UpdateHeaderRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.BlockHeadersMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// FetchBodies sends a block body retrieval request to the remote peer.
|
||||
func (p *peerConnection) FetchBodies(request *fetchRequest) error {
|
||||
// Short circuit if the peer is already fetching
|
||||
if !atomic.CompareAndSwapInt32(&p.blockIdle, 0, 1) {
|
||||
return errAlreadyFetching
|
||||
}
|
||||
p.blockStarted = time.Now()
|
||||
|
||||
go func() {
|
||||
// Convert the header set to a retrievable slice
|
||||
hashes := make([]common.Hash, 0, len(request.Headers))
|
||||
for _, header := range request.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
p.peer.RequestBodies(hashes)
|
||||
}()
|
||||
|
||||
return nil
|
||||
// UpdateBodyRate updates the peer's estimated body retrieval throughput with the
|
||||
// current measurement.
|
||||
func (p *peerConnection) UpdateBodyRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.BlockBodiesMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// FetchReceipts sends a receipt retrieval request to the remote peer.
|
||||
func (p *peerConnection) FetchReceipts(request *fetchRequest) error {
|
||||
// Short circuit if the peer is already fetching
|
||||
if !atomic.CompareAndSwapInt32(&p.receiptIdle, 0, 1) {
|
||||
return errAlreadyFetching
|
||||
}
|
||||
p.receiptStarted = time.Now()
|
||||
|
||||
go func() {
|
||||
// Convert the header set to a retrievable slice
|
||||
hashes := make([]common.Hash, 0, len(request.Headers))
|
||||
for _, header := range request.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
p.peer.RequestReceipts(hashes)
|
||||
}()
|
||||
|
||||
return nil
|
||||
// UpdateReceiptRate updates the peer's estimated receipt retrieval throughput
|
||||
// with the current measurement.
|
||||
func (p *peerConnection) UpdateReceiptRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.ReceiptsMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// FetchNodeData sends a node state data retrieval request to the remote peer.
|
||||
func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
|
||||
// Short circuit if the peer is already fetching
|
||||
if !atomic.CompareAndSwapInt32(&p.stateIdle, 0, 1) {
|
||||
return errAlreadyFetching
|
||||
}
|
||||
p.stateStarted = time.Now()
|
||||
|
||||
go p.peer.RequestNodeData(hashes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval
|
||||
// requests. Its estimated header retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) {
|
||||
p.rates.Update(eth.BlockHeadersMsg, deliveryTime.Sub(p.headerStarted), delivered)
|
||||
atomic.StoreInt32(&p.headerIdle, 0)
|
||||
}
|
||||
|
||||
// SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval
|
||||
// requests. Its estimated body retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) {
|
||||
p.rates.Update(eth.BlockBodiesMsg, deliveryTime.Sub(p.blockStarted), delivered)
|
||||
atomic.StoreInt32(&p.blockIdle, 0)
|
||||
}
|
||||
|
||||
// SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt
|
||||
// retrieval requests. Its estimated receipt retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) {
|
||||
p.rates.Update(eth.ReceiptsMsg, deliveryTime.Sub(p.receiptStarted), delivered)
|
||||
atomic.StoreInt32(&p.receiptIdle, 0)
|
||||
}
|
||||
|
||||
// SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie
|
||||
// data retrieval requests. Its estimated state retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) {
|
||||
p.rates.Update(eth.NodeDataMsg, deliveryTime.Sub(p.stateStarted), delivered)
|
||||
atomic.StoreInt32(&p.stateIdle, 0)
|
||||
}
|
||||
|
||||
// HeaderCapacity retrieves the peers header download allowance based on its
|
||||
// HeaderCapacity retrieves the peer's header download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.BlockHeadersMsg, targetRTT)
|
||||
@@ -238,9 +135,9 @@ func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int {
|
||||
return cap
|
||||
}
|
||||
|
||||
// BlockCapacity retrieves the peers block download allowance based on its
|
||||
// BodyCapacity retrieves the peer's body download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int {
|
||||
func (p *peerConnection) BodyCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.BlockBodiesMsg, targetRTT)
|
||||
if cap > MaxBlockFetch {
|
||||
cap = MaxBlockFetch
|
||||
@@ -258,16 +155,6 @@ func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int {
|
||||
return cap
|
||||
}
|
||||
|
||||
// NodeDataCapacity retrieves the peers state download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.NodeDataMsg, targetRTT)
|
||||
if cap > MaxStateFetch {
|
||||
cap = MaxStateFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
||||
// that a peer is known not to have (i.e. have been requested before). If the
|
||||
// set reaches its maximum allowed capacity, items are randomly dropped off.
|
||||
@@ -294,14 +181,19 @@ func (p *peerConnection) Lacks(hash common.Hash) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// peeringEvent is sent on the peer event feed when a remote peer connects or
|
||||
// disconnects.
|
||||
type peeringEvent struct {
|
||||
peer *peerConnection
|
||||
join bool
|
||||
}
|
||||
|
||||
// peerSet represents the collection of active peer participating in the chain
|
||||
// download procedure.
|
||||
type peerSet struct {
|
||||
peers map[string]*peerConnection
|
||||
rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat
|
||||
|
||||
newPeerFeed event.Feed
|
||||
peerDropFeed event.Feed
|
||||
peers map[string]*peerConnection
|
||||
rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat
|
||||
events event.Feed // Feed to publish peer lifecycle events on
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@@ -314,14 +206,9 @@ func newPeerSet() *peerSet {
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeNewPeers subscribes to peer arrival events.
|
||||
func (ps *peerSet) SubscribeNewPeers(ch chan<- *peerConnection) event.Subscription {
|
||||
return ps.newPeerFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
// SubscribePeerDrops subscribes to peer departure events.
|
||||
func (ps *peerSet) SubscribePeerDrops(ch chan<- *peerConnection) event.Subscription {
|
||||
return ps.peerDropFeed.Subscribe(ch)
|
||||
// SubscribeEvents subscribes to peer arrival and departure events.
|
||||
func (ps *peerSet) SubscribeEvents(ch chan<- *peeringEvent) event.Subscription {
|
||||
return ps.events.Subscribe(ch)
|
||||
}
|
||||
|
||||
// Reset iterates over the current peer set, and resets each of the known peers
|
||||
@@ -355,7 +242,7 @@ func (ps *peerSet) Register(p *peerConnection) error {
|
||||
ps.peers[p.id] = p
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.newPeerFeed.Send(p)
|
||||
ps.events.Send(&peeringEvent{peer: p, join: true})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -372,7 +259,7 @@ func (ps *peerSet) Unregister(id string) error {
|
||||
ps.rates.Untrack(id)
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.peerDropFeed.Send(p)
|
||||
ps.events.Send(&peeringEvent{peer: p, join: false})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -404,82 +291,6 @@ func (ps *peerSet) AllPeers() []*peerConnection {
|
||||
return list
|
||||
}
|
||||
|
||||
// HeaderIdlePeers retrieves a flat list of all the currently header-idle peers
|
||||
// within the active peer set, ordered by their reputation.
|
||||
func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
||||
idle := func(p *peerConnection) bool {
|
||||
return atomic.LoadInt32(&p.headerIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) int {
|
||||
return p.rates.Capacity(eth.BlockHeadersMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
|
||||
}
|
||||
|
||||
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
|
||||
// the active peer set, ordered by their reputation.
|
||||
func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
|
||||
idle := func(p *peerConnection) bool {
|
||||
return atomic.LoadInt32(&p.blockIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) int {
|
||||
return p.rates.Capacity(eth.BlockBodiesMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
|
||||
}
|
||||
|
||||
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
|
||||
// within the active peer set, ordered by their reputation.
|
||||
func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
|
||||
idle := func(p *peerConnection) bool {
|
||||
return atomic.LoadInt32(&p.receiptIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) int {
|
||||
return p.rates.Capacity(eth.ReceiptsMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
|
||||
}
|
||||
|
||||
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
|
||||
// peers within the active peer set, ordered by their reputation.
|
||||
func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
||||
idle := func(p *peerConnection) bool {
|
||||
return atomic.LoadInt32(&p.stateIdle) == 0
|
||||
}
|
||||
throughput := func(p *peerConnection) int {
|
||||
return p.rates.Capacity(eth.NodeDataMsg, time.Second)
|
||||
}
|
||||
return ps.idlePeers(eth.ETH66, eth.ETH66, idle, throughput)
|
||||
}
|
||||
|
||||
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
||||
// protocol version constraints, using the provided function to check idleness.
|
||||
// The resulting set of peers are sorted by their capacity.
|
||||
func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, capacity func(*peerConnection) int) ([]*peerConnection, int) {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
var (
|
||||
total = 0
|
||||
idle = make([]*peerConnection, 0, len(ps.peers))
|
||||
tps = make([]int, 0, len(ps.peers))
|
||||
)
|
||||
for _, p := range ps.peers {
|
||||
if p.version >= minProtocol && p.version <= maxProtocol {
|
||||
if idleCheck(p) {
|
||||
idle = append(idle, p)
|
||||
tps = append(tps, capacity(p))
|
||||
}
|
||||
total++
|
||||
}
|
||||
}
|
||||
|
||||
// And sort them
|
||||
sortPeers := &peerCapacitySort{idle, tps}
|
||||
sort.Sort(sortPeers)
|
||||
return sortPeers.p, total
|
||||
}
|
||||
|
||||
// peerCapacitySort implements sort.Interface.
|
||||
// It sorts peer connections by capacity (descending).
|
||||
type peerCapacitySort struct {
|
||||
|
||||
+106
-117
@@ -31,7 +31,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,8 +53,8 @@ var (
|
||||
// fetchRequest is a currently running data retrieval operation.
|
||||
type fetchRequest struct {
|
||||
Peer *peerConnection // Peer to which the request was sent
|
||||
From uint64 // [eth/62] Requested chain element index (used for skeleton fills only)
|
||||
Headers []*types.Header // [eth/62] Requested headers, sorted by request order
|
||||
From uint64 // Requested chain element index (used for skeleton fills only)
|
||||
Headers []*types.Header // Requested headers, sorted by request order
|
||||
Time time.Time // Time when the request was made
|
||||
}
|
||||
|
||||
@@ -119,6 +118,7 @@ type queue struct {
|
||||
headerPeerMiss map[string]map[uint64]struct{} // Set of per-peer header batches known to be unavailable
|
||||
headerPendPool map[string]*fetchRequest // Currently pending header retrieval operations
|
||||
headerResults []*types.Header // Result cache accumulating the completed headers
|
||||
headerHashes []common.Hash // Result cache accumulating the completed header hashes
|
||||
headerProced int // Number of headers already processed from the results
|
||||
headerOffset uint64 // Number of the first header in the result cache
|
||||
headerContCh chan bool // Channel to notify when header download finishes
|
||||
@@ -127,10 +127,12 @@ type queue struct {
|
||||
blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers
|
||||
blockTaskQueue *prque.Prque // Priority queue of the headers to fetch the blocks (bodies) for
|
||||
blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations
|
||||
blockWakeCh chan bool // Channel to notify the block fetcher of new tasks
|
||||
|
||||
receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers
|
||||
receiptTaskQueue *prque.Prque // Priority queue of the headers to fetch the receipts for
|
||||
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
||||
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
||||
|
||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||
@@ -146,9 +148,11 @@ type queue struct {
|
||||
func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue {
|
||||
lock := new(sync.RWMutex)
|
||||
q := &queue{
|
||||
headerContCh: make(chan bool),
|
||||
headerContCh: make(chan bool, 1),
|
||||
blockTaskQueue: prque.New(nil),
|
||||
blockWakeCh: make(chan bool, 1),
|
||||
receiptTaskQueue: prque.New(nil),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
active: sync.NewCond(lock),
|
||||
lock: lock,
|
||||
}
|
||||
@@ -196,8 +200,8 @@ func (q *queue) PendingHeaders() int {
|
||||
return q.headerTaskQueue.Size()
|
||||
}
|
||||
|
||||
// PendingBlocks retrieves the number of block (body) requests pending for retrieval.
|
||||
func (q *queue) PendingBlocks() int {
|
||||
// PendingBodies retrieves the number of block body requests pending for retrieval.
|
||||
func (q *queue) PendingBodies() int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
@@ -212,15 +216,6 @@ func (q *queue) PendingReceipts() int {
|
||||
return q.receiptTaskQueue.Size()
|
||||
}
|
||||
|
||||
// InFlightHeaders retrieves whether there are header fetch requests currently
|
||||
// in flight.
|
||||
func (q *queue) InFlightHeaders() bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return len(q.headerPendPool) > 0
|
||||
}
|
||||
|
||||
// InFlightBlocks retrieves whether there are block fetch requests currently in
|
||||
// flight.
|
||||
func (q *queue) InFlightBlocks() bool {
|
||||
@@ -265,6 +260,7 @@ func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
|
||||
q.headerTaskQueue = prque.New(nil)
|
||||
q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains
|
||||
q.headerResults = make([]*types.Header, len(skeleton)*MaxHeaderFetch)
|
||||
q.headerHashes = make([]common.Hash, len(skeleton)*MaxHeaderFetch)
|
||||
q.headerProced = 0
|
||||
q.headerOffset = from
|
||||
q.headerContCh = make(chan bool, 1)
|
||||
@@ -279,27 +275,27 @@ func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
|
||||
|
||||
// RetrieveHeaders retrieves the header chain assemble based on the scheduled
|
||||
// skeleton.
|
||||
func (q *queue) RetrieveHeaders() ([]*types.Header, int) {
|
||||
func (q *queue) RetrieveHeaders() ([]*types.Header, []common.Hash, int) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
headers, proced := q.headerResults, q.headerProced
|
||||
q.headerResults, q.headerProced = nil, 0
|
||||
headers, hashes, proced := q.headerResults, q.headerHashes, q.headerProced
|
||||
q.headerResults, q.headerHashes, q.headerProced = nil, nil, 0
|
||||
|
||||
return headers, proced
|
||||
return headers, hashes, proced
|
||||
}
|
||||
|
||||
// Schedule adds a set of headers for the download queue for scheduling, returning
|
||||
// the new headers encountered.
|
||||
func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
||||
func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uint64) []*types.Header {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// Insert all the headers prioritised by the contained block number
|
||||
inserts := make([]*types.Header, 0, len(headers))
|
||||
for _, header := range headers {
|
||||
for i, header := range headers {
|
||||
// Make sure chain order is honoured and preserved throughout
|
||||
hash := header.Hash()
|
||||
hash := hashes[i]
|
||||
if header.Number == nil || header.Number.Uint64() != from {
|
||||
log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from)
|
||||
break
|
||||
@@ -318,7 +314,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
||||
q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
// Queue for receipt retrieval
|
||||
if q.mode == FastSync && !header.EmptyReceipts() {
|
||||
if q.mode == SnapSync && !header.EmptyReceipts() {
|
||||
if _, ok := q.receiptTaskPool[hash]; ok {
|
||||
log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash)
|
||||
} else {
|
||||
@@ -383,6 +379,13 @@ func (q *queue) Results(block bool) []*fetchResult {
|
||||
throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
||||
throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold)
|
||||
|
||||
// With results removed from the cache, wake throttled fetchers
|
||||
for _, ch := range []chan bool{q.blockWakeCh, q.receiptWakeCh} {
|
||||
select {
|
||||
case ch <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
// Log some info at certain times
|
||||
if time.Since(q.lastStatLog) > 60*time.Second {
|
||||
q.lastStatLog = time.Now()
|
||||
@@ -503,7 +506,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||
// we can ask the resultcache if this header is within the
|
||||
// "prioritized" segment of blocks. If it is not, we need to throttle
|
||||
|
||||
stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == FastSync)
|
||||
stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == SnapSync)
|
||||
if stale {
|
||||
// Don't put back in the task queue, this item has already been
|
||||
// delivered upstream
|
||||
@@ -566,40 +569,6 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||
return request, progress, throttled
|
||||
}
|
||||
|
||||
// CancelHeaders aborts a fetch request, returning all pending skeleton indexes to the queue.
|
||||
func (q *queue) CancelHeaders(request *fetchRequest) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.cancel(request, q.headerTaskQueue, q.headerPendPool)
|
||||
}
|
||||
|
||||
// CancelBodies aborts a body fetch request, returning all pending headers to the
|
||||
// task queue.
|
||||
func (q *queue) CancelBodies(request *fetchRequest) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.cancel(request, q.blockTaskQueue, q.blockPendPool)
|
||||
}
|
||||
|
||||
// CancelReceipts aborts a body fetch request, returning all pending headers to
|
||||
// the task queue.
|
||||
func (q *queue) CancelReceipts(request *fetchRequest) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.cancel(request, q.receiptTaskQueue, q.receiptPendPool)
|
||||
}
|
||||
|
||||
// Cancel aborts a fetch request, returning all pending hashes to the task queue.
|
||||
func (q *queue) cancel(request *fetchRequest, taskQueue *prque.Prque, pendPool map[string]*fetchRequest) {
|
||||
if request.From > 0 {
|
||||
taskQueue.Push(request.From, -int64(request.From))
|
||||
}
|
||||
for _, header := range request.Headers {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
delete(pendPool, request.Peer.id)
|
||||
}
|
||||
|
||||
// Revoke cancels all pending requests belonging to a given peer. This method is
|
||||
// meant to be called during a peer drop to quickly reassign owned data fetches
|
||||
// to remaining nodes.
|
||||
@@ -607,6 +576,10 @@ func (q *queue) Revoke(peerID string) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
if request, ok := q.headerPendPool[peerID]; ok {
|
||||
q.headerTaskQueue.Push(request.From, -int64(request.From))
|
||||
delete(q.headerPendPool, peerID)
|
||||
}
|
||||
if request, ok := q.blockPendPool[peerID]; ok {
|
||||
for _, header := range request.Headers {
|
||||
q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
@@ -621,62 +594,60 @@ func (q *queue) Revoke(peerID string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ExpireHeaders checks for in flight requests that exceeded a timeout allowance,
|
||||
// canceling them and returning the responsible peers for penalisation.
|
||||
func (q *queue) ExpireHeaders(timeout time.Duration) map[string]int {
|
||||
// ExpireHeaders cancels a request that timed out and moves the pending fetch
|
||||
// task back into the queue for rescheduling.
|
||||
func (q *queue) ExpireHeaders(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.expire(timeout, q.headerPendPool, q.headerTaskQueue, headerTimeoutMeter)
|
||||
headerTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.headerPendPool, q.headerTaskQueue)
|
||||
}
|
||||
|
||||
// ExpireBodies checks for in flight block body requests that exceeded a timeout
|
||||
// allowance, canceling them and returning the responsible peers for penalisation.
|
||||
func (q *queue) ExpireBodies(timeout time.Duration) map[string]int {
|
||||
func (q *queue) ExpireBodies(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.expire(timeout, q.blockPendPool, q.blockTaskQueue, bodyTimeoutMeter)
|
||||
bodyTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.blockPendPool, q.blockTaskQueue)
|
||||
}
|
||||
|
||||
// ExpireReceipts checks for in flight receipt requests that exceeded a timeout
|
||||
// allowance, canceling them and returning the responsible peers for penalisation.
|
||||
func (q *queue) ExpireReceipts(timeout time.Duration) map[string]int {
|
||||
func (q *queue) ExpireReceipts(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.expire(timeout, q.receiptPendPool, q.receiptTaskQueue, receiptTimeoutMeter)
|
||||
receiptTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.receiptPendPool, q.receiptTaskQueue)
|
||||
}
|
||||
|
||||
// expire is the generic check that move expired tasks from a pending pool back
|
||||
// into a task pool, returning all entities caught with expired tasks.
|
||||
// expire is the generic check that moves a specific expired task from a pending
|
||||
// pool back into a task pool.
|
||||
//
|
||||
// Note, this method expects the queue lock to be already held. The
|
||||
// reason the lock is not obtained in here is because the parameters already need
|
||||
// to access the queue, so they already need a lock anyway.
|
||||
func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest, taskQueue *prque.Prque, timeoutMeter metrics.Meter) map[string]int {
|
||||
// Iterate over the expired requests and return each to the queue
|
||||
expiries := make(map[string]int)
|
||||
for id, request := range pendPool {
|
||||
if time.Since(request.Time) > timeout {
|
||||
// Update the metrics with the timeout
|
||||
timeoutMeter.Mark(1)
|
||||
|
||||
// Return any non satisfied requests to the pool
|
||||
if request.From > 0 {
|
||||
taskQueue.Push(request.From, -int64(request.From))
|
||||
}
|
||||
for _, header := range request.Headers {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
// Add the peer to the expiry report along the number of failed requests
|
||||
expiries[id] = len(request.Headers)
|
||||
|
||||
// Remove the expired requests from the pending pool directly
|
||||
delete(pendPool, id)
|
||||
}
|
||||
// Note, this method expects the queue lock to be already held. The reason the
|
||||
// lock is not obtained in here is that the parameters already need to access
|
||||
// the queue, so they already need a lock anyway.
|
||||
func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue *prque.Prque) int {
|
||||
// Retrieve the request being expired and log an error if it's non-existnet,
|
||||
// as there's no order of events that should lead to such expirations.
|
||||
req := pendPool[peer]
|
||||
if req == nil {
|
||||
log.Error("Expired request does not exist", "peer", peer)
|
||||
return 0
|
||||
}
|
||||
return expiries
|
||||
delete(pendPool, peer)
|
||||
|
||||
// Return any non-satisfied requests to the pool
|
||||
if req.From > 0 {
|
||||
taskQueue.Push(req.From, -int64(req.From))
|
||||
}
|
||||
for _, header := range req.Headers {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
return len(req.Headers)
|
||||
}
|
||||
|
||||
// DeliverHeaders injects a header retrieval response into the header results
|
||||
@@ -684,9 +655,9 @@ func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest,
|
||||
// if they do not map correctly to the skeleton.
|
||||
//
|
||||
// If the headers are accepted, the method makes an attempt to deliver the set
|
||||
// of ready headers to the processor to keep the pipeline full. However it will
|
||||
// of ready headers to the processor to keep the pipeline full. However, it will
|
||||
// not block to prevent stalling other pending deliveries.
|
||||
func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh chan []*types.Header) (int, error) {
|
||||
func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []common.Hash, headerProcCh chan *headerTask) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
@@ -700,28 +671,31 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
// Short circuit if the data was never requested
|
||||
request := q.headerPendPool[id]
|
||||
if request == nil {
|
||||
headerDropMeter.Mark(int64(len(headers)))
|
||||
return 0, errNoFetchesPending
|
||||
}
|
||||
headerReqTimer.UpdateSince(request.Time)
|
||||
delete(q.headerPendPool, id)
|
||||
|
||||
headerReqTimer.UpdateSince(request.Time)
|
||||
headerInMeter.Mark(int64(len(headers)))
|
||||
|
||||
// Ensure headers can be mapped onto the skeleton chain
|
||||
target := q.headerTaskPool[request.From].Hash()
|
||||
|
||||
accepted := len(headers) == MaxHeaderFetch
|
||||
if accepted {
|
||||
if headers[0].Number.Uint64() != request.From {
|
||||
logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From)
|
||||
logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", hashes[0], "expected", request.From)
|
||||
accepted = false
|
||||
} else if headers[len(headers)-1].Hash() != target {
|
||||
logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target)
|
||||
} else if hashes[len(headers)-1] != target {
|
||||
logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", hashes[len(headers)-1], "expected", target)
|
||||
accepted = false
|
||||
}
|
||||
}
|
||||
if accepted {
|
||||
parentHash := headers[0].Hash()
|
||||
parentHash := hashes[0]
|
||||
for i, header := range headers[1:] {
|
||||
hash := header.Hash()
|
||||
hash := hashes[i+1]
|
||||
if want := request.From + 1 + uint64(i); header.Number.Uint64() != want {
|
||||
logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want)
|
||||
accepted = false
|
||||
@@ -739,6 +713,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
// If the batch of headers wasn't accepted, mark as unavailable
|
||||
if !accepted {
|
||||
logger.Trace("Skeleton filling not accepted", "from", request.From)
|
||||
headerDropMeter.Mark(int64(len(headers)))
|
||||
|
||||
miss := q.headerPeerMiss[id]
|
||||
if miss == nil {
|
||||
@@ -752,6 +727,8 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
}
|
||||
// Clean up a successful fetch and try to deliver any sub-results
|
||||
copy(q.headerResults[request.From-q.headerOffset:], headers)
|
||||
copy(q.headerHashes[request.From-q.headerOffset:], hashes)
|
||||
|
||||
delete(q.headerTaskPool, request.From)
|
||||
|
||||
ready := 0
|
||||
@@ -760,13 +737,19 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
}
|
||||
if ready > 0 {
|
||||
// Headers are ready for delivery, gather them and push forward (non blocking)
|
||||
process := make([]*types.Header, ready)
|
||||
copy(process, q.headerResults[q.headerProced:q.headerProced+ready])
|
||||
processHeaders := make([]*types.Header, ready)
|
||||
copy(processHeaders, q.headerResults[q.headerProced:q.headerProced+ready])
|
||||
|
||||
processHashes := make([]common.Hash, ready)
|
||||
copy(processHashes, q.headerHashes[q.headerProced:q.headerProced+ready])
|
||||
|
||||
select {
|
||||
case headerProcCh <- process:
|
||||
logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number)
|
||||
q.headerProced += len(process)
|
||||
case headerProcCh <- &headerTask{
|
||||
headers: processHeaders,
|
||||
hashes: processHashes,
|
||||
}:
|
||||
logger.Trace("Pre-scheduled new headers", "count", len(processHeaders), "from", processHeaders[0].Number)
|
||||
q.headerProced += len(processHeaders)
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -780,15 +763,15 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
// DeliverBodies injects a block body retrieval response into the results queue.
|
||||
// The method returns the number of blocks bodies accepted from the delivery and
|
||||
// also wakes any threads waiting for data delivery.
|
||||
func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLists [][]*types.Header) (int, error) {
|
||||
func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash, uncleLists [][]*types.Header, uncleListHashes []common.Hash) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
trieHasher := trie.NewStackTrie(nil)
|
||||
|
||||
validate := func(index int, header *types.Header) error {
|
||||
if types.DeriveSha(types.Transactions(txLists[index]), trieHasher) != header.TxHash {
|
||||
if txListHashes[index] != header.TxHash {
|
||||
return errInvalidBody
|
||||
}
|
||||
if types.CalcUncleHash(uncleLists[index]) != header.UncleHash {
|
||||
if uncleListHashes[index] != header.UncleHash {
|
||||
return errInvalidBody
|
||||
}
|
||||
return nil
|
||||
@@ -800,18 +783,18 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi
|
||||
result.SetBodyDone()
|
||||
}
|
||||
return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool,
|
||||
bodyReqTimer, len(txLists), validate, reconstruct)
|
||||
bodyReqTimer, bodyInMeter, bodyDropMeter, len(txLists), validate, reconstruct)
|
||||
}
|
||||
|
||||
// DeliverReceipts injects a receipt retrieval response into the results queue.
|
||||
// The method returns the number of transaction receipts accepted from the delivery
|
||||
// and also wakes any threads waiting for data delivery.
|
||||
func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, error) {
|
||||
func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, receiptListHashes []common.Hash) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
trieHasher := trie.NewStackTrie(nil)
|
||||
|
||||
validate := func(index int, header *types.Header) error {
|
||||
if types.DeriveSha(types.Receipts(receiptList[index]), trieHasher) != header.ReceiptHash {
|
||||
if receiptListHashes[index] != header.ReceiptHash {
|
||||
return errInvalidReceipt
|
||||
}
|
||||
return nil
|
||||
@@ -821,7 +804,7 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int,
|
||||
result.SetReceiptsDone()
|
||||
}
|
||||
return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool,
|
||||
receiptReqTimer, len(receiptList), validate, reconstruct)
|
||||
receiptReqTimer, receiptInMeter, receiptDropMeter, len(receiptList), validate, reconstruct)
|
||||
}
|
||||
|
||||
// deliver injects a data retrieval response into the results queue.
|
||||
@@ -830,18 +813,22 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int,
|
||||
// reason this lock is not obtained in here is because the parameters already need
|
||||
// to access the queue, so they already need a lock anyway.
|
||||
func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||
taskQueue *prque.Prque, pendPool map[string]*fetchRequest, reqTimer metrics.Timer,
|
||||
taskQueue *prque.Prque, pendPool map[string]*fetchRequest,
|
||||
reqTimer metrics.Timer, resInMeter metrics.Meter, resDropMeter metrics.Meter,
|
||||
results int, validate func(index int, header *types.Header) error,
|
||||
reconstruct func(index int, result *fetchResult)) (int, error) {
|
||||
|
||||
// Short circuit if the data was never requested
|
||||
request := pendPool[id]
|
||||
if request == nil {
|
||||
resDropMeter.Mark(int64(results))
|
||||
return 0, errNoFetchesPending
|
||||
}
|
||||
reqTimer.UpdateSince(request.Time)
|
||||
delete(pendPool, id)
|
||||
|
||||
reqTimer.UpdateSince(request.Time)
|
||||
resInMeter.Mark(int64(results))
|
||||
|
||||
// If no data items were retrieved, mark them as unavailable for the origin peer
|
||||
if results == 0 {
|
||||
for _, header := range request.Headers {
|
||||
@@ -883,6 +870,8 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||
delete(taskPool, hashes[accepted])
|
||||
accepted++
|
||||
}
|
||||
resDropMeter.Mark(int64(results - accepted))
|
||||
|
||||
// Return all failed or missing fetches to the queue
|
||||
for _, header := range request.Headers[accepted:] {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -104,17 +105,22 @@ func TestBasics(t *testing.T) {
|
||||
if !q.Idle() {
|
||||
t.Errorf("new queue should be idle")
|
||||
}
|
||||
q.Prepare(1, FastSync)
|
||||
q.Prepare(1, SnapSync)
|
||||
if res := q.Results(false); len(res) != 0 {
|
||||
t.Fatal("new queue should have 0 results")
|
||||
}
|
||||
|
||||
// Schedule a batch of headers
|
||||
q.Schedule(chain.headers(), 1)
|
||||
headers := chain.headers()
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
q.Schedule(headers, hashes, 1)
|
||||
if q.Idle() {
|
||||
t.Errorf("queue should not be idle")
|
||||
}
|
||||
if got, exp := q.PendingBlocks(), chain.Len(); got != exp {
|
||||
if got, exp := q.PendingBodies(), chain.Len(); got != exp {
|
||||
t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
|
||||
}
|
||||
// Only non-empty receipts get added to task-queue
|
||||
@@ -197,13 +203,19 @@ func TestEmptyBlocks(t *testing.T) {
|
||||
|
||||
q := newQueue(10, 10)
|
||||
|
||||
q.Prepare(1, FastSync)
|
||||
q.Prepare(1, SnapSync)
|
||||
|
||||
// Schedule a batch of headers
|
||||
q.Schedule(emptyChain.headers(), 1)
|
||||
headers := emptyChain.headers()
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
q.Schedule(headers, hashes, 1)
|
||||
if q.Idle() {
|
||||
t.Errorf("queue should not be idle")
|
||||
}
|
||||
if got, exp := q.PendingBlocks(), len(emptyChain.blocks); got != exp {
|
||||
if got, exp := q.PendingBodies(), len(emptyChain.blocks); got != exp {
|
||||
t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
|
||||
}
|
||||
if got, exp := q.PendingReceipts(), 0; got != exp {
|
||||
@@ -272,7 +284,7 @@ func XTestDelivery(t *testing.T) {
|
||||
}
|
||||
q := newQueue(10, 10)
|
||||
var wg sync.WaitGroup
|
||||
q.Prepare(1, FastSync)
|
||||
q.Prepare(1, SnapSync)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// deliver headers
|
||||
@@ -280,11 +292,15 @@ func XTestDelivery(t *testing.T) {
|
||||
c := 1
|
||||
for {
|
||||
//fmt.Printf("getting headers from %d\n", c)
|
||||
hdrs := world.headers(c)
|
||||
l := len(hdrs)
|
||||
headers := world.headers(c)
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
l := len(headers)
|
||||
//fmt.Printf("scheduling %d headers, first %d last %d\n",
|
||||
// l, hdrs[0].Number.Uint64(), hdrs[len(hdrs)-1].Number.Uint64())
|
||||
q.Schedule(hdrs, uint64(c))
|
||||
// l, headers[0].Number.Uint64(), headers[len(headers)-1].Number.Uint64())
|
||||
q.Schedule(headers, hashes, uint64(c))
|
||||
c += l
|
||||
}
|
||||
}()
|
||||
@@ -311,18 +327,31 @@ func XTestDelivery(t *testing.T) {
|
||||
peer := dummyPeer(fmt.Sprintf("peer-%d", i))
|
||||
f, _, _ := q.ReserveBodies(peer, rand.Intn(30))
|
||||
if f != nil {
|
||||
var emptyList []*types.Header
|
||||
var txs [][]*types.Transaction
|
||||
var uncles [][]*types.Header
|
||||
var (
|
||||
emptyList []*types.Header
|
||||
txset [][]*types.Transaction
|
||||
uncleset [][]*types.Header
|
||||
)
|
||||
numToSkip := rand.Intn(len(f.Headers))
|
||||
for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] {
|
||||
txs = append(txs, world.getTransactions(hdr.Number.Uint64()))
|
||||
uncles = append(uncles, emptyList)
|
||||
txset = append(txset, world.getTransactions(hdr.Number.Uint64()))
|
||||
uncleset = append(uncleset, emptyList)
|
||||
}
|
||||
var (
|
||||
txsHashes = make([]common.Hash, len(txset))
|
||||
uncleHashes = make([]common.Hash, len(uncleset))
|
||||
)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
for i, txs := range txset {
|
||||
txsHashes[i] = types.DeriveSha(types.Transactions(txs), hasher)
|
||||
}
|
||||
for i, uncles := range uncleset {
|
||||
uncleHashes[i] = types.CalcUncleHash(uncles)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, err := q.DeliverBodies(peer.id, txs, uncles)
|
||||
_, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes)
|
||||
if err != nil {
|
||||
fmt.Printf("delivered %d bodies %v\n", len(txs), err)
|
||||
fmt.Printf("delivered %d bodies %v\n", len(txset), err)
|
||||
}
|
||||
} else {
|
||||
i++
|
||||
@@ -341,7 +370,12 @@ func XTestDelivery(t *testing.T) {
|
||||
for _, hdr := range f.Headers {
|
||||
rcs = append(rcs, world.getReceipts(hdr.Number.Uint64()))
|
||||
}
|
||||
_, err := q.DeliverReceipts(peer.id, rcs)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
hashes := make([]common.Hash, len(rcs))
|
||||
for i, receipt := range rcs {
|
||||
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
|
||||
}
|
||||
_, err := q.DeliverReceipts(peer.id, rcs, hashes)
|
||||
if err != nil {
|
||||
fmt.Printf("delivered %d receipts %v\n", len(rcs), err)
|
||||
}
|
||||
|
||||
+13
-505
@@ -17,48 +17,12 @@
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// stateReq represents a batch of state fetch requests grouped together into
|
||||
// a single data retrieval network packet.
|
||||
type stateReq struct {
|
||||
nItems uint16 // Number of items requested for download (max is 384, so uint16 is sufficient)
|
||||
trieTasks map[common.Hash]*trieTask // Trie node download tasks to track previous attempts
|
||||
codeTasks map[common.Hash]*codeTask // Byte code download tasks to track previous attempts
|
||||
timeout time.Duration // Maximum round trip time for this to complete
|
||||
timer *time.Timer // Timer to fire when the RTT timeout expires
|
||||
peer *peerConnection // Peer that we're requesting from
|
||||
delivered time.Time // Time when the packet was delivered (independent when we process it)
|
||||
response [][]byte // Response data of the peer (nil for timeouts)
|
||||
dropped bool // Flag whether the peer dropped off early
|
||||
}
|
||||
|
||||
// timedOut returns if this request timed out.
|
||||
func (req *stateReq) timedOut() bool {
|
||||
return req.response == nil
|
||||
}
|
||||
|
||||
// stateSyncStats is a collection of progress stats to report during a state trie
|
||||
// sync to RPC requests as well as to display in user logs.
|
||||
type stateSyncStats struct {
|
||||
processed uint64 // Number of state entries processed
|
||||
duplicate uint64 // Number of state entries downloaded twice
|
||||
unexpected uint64 // Number of non-requested state entries received
|
||||
pending uint64 // Number of still pending state entries
|
||||
}
|
||||
|
||||
// syncState starts downloading state with the given root hash.
|
||||
func (d *Downloader) syncState(root common.Hash) *stateSync {
|
||||
// Create the state sync
|
||||
@@ -85,8 +49,6 @@ func (d *Downloader) stateFetcher() {
|
||||
for next := s; next != nil; {
|
||||
next = d.runStateSync(next)
|
||||
}
|
||||
case <-d.stateCh:
|
||||
// Ignore state responses while no sync is running.
|
||||
case <-d.quitCh:
|
||||
return
|
||||
}
|
||||
@@ -96,216 +58,44 @@ func (d *Downloader) stateFetcher() {
|
||||
// runStateSync runs a state synchronisation until it completes or another root
|
||||
// hash is requested to be switched over to.
|
||||
func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
||||
var (
|
||||
active = make(map[string]*stateReq) // Currently in-flight requests
|
||||
finished []*stateReq // Completed or failed requests
|
||||
timeout = make(chan *stateReq) // Timed out active requests
|
||||
)
|
||||
log.Trace("State sync starting", "root", s.root)
|
||||
|
||||
defer func() {
|
||||
// Cancel active request timers on exit. Also set peers to idle so they're
|
||||
// available for the next sync.
|
||||
for _, req := range active {
|
||||
req.timer.Stop()
|
||||
req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
|
||||
}
|
||||
}()
|
||||
go s.run()
|
||||
defer s.Cancel()
|
||||
|
||||
// Listen for peer departure events to cancel assigned tasks
|
||||
peerDrop := make(chan *peerConnection, 1024)
|
||||
peerSub := s.d.peers.SubscribePeerDrops(peerDrop)
|
||||
defer peerSub.Unsubscribe()
|
||||
|
||||
for {
|
||||
// Enable sending of the first buffered element if there is one.
|
||||
var (
|
||||
deliverReq *stateReq
|
||||
deliverReqCh chan *stateReq
|
||||
)
|
||||
if len(finished) > 0 {
|
||||
deliverReq = finished[0]
|
||||
deliverReqCh = s.deliver
|
||||
}
|
||||
|
||||
select {
|
||||
// The stateSync lifecycle:
|
||||
case next := <-d.stateSyncStart:
|
||||
d.spindownStateSync(active, finished, timeout, peerDrop)
|
||||
return next
|
||||
|
||||
case <-s.done:
|
||||
d.spindownStateSync(active, finished, timeout, peerDrop)
|
||||
return nil
|
||||
|
||||
// Send the next finished request to the current sync:
|
||||
case deliverReqCh <- deliverReq:
|
||||
// Shift out the first request, but also set the emptied slot to nil for GC
|
||||
copy(finished, finished[1:])
|
||||
finished[len(finished)-1] = nil
|
||||
finished = finished[:len(finished)-1]
|
||||
|
||||
// Handle incoming state packs:
|
||||
case pack := <-d.stateCh:
|
||||
// Discard any data not requested (or previously timed out)
|
||||
req := active[pack.PeerId()]
|
||||
if req == nil {
|
||||
log.Debug("Unrequested node data", "peer", pack.PeerId(), "len", pack.Items())
|
||||
continue
|
||||
}
|
||||
// Finalize the request and queue up for processing
|
||||
req.timer.Stop()
|
||||
req.response = pack.(*statePack).states
|
||||
req.delivered = time.Now()
|
||||
|
||||
finished = append(finished, req)
|
||||
delete(active, pack.PeerId())
|
||||
|
||||
// Handle dropped peer connections:
|
||||
case p := <-peerDrop:
|
||||
// Skip if no request is currently pending
|
||||
req := active[p.id]
|
||||
if req == nil {
|
||||
continue
|
||||
}
|
||||
// Finalize the request and queue up for processing
|
||||
req.timer.Stop()
|
||||
req.dropped = true
|
||||
req.delivered = time.Now()
|
||||
|
||||
finished = append(finished, req)
|
||||
delete(active, p.id)
|
||||
|
||||
// Handle timed-out requests:
|
||||
case req := <-timeout:
|
||||
// If the peer is already requesting something else, ignore the stale timeout.
|
||||
// This can happen when the timeout and the delivery happens simultaneously,
|
||||
// causing both pathways to trigger.
|
||||
if active[req.peer.id] != req {
|
||||
continue
|
||||
}
|
||||
req.delivered = time.Now()
|
||||
// Move the timed out data back into the download queue
|
||||
finished = append(finished, req)
|
||||
delete(active, req.peer.id)
|
||||
|
||||
// Track outgoing state requests:
|
||||
case req := <-d.trackStateReq:
|
||||
// If an active request already exists for this peer, we have a problem. In
|
||||
// theory the trie node schedule must never assign two requests to the same
|
||||
// peer. In practice however, a peer might receive a request, disconnect and
|
||||
// immediately reconnect before the previous times out. In this case the first
|
||||
// request is never honored, alas we must not silently overwrite it, as that
|
||||
// causes valid requests to go missing and sync to get stuck.
|
||||
if old := active[req.peer.id]; old != nil {
|
||||
log.Warn("Busy peer assigned new state fetch", "peer", old.peer.id)
|
||||
// Move the previous request to the finished set
|
||||
old.timer.Stop()
|
||||
old.dropped = true
|
||||
old.delivered = time.Now()
|
||||
finished = append(finished, old)
|
||||
}
|
||||
// Start a timer to notify the sync loop if the peer stalled.
|
||||
req.timer = time.AfterFunc(req.timeout, func() {
|
||||
timeout <- req
|
||||
})
|
||||
active[req.peer.id] = req
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spindownStateSync 'drains' the outstanding requests; some will be delivered and other
|
||||
// will time out. This is to ensure that when the next stateSync starts working, all peers
|
||||
// are marked as idle and de facto _are_ idle.
|
||||
func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) {
|
||||
log.Trace("State sync spinning down", "active", len(active), "finished", len(finished))
|
||||
for len(active) > 0 {
|
||||
var (
|
||||
req *stateReq
|
||||
reason string
|
||||
)
|
||||
select {
|
||||
// Handle (drop) incoming state packs:
|
||||
case pack := <-d.stateCh:
|
||||
req = active[pack.PeerId()]
|
||||
reason = "delivered"
|
||||
// Handle dropped peer connections:
|
||||
case p := <-peerDrop:
|
||||
req = active[p.id]
|
||||
reason = "peerdrop"
|
||||
// Handle timed-out requests:
|
||||
case req = <-timeout:
|
||||
reason = "timeout"
|
||||
}
|
||||
if req == nil {
|
||||
continue
|
||||
}
|
||||
req.peer.log.Trace("State peer marked idle (spindown)", "req.items", int(req.nItems), "reason", reason)
|
||||
req.timer.Stop()
|
||||
delete(active, req.peer.id)
|
||||
req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
|
||||
}
|
||||
// The 'finished' set contains deliveries that we were going to pass to processing.
|
||||
// Those are now moot, but we still need to set those peers as idle, which would
|
||||
// otherwise have been done after processing
|
||||
for _, req := range finished {
|
||||
req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
// stateSync schedules requests for downloading a particular state trie defined
|
||||
// by a given state root.
|
||||
type stateSync struct {
|
||||
d *Downloader // Downloader instance to access and manage current peerset
|
||||
d *Downloader // Downloader instance to access and manage current peerset
|
||||
root common.Hash // State root currently being synced
|
||||
|
||||
root common.Hash // State root currently being synced
|
||||
sched *trie.Sync // State trie sync scheduler defining the tasks
|
||||
keccak crypto.KeccakState // Keccak256 hasher to verify deliveries with
|
||||
|
||||
trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval
|
||||
codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval
|
||||
|
||||
numUncommitted int
|
||||
bytesUncommitted int
|
||||
|
||||
started chan struct{} // Started is signalled once the sync loop starts
|
||||
|
||||
deliver chan *stateReq // Delivery channel multiplexing peer responses
|
||||
cancel chan struct{} // Channel to signal a termination request
|
||||
cancelOnce sync.Once // Ensures cancel only ever gets called once
|
||||
done chan struct{} // Channel to signal termination completion
|
||||
err error // Any error hit during sync (set before completion)
|
||||
}
|
||||
|
||||
// trieTask represents a single trie node download task, containing a set of
|
||||
// peers already attempted retrieval from to detect stalled syncs and abort.
|
||||
type trieTask struct {
|
||||
path [][]byte
|
||||
attempts map[string]struct{}
|
||||
}
|
||||
|
||||
// codeTask represents a single byte code download task, containing a set of
|
||||
// peers already attempted retrieval from to detect stalled syncs and abort.
|
||||
type codeTask struct {
|
||||
attempts map[string]struct{}
|
||||
started chan struct{} // Started is signalled once the sync loop starts
|
||||
cancel chan struct{} // Channel to signal a termination request
|
||||
cancelOnce sync.Once // Ensures cancel only ever gets called once
|
||||
done chan struct{} // Channel to signal termination completion
|
||||
err error // Any error hit during sync (set before completion)
|
||||
}
|
||||
|
||||
// newStateSync creates a new state trie download scheduler. This method does not
|
||||
// yet start the sync. The user needs to call run to initiate.
|
||||
func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
return &stateSync{
|
||||
d: d,
|
||||
root: root,
|
||||
sched: state.NewStateSync(root, d.stateDB, d.stateBloom, nil),
|
||||
keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState),
|
||||
trieTasks: make(map[common.Hash]*trieTask),
|
||||
codeTasks: make(map[common.Hash]*codeTask),
|
||||
deliver: make(chan *stateReq),
|
||||
cancel: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
d: d,
|
||||
root: root,
|
||||
cancel: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,11 +104,7 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
// finish.
|
||||
func (s *stateSync) run() {
|
||||
close(s.started)
|
||||
if s.d.snapSync {
|
||||
s.err = s.d.SnapSyncer.Sync(s.root, s.cancel)
|
||||
} else {
|
||||
s.err = s.loop()
|
||||
}
|
||||
s.err = s.d.SnapSyncer.Sync(s.root, s.cancel)
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
@@ -335,281 +121,3 @@ func (s *stateSync) Cancel() error {
|
||||
})
|
||||
return s.Wait()
|
||||
}
|
||||
|
||||
// loop is the main event loop of a state trie sync. It it responsible for the
|
||||
// assignment of new tasks to peers (including sending it to them) as well as
|
||||
// for the processing of inbound data. Note, that the loop does not directly
|
||||
// receive data from peers, rather those are buffered up in the downloader and
|
||||
// pushed here async. The reason is to decouple processing from data receipt
|
||||
// and timeouts.
|
||||
func (s *stateSync) loop() (err error) {
|
||||
// Listen for new peer events to assign tasks to them
|
||||
newPeer := make(chan *peerConnection, 1024)
|
||||
peerSub := s.d.peers.SubscribeNewPeers(newPeer)
|
||||
defer peerSub.Unsubscribe()
|
||||
defer func() {
|
||||
cerr := s.commit(true)
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
// Keep assigning new tasks until the sync completes or aborts
|
||||
for s.sched.Pending() > 0 {
|
||||
if err = s.commit(false); err != nil {
|
||||
return err
|
||||
}
|
||||
s.assignTasks()
|
||||
// Tasks assigned, wait for something to happen
|
||||
select {
|
||||
case <-newPeer:
|
||||
// New peer arrived, try to assign it download tasks
|
||||
|
||||
case <-s.cancel:
|
||||
return errCancelStateFetch
|
||||
|
||||
case <-s.d.cancelCh:
|
||||
return errCanceled
|
||||
|
||||
case req := <-s.deliver:
|
||||
// Response, disconnect or timeout triggered, drop the peer if stalling
|
||||
log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
|
||||
if req.nItems <= 2 && !req.dropped && req.timedOut() {
|
||||
// 2 items are the minimum requested, if even that times out, we've no use of
|
||||
// this peer at the moment.
|
||||
log.Warn("Stalling state sync, dropping peer", "peer", req.peer.id)
|
||||
if s.d.dropPeer == nil {
|
||||
// The dropPeer method is nil when `--copydb` is used for a local copy.
|
||||
// Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored
|
||||
req.peer.log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", req.peer.id)
|
||||
} else {
|
||||
s.d.dropPeer(req.peer.id)
|
||||
|
||||
// If this peer was the master peer, abort sync immediately
|
||||
s.d.cancelLock.RLock()
|
||||
master := req.peer.id == s.d.cancelPeer
|
||||
s.d.cancelLock.RUnlock()
|
||||
|
||||
if master {
|
||||
s.d.cancel()
|
||||
return errTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process all the received blobs and check for stale delivery
|
||||
delivered, err := s.process(req)
|
||||
req.peer.SetNodeDataIdle(delivered, req.delivered)
|
||||
if err != nil {
|
||||
log.Warn("Node data write error", "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stateSync) commit(force bool) error {
|
||||
if !force && s.bytesUncommitted < ethdb.IdealBatchSize {
|
||||
return nil
|
||||
}
|
||||
start := time.Now()
|
||||
b := s.d.stateDB.NewBatch()
|
||||
if err := s.sched.Commit(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Write(); err != nil {
|
||||
return fmt.Errorf("DB write error: %v", err)
|
||||
}
|
||||
s.updateStats(s.numUncommitted, 0, 0, time.Since(start))
|
||||
s.numUncommitted = 0
|
||||
s.bytesUncommitted = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// assignTasks attempts to assign new tasks to all idle peers, either from the
|
||||
// batch currently being retried, or fetching new data from the trie sync itself.
|
||||
func (s *stateSync) assignTasks() {
|
||||
// Iterate over all idle peers and try to assign them state fetches
|
||||
peers, _ := s.d.peers.NodeDataIdlePeers()
|
||||
for _, p := range peers {
|
||||
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
||||
cap := p.NodeDataCapacity(s.d.peers.rates.TargetRoundTrip())
|
||||
req := &stateReq{peer: p, timeout: s.d.peers.rates.TargetTimeout()}
|
||||
|
||||
nodes, _, codes := s.fillTasks(cap, req)
|
||||
|
||||
// If the peer was assigned tasks to fetch, send the network request
|
||||
if len(nodes)+len(codes) > 0 {
|
||||
req.peer.log.Trace("Requesting batch of state data", "nodes", len(nodes), "codes", len(codes), "root", s.root)
|
||||
select {
|
||||
case s.d.trackStateReq <- req:
|
||||
req.peer.FetchNodeData(append(nodes, codes...)) // Unified retrieval under eth/6x
|
||||
case <-s.cancel:
|
||||
case <-s.d.cancelCh:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillTasks fills the given request object with a maximum of n state download
|
||||
// tasks to send to the remote peer.
|
||||
func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths []trie.SyncPath, codes []common.Hash) {
|
||||
// Refill available tasks from the scheduler.
|
||||
if fill := n - (len(s.trieTasks) + len(s.codeTasks)); fill > 0 {
|
||||
nodes, paths, codes := s.sched.Missing(fill)
|
||||
for i, hash := range nodes {
|
||||
s.trieTasks[hash] = &trieTask{
|
||||
path: paths[i],
|
||||
attempts: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
for _, hash := range codes {
|
||||
s.codeTasks[hash] = &codeTask{
|
||||
attempts: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find tasks that haven't been tried with the request's peer. Prefer code
|
||||
// over trie nodes as those can be written to disk and forgotten about.
|
||||
nodes = make([]common.Hash, 0, n)
|
||||
paths = make([]trie.SyncPath, 0, n)
|
||||
codes = make([]common.Hash, 0, n)
|
||||
|
||||
req.trieTasks = make(map[common.Hash]*trieTask, n)
|
||||
req.codeTasks = make(map[common.Hash]*codeTask, n)
|
||||
|
||||
for hash, t := range s.codeTasks {
|
||||
// Stop when we've gathered enough requests
|
||||
if len(nodes)+len(codes) == n {
|
||||
break
|
||||
}
|
||||
// Skip any requests we've already tried from this peer
|
||||
if _, ok := t.attempts[req.peer.id]; ok {
|
||||
continue
|
||||
}
|
||||
// Assign the request to this peer
|
||||
t.attempts[req.peer.id] = struct{}{}
|
||||
codes = append(codes, hash)
|
||||
req.codeTasks[hash] = t
|
||||
delete(s.codeTasks, hash)
|
||||
}
|
||||
for hash, t := range s.trieTasks {
|
||||
// Stop when we've gathered enough requests
|
||||
if len(nodes)+len(codes) == n {
|
||||
break
|
||||
}
|
||||
// Skip any requests we've already tried from this peer
|
||||
if _, ok := t.attempts[req.peer.id]; ok {
|
||||
continue
|
||||
}
|
||||
// Assign the request to this peer
|
||||
t.attempts[req.peer.id] = struct{}{}
|
||||
|
||||
nodes = append(nodes, hash)
|
||||
paths = append(paths, t.path)
|
||||
|
||||
req.trieTasks[hash] = t
|
||||
delete(s.trieTasks, hash)
|
||||
}
|
||||
req.nItems = uint16(len(nodes) + len(codes))
|
||||
return nodes, paths, codes
|
||||
}
|
||||
|
||||
// process iterates over a batch of delivered state data, injecting each item
|
||||
// into a running state sync, re-queuing any items that were requested but not
|
||||
// delivered. Returns whether the peer actually managed to deliver anything of
|
||||
// value, and any error that occurred.
|
||||
func (s *stateSync) process(req *stateReq) (int, error) {
|
||||
// Collect processing stats and update progress if valid data was received
|
||||
duplicate, unexpected, successful := 0, 0, 0
|
||||
|
||||
defer func(start time.Time) {
|
||||
if duplicate > 0 || unexpected > 0 {
|
||||
s.updateStats(0, duplicate, unexpected, time.Since(start))
|
||||
}
|
||||
}(time.Now())
|
||||
|
||||
// Iterate over all the delivered data and inject one-by-one into the trie
|
||||
for _, blob := range req.response {
|
||||
hash, err := s.processNodeData(blob)
|
||||
switch err {
|
||||
case nil:
|
||||
s.numUncommitted++
|
||||
s.bytesUncommitted += len(blob)
|
||||
successful++
|
||||
case trie.ErrNotRequested:
|
||||
unexpected++
|
||||
case trie.ErrAlreadyProcessed:
|
||||
duplicate++
|
||||
default:
|
||||
return successful, fmt.Errorf("invalid state node %s: %v", hash.TerminalString(), err)
|
||||
}
|
||||
// Delete from both queues (one delivery is enough for the syncer)
|
||||
delete(req.trieTasks, hash)
|
||||
delete(req.codeTasks, hash)
|
||||
}
|
||||
// Put unfulfilled tasks back into the retry queue
|
||||
npeers := s.d.peers.Len()
|
||||
for hash, task := range req.trieTasks {
|
||||
// If the node did deliver something, missing items may be due to a protocol
|
||||
// limit or a previous timeout + delayed delivery. Both cases should permit
|
||||
// the node to retry the missing items (to avoid single-peer stalls).
|
||||
if len(req.response) > 0 || req.timedOut() {
|
||||
delete(task.attempts, req.peer.id)
|
||||
}
|
||||
// If we've requested the node too many times already, it may be a malicious
|
||||
// sync where nobody has the right data. Abort.
|
||||
if len(task.attempts) >= npeers {
|
||||
return successful, fmt.Errorf("trie node %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
|
||||
}
|
||||
// Missing item, place into the retry queue.
|
||||
s.trieTasks[hash] = task
|
||||
}
|
||||
for hash, task := range req.codeTasks {
|
||||
// If the node did deliver something, missing items may be due to a protocol
|
||||
// limit or a previous timeout + delayed delivery. Both cases should permit
|
||||
// the node to retry the missing items (to avoid single-peer stalls).
|
||||
if len(req.response) > 0 || req.timedOut() {
|
||||
delete(task.attempts, req.peer.id)
|
||||
}
|
||||
// If we've requested the node too many times already, it may be a malicious
|
||||
// sync where nobody has the right data. Abort.
|
||||
if len(task.attempts) >= npeers {
|
||||
return successful, fmt.Errorf("byte code %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
|
||||
}
|
||||
// Missing item, place into the retry queue.
|
||||
s.codeTasks[hash] = task
|
||||
}
|
||||
return successful, nil
|
||||
}
|
||||
|
||||
// processNodeData tries to inject a trie node data blob delivered from a remote
|
||||
// peer into the state trie, returning whether anything useful was written or any
|
||||
// error occurred.
|
||||
func (s *stateSync) processNodeData(blob []byte) (common.Hash, error) {
|
||||
res := trie.SyncResult{Data: blob}
|
||||
s.keccak.Reset()
|
||||
s.keccak.Write(blob)
|
||||
s.keccak.Read(res.Hash[:])
|
||||
err := s.sched.Process(res)
|
||||
return res.Hash, err
|
||||
}
|
||||
|
||||
// updateStats bumps the various state sync progress counters and displays a log
|
||||
// message for the user to see.
|
||||
func (s *stateSync) updateStats(written, duplicate, unexpected int, duration time.Duration) {
|
||||
s.d.syncStatsLock.Lock()
|
||||
defer s.d.syncStatsLock.Unlock()
|
||||
|
||||
s.d.syncStatsState.pending = uint64(s.sched.Pending())
|
||||
s.d.syncStatsState.processed += uint64(written)
|
||||
s.d.syncStatsState.duplicate += uint64(duplicate)
|
||||
s.d.syncStatsState.unexpected += uint64(unexpected)
|
||||
|
||||
if written > 0 || duplicate > 0 || unexpected > 0 {
|
||||
log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "trieretry", len(s.trieTasks), "coderetry", len(s.codeTasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected)
|
||||
}
|
||||
if written > 0 {
|
||||
rawdb.WriteFastTrieProgress(s.d.stateDB, s.d.syncStatsState.processed)
|
||||
}
|
||||
}
|
||||
|
||||
+112
-115
@@ -20,12 +20,14 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
@@ -39,73 +41,110 @@ var (
|
||||
)
|
||||
|
||||
// The common prefix of all test chains:
|
||||
var testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis)
|
||||
var testChainBase *testChain
|
||||
|
||||
// Different forks on top of the base chain:
|
||||
var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain
|
||||
|
||||
var pregenerated bool
|
||||
|
||||
func init() {
|
||||
// Reduce some of the parameters to make the tester faster
|
||||
fullMaxForkAncestry = 10000
|
||||
lightMaxForkAncestry = 10000
|
||||
blockCacheMaxItems = 1024
|
||||
fsHeaderSafetyNet = 256
|
||||
fsHeaderContCheck = 500 * time.Millisecond
|
||||
|
||||
testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis)
|
||||
|
||||
var forkLen = int(fullMaxForkAncestry + 50)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Generate the test chains to seed the peers with
|
||||
wg.Add(3)
|
||||
go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }()
|
||||
go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }()
|
||||
go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }()
|
||||
wg.Wait()
|
||||
|
||||
// Generate the test peers used by the tests to avoid overloading during testing.
|
||||
// These seemingly random chains are used in various downloader tests. We're just
|
||||
// pre-generating them here.
|
||||
chains := []*testChain{
|
||||
testChainBase,
|
||||
testChainForkLightA,
|
||||
testChainForkLightB,
|
||||
testChainForkHeavy,
|
||||
testChainBase.shorten(1),
|
||||
testChainBase.shorten(blockCacheMaxItems - 15),
|
||||
testChainBase.shorten((blockCacheMaxItems - 15) / 2),
|
||||
testChainBase.shorten(blockCacheMaxItems - 15 - 5),
|
||||
testChainBase.shorten(MaxHeaderFetch),
|
||||
testChainBase.shorten(800),
|
||||
testChainBase.shorten(800 / 2),
|
||||
testChainBase.shorten(800 / 3),
|
||||
testChainBase.shorten(800 / 4),
|
||||
testChainBase.shorten(800 / 5),
|
||||
testChainBase.shorten(800 / 6),
|
||||
testChainBase.shorten(800 / 7),
|
||||
testChainBase.shorten(800 / 8),
|
||||
testChainBase.shorten(3*fsHeaderSafetyNet + 256 + fsMinFullBlocks),
|
||||
testChainBase.shorten(fsMinFullBlocks + 256 - 1),
|
||||
testChainForkLightA.shorten(len(testChainBase.blocks) + 80),
|
||||
testChainForkLightB.shorten(len(testChainBase.blocks) + 81),
|
||||
testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch),
|
||||
testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch),
|
||||
testChainForkHeavy.shorten(len(testChainBase.blocks) + 79),
|
||||
}
|
||||
wg.Add(len(chains))
|
||||
for _, chain := range chains {
|
||||
go func(blocks []*types.Block) {
|
||||
newTestBlockchain(blocks)
|
||||
wg.Done()
|
||||
}(chain.blocks[1:])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Mark the chains pregenerated. Generating a new one will lead to a panic.
|
||||
pregenerated = true
|
||||
}
|
||||
|
||||
type testChain struct {
|
||||
genesis *types.Block
|
||||
chain []common.Hash
|
||||
headerm map[common.Hash]*types.Header
|
||||
blockm map[common.Hash]*types.Block
|
||||
receiptm map[common.Hash][]*types.Receipt
|
||||
tdm map[common.Hash]*big.Int
|
||||
blocks []*types.Block
|
||||
}
|
||||
|
||||
// newTestChain creates a blockchain of the given length.
|
||||
func newTestChain(length int, genesis *types.Block) *testChain {
|
||||
tc := new(testChain).copy(length)
|
||||
tc.genesis = genesis
|
||||
tc.chain = append(tc.chain, genesis.Hash())
|
||||
tc.headerm[tc.genesis.Hash()] = tc.genesis.Header()
|
||||
tc.tdm[tc.genesis.Hash()] = tc.genesis.Difficulty()
|
||||
tc.blockm[tc.genesis.Hash()] = tc.genesis
|
||||
tc := &testChain{
|
||||
blocks: []*types.Block{genesis},
|
||||
}
|
||||
tc.generate(length-1, 0, genesis, false)
|
||||
return tc
|
||||
}
|
||||
|
||||
// makeFork creates a fork on top of the test chain.
|
||||
func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain {
|
||||
fork := tc.copy(tc.len() + length)
|
||||
fork.generate(length, seed, tc.headBlock(), heavy)
|
||||
fork := tc.copy(len(tc.blocks) + length)
|
||||
fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy)
|
||||
return fork
|
||||
}
|
||||
|
||||
// shorten creates a copy of the chain with the given length. It panics if the
|
||||
// length is longer than the number of available blocks.
|
||||
func (tc *testChain) shorten(length int) *testChain {
|
||||
if length > tc.len() {
|
||||
panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, tc.len()))
|
||||
if length > len(tc.blocks) {
|
||||
panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks)))
|
||||
}
|
||||
return tc.copy(length)
|
||||
}
|
||||
|
||||
func (tc *testChain) copy(newlen int) *testChain {
|
||||
cpy := &testChain{
|
||||
genesis: tc.genesis,
|
||||
headerm: make(map[common.Hash]*types.Header, newlen),
|
||||
blockm: make(map[common.Hash]*types.Block, newlen),
|
||||
receiptm: make(map[common.Hash][]*types.Receipt, newlen),
|
||||
tdm: make(map[common.Hash]*big.Int, newlen),
|
||||
if newlen > len(tc.blocks) {
|
||||
newlen = len(tc.blocks)
|
||||
}
|
||||
for i := 0; i < len(tc.chain) && i < newlen; i++ {
|
||||
hash := tc.chain[i]
|
||||
cpy.chain = append(cpy.chain, tc.chain[i])
|
||||
cpy.tdm[hash] = tc.tdm[hash]
|
||||
cpy.blockm[hash] = tc.blockm[hash]
|
||||
cpy.headerm[hash] = tc.headerm[hash]
|
||||
cpy.receiptm[hash] = tc.receiptm[hash]
|
||||
cpy := &testChain{
|
||||
blocks: append([]*types.Block{}, tc.blocks[:newlen]...),
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
@@ -115,17 +154,14 @@ func (tc *testChain) copy(newlen int) *testChain {
|
||||
// contains a transaction and every 5th an uncle to allow testing correct block
|
||||
// reassembly.
|
||||
func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) {
|
||||
// start := time.Now()
|
||||
// defer func() { fmt.Printf("test chain generated in %v\n", time.Since(start)) }()
|
||||
|
||||
blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) {
|
||||
blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) {
|
||||
block.SetCoinbase(common.Address{seed})
|
||||
// If a heavy chain is requested, delay blocks to raise difficulty
|
||||
if heavy {
|
||||
block.OffsetTime(-1)
|
||||
block.OffsetTime(-9)
|
||||
}
|
||||
// Include transactions to the miner to make blocks more interesting.
|
||||
if parent == tc.genesis && i%22 == 0 {
|
||||
if parent == tc.blocks[0] && i%22 == 0 {
|
||||
signer := types.MakeSigner(params.TestChainConfig, block.Number())
|
||||
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
|
||||
if err != nil {
|
||||
@@ -136,95 +172,56 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool)
|
||||
// if the block number is a multiple of 5, add a bonus uncle to the block
|
||||
if i > 0 && i%5 == 0 {
|
||||
block.AddUncle(&types.Header{
|
||||
ParentHash: block.PrevBlock(i - 1).Hash(),
|
||||
ParentHash: block.PrevBlock(i - 2).Hash(),
|
||||
Number: big.NewInt(block.Number().Int64() - 1),
|
||||
})
|
||||
}
|
||||
})
|
||||
tc.blocks = append(tc.blocks, blocks...)
|
||||
}
|
||||
|
||||
// Convert the block-chain into a hash-chain and header/block maps
|
||||
td := new(big.Int).Set(tc.td(parent.Hash()))
|
||||
for i, b := range blocks {
|
||||
td := td.Add(td, b.Difficulty())
|
||||
hash := b.Hash()
|
||||
tc.chain = append(tc.chain, hash)
|
||||
tc.blockm[hash] = b
|
||||
tc.headerm[hash] = b.Header()
|
||||
tc.receiptm[hash] = receipts[i]
|
||||
tc.tdm[hash] = new(big.Int).Set(td)
|
||||
var (
|
||||
testBlockchains = make(map[common.Hash]*testBlockchain)
|
||||
testBlockchainsLock sync.Mutex
|
||||
)
|
||||
|
||||
type testBlockchain struct {
|
||||
chain *core.BlockChain
|
||||
gen sync.Once
|
||||
}
|
||||
|
||||
// newTestBlockchain creates a blockchain database built by running the given blocks,
|
||||
// either actually running them, or reusing a previously created one. The returned
|
||||
// chains are *shared*, so *do not* mutate them.
|
||||
func newTestBlockchain(blocks []*types.Block) *core.BlockChain {
|
||||
// Retrieve an existing database, or create a new one
|
||||
head := testGenesis.Hash()
|
||||
if len(blocks) > 0 {
|
||||
head = blocks[len(blocks)-1].Hash()
|
||||
}
|
||||
}
|
||||
|
||||
// len returns the total number of blocks in the chain.
|
||||
func (tc *testChain) len() int {
|
||||
return len(tc.chain)
|
||||
}
|
||||
|
||||
// headBlock returns the head of the chain.
|
||||
func (tc *testChain) headBlock() *types.Block {
|
||||
return tc.blockm[tc.chain[len(tc.chain)-1]]
|
||||
}
|
||||
|
||||
// td returns the total difficulty of the given block.
|
||||
func (tc *testChain) td(hash common.Hash) *big.Int {
|
||||
return tc.tdm[hash]
|
||||
}
|
||||
|
||||
// headersByHash returns headers in order from the given hash.
|
||||
func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int, reverse bool) []*types.Header {
|
||||
num, _ := tc.hashToNumber(origin)
|
||||
return tc.headersByNumber(num, amount, skip, reverse)
|
||||
}
|
||||
|
||||
// headersByNumber returns headers from the given number.
|
||||
func (tc *testChain) headersByNumber(origin uint64, amount int, skip int, reverse bool) []*types.Header {
|
||||
result := make([]*types.Header, 0, amount)
|
||||
|
||||
if !reverse {
|
||||
for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 {
|
||||
if header, ok := tc.headerm[tc.chain[int(num)]]; ok {
|
||||
result = append(result, header)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for num := int64(origin); num >= 0 && len(result) < amount; num -= int64(skip) + 1 {
|
||||
if header, ok := tc.headerm[tc.chain[int(num)]]; ok {
|
||||
result = append(result, header)
|
||||
}
|
||||
}
|
||||
testBlockchainsLock.Lock()
|
||||
if _, ok := testBlockchains[head]; !ok {
|
||||
testBlockchains[head] = new(testBlockchain)
|
||||
}
|
||||
return result
|
||||
}
|
||||
tbc := testBlockchains[head]
|
||||
testBlockchainsLock.Unlock()
|
||||
|
||||
// receipts returns the receipts of the given block hashes.
|
||||
func (tc *testChain) receipts(hashes []common.Hash) [][]*types.Receipt {
|
||||
results := make([][]*types.Receipt, 0, len(hashes))
|
||||
for _, hash := range hashes {
|
||||
if receipt, ok := tc.receiptm[hash]; ok {
|
||||
results = append(results, receipt)
|
||||
// Ensure that the database is generated
|
||||
tbc.gen.Do(func() {
|
||||
if pregenerated {
|
||||
panic("Requested chain generation outside of init")
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
core.GenesisBlockForTesting(db, testAddress, big.NewInt(1000000000000000))
|
||||
|
||||
// bodies returns the block bodies of the given block hashes.
|
||||
func (tc *testChain) bodies(hashes []common.Hash) ([][]*types.Transaction, [][]*types.Header) {
|
||||
transactions := make([][]*types.Transaction, 0, len(hashes))
|
||||
uncles := make([][]*types.Header, 0, len(hashes))
|
||||
for _, hash := range hashes {
|
||||
if block, ok := tc.blockm[hash]; ok {
|
||||
transactions = append(transactions, block.Transactions())
|
||||
uncles = append(uncles, block.Uncles())
|
||||
chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return transactions, uncles
|
||||
}
|
||||
|
||||
func (tc *testChain) hashToNumber(target common.Hash) (uint64, bool) {
|
||||
for num, hash := range tc.chain {
|
||||
if hash == target {
|
||||
return uint64(num), true
|
||||
if n, err := chain.InsertChain(blocks); err != nil {
|
||||
panic(fmt.Sprintf("block %d: %v", n, err))
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
tbc.chain = chain
|
||||
})
|
||||
return tbc.chain
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
||||
type peerDropFn func(id string)
|
||||
|
||||
// dataPack is a data message returned by a peer for some query.
|
||||
type dataPack interface {
|
||||
PeerId() string
|
||||
Items() int
|
||||
Stats() string
|
||||
}
|
||||
|
||||
// headerPack is a batch of block headers returned by a peer.
|
||||
type headerPack struct {
|
||||
peerID string
|
||||
headers []*types.Header
|
||||
}
|
||||
|
||||
func (p *headerPack) PeerId() string { return p.peerID }
|
||||
func (p *headerPack) Items() int { return len(p.headers) }
|
||||
func (p *headerPack) Stats() string { return fmt.Sprintf("%d", len(p.headers)) }
|
||||
|
||||
// bodyPack is a batch of block bodies returned by a peer.
|
||||
type bodyPack struct {
|
||||
peerID string
|
||||
transactions [][]*types.Transaction
|
||||
uncles [][]*types.Header
|
||||
}
|
||||
|
||||
func (p *bodyPack) PeerId() string { return p.peerID }
|
||||
func (p *bodyPack) Items() int {
|
||||
if len(p.transactions) <= len(p.uncles) {
|
||||
return len(p.transactions)
|
||||
}
|
||||
return len(p.uncles)
|
||||
}
|
||||
func (p *bodyPack) Stats() string { return fmt.Sprintf("%d:%d", len(p.transactions), len(p.uncles)) }
|
||||
|
||||
// receiptPack is a batch of receipts returned by a peer.
|
||||
type receiptPack struct {
|
||||
peerID string
|
||||
receipts [][]*types.Receipt
|
||||
}
|
||||
|
||||
func (p *receiptPack) PeerId() string { return p.peerID }
|
||||
func (p *receiptPack) Items() int { return len(p.receipts) }
|
||||
func (p *receiptPack) Stats() string { return fmt.Sprintf("%d", len(p.receipts)) }
|
||||
|
||||
// statePack is a batch of states returned by a peer.
|
||||
type statePack struct {
|
||||
peerID string
|
||||
states [][]byte
|
||||
}
|
||||
|
||||
func (p *statePack) PeerId() string { return p.peerID }
|
||||
func (p *statePack) Items() int { return len(p.states) }
|
||||
func (p *statePack) Stats() string { return fmt.Sprintf("%d", len(p.states)) }
|
||||
+29
-24
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"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/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@@ -204,35 +205,39 @@ type Config struct {
|
||||
|
||||
// Arrow Glacier block override (TODO: remove after the fork)
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
|
||||
// OverrideTerminalTotalDifficulty (TODO: remove after the fork)
|
||||
OverrideTerminalTotalDifficulty *big.Int `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
|
||||
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
|
||||
// If proof-of-authority is requested, set it up
|
||||
var engine consensus.Engine
|
||||
if chainConfig.Clique != nil {
|
||||
return clique.New(chainConfig.Clique, db)
|
||||
engine = clique.New(chainConfig.Clique, db)
|
||||
} else {
|
||||
switch config.PowMode {
|
||||
case ethash.ModeFake:
|
||||
log.Warn("Ethash used in fake mode")
|
||||
case ethash.ModeTest:
|
||||
log.Warn("Ethash used in test mode")
|
||||
case ethash.ModeShared:
|
||||
log.Warn("Ethash used in shared mode")
|
||||
}
|
||||
engine = ethash.New(ethash.Config{
|
||||
PowMode: config.PowMode,
|
||||
CacheDir: stack.ResolvePath(config.CacheDir),
|
||||
CachesInMem: config.CachesInMem,
|
||||
CachesOnDisk: config.CachesOnDisk,
|
||||
CachesLockMmap: config.CachesLockMmap,
|
||||
DatasetDir: config.DatasetDir,
|
||||
DatasetsInMem: config.DatasetsInMem,
|
||||
DatasetsOnDisk: config.DatasetsOnDisk,
|
||||
DatasetsLockMmap: config.DatasetsLockMmap,
|
||||
NotifyFull: config.NotifyFull,
|
||||
}, notify, noverify)
|
||||
engine.(*ethash.Ethash).SetThreads(-1) // Disable CPU mining
|
||||
}
|
||||
// Otherwise assume proof-of-work
|
||||
switch config.PowMode {
|
||||
case ethash.ModeFake:
|
||||
log.Warn("Ethash used in fake mode")
|
||||
case ethash.ModeTest:
|
||||
log.Warn("Ethash used in test mode")
|
||||
case ethash.ModeShared:
|
||||
log.Warn("Ethash used in shared mode")
|
||||
}
|
||||
engine := ethash.New(ethash.Config{
|
||||
PowMode: config.PowMode,
|
||||
CacheDir: stack.ResolvePath(config.CacheDir),
|
||||
CachesInMem: config.CachesInMem,
|
||||
CachesOnDisk: config.CachesOnDisk,
|
||||
CachesLockMmap: config.CachesLockMmap,
|
||||
DatasetDir: config.DatasetDir,
|
||||
DatasetsInMem: config.DatasetsInMem,
|
||||
DatasetsOnDisk: config.DatasetsOnDisk,
|
||||
DatasetsLockMmap: config.DatasetsLockMmap,
|
||||
NotifyFull: config.NotifyFull,
|
||||
}, notify, noverify)
|
||||
engine.SetThreads(-1) // Disable CPU mining
|
||||
return engine
|
||||
return beacon.New(engine)
|
||||
}
|
||||
|
||||
+90
-84
@@ -18,48 +18,49 @@ import (
|
||||
// MarshalTOML marshals as TOML.
|
||||
func (c Config) MarshalTOML() (interface{}, error) {
|
||||
type Config struct {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId uint64
|
||||
SyncMode downloader.SyncMode
|
||||
EthDiscoveryURLs []string
|
||||
SnapDiscoveryURLs []string
|
||||
NoPruning bool
|
||||
NoPrefetch bool
|
||||
TxLookupLimit uint64 `toml:",omitempty"`
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
LightServ int `toml:",omitempty"`
|
||||
LightIngress int `toml:",omitempty"`
|
||||
LightEgress int `toml:",omitempty"`
|
||||
LightPeers int `toml:",omitempty"`
|
||||
LightNoPrune bool `toml:",omitempty"`
|
||||
LightNoSyncServe bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce bool `toml:",omitempty"`
|
||||
SkipBcVersionCheck bool `toml:"-"`
|
||||
DatabaseHandles int `toml:"-"`
|
||||
DatabaseCache int
|
||||
DatabaseFreezer string
|
||||
TrieCleanCache int
|
||||
TrieCleanCacheJournal string `toml:",omitempty"`
|
||||
TrieCleanCacheRejournal time.Duration `toml:",omitempty"`
|
||||
TrieDirtyCache int
|
||||
TrieTimeout time.Duration
|
||||
SnapshotCache int
|
||||
Preimages bool
|
||||
Miner miner.Config
|
||||
Ethash ethash.Config
|
||||
TxPool core.TxPoolConfig
|
||||
GPO gasprice.Config
|
||||
EnablePreimageRecording bool
|
||||
DocRoot string `toml:"-"`
|
||||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId uint64
|
||||
SyncMode downloader.SyncMode
|
||||
EthDiscoveryURLs []string
|
||||
SnapDiscoveryURLs []string
|
||||
NoPruning bool
|
||||
NoPrefetch bool
|
||||
TxLookupLimit uint64 `toml:",omitempty"`
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
LightServ int `toml:",omitempty"`
|
||||
LightIngress int `toml:",omitempty"`
|
||||
LightEgress int `toml:",omitempty"`
|
||||
LightPeers int `toml:",omitempty"`
|
||||
LightNoPrune bool `toml:",omitempty"`
|
||||
LightNoSyncServe bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce bool `toml:",omitempty"`
|
||||
SkipBcVersionCheck bool `toml:"-"`
|
||||
DatabaseHandles int `toml:"-"`
|
||||
DatabaseCache int
|
||||
DatabaseFreezer string
|
||||
TrieCleanCache int
|
||||
TrieCleanCacheJournal string `toml:",omitempty"`
|
||||
TrieCleanCacheRejournal time.Duration `toml:",omitempty"`
|
||||
TrieDirtyCache int
|
||||
TrieTimeout time.Duration
|
||||
SnapshotCache int
|
||||
Preimages bool
|
||||
Miner miner.Config
|
||||
Ethash ethash.Config
|
||||
TxPool core.TxPoolConfig
|
||||
GPO gasprice.Config
|
||||
EnablePreimageRecording bool
|
||||
DocRoot string `toml:"-"`
|
||||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
OverrideTerminalTotalDifficulty *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
@@ -104,54 +105,56 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.Checkpoint = c.Checkpoint
|
||||
enc.CheckpointOracle = c.CheckpointOracle
|
||||
enc.OverrideArrowGlacier = c.OverrideArrowGlacier
|
||||
enc.OverrideTerminalTotalDifficulty = c.OverrideTerminalTotalDifficulty
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
// UnmarshalTOML unmarshals from TOML.
|
||||
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
type Config struct {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId *uint64
|
||||
SyncMode *downloader.SyncMode
|
||||
EthDiscoveryURLs []string
|
||||
SnapDiscoveryURLs []string
|
||||
NoPruning *bool
|
||||
NoPrefetch *bool
|
||||
TxLookupLimit *uint64 `toml:",omitempty"`
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
LightServ *int `toml:",omitempty"`
|
||||
LightIngress *int `toml:",omitempty"`
|
||||
LightEgress *int `toml:",omitempty"`
|
||||
LightPeers *int `toml:",omitempty"`
|
||||
LightNoPrune *bool `toml:",omitempty"`
|
||||
LightNoSyncServe *bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint *bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction *int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce *bool `toml:",omitempty"`
|
||||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
DatabaseHandles *int `toml:"-"`
|
||||
DatabaseCache *int
|
||||
DatabaseFreezer *string
|
||||
TrieCleanCache *int
|
||||
TrieCleanCacheJournal *string `toml:",omitempty"`
|
||||
TrieCleanCacheRejournal *time.Duration `toml:",omitempty"`
|
||||
TrieDirtyCache *int
|
||||
TrieTimeout *time.Duration
|
||||
SnapshotCache *int
|
||||
Preimages *bool
|
||||
Miner *miner.Config
|
||||
Ethash *ethash.Config
|
||||
TxPool *core.TxPoolConfig
|
||||
GPO *gasprice.Config
|
||||
EnablePreimageRecording *bool
|
||||
DocRoot *string `toml:"-"`
|
||||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId *uint64
|
||||
SyncMode *downloader.SyncMode
|
||||
EthDiscoveryURLs []string
|
||||
SnapDiscoveryURLs []string
|
||||
NoPruning *bool
|
||||
NoPrefetch *bool
|
||||
TxLookupLimit *uint64 `toml:",omitempty"`
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
LightServ *int `toml:",omitempty"`
|
||||
LightIngress *int `toml:",omitempty"`
|
||||
LightEgress *int `toml:",omitempty"`
|
||||
LightPeers *int `toml:",omitempty"`
|
||||
LightNoPrune *bool `toml:",omitempty"`
|
||||
LightNoSyncServe *bool `toml:",omitempty"`
|
||||
SyncFromCheckpoint *bool `toml:",omitempty"`
|
||||
UltraLightServers []string `toml:",omitempty"`
|
||||
UltraLightFraction *int `toml:",omitempty"`
|
||||
UltraLightOnlyAnnounce *bool `toml:",omitempty"`
|
||||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
DatabaseHandles *int `toml:"-"`
|
||||
DatabaseCache *int
|
||||
DatabaseFreezer *string
|
||||
TrieCleanCache *int
|
||||
TrieCleanCacheJournal *string `toml:",omitempty"`
|
||||
TrieCleanCacheRejournal *time.Duration `toml:",omitempty"`
|
||||
TrieDirtyCache *int
|
||||
TrieTimeout *time.Duration
|
||||
SnapshotCache *int
|
||||
Preimages *bool
|
||||
Miner *miner.Config
|
||||
Ethash *ethash.Config
|
||||
TxPool *core.TxPoolConfig
|
||||
GPO *gasprice.Config
|
||||
EnablePreimageRecording *bool
|
||||
DocRoot *string `toml:"-"`
|
||||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
OverrideTerminalTotalDifficulty *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
@@ -283,5 +286,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.OverrideArrowGlacier != nil {
|
||||
c.OverrideArrowGlacier = dec.OverrideArrowGlacier
|
||||
}
|
||||
if dec.OverrideTerminalTotalDifficulty != nil {
|
||||
c.OverrideTerminalTotalDifficulty = dec.OverrideTerminalTotalDifficulty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
@@ -74,10 +75,10 @@ type HeaderRetrievalFn func(common.Hash) *types.Header
|
||||
type blockRetrievalFn func(common.Hash) *types.Block
|
||||
|
||||
// headerRequesterFn is a callback type for sending a header retrieval request.
|
||||
type headerRequesterFn func(common.Hash) error
|
||||
type headerRequesterFn func(common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// bodyRequesterFn is a callback type for sending a body retrieval request.
|
||||
type bodyRequesterFn func([]common.Hash) error
|
||||
type bodyRequesterFn func([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
|
||||
type headerVerifierFn func(header *types.Header) error
|
||||
@@ -461,15 +462,28 @@ func (f *BlockFetcher) loop() {
|
||||
|
||||
// Create a closure of the fetch and schedule in on a new thread
|
||||
fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
|
||||
go func() {
|
||||
go func(peer string) {
|
||||
if f.fetchingHook != nil {
|
||||
f.fetchingHook(hashes)
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
headerFetchMeter.Mark(1)
|
||||
fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals
|
||||
go func(hash common.Hash) {
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := fetchHeader(hash, resCh)
|
||||
if err != nil {
|
||||
return // Legacy code, yolo
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
res := <-resCh
|
||||
res.Done <- nil
|
||||
|
||||
f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersPacket), time.Now().Add(res.Time))
|
||||
}(hash)
|
||||
}
|
||||
}()
|
||||
}(peer)
|
||||
}
|
||||
// Schedule the next fetch if blocks are still pending
|
||||
f.rescheduleFetch(fetchTimer)
|
||||
@@ -497,8 +511,24 @@ func (f *BlockFetcher) loop() {
|
||||
if f.completingHook != nil {
|
||||
f.completingHook(hashes)
|
||||
}
|
||||
fetchBodies := f.completing[hashes[0]].fetchBodies
|
||||
bodyFetchMeter.Mark(int64(len(hashes)))
|
||||
go f.completing[hashes[0]].fetchBodies(hashes)
|
||||
|
||||
go func(peer string, hashes []common.Hash) {
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := fetchBodies(hashes, resCh)
|
||||
if err != nil {
|
||||
return // Legacy code, yolo
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
res := <-resCh
|
||||
res.Done <- nil
|
||||
|
||||
txs, uncles := res.Res.(*eth.BlockBodiesPacket).Unpack()
|
||||
f.FilterBodies(peer, txs, uncles, time.Now())
|
||||
}(peer, hashes)
|
||||
}
|
||||
// Schedule the next fetch if blocks are still pending
|
||||
f.rescheduleComplete(completeTimer)
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"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/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
@@ -60,8 +61,8 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
|
||||
block.AddTx(tx)
|
||||
}
|
||||
// If the block number is a multiple of 5, add a bonus uncle to the block
|
||||
if i%5 == 0 {
|
||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
|
||||
if i > 0 && i%5 == 0 {
|
||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))})
|
||||
}
|
||||
})
|
||||
hashes := make([]common.Hash, n+1)
|
||||
@@ -195,16 +196,26 @@ func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*t
|
||||
closure[hash] = block
|
||||
}
|
||||
// Create a function that return a header from the closure
|
||||
return func(hash common.Hash) error {
|
||||
return func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
// Gather the blocks to return
|
||||
headers := make([]*types.Header, 0, 1)
|
||||
if block, ok := closure[hash]; ok {
|
||||
headers = append(headers, block.Header())
|
||||
}
|
||||
// Return on a new thread
|
||||
go f.fetcher.FilterHeaders(peer, headers, time.Now().Add(drift))
|
||||
|
||||
return nil
|
||||
req := ð.Request{
|
||||
Peer: peer,
|
||||
}
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.BlockHeadersPacket)(&headers),
|
||||
Time: drift,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +226,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ
|
||||
closure[hash] = block
|
||||
}
|
||||
// Create a function that returns blocks from the closure
|
||||
return func(hashes []common.Hash) error {
|
||||
return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
// Gather the block bodies to return
|
||||
transactions := make([][]*types.Transaction, 0, len(hashes))
|
||||
uncles := make([][]*types.Header, 0, len(hashes))
|
||||
@@ -227,14 +238,33 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ
|
||||
}
|
||||
}
|
||||
// Return on a new thread
|
||||
go f.fetcher.FilterBodies(peer, transactions, uncles, time.Now().Add(drift))
|
||||
|
||||
return nil
|
||||
bodies := make([]*eth.BlockBody, len(transactions))
|
||||
for i, txs := range transactions {
|
||||
bodies[i] = ð.BlockBody{
|
||||
Transactions: txs,
|
||||
Uncles: uncles[i],
|
||||
}
|
||||
}
|
||||
req := ð.Request{
|
||||
Peer: peer,
|
||||
}
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.BlockBodiesPacket)(&bodies),
|
||||
Time: drift,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
}
|
||||
|
||||
// verifyFetchingEvent verifies that one single event arrive on a fetching channel.
|
||||
func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-fetching:
|
||||
@@ -252,6 +282,8 @@ func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool)
|
||||
|
||||
// verifyCompletingEvent verifies that one single event arrive on an completing channel.
|
||||
func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-completing:
|
||||
@@ -269,6 +301,8 @@ func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive b
|
||||
|
||||
// verifyImportEvent verifies that one single event arrive on an import channel.
|
||||
func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-imported:
|
||||
@@ -287,6 +321,8 @@ func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
|
||||
// verifyImportCount verifies that exactly count number of events arrive on an
|
||||
// import hook channel.
|
||||
func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
select {
|
||||
case <-imported:
|
||||
@@ -299,6 +335,8 @@ func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
|
||||
|
||||
// verifyImportDone verifies that no more events are arriving on an import channel.
|
||||
func verifyImportDone(t *testing.T, imported chan interface{}) {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case <-imported:
|
||||
t.Fatalf("extra block imported")
|
||||
@@ -308,6 +346,8 @@ func verifyImportDone(t *testing.T, imported chan interface{}) {
|
||||
|
||||
// verifyChainHeight verifies the chain height is as expected.
|
||||
func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) {
|
||||
t.Helper()
|
||||
|
||||
if fetcher.chainHeight() != height {
|
||||
t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height)
|
||||
}
|
||||
@@ -368,13 +408,13 @@ func testConcurrentAnnouncements(t *testing.T, light bool) {
|
||||
secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0)
|
||||
|
||||
counter := uint32(0)
|
||||
firstHeaderWrapper := func(hash common.Hash) error {
|
||||
firstHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
return firstHeaderFetcher(hash)
|
||||
return firstHeaderFetcher(hash, sink)
|
||||
}
|
||||
secondHeaderWrapper := func(hash common.Hash) error {
|
||||
secondHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
return secondHeaderFetcher(hash)
|
||||
return secondHeaderFetcher(hash, sink)
|
||||
}
|
||||
// Iteratively announce blocks until all are imported
|
||||
imported := make(chan interface{})
|
||||
@@ -468,15 +508,20 @@ func testPendingDeduplication(t *testing.T, light bool) {
|
||||
|
||||
delay := 50 * time.Millisecond
|
||||
counter := uint32(0)
|
||||
headerWrapper := func(hash common.Hash) error {
|
||||
headerWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
|
||||
// Simulate a long running fetch
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
headerFetcher(hash)
|
||||
}()
|
||||
return nil
|
||||
resink := make(chan *eth.Response)
|
||||
req, err := headerFetcher(hash, resink)
|
||||
if err == nil {
|
||||
go func() {
|
||||
res := <-resink
|
||||
time.Sleep(delay)
|
||||
sink <- res
|
||||
}()
|
||||
}
|
||||
return req, err
|
||||
}
|
||||
checkNonExist := func() bool {
|
||||
return tester.getBlock(hashes[0]) == nil
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
@@ -51,7 +50,6 @@ type PublicFilterAPI struct {
|
||||
backend Backend
|
||||
mux *event.TypeMux
|
||||
quit chan struct{}
|
||||
chainDb ethdb.Database
|
||||
events *EventSystem
|
||||
filtersMu sync.Mutex
|
||||
filters map[rpc.ID]*filter
|
||||
@@ -62,7 +60,6 @@ type PublicFilterAPI struct {
|
||||
func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI {
|
||||
api := &PublicFilterAPI{
|
||||
backend: backend,
|
||||
chainDb: backend.ChainDb(),
|
||||
events: NewEventSystem(backend, lightMode),
|
||||
filters: make(map[rpc.ID]*filter),
|
||||
timeout: timeout,
|
||||
|
||||
@@ -144,7 +144,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
|
||||
// Construct testing chain
|
||||
diskdb := rawdb.NewMemoryDatabase()
|
||||
gspec.Commit(diskdb)
|
||||
chain, err := core.NewBlockChain(diskdb, &core.CacheConfig{TrieCleanNoPrefetch: true}, &config, engine, vm.Config{}, nil, nil)
|
||||
chain, err := core.NewBlockChain(diskdb, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec.Config, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create local chain, %v", err)
|
||||
}
|
||||
|
||||
+162
-44
@@ -25,6 +25,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@@ -37,7 +39,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -79,9 +80,10 @@ type handlerConfig struct {
|
||||
Database ethdb.Database // Database for direct sync insertions
|
||||
Chain *core.BlockChain // Blockchain to serve data from
|
||||
TxPool txPool // Transaction pool to propagate from
|
||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
||||
Network uint64 // Network identifier to adfvertise
|
||||
Sync downloader.SyncMode // Whether to fast or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for fast sync bloom
|
||||
Sync downloader.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
||||
Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged
|
||||
@@ -91,8 +93,7 @@ type handler struct {
|
||||
networkID uint64
|
||||
forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node
|
||||
|
||||
fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks)
|
||||
snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol
|
||||
snapSync uint32 // Flag whether snap sync is enabled (gets disabled if we already have blocks)
|
||||
acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
|
||||
|
||||
checkpointNumber uint64 // Block number for the sync progress validator to cross reference
|
||||
@@ -104,10 +105,10 @@ type handler struct {
|
||||
maxPeers int
|
||||
|
||||
downloader *downloader.Downloader
|
||||
stateBloom *trie.SyncBloom
|
||||
blockFetcher *fetcher.BlockFetcher
|
||||
txFetcher *fetcher.TxFetcher
|
||||
peers *peerSet
|
||||
merger *consensus.Merger
|
||||
|
||||
eventMux *event.TypeMux
|
||||
txsCh chan core.NewTxsEvent
|
||||
@@ -138,33 +139,31 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
txpool: config.TxPool,
|
||||
chain: config.Chain,
|
||||
peers: newPeerSet(),
|
||||
merger: config.Merger,
|
||||
whitelist: config.Whitelist,
|
||||
quitSync: make(chan struct{}),
|
||||
}
|
||||
if config.Sync == downloader.FullSync {
|
||||
// The database seems empty as the current block is the genesis. Yet the fast
|
||||
// block is ahead, so fast sync was enabled for this node at a certain point.
|
||||
// The database seems empty as the current block is the genesis. Yet the snap
|
||||
// block is ahead, so snap sync was enabled for this node at a certain point.
|
||||
// The scenarios where this can happen is
|
||||
// * if the user manually (or via a bad block) rolled back a fast sync node
|
||||
// * if the user manually (or via a bad block) rolled back a snap sync node
|
||||
// below the sync point.
|
||||
// * the last fast sync is not finished while user specifies a full sync this
|
||||
// * the last snap sync is not finished while user specifies a full sync this
|
||||
// time. But we don't have any recent state for full sync.
|
||||
// In these cases however it's safe to reenable fast sync.
|
||||
// In these cases however it's safe to reenable snap sync.
|
||||
fullBlock, fastBlock := h.chain.CurrentBlock(), h.chain.CurrentFastBlock()
|
||||
if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 {
|
||||
h.fastSync = uint32(1)
|
||||
log.Warn("Switch sync mode from full sync to fast sync")
|
||||
h.snapSync = uint32(1)
|
||||
log.Warn("Switch sync mode from full sync to snap sync")
|
||||
}
|
||||
} else {
|
||||
if h.chain.CurrentBlock().NumberU64() > 0 {
|
||||
// Print warning log if database is not empty to run fast sync.
|
||||
log.Warn("Switch sync mode from fast sync to full sync")
|
||||
// Print warning log if database is not empty to run snap sync.
|
||||
log.Warn("Switch sync mode from snap sync to full sync")
|
||||
} else {
|
||||
// If fast sync was requested and our database is empty, grant it
|
||||
h.fastSync = uint32(1)
|
||||
if config.Sync == downloader.SnapSync {
|
||||
h.snapSync = uint32(1)
|
||||
}
|
||||
// If snap sync was requested and our database is empty, grant it
|
||||
h.snapSync = uint32(1)
|
||||
}
|
||||
}
|
||||
// If we have trusted checkpoints, enforce them on the chain
|
||||
@@ -172,26 +171,48 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1
|
||||
h.checkpointHash = config.Checkpoint.SectionHead
|
||||
}
|
||||
// Construct the downloader (long sync) and its backing state bloom if fast
|
||||
// Construct the downloader (long sync) and its backing state bloom if snap
|
||||
// sync is requested. The downloader is responsible for deallocating the state
|
||||
// bloom when it's done.
|
||||
// Note: we don't enable it if snap-sync is performed, since it's very heavy
|
||||
// and the heal-portion of the snap sync is much lighter than fast. What we particularly
|
||||
// want to avoid, is a 90%-finished (but restarted) snap-sync to begin
|
||||
// indexing the entire trie
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 {
|
||||
h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database)
|
||||
}
|
||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer)
|
||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer)
|
||||
|
||||
// Construct the fetcher (short sync)
|
||||
validator := func(header *types.Header) error {
|
||||
// All the block fetcher activities should be disabled
|
||||
// after the transition. Print the warning log.
|
||||
if h.merger.PoSFinalized() {
|
||||
log.Warn("Unexpected validation activity", "hash", header.Hash(), "number", header.Number)
|
||||
return errors.New("unexpected behavior after transition")
|
||||
}
|
||||
// Reject all the PoS style headers in the first place. No matter
|
||||
// the chain has finished the transition or not, the PoS headers
|
||||
// should only come from the trusted consensus layer instead of
|
||||
// p2p network.
|
||||
if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
|
||||
if beacon.IsPoSHeader(header) {
|
||||
return errors.New("unexpected post-merge header")
|
||||
}
|
||||
}
|
||||
return h.chain.Engine().VerifyHeader(h.chain, header, true)
|
||||
}
|
||||
heighter := func() uint64 {
|
||||
return h.chain.CurrentBlock().NumberU64()
|
||||
}
|
||||
inserter := func(blocks types.Blocks) (int, error) {
|
||||
// All the block fetcher activities should be disabled
|
||||
// after the transition. Print the warning log.
|
||||
if h.merger.PoSFinalized() {
|
||||
var ctx []interface{}
|
||||
ctx = append(ctx, "blocks", len(blocks))
|
||||
if len(blocks) > 0 {
|
||||
ctx = append(ctx, "firsthash", blocks[0].Hash())
|
||||
ctx = append(ctx, "firstnumber", blocks[0].Number())
|
||||
ctx = append(ctx, "lasthash", blocks[len(blocks)-1].Hash())
|
||||
ctx = append(ctx, "lastnumber", blocks[len(blocks)-1].Number())
|
||||
}
|
||||
log.Warn("Unexpected insertion activity", ctx...)
|
||||
return 0, errors.New("unexpected behavior after transition")
|
||||
}
|
||||
// If sync hasn't reached the checkpoint yet, deny importing weird blocks.
|
||||
//
|
||||
// Ideally we would also compare the head block's timestamp and similarly reject
|
||||
@@ -202,15 +223,38 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||
log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
|
||||
return 0, nil
|
||||
}
|
||||
// If fast sync is running, deny importing weird blocks. This is a problematic
|
||||
// clause when starting up a new network, because fast-syncing miners might not
|
||||
// If snap sync is running, deny importing weird blocks. This is a problematic
|
||||
// clause when starting up a new network, because snap-syncing miners might not
|
||||
// accept each others' blocks until a restart. Unfortunately we haven't figured
|
||||
// out a way yet where nodes can decide unilaterally whether the network is new
|
||||
// or not. This should be fixed if we figure out a solution.
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 {
|
||||
if atomic.LoadUint32(&h.snapSync) == 1 {
|
||||
log.Warn("Fast syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
|
||||
return 0, nil
|
||||
}
|
||||
if h.merger.TDDReached() {
|
||||
// The blocks from the p2p network is regarded as untrusted
|
||||
// after the transition. In theory block gossip should be disabled
|
||||
// entirely whenever the transition is started. But in order to
|
||||
// handle the transition boundary reorg in the consensus-layer,
|
||||
// the legacy blocks are still accepted, but only for the terminal
|
||||
// pow blocks. Spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3675.md#halt-the-importing-of-pow-blocks
|
||||
for i, block := range blocks {
|
||||
ptd := h.chain.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||
if ptd == nil {
|
||||
return 0, nil
|
||||
}
|
||||
td := new(big.Int).Add(ptd, block.Difficulty())
|
||||
if !h.chain.Config().IsTerminalPoWBlock(ptd, td) {
|
||||
log.Info("Filtered out non-termimal pow block", "number", block.NumberU64(), "hash", block.Hash())
|
||||
return 0, nil
|
||||
}
|
||||
if err := h.chain.InsertBlockWithoutSetHead(block); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
n, err := h.chain.InsertChain(blocks)
|
||||
if err == nil {
|
||||
atomic.StoreUint32(&h.acceptTxs, 1) // Mark initial sync done on any fetcher import
|
||||
@@ -308,30 +352,93 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||
// after this will be sent via broadcasts.
|
||||
h.syncTransactions(peer)
|
||||
|
||||
// Create a notification channel for pending requests if the peer goes down
|
||||
dead := make(chan struct{})
|
||||
defer close(dead)
|
||||
|
||||
// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
|
||||
if h.checkpointHash != (common.Hash{}) {
|
||||
// Request the peer's checkpoint header for chain height/weight validation
|
||||
if err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false); err != nil {
|
||||
resCh := make(chan *eth.Response)
|
||||
if _, err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false, resCh); err != nil {
|
||||
return err
|
||||
}
|
||||
// Start a timer to disconnect if the peer doesn't reply in time
|
||||
p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() {
|
||||
peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
h.removePeer(peer.ID())
|
||||
})
|
||||
// Make sure it's cleaned up if the peer dies off
|
||||
defer func() {
|
||||
if p.syncDrop != nil {
|
||||
p.syncDrop.Stop()
|
||||
p.syncDrop = nil
|
||||
go func() {
|
||||
timeout := time.NewTimer(syncChallengeTimeout)
|
||||
defer timeout.Stop()
|
||||
|
||||
select {
|
||||
case res := <-resCh:
|
||||
headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersPacket))
|
||||
if len(headers) == 0 {
|
||||
// If we're doing a snap sync, we must enforce the checkpoint
|
||||
// block to avoid eclipse attacks. Unsynced nodes are welcome
|
||||
// to connect after we're done joining the network.
|
||||
if atomic.LoadUint32(&h.snapSync) == 1 {
|
||||
peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
res.Done <- errors.New("unsynced node cannot serve sync")
|
||||
return
|
||||
}
|
||||
res.Done <- nil
|
||||
return
|
||||
}
|
||||
// Validate the header and either drop the peer or continue
|
||||
if len(headers) > 1 {
|
||||
res.Done <- errors.New("too many headers in checkpoint response")
|
||||
return
|
||||
}
|
||||
if headers[0].Hash() != h.checkpointHash {
|
||||
res.Done <- errors.New("checkpoint hash mismatch")
|
||||
return
|
||||
}
|
||||
res.Done <- nil
|
||||
|
||||
case <-timeout.C:
|
||||
peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
h.removePeer(peer.ID())
|
||||
|
||||
case <-dead:
|
||||
// Peer handler terminated, abort all goroutines
|
||||
}
|
||||
}()
|
||||
}
|
||||
// If we have any explicit whitelist block hashes, request them
|
||||
for number := range h.whitelist {
|
||||
if err := peer.RequestHeadersByNumber(number, 1, 0, false); err != nil {
|
||||
for number, hash := range h.whitelist {
|
||||
resCh := make(chan *eth.Response)
|
||||
if _, err := peer.RequestHeadersByNumber(number, 1, 0, false, resCh); err != nil {
|
||||
return err
|
||||
}
|
||||
go func(number uint64, hash common.Hash) {
|
||||
timeout := time.NewTimer(syncChallengeTimeout)
|
||||
defer timeout.Stop()
|
||||
|
||||
select {
|
||||
case res := <-resCh:
|
||||
headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersPacket))
|
||||
if len(headers) == 0 {
|
||||
// Whitelisted blocks are allowed to be missing if the remote
|
||||
// node is not yet synced
|
||||
res.Done <- nil
|
||||
return
|
||||
}
|
||||
// Validate the header and either drop the peer or continue
|
||||
if len(headers) > 1 {
|
||||
res.Done <- errors.New("too many headers in whitelist response")
|
||||
return
|
||||
}
|
||||
if headers[0].Number.Uint64() != number || headers[0].Hash() != hash {
|
||||
peer.Log().Info("Whitelist mismatch, dropping peer", "number", number, "hash", headers[0].Hash(), "want", hash)
|
||||
res.Done <- errors.New("whitelist block mismatch")
|
||||
return
|
||||
}
|
||||
peer.Log().Debug("Whitelist block verified", "number", number, "hash", hash)
|
||||
|
||||
case <-timeout.C:
|
||||
peer.Log().Warn("Whitelist challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
h.removePeer(peer.ID())
|
||||
}
|
||||
}(number, hash)
|
||||
}
|
||||
// Handle incoming messages until the connection is torn down
|
||||
return handler(peer)
|
||||
@@ -432,6 +539,17 @@ func (h *handler) Stop() {
|
||||
// BroadcastBlock will either propagate a block to a subset of its peers, or
|
||||
// will only announce its availability (depending what's requested).
|
||||
func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
|
||||
// Disable the block propagation if the chain has already entered the PoS
|
||||
// stage. The block propagation is delegated to the consensus layer.
|
||||
if h.merger.PoSFinalized() {
|
||||
return
|
||||
}
|
||||
// Disable the block propagation if it's the post-merge block.
|
||||
if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
|
||||
if beacon.IsPoSHeader(block.Header()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
hash := block.Hash()
|
||||
peers := h.peers.peersWithoutBlock(hash)
|
||||
|
||||
|
||||
+18
-98
@@ -17,7 +17,6 @@
|
||||
package eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
@@ -27,18 +26,15 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// ethHandler implements the eth.Backend interface to handle the various network
|
||||
// packets that are sent as replies or broadcasts.
|
||||
type ethHandler handler
|
||||
|
||||
func (h *ethHandler) Chain() *core.BlockChain { return h.chain }
|
||||
func (h *ethHandler) StateBloom() *trie.SyncBloom { return h.stateBloom }
|
||||
func (h *ethHandler) TxPool() eth.TxPool { return h.txpool }
|
||||
func (h *ethHandler) Chain() *core.BlockChain { return h.chain }
|
||||
func (h *ethHandler) TxPool() eth.TxPool { return h.txpool }
|
||||
|
||||
// RunPeer is invoked when a peer joins on the `eth` protocol.
|
||||
func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error {
|
||||
@@ -64,25 +60,6 @@ func (h *ethHandler) AcceptTxs() bool {
|
||||
func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||
// Consume any broadcasts and announces, forwarding the rest to the downloader
|
||||
switch packet := packet.(type) {
|
||||
case *eth.BlockHeadersPacket:
|
||||
return h.handleHeaders(peer, *packet)
|
||||
|
||||
case *eth.BlockBodiesPacket:
|
||||
txset, uncleset := packet.Unpack()
|
||||
return h.handleBodies(peer, txset, uncleset)
|
||||
|
||||
case *eth.NodeDataPacket:
|
||||
if err := h.downloader.DeliverNodeData(peer.ID(), *packet); err != nil {
|
||||
log.Debug("Failed to deliver node state data", "err", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *eth.ReceiptsPacket:
|
||||
if err := h.downloader.DeliverReceipts(peer.ID(), *packet); err != nil {
|
||||
log.Debug("Failed to deliver receipts", "err", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *eth.NewBlockHashesPacket:
|
||||
hashes, numbers := packet.Unpack()
|
||||
return h.handleBlockAnnounces(peer, hashes, numbers)
|
||||
@@ -104,82 +81,17 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||
}
|
||||
}
|
||||
|
||||
// handleHeaders is invoked from a peer's message handler when it transmits a batch
|
||||
// of headers for the local node to process.
|
||||
func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error {
|
||||
p := h.peers.peer(peer.ID())
|
||||
if p == nil {
|
||||
return errors.New("unregistered during callback")
|
||||
}
|
||||
// If no headers were received, but we're expencting a checkpoint header, consider it that
|
||||
if len(headers) == 0 && p.syncDrop != nil {
|
||||
// Stop the timer either way, decide later to drop or not
|
||||
p.syncDrop.Stop()
|
||||
p.syncDrop = nil
|
||||
|
||||
// If we're doing a fast (or snap) sync, we must enforce the checkpoint block to avoid
|
||||
// eclipse attacks. Unsynced nodes are welcome to connect after we're done
|
||||
// joining the network
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 {
|
||||
peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
return errors.New("unsynced node cannot serve sync")
|
||||
}
|
||||
}
|
||||
// Filter out any explicitly requested headers, deliver the rest to the downloader
|
||||
filter := len(headers) == 1
|
||||
if filter {
|
||||
// If it's a potential sync progress check, validate the content and advertised chain weight
|
||||
if p.syncDrop != nil && headers[0].Number.Uint64() == h.checkpointNumber {
|
||||
// Disable the sync drop timer
|
||||
p.syncDrop.Stop()
|
||||
p.syncDrop = nil
|
||||
|
||||
// Validate the header and either drop the peer or continue
|
||||
if headers[0].Hash() != h.checkpointHash {
|
||||
return errors.New("checkpoint hash mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Otherwise if it's a whitelisted block, validate against the set
|
||||
if want, ok := h.whitelist[headers[0].Number.Uint64()]; ok {
|
||||
if hash := headers[0].Hash(); want != hash {
|
||||
peer.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want)
|
||||
return errors.New("whitelist block mismatch")
|
||||
}
|
||||
peer.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want)
|
||||
}
|
||||
// Irrelevant of the fork checks, send the header to the fetcher just in case
|
||||
headers = h.blockFetcher.FilterHeaders(peer.ID(), headers, time.Now())
|
||||
}
|
||||
if len(headers) > 0 || !filter {
|
||||
err := h.downloader.DeliverHeaders(peer.ID(), headers)
|
||||
if err != nil {
|
||||
log.Debug("Failed to deliver headers", "err", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBodies is invoked from a peer's message handler when it transmits a batch
|
||||
// of block bodies for the local node to process.
|
||||
func (h *ethHandler) handleBodies(peer *eth.Peer, txs [][]*types.Transaction, uncles [][]*types.Header) error {
|
||||
// Filter out any explicitly requested bodies, deliver the rest to the downloader
|
||||
filter := len(txs) > 0 || len(uncles) > 0
|
||||
if filter {
|
||||
txs, uncles = h.blockFetcher.FilterBodies(peer.ID(), txs, uncles, time.Now())
|
||||
}
|
||||
if len(txs) > 0 || len(uncles) > 0 || !filter {
|
||||
err := h.downloader.DeliverBodies(peer.ID(), txs, uncles)
|
||||
if err != nil {
|
||||
log.Debug("Failed to deliver bodies", "err", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBlockAnnounces is invoked from a peer's message handler when it transmits a
|
||||
// batch of block announcements for the local node to process.
|
||||
func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, numbers []uint64) error {
|
||||
// Drop all incoming block announces from the p2p network if
|
||||
// the chain already entered the pos stage and disconnect the
|
||||
// remote peer.
|
||||
if h.merger.PoSFinalized() {
|
||||
// TODO (MariusVanDerWijden) drop non-updated peers after the merge
|
||||
return nil
|
||||
// return errors.New("unexpected block announces")
|
||||
}
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
var (
|
||||
unknownHashes = make([]common.Hash, 0, len(hashes))
|
||||
@@ -200,6 +112,14 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash,
|
||||
// handleBlockBroadcast is invoked from a peer's message handler when it transmits a
|
||||
// block broadcast for the local node to process.
|
||||
func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td *big.Int) error {
|
||||
// Drop all incoming block announces from the p2p network if
|
||||
// the chain already entered the pos stage and disconnect the
|
||||
// remote peer.
|
||||
if h.merger.PoSFinalized() {
|
||||
// TODO (MariusVanDerWijden) drop non-updated peers after the merge
|
||||
return nil
|
||||
// return errors.New("unexpected block announces")
|
||||
}
|
||||
// Schedule the block for import
|
||||
h.blockFetcher.Enqueue(peer.ID(), block)
|
||||
|
||||
|
||||
+24
-18
@@ -25,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
@@ -37,7 +38,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// testEthHandler is a mock event handler to listen for inbound network requests
|
||||
@@ -49,7 +50,6 @@ type testEthHandler struct {
|
||||
}
|
||||
|
||||
func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") }
|
||||
func (h *testEthHandler) StateBloom() *trie.SyncBloom { panic("no backing state bloom") }
|
||||
func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
|
||||
func (h *testEthHandler) AcceptTxs() bool { return true }
|
||||
func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") }
|
||||
@@ -115,6 +115,7 @@ func testForkIDSplit(t *testing.T, protocol uint) {
|
||||
Database: dbNoFork,
|
||||
Chain: chainNoFork,
|
||||
TxPool: newTestTxPool(),
|
||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
||||
Network: 1,
|
||||
Sync: downloader.FullSync,
|
||||
BloomCache: 1,
|
||||
@@ -123,6 +124,7 @@ func testForkIDSplit(t *testing.T, protocol uint) {
|
||||
Database: dbProFork,
|
||||
Chain: chainProFork,
|
||||
TxPool: newTestTxPool(),
|
||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
||||
Network: 1,
|
||||
Sync: downloader.FullSync,
|
||||
BloomCache: 1,
|
||||
@@ -351,7 +353,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for len(seen) < len(insert) {
|
||||
switch protocol {
|
||||
case 65, 66:
|
||||
case 66:
|
||||
select {
|
||||
case hashes := <-anns:
|
||||
for _, hash := range hashes {
|
||||
@@ -361,7 +363,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
|
||||
seen[hash] = struct{}{}
|
||||
}
|
||||
case <-bcasts:
|
||||
t.Errorf("initial tx broadcast received on post eth/65")
|
||||
t.Errorf("initial tx broadcast received on post eth/66")
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -386,6 +388,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
// to receive them. We need multiple sinks since a one-to-one peering would
|
||||
// broadcast all transactions without announcement.
|
||||
source := newTestHandler()
|
||||
source.handler.snapSync = 0 // Avoid requiring snap, otherwise some will be dropped below
|
||||
defer source.close()
|
||||
|
||||
sinks := make([]*testHandler, 10)
|
||||
@@ -403,7 +406,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
defer sourcePipe.Close()
|
||||
defer sinkPipe.Close()
|
||||
|
||||
sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, source.txpool)
|
||||
sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i + 1)}, "", nil, sourcePipe), sourcePipe, source.txpool)
|
||||
sinkPeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, sink.txpool)
|
||||
defer sourcePeer.Close()
|
||||
defer sinkPeer.Close()
|
||||
@@ -435,12 +438,13 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
|
||||
// Iterate through all the sinks and ensure they all got the transactions
|
||||
for i := range sinks {
|
||||
for arrived := 0; arrived < len(txs); {
|
||||
for arrived, timeout := 0, false; arrived < len(txs) && !timeout; {
|
||||
select {
|
||||
case event := <-txChs[i]:
|
||||
arrived += len(event.Txs)
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs))
|
||||
timeout = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,23 +464,23 @@ func TestCheckpointChallenge(t *testing.T) {
|
||||
}{
|
||||
// If checkpointing is not enabled locally, don't challenge and don't drop
|
||||
{downloader.FullSync, false, false, false, false, false},
|
||||
{downloader.FastSync, false, false, false, false, false},
|
||||
{downloader.SnapSync, false, false, false, false, false},
|
||||
|
||||
// If checkpointing is enabled locally and remote response is empty, only drop during fast sync
|
||||
{downloader.FullSync, true, false, true, false, false},
|
||||
{downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
|
||||
{downloader.SnapSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
|
||||
|
||||
// If checkpointing is enabled locally and remote response mismatches, always drop
|
||||
{downloader.FullSync, true, false, false, false, true},
|
||||
{downloader.FastSync, true, false, false, false, true},
|
||||
{downloader.SnapSync, true, false, false, false, true},
|
||||
|
||||
// If checkpointing is enabled locally and remote response matches, never drop
|
||||
{downloader.FullSync, true, false, false, true, false},
|
||||
{downloader.FastSync, true, false, false, true, false},
|
||||
{downloader.SnapSync, true, false, false, true, false},
|
||||
|
||||
// If checkpointing is enabled locally and remote times out, always drop
|
||||
{downloader.FullSync, true, true, false, true, true},
|
||||
{downloader.FastSync, true, true, false, true, true},
|
||||
{downloader.SnapSync, true, true, false, true, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) {
|
||||
@@ -497,10 +501,10 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||
handler := newTestHandler()
|
||||
defer handler.close()
|
||||
|
||||
if syncmode == downloader.FastSync {
|
||||
atomic.StoreUint32(&handler.handler.fastSync, 1)
|
||||
if syncmode == downloader.SnapSync {
|
||||
atomic.StoreUint32(&handler.handler.snapSync, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&handler.handler.fastSync, 0)
|
||||
atomic.StoreUint32(&handler.handler.snapSync, 0)
|
||||
}
|
||||
var response *types.Header
|
||||
if checkpoint {
|
||||
@@ -557,15 +561,17 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||
// Create a block to reply to the challenge if no timeout is simulated.
|
||||
if !timeout {
|
||||
if empty {
|
||||
if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{}); err != nil {
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else if match {
|
||||
if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{response}); err != nil {
|
||||
responseRlp, _ := rlp.EncodeToBytes(response)
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{{Number: response.Number}}); err != nil {
|
||||
responseRlp, _ := rlp.EncodeToBytes(types.Header{Number: response.Number})
|
||||
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -22,6 +22,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
@@ -149,8 +150,9 @@ func newTestHandlerWithBlocks(blocks int) *testHandler {
|
||||
Database: db,
|
||||
Chain: chain,
|
||||
TxPool: txpool,
|
||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
||||
Network: 1,
|
||||
Sync: downloader.FastSync,
|
||||
Sync: downloader.SnapSync,
|
||||
BloomCache: 1,
|
||||
})
|
||||
handler.Start(1000)
|
||||
|
||||
+1
-6
@@ -18,8 +18,6 @@ package eth
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
@@ -36,11 +34,8 @@ type ethPeerInfo struct {
|
||||
// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
|
||||
type ethPeer struct {
|
||||
*eth.Peer
|
||||
snapExt *snapPeer // Satellite `snap` connection
|
||||
|
||||
syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time
|
||||
snapExt *snapPeer // Satellite `snap` connection
|
||||
snapWait chan struct{} // Notification channel for snap connections
|
||||
lock sync.RWMutex // Mutex protecting the internal fields
|
||||
}
|
||||
|
||||
// info gathers and returns some `eth` protocol metadata known about a peer.
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package 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 belogs 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 pendign 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 fulfilment, a dangling repsponse
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,16 +29,12 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
// softResponseLimit is the target maximum size of replies to data retrievals.
|
||||
softResponseLimit = 2 * 1024 * 1024
|
||||
|
||||
// estHeaderSize is the approximate size of an RLP encoded block header.
|
||||
estHeaderSize = 500
|
||||
|
||||
// maxHeadersServe is the maximum number of block headers to serve. This number
|
||||
// is there to limit the number of disk lookups.
|
||||
maxHeadersServe = 1024
|
||||
@@ -69,9 +65,6 @@ type Backend interface {
|
||||
// Chain retrieves the blockchain object to serve data.
|
||||
Chain() *core.BlockChain
|
||||
|
||||
// StateBloom retrieves the bloom filter - if any - for state trie nodes.
|
||||
StateBloom() *trie.SyncBloom
|
||||
|
||||
// TxPool retrieves the transaction pool object to serve data.
|
||||
TxPool() TxPool
|
||||
|
||||
@@ -96,7 +89,7 @@ type Backend interface {
|
||||
|
||||
// TxPool defines the methods needed by the protocol handler to serve transactions.
|
||||
type TxPool interface {
|
||||
// Get retrieves the the transaction from the local txpool with the given hash.
|
||||
// Get retrieves the transaction from the local txpool with the given hash.
|
||||
Get(hash common.Hash) *types.Transaction
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -91,9 +90,8 @@ func (b *testBackend) close() {
|
||||
b.chain.Stop()
|
||||
}
|
||||
|
||||
func (b *testBackend) Chain() *core.BlockChain { return b.chain }
|
||||
func (b *testBackend) StateBloom() *trie.SyncBloom { return nil }
|
||||
func (b *testBackend) TxPool() TxPool { return b.txpool }
|
||||
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 mainentance and handshakes. All that
|
||||
@@ -138,11 +136,13 @@ func testGetBlockHeaders(t *testing.T, protocol uint) {
|
||||
query *GetBlockHeadersPacket // 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 and number too
|
||||
// A single random block should be retrievable by hash
|
||||
{
|
||||
&GetBlockHeadersPacket{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
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1},
|
||||
[]common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
|
||||
},
|
||||
@@ -182,10 +182,15 @@ func testGetBlockHeaders(t *testing.T, protocol uint) {
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 0}, Amount: 1},
|
||||
[]common.Hash{backend.chain.GetBlockByNumber(0).Hash()},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64()}, Amount: 1},
|
||||
[]common.Hash{backend.chain.CurrentBlock().Hash()},
|
||||
},
|
||||
{ // If the peer requests a bit into the future, we deliver what we have
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64()}, Amount: 10},
|
||||
[]common.Hash{backend.chain.CurrentBlock().Hash()},
|
||||
},
|
||||
// Ensure protocol limits are honored
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
|
||||
@@ -282,7 +287,7 @@ func testGetBlockHeaders(t *testing.T, protocol uint) {
|
||||
RequestId: 456,
|
||||
BlockHeadersPacket: headers,
|
||||
}); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
t.Errorf("test %d by hash: headers mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+155
-40
@@ -21,6 +21,7 @@ import (
|
||||
"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"
|
||||
@@ -34,11 +35,22 @@ func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
response := answerGetBlockHeadersQuery(backend, query.GetBlockHeadersPacket, peer)
|
||||
return peer.ReplyBlockHeaders(query.RequestId, response)
|
||||
response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersPacket, peer)
|
||||
return peer.ReplyBlockHeadersRLP(query.RequestId, response)
|
||||
}
|
||||
|
||||
func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, peer *Peer) []*types.Header {
|
||||
// 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 *GetBlockHeadersPacket, 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 *GetBlockHeadersPacket, peer *Peer) []rlp.RawValue {
|
||||
hashMode := query.Origin.Hash != (common.Hash{})
|
||||
first := true
|
||||
maxNonCanonical := uint64(100)
|
||||
@@ -46,7 +58,7 @@ func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, p
|
||||
// Gather headers until the fetch or network limits is reached
|
||||
var (
|
||||
bytes common.StorageSize
|
||||
headers []*types.Header
|
||||
headers []rlp.RawValue
|
||||
unknown bool
|
||||
lookups int
|
||||
)
|
||||
@@ -58,22 +70,25 @@ func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, p
|
||||
if hashMode {
|
||||
if first {
|
||||
first = false
|
||||
origin = backend.Chain().GetHeaderByHash(query.Origin.Hash)
|
||||
origin = chain.GetHeaderByHash(query.Origin.Hash)
|
||||
if origin != nil {
|
||||
query.Origin.Number = origin.Number.Uint64()
|
||||
}
|
||||
} else {
|
||||
origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number)
|
||||
origin = chain.GetHeader(query.Origin.Hash, query.Origin.Number)
|
||||
}
|
||||
} else {
|
||||
origin = backend.Chain().GetHeaderByNumber(query.Origin.Number)
|
||||
origin = chain.GetHeaderByNumber(query.Origin.Number)
|
||||
}
|
||||
if origin == nil {
|
||||
break
|
||||
}
|
||||
headers = append(headers, origin)
|
||||
bytes += estHeaderSize
|
||||
|
||||
if rlpData, err := rlp.EncodeToBytes(origin); err != nil {
|
||||
log.Crit("Unable to decode 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:
|
||||
@@ -82,7 +97,7 @@ func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, p
|
||||
if ancestor == 0 {
|
||||
unknown = true
|
||||
} else {
|
||||
query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
|
||||
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:
|
||||
@@ -96,9 +111,9 @@ func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, p
|
||||
peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
|
||||
unknown = true
|
||||
} else {
|
||||
if header := backend.Chain().GetHeaderByNumber(next); header != nil {
|
||||
if header := chain.GetHeaderByNumber(next); header != nil {
|
||||
nextHash := header.Hash()
|
||||
expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
|
||||
expOldHash, _ := chain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
|
||||
if expOldHash == query.Origin.Hash {
|
||||
query.Origin.Hash, query.Origin.Number = nextHash, next
|
||||
} else {
|
||||
@@ -124,17 +139,82 @@ func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, p
|
||||
return headers
|
||||
}
|
||||
|
||||
func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersPacket) []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 handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
// Decode the block body retrieval message
|
||||
var query GetBlockBodiesPacket66
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
response := answerGetBlockBodiesQuery(backend, query.GetBlockBodiesPacket, peer)
|
||||
response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesPacket)
|
||||
return peer.ReplyBlockBodiesRLP(query.RequestId, response)
|
||||
}
|
||||
|
||||
func answerGetBlockBodiesQuery(backend Backend, query GetBlockBodiesPacket, peer *Peer) []rlp.RawValue {
|
||||
// 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 GetBlockBodiesPacket) []rlp.RawValue {
|
||||
// Gather blocks until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
@@ -145,7 +225,7 @@ func answerGetBlockBodiesQuery(backend Backend, query GetBlockBodiesPacket, peer
|
||||
lookups >= 2*maxBodiesServe {
|
||||
break
|
||||
}
|
||||
if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 {
|
||||
if data := chain.GetBodyRLP(hash); len(data) != 0 {
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
}
|
||||
@@ -159,11 +239,13 @@ func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
response := answerGetNodeDataQuery(backend, query.GetNodeDataPacket, peer)
|
||||
response := ServiceGetNodeDataQuery(backend.Chain(), query.GetNodeDataPacket)
|
||||
return peer.ReplyNodeData(query.RequestId, response)
|
||||
}
|
||||
|
||||
func answerGetNodeDataQuery(backend Backend, query GetNodeDataPacket, peer *Peer) [][]byte {
|
||||
// ServiceGetNodeDataQuery assembles the response to a node data query. It is
|
||||
// exposed to allow external packages to test protocol behavior.
|
||||
func ServiceGetNodeDataQuery(chain *core.BlockChain, query GetNodeDataPacket) [][]byte {
|
||||
// Gather state data until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
@@ -175,14 +257,10 @@ func answerGetNodeDataQuery(backend Backend, query GetNodeDataPacket, peer *Peer
|
||||
break
|
||||
}
|
||||
// Retrieve the requested state entry
|
||||
if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) {
|
||||
// Only lookup the trie node if there's chance that we actually have it
|
||||
continue
|
||||
}
|
||||
entry, err := backend.Chain().TrieNode(hash)
|
||||
entry, err := chain.TrieNode(hash)
|
||||
if len(entry) == 0 || err != nil {
|
||||
// Read the contract code with prefix only to save unnecessary lookups.
|
||||
entry, err = backend.Chain().ContractCodeWithPrefix(hash)
|
||||
entry, err = chain.ContractCodeWithPrefix(hash)
|
||||
}
|
||||
if err == nil && len(entry) > 0 {
|
||||
nodes = append(nodes, entry)
|
||||
@@ -198,11 +276,13 @@ func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
response := answerGetReceiptsQuery(backend, query.GetReceiptsPacket, peer)
|
||||
response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsPacket)
|
||||
return peer.ReplyReceiptsRLP(query.RequestId, response)
|
||||
}
|
||||
|
||||
func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer) []rlp.RawValue {
|
||||
// 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 GetReceiptsPacket) []rlp.RawValue {
|
||||
// Gather state data until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
@@ -214,9 +294,9 @@ func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer
|
||||
break
|
||||
}
|
||||
// Retrieve the requested block's receipts
|
||||
results := backend.Chain().GetReceiptsByHash(hash)
|
||||
results := chain.GetReceiptsByHash(hash)
|
||||
if results == nil {
|
||||
if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
|
||||
if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -277,9 +357,18 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId)
|
||||
|
||||
return backend.Handle(peer, &res.BlockHeadersPacket)
|
||||
metadata := func() interface{} {
|
||||
hashes := make([]common.Hash, len(res.BlockHeadersPacket))
|
||||
for i, header := range res.BlockHeadersPacket {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
return peer.dispatchResponse(&Response{
|
||||
id: res.RequestId,
|
||||
code: BlockHeadersMsg,
|
||||
Res: &res.BlockHeadersPacket,
|
||||
}, metadata)
|
||||
}
|
||||
|
||||
func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
@@ -288,9 +377,23 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, BlockBodiesMsg, res.RequestId)
|
||||
|
||||
return backend.Handle(peer, &res.BlockBodiesPacket)
|
||||
metadata := func() interface{} {
|
||||
var (
|
||||
txsHashes = make([]common.Hash, len(res.BlockBodiesPacket))
|
||||
uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket))
|
||||
)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
for i, body := range res.BlockBodiesPacket {
|
||||
txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher)
|
||||
uncleHashes[i] = types.CalcUncleHash(body.Uncles)
|
||||
}
|
||||
return [][]common.Hash{txsHashes, uncleHashes}
|
||||
}
|
||||
return peer.dispatchResponse(&Response{
|
||||
id: res.RequestId,
|
||||
code: BlockBodiesMsg,
|
||||
Res: &res.BlockBodiesPacket,
|
||||
}, metadata)
|
||||
}
|
||||
|
||||
func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
@@ -299,9 +402,11 @@ func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, NodeDataMsg, res.RequestId)
|
||||
|
||||
return backend.Handle(peer, &res.NodeDataPacket)
|
||||
return peer.dispatchResponse(&Response{
|
||||
id: res.RequestId,
|
||||
code: NodeDataMsg,
|
||||
Res: &res.NodeDataPacket,
|
||||
}, nil) // No post-processing, we're not using this packet anymore
|
||||
}
|
||||
|
||||
func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
@@ -310,9 +415,19 @@ func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error {
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, ReceiptsMsg, res.RequestId)
|
||||
|
||||
return backend.Handle(peer, &res.ReceiptsPacket)
|
||||
metadata := func() interface{} {
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
hashes := make([]common.Hash, len(res.ReceiptsPacket))
|
||||
for i, receipt := range res.ReceiptsPacket {
|
||||
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
return peer.dispatchResponse(&Response{
|
||||
id: res.RequestId,
|
||||
code: ReceiptsMsg,
|
||||
Res: &res.ReceiptsPacket,
|
||||
}, metadata)
|
||||
}
|
||||
|
||||
func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error {
|
||||
|
||||
+114
-52
@@ -84,6 +84,10 @@ type Peer struct {
|
||||
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 fulfilment
|
||||
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
|
||||
}
|
||||
@@ -102,6 +106,9 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Pe
|
||||
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{}),
|
||||
}
|
||||
@@ -109,6 +116,7 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Pe
|
||||
go peer.broadcastBlocks()
|
||||
go peer.broadcastTransactions()
|
||||
go peer.announceTransactions()
|
||||
go peer.dispatcher()
|
||||
|
||||
return peer
|
||||
}
|
||||
@@ -289,10 +297,10 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
||||
}
|
||||
|
||||
// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders.
|
||||
func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error {
|
||||
return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
BlockHeadersPacket: headers,
|
||||
func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersRLPPacket66{
|
||||
RequestId: id,
|
||||
BlockHeadersRLPPacket: headers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -323,94 +331,148 @@ func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error {
|
||||
|
||||
// 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) error {
|
||||
func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) {
|
||||
p.Log().Debug("Fetching single header", "hash", hash)
|
||||
id := rand.Uint64()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Hash: hash},
|
||||
Amount: uint64(1),
|
||||
Skip: uint64(0),
|
||||
Reverse: false,
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetBlockHeadersMsg,
|
||||
want: BlockHeadersMsg,
|
||||
data: &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
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) error {
|
||||
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()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Hash: origin},
|
||||
Amount: uint64(amount),
|
||||
Skip: uint64(skip),
|
||||
Reverse: reverse,
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetBlockHeadersMsg,
|
||||
want: BlockHeadersMsg,
|
||||
data: &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
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) error {
|
||||
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()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Number: origin},
|
||||
Amount: uint64(amount),
|
||||
Skip: uint64(skip),
|
||||
Reverse: reverse,
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetBlockHeadersMsg,
|
||||
want: BlockHeadersMsg,
|
||||
data: &GetBlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
GetBlockHeadersPacket: &GetBlockHeadersPacket{
|
||||
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) error {
|
||||
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()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetBlockBodiesMsg, BlockBodiesMsg, id)
|
||||
return p2p.Send(p.rw, GetBlockBodiesMsg, &GetBlockBodiesPacket66{
|
||||
RequestId: id,
|
||||
GetBlockBodiesPacket: hashes,
|
||||
})
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetBlockBodiesMsg,
|
||||
want: BlockBodiesMsg,
|
||||
data: &GetBlockBodiesPacket66{
|
||||
RequestId: id,
|
||||
GetBlockBodiesPacket: hashes,
|
||||
},
|
||||
}
|
||||
if err := p.dispatchRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// RequestNodeData fetches a batch of arbitrary data from a node's known state
|
||||
// data, corresponding to the specified hashes.
|
||||
func (p *Peer) RequestNodeData(hashes []common.Hash) error {
|
||||
func (p *Peer) RequestNodeData(hashes []common.Hash, sink chan *Response) (*Request, error) {
|
||||
p.Log().Debug("Fetching batch of state data", "count", len(hashes))
|
||||
id := rand.Uint64()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetNodeDataMsg, NodeDataMsg, id)
|
||||
return p2p.Send(p.rw, GetNodeDataMsg, &GetNodeDataPacket66{
|
||||
RequestId: id,
|
||||
GetNodeDataPacket: hashes,
|
||||
})
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetNodeDataMsg,
|
||||
want: NodeDataMsg,
|
||||
data: &GetNodeDataPacket66{
|
||||
RequestId: id,
|
||||
GetNodeDataPacket: 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) error {
|
||||
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()
|
||||
|
||||
requestTracker.Track(p.id, p.version, GetReceiptsMsg, ReceiptsMsg, id)
|
||||
return p2p.Send(p.rw, GetReceiptsMsg, &GetReceiptsPacket66{
|
||||
RequestId: id,
|
||||
GetReceiptsPacket: hashes,
|
||||
})
|
||||
req := &Request{
|
||||
id: id,
|
||||
sink: sink,
|
||||
code: GetReceiptsMsg,
|
||||
want: ReceiptsMsg,
|
||||
data: &GetReceiptsPacket66{
|
||||
RequestId: id,
|
||||
GetReceiptsPacket: hashes,
|
||||
},
|
||||
}
|
||||
if err := p.dispatchRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// RequestTxs fetches a batch of transactions from a remote node.
|
||||
|
||||
@@ -175,6 +175,16 @@ type BlockHeadersPacket66 struct {
|
||||
BlockHeadersPacket
|
||||
}
|
||||
|
||||
// BlockHeadersRLPPacket represents a block header response, to use when we already
|
||||
// have the headers rlp encoded.
|
||||
type BlockHeadersRLPPacket []rlp.RawValue
|
||||
|
||||
// BlockHeadersPacket represents a block header response over eth/66.
|
||||
type BlockHeadersRLPPacket66 struct {
|
||||
RequestId uint64
|
||||
BlockHeadersRLPPacket
|
||||
}
|
||||
|
||||
// NewBlockPacket is the network packet for the block propagation message.
|
||||
type NewBlockPacket struct {
|
||||
Block *types.Block
|
||||
|
||||
+297
-261
@@ -99,8 +99,8 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||
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)
|
||||
return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error {
|
||||
return Handle(backend, peer)
|
||||
})
|
||||
},
|
||||
NodeInfo: func() interface{} {
|
||||
@@ -116,21 +116,21 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||
return protocols
|
||||
}
|
||||
|
||||
// handle is the callback invoked to manage the life cycle of a `snap` peer.
|
||||
// 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 {
|
||||
func Handle(backend Backend, peer *Peer) error {
|
||||
for {
|
||||
if err := handleMessage(backend, peer); err != nil {
|
||||
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
|
||||
// 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 {
|
||||
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 {
|
||||
@@ -161,60 +161,10 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
if req.Bytes > softResponseLimit {
|
||||
req.Bytes = softResponseLimit
|
||||
}
|
||||
// Retrieve the requested state and bail out if non existent
|
||||
tr, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB())
|
||||
if err != nil {
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
|
||||
}
|
||||
it, err := backend.Chain().Snapshots().AccountIterator(req.Root, req.Origin)
|
||||
if err != nil {
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
|
||||
}
|
||||
// Iterate over the requested range and pile accounts up
|
||||
var (
|
||||
accounts []*AccountData
|
||||
size uint64
|
||||
last common.Hash
|
||||
)
|
||||
for it.Next() && size < req.Bytes {
|
||||
hash, account := it.Hash(), common.CopyBytes(it.Account())
|
||||
// Service the request, potentially returning nothing in case of errors
|
||||
accounts, proofs := ServiceGetAccountRangeQuery(backend.Chain(), &req)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
// Generate the Merkle proofs for the first and last account
|
||||
proof := light.NewNodeSet()
|
||||
if err := tr.Prove(req.Origin[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove account range", "origin", req.Origin, "err", err)
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
|
||||
}
|
||||
if last != (common.Hash{}) {
|
||||
if err := tr.Prove(last[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove account range", "last", last, "err", err)
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
|
||||
}
|
||||
}
|
||||
var proofs [][]byte
|
||||
for _, blob := range proof.NodeList() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
// Send back anything accumulated
|
||||
// Send back anything accumulated (or empty in case of errors)
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{
|
||||
ID: req.ID,
|
||||
Accounts: accounts,
|
||||
@@ -243,111 +193,10 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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 faulst annoy the local user
|
||||
// TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional)
|
||||
// Service the request, potentially returning nothing in case of errors
|
||||
slots, proofs := ServiceGetStorageRangesQuery(backend.Chain(), &req)
|
||||
|
||||
// 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.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
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 := backend.Chain().Snapshots().StorageIterator(req.Root, account, origin)
|
||||
if err != nil {
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Request started at a non-zero hash or was capped prematurely, add
|
||||
// the endpoint Merkle proofs
|
||||
accTrie, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB())
|
||||
if err != nil {
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
var acc types.StateAccount
|
||||
if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil {
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
stTrie, err := trie.New(acc.Root, backend.Chain().StateCache().TrieDB())
|
||||
if err != nil {
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
proof := light.NewNodeSet()
|
||||
if err := stTrie.Prove(origin[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err)
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
if last != (common.Hash{}) {
|
||||
if err := stTrie.Prove(last[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove storage range", "last", last, "err", err)
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
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
|
||||
}
|
||||
}
|
||||
// Send back anything accumulated
|
||||
// Send back anything accumulated (or empty in case of errors)
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{
|
||||
ID: req.ID,
|
||||
Slots: slots,
|
||||
@@ -378,31 +227,10 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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 == emptyCode {
|
||||
// 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 := backend.Chain().ContractCode(hash); err == nil {
|
||||
codes = append(codes, blob)
|
||||
bytes += uint64(len(blob))
|
||||
}
|
||||
if bytes > req.Bytes {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Send back anything accumulated
|
||||
// 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,
|
||||
@@ -424,80 +252,12 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
if req.Bytes > softResponseLimit {
|
||||
req.Bytes = softResponseLimit
|
||||
}
|
||||
// Make sure we have the state associated with the request
|
||||
triedb := backend.Chain().StateCache().TrieDB()
|
||||
|
||||
accTrie, err := trie.NewSecure(req.Root, triedb)
|
||||
// Service the request, potentially returning nothing in case of errors
|
||||
nodes, err := ServiceGetTrieNodesQuery(backend.Chain(), &req, start)
|
||||
if err != nil {
|
||||
// We don't have the requested state available, bail out
|
||||
return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID})
|
||||
return err
|
||||
}
|
||||
snap := backend.Chain().Snapshots().Snapshot(req.Root)
|
||||
if snap == nil {
|
||||
// We don't have the requested state snapshotted yet, bail out.
|
||||
// In reality we could still serve using the account and storage
|
||||
// tries only, but let's protect the node a bit while it's doing
|
||||
// snapshot generation.
|
||||
return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID})
|
||||
}
|
||||
// Retrieve trie nodes until the packet size limit is reached
|
||||
var (
|
||||
nodes [][]byte
|
||||
bytes uint64
|
||||
loads int // Trie hash expansions to cound database reads
|
||||
)
|
||||
for _, pathset := range req.Paths {
|
||||
switch len(pathset) {
|
||||
case 0:
|
||||
// Ensure we penalize invalid requests
|
||||
return 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.TryGetNode(pathset[0])
|
||||
loads += resolved // always account database reads, even for failures
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
nodes = append(nodes, blob)
|
||||
bytes += uint64(len(blob))
|
||||
|
||||
default:
|
||||
// Storage slots requested, open the storage trie and retrieve from there
|
||||
account, err := snap.Account(common.BytesToHash(pathset[0]))
|
||||
loads++ // always account database reads, even for failures
|
||||
if err != nil || account == nil {
|
||||
break
|
||||
}
|
||||
stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb)
|
||||
loads++ // always account database reads, even for failures
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, path := range pathset[1:] {
|
||||
blob, resolved, err := stTrie.TryGetNode(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
|
||||
}
|
||||
}
|
||||
// Send back anything accumulated
|
||||
// Send back anything accumulated (or empty in case of errors)
|
||||
return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{
|
||||
ID: req.ID,
|
||||
Nodes: nodes,
|
||||
@@ -518,6 +278,282 @@ func handleMessage(backend Backend, peer *Peer) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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(req.Root, chain.StateCache().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() && size < req.Bytes {
|
||||
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
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
// Generate the Merkle proofs for the first and last account
|
||||
proof := light.NewNodeSet()
|
||||
if err := tr.Prove(req.Origin[:], 0, 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[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove account range", "last", last, "err", err)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
var proofs [][]byte
|
||||
for _, blob := range proof.NodeList() {
|
||||
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 faulst 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.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Request started at a non-zero hash or was capped prematurely, add
|
||||
// the endpoint Merkle proofs
|
||||
accTrie, err := trie.New(req.Root, chain.StateCache().TrieDB())
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
var acc types.StateAccount
|
||||
if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
stTrie, err := trie.New(acc.Root, chain.StateCache().TrieDB())
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
proof := light.NewNodeSet()
|
||||
if err := stTrie.Prove(origin[:], 0, 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[:], 0, proof); err != nil {
|
||||
log.Warn("Failed to prove storage range", "last", last, "err", err)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
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 == emptyCode {
|
||||
// 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.ContractCode(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.StateCache().TrieDB()
|
||||
|
||||
accTrie, err := trie.NewSecure(req.Root, triedb)
|
||||
if err != nil {
|
||||
// We don't have the requested state available, bail out
|
||||
return nil, nil
|
||||
}
|
||||
snap := chain.Snapshots().Snapshot(req.Root)
|
||||
if snap == nil {
|
||||
// We don't have the requested state snapshotted yet, bail out.
|
||||
// In reality we could still serve using the account and storage
|
||||
// tries only, but let's protect the node a bit while it's doing
|
||||
// snapshot generation.
|
||||
return nil, nil
|
||||
}
|
||||
// Retrieve trie nodes until the packet size limit is reached
|
||||
var (
|
||||
nodes [][]byte
|
||||
bytes uint64
|
||||
loads int // Trie hash expansions to cound 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.TryGetNode(pathset[0])
|
||||
loads += resolved // always account database reads, even for failures
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
nodes = append(nodes, blob)
|
||||
bytes += uint64(len(blob))
|
||||
|
||||
default:
|
||||
// Storage slots requested, open the storage trie and retrieve from there
|
||||
account, err := snap.Account(common.BytesToHash(pathset[0]))
|
||||
loads++ // always account database reads, even for failures
|
||||
if err != nil || account == nil {
|
||||
break
|
||||
}
|
||||
stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb)
|
||||
loads++ // always account database reads, even for failures
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, path := range pathset[1:] {
|
||||
blob, resolved, err := stTrie.TryGetNode(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{}
|
||||
|
||||
@@ -33,9 +33,9 @@ type Peer struct {
|
||||
logger log.Logger // Contextual logger with the peer id injected
|
||||
}
|
||||
|
||||
// newPeer create a wrapper for a network connection and negotiated protocol
|
||||
// NewPeer create a wrapper for a network connection and negotiated protocol
|
||||
// version.
|
||||
func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
id := p.ID().String()
|
||||
return &Peer{
|
||||
id: id,
|
||||
@@ -46,6 +46,16 @@ func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
snap1 = 1
|
||||
SNAP1 = 1
|
||||
)
|
||||
|
||||
// ProtocolName is the official short name of the `snap` protocol used during
|
||||
@@ -36,11 +36,11 @@ const ProtocolName = "snap"
|
||||
|
||||
// ProtocolVersions are the supported versions of the `snap` protocol (first
|
||||
// is primary).
|
||||
var ProtocolVersions = []uint{snap1}
|
||||
var ProtocolVersions = []uint{SNAP1}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to
|
||||
// different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{snap1: 8}
|
||||
var protocolLengths = map[uint]uint64{SNAP1: 8}
|
||||
|
||||
// maxMessageSize is the maximum cap on the size of a protocol message.
|
||||
const maxMessageSize = 10 * 1024 * 1024
|
||||
|
||||
@@ -325,10 +325,10 @@ type healTask struct {
|
||||
codeTasks map[common.Hash]struct{} // Set of byte code tasks currently queued for retrieval
|
||||
}
|
||||
|
||||
// syncProgress is a database entry to allow suspending and resuming a snapshot state
|
||||
// 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 {
|
||||
type SyncProgress struct {
|
||||
Tasks []*accountTask // The suspended account tasks (contract tasks within)
|
||||
|
||||
// Status report during syncing phase
|
||||
@@ -342,12 +342,15 @@ type syncProgress struct {
|
||||
// Status report during healing phase
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -543,7 +546,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error {
|
||||
s.lock.Lock()
|
||||
s.root = root
|
||||
s.healer = &healTask{
|
||||
scheduler: state.NewStateSync(root, s.db, nil, s.onHealState),
|
||||
scheduler: state.NewStateSync(root, s.db, s.onHealState),
|
||||
trieTasks: make(map[common.Hash]trie.SyncPath),
|
||||
codeTasks: make(map[common.Hash]struct{}),
|
||||
}
|
||||
@@ -671,7 +674,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error {
|
||||
// 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
|
||||
var progress SyncProgress
|
||||
|
||||
if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil {
|
||||
if err := json.Unmarshal(status, &progress); err != nil {
|
||||
@@ -775,7 +778,7 @@ func (s *Syncer) saveSyncStatus() {
|
||||
}
|
||||
}
|
||||
// Store the actual progress markers
|
||||
progress := &syncProgress{
|
||||
progress := &SyncProgress{
|
||||
Tasks: s.tasks,
|
||||
AccountSynced: s.accountSynced,
|
||||
AccountBytes: s.accountBytes,
|
||||
@@ -795,6 +798,31 @@ func (s *Syncer) saveSyncStatus() {
|
||||
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()
|
||||
|
||||
progress := &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,
|
||||
}
|
||||
pending := new(SyncPending)
|
||||
if s.healer != nil {
|
||||
pending.TrienodeHeal = uint64(len(s.healer.trieTasks))
|
||||
pending.BytecodeHeal = uint64(len(s.healer.codeTasks))
|
||||
}
|
||||
return progress, pending
|
||||
}
|
||||
|
||||
// cleanAccountTasks removes account range retrieval tasks that have already been
|
||||
// completed.
|
||||
func (s *Syncer) cleanAccountTasks() {
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// stateAtBlock retrieves the state database associated with a certain block.
|
||||
// StateAtBlock retrieves the state database associated with a certain block.
|
||||
// If no state is locally available for the given block, a number of blocks
|
||||
// are attempted to be reexecuted to generate the desired state. The optional
|
||||
// base layer statedb can be passed then it's regarded as the statedb of the
|
||||
@@ -45,7 +45,7 @@ import (
|
||||
// storing trash persistently
|
||||
// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
|
||||
// it would be preferrable to start from a fresh state, if we have it on disk.
|
||||
func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
||||
func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
||||
var (
|
||||
current *types.Block
|
||||
database state.Database
|
||||
@@ -171,7 +171,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
|
||||
}
|
||||
// Lookup the statedb of parent block from the live database,
|
||||
// otherwise regenerate it on the flight.
|
||||
statedb, err := eth.stateAtBlock(parent, reexec, nil, true, false)
|
||||
statedb, err := eth.StateAtBlock(parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, vm.BlockContext{}, nil, err
|
||||
}
|
||||
|
||||
+16
-20
@@ -145,7 +145,10 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
if cs.doneCh != nil {
|
||||
return nil // Sync already running.
|
||||
}
|
||||
|
||||
// Disable the td based sync trigger after the transition
|
||||
if cs.handler.merger.TDDReached() {
|
||||
return nil
|
||||
}
|
||||
// Ensure we're at minimum peer count.
|
||||
minPeers := defaultMinSyncPeers
|
||||
if cs.forced {
|
||||
@@ -162,10 +165,7 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
return nil
|
||||
}
|
||||
mode, ourTD := cs.modeAndLocalHead()
|
||||
if mode == downloader.FastSync && atomic.LoadUint32(&cs.handler.snapSync) == 1 {
|
||||
// Fast sync via the snap protocol
|
||||
mode = downloader.SnapSync
|
||||
}
|
||||
|
||||
op := peerToSyncOp(mode, peer)
|
||||
if op.td.Cmp(ourTD) <= 0 {
|
||||
return nil // We're in sync.
|
||||
@@ -179,19 +179,19 @@ func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
|
||||
}
|
||||
|
||||
func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
|
||||
// If we're in fast sync mode, return that directly
|
||||
if atomic.LoadUint32(&cs.handler.fastSync) == 1 {
|
||||
// If we're in snap sync mode, return that directly
|
||||
if atomic.LoadUint32(&cs.handler.snapSync) == 1 {
|
||||
block := cs.handler.chain.CurrentFastBlock()
|
||||
td := cs.handler.chain.GetTd(block.Hash(), block.NumberU64())
|
||||
return downloader.FastSync, td
|
||||
return downloader.SnapSync, td
|
||||
}
|
||||
// We are probably in full sync, but we might have rewound to before the
|
||||
// fast sync pivot, check if we should reenable
|
||||
// snap sync pivot, check if we should reenable
|
||||
if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil {
|
||||
if head := cs.handler.chain.CurrentBlock(); head.NumberU64() < *pivot {
|
||||
block := cs.handler.chain.CurrentFastBlock()
|
||||
td := cs.handler.chain.GetTd(block.Hash(), block.NumberU64())
|
||||
return downloader.FastSync, td
|
||||
return downloader.SnapSync, td
|
||||
}
|
||||
}
|
||||
// Nope, we're really full syncing
|
||||
@@ -208,15 +208,15 @@ func (cs *chainSyncer) startSync(op *chainSyncOp) {
|
||||
|
||||
// doSync synchronizes the local blockchain with a remote peer.
|
||||
func (h *handler) doSync(op *chainSyncOp) error {
|
||||
if op.mode == downloader.FastSync || op.mode == downloader.SnapSync {
|
||||
// Before launch the fast sync, we have to ensure user uses the same
|
||||
if op.mode == downloader.SnapSync {
|
||||
// Before launch the snap sync, we have to ensure user uses the same
|
||||
// txlookup limit.
|
||||
// The main concern here is: during the fast sync Geth won't index the
|
||||
// The main concern here is: during the snap sync Geth won't index the
|
||||
// block(generate tx indices) before the HEAD-limit. But if user changes
|
||||
// the limit in the next fast sync(e.g. user kill Geth manually and
|
||||
// the limit in the next snap sync(e.g. user kill Geth manually and
|
||||
// restart) then it will be hard for Geth to figure out the oldest block
|
||||
// has been indexed. So here for the user-experience wise, it's non-optimal
|
||||
// that user can't change limit during the fast sync. If changed, Geth
|
||||
// that user can't change limit during the snap sync. If changed, Geth
|
||||
// will just blindly use the original one.
|
||||
limit := h.chain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
|
||||
@@ -226,15 +226,11 @@ func (h *handler) doSync(op *chainSyncOp) error {
|
||||
log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
|
||||
}
|
||||
}
|
||||
// Run the sync cycle, and disable fast sync if we're past the pivot block
|
||||
// Run the sync cycle, and disable snap sync if we're past the pivot block
|
||||
err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 {
|
||||
log.Info("Fast sync complete, auto disabling")
|
||||
atomic.StoreUint32(&h.fastSync, 0)
|
||||
}
|
||||
if atomic.LoadUint32(&h.snapSync) == 1 {
|
||||
log.Info("Snap sync complete, auto disabling")
|
||||
atomic.StoreUint32(&h.snapSync, 0)
|
||||
|
||||
+41
-24
@@ -23,57 +23,74 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// Tests that fast sync is disabled after a successful sync cycle.
|
||||
func TestFastSyncDisabling66(t *testing.T) { testFastSyncDisabling(t, eth.ETH66) }
|
||||
// Tests that snap sync is disabled after a successful sync cycle.
|
||||
func TestSnapSyncDisabling66(t *testing.T) { testSnapSyncDisabling(t, eth.ETH66, snap.SNAP1) }
|
||||
|
||||
// Tests that fast sync gets disabled as soon as a real block is successfully
|
||||
// Tests that snap sync gets disabled as soon as a real block is successfully
|
||||
// imported into the blockchain.
|
||||
func testFastSyncDisabling(t *testing.T, protocol uint) {
|
||||
func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create an empty handler and ensure it's in fast sync mode
|
||||
// Create an empty handler and ensure it's in snap sync mode
|
||||
empty := newTestHandler()
|
||||
if atomic.LoadUint32(&empty.handler.fastSync) == 0 {
|
||||
t.Fatalf("fast sync disabled on pristine blockchain")
|
||||
if atomic.LoadUint32(&empty.handler.snapSync) == 0 {
|
||||
t.Fatalf("snap sync disabled on pristine blockchain")
|
||||
}
|
||||
defer empty.close()
|
||||
|
||||
// Create a full handler and ensure fast sync ends up disabled
|
||||
// Create a full handler and ensure snap sync ends up disabled
|
||||
full := newTestHandlerWithBlocks(1024)
|
||||
if atomic.LoadUint32(&full.handler.fastSync) == 1 {
|
||||
t.Fatalf("fast sync not disabled on non-empty blockchain")
|
||||
if atomic.LoadUint32(&full.handler.snapSync) == 1 {
|
||||
t.Fatalf("snap sync not disabled on non-empty blockchain")
|
||||
}
|
||||
defer full.close()
|
||||
|
||||
// Sync up the two handlers
|
||||
emptyPipe, fullPipe := p2p.MsgPipe()
|
||||
defer emptyPipe.Close()
|
||||
defer fullPipe.Close()
|
||||
// Sync up the two handlers via both `eth` and `snap`
|
||||
caps := []p2p.Cap{{Name: "eth", Version: ethVer}, {Name: "snap", Version: snapVer}}
|
||||
|
||||
emptyPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), emptyPipe, empty.txpool)
|
||||
fullPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), fullPipe, full.txpool)
|
||||
defer emptyPeer.Close()
|
||||
defer fullPeer.Close()
|
||||
emptyPipeEth, fullPipeEth := p2p.MsgPipe()
|
||||
defer emptyPipeEth.Close()
|
||||
defer fullPipeEth.Close()
|
||||
|
||||
go empty.handler.runEthPeer(emptyPeer, func(peer *eth.Peer) error {
|
||||
emptyPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{1}, "", caps), emptyPipeEth, empty.txpool)
|
||||
fullPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{2}, "", caps), fullPipeEth, full.txpool)
|
||||
defer emptyPeerEth.Close()
|
||||
defer fullPeerEth.Close()
|
||||
|
||||
go empty.handler.runEthPeer(emptyPeerEth, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(empty.handler), peer)
|
||||
})
|
||||
go full.handler.runEthPeer(fullPeer, func(peer *eth.Peer) error {
|
||||
go full.handler.runEthPeer(fullPeerEth, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(full.handler), peer)
|
||||
})
|
||||
|
||||
emptyPipeSnap, fullPipeSnap := p2p.MsgPipe()
|
||||
defer emptyPipeSnap.Close()
|
||||
defer fullPipeSnap.Close()
|
||||
|
||||
emptyPeerSnap := snap.NewPeer(snapVer, p2p.NewPeer(enode.ID{1}, "", caps), emptyPipeSnap)
|
||||
fullPeerSnap := snap.NewPeer(snapVer, p2p.NewPeer(enode.ID{2}, "", caps), fullPipeSnap)
|
||||
|
||||
go empty.handler.runSnapExtension(emptyPeerSnap, func(peer *snap.Peer) error {
|
||||
return snap.Handle((*snapHandler)(empty.handler), peer)
|
||||
})
|
||||
go full.handler.runSnapExtension(fullPeerSnap, func(peer *snap.Peer) error {
|
||||
return snap.Handle((*snapHandler)(full.handler), peer)
|
||||
})
|
||||
// Wait a bit for the above handlers to start
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
|
||||
// Check that fast sync was disabled
|
||||
op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD())
|
||||
// Check that snap sync was disabled
|
||||
op := peerToSyncOp(downloader.SnapSync, empty.handler.peers.peerWithHighestTD())
|
||||
if err := empty.handler.doSync(op); err != nil {
|
||||
t.Fatal("sync failed:", err)
|
||||
}
|
||||
if atomic.LoadUint32(&empty.handler.fastSync) == 1 {
|
||||
t.Fatalf("fast sync not disabled after successful synchronisation")
|
||||
if atomic.LoadUint32(&empty.handler.snapSync) == 1 {
|
||||
t.Fatalf("snap sync not disabled after successful synchronisation")
|
||||
}
|
||||
}
|
||||
|
||||
+15
-14
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -166,7 +167,7 @@ func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber
|
||||
|
||||
// TraceConfig holds extra parameters to trace functions.
|
||||
type TraceConfig struct {
|
||||
*vm.LogConfig
|
||||
*logger.Config
|
||||
Tracer *string
|
||||
Timeout *string
|
||||
Reexec *uint64
|
||||
@@ -175,7 +176,7 @@ type TraceConfig struct {
|
||||
// TraceCallConfig is the config for traceCall API. It holds one more
|
||||
// field to override the state for tracing.
|
||||
type TraceCallConfig struct {
|
||||
*vm.LogConfig
|
||||
*logger.Config
|
||||
Tracer *string
|
||||
Timeout *string
|
||||
Reexec *uint64
|
||||
@@ -184,7 +185,7 @@ type TraceCallConfig struct {
|
||||
|
||||
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
||||
type StdTraceConfig struct {
|
||||
vm.LogConfig
|
||||
logger.Config
|
||||
Reexec *uint64
|
||||
TxHash common.Hash
|
||||
}
|
||||
@@ -670,11 +671,11 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||
}
|
||||
// Retrieve the tracing configurations, or use default values
|
||||
var (
|
||||
logConfig vm.LogConfig
|
||||
logConfig logger.Config
|
||||
txHash common.Hash
|
||||
)
|
||||
if config != nil {
|
||||
logConfig = config.LogConfig
|
||||
logConfig = config.Config
|
||||
txHash = config.TxHash
|
||||
}
|
||||
logConfig.Debug = true
|
||||
@@ -699,7 +700,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||
chainConfigCopy := new(params.ChainConfig)
|
||||
*chainConfigCopy = *chainConfig
|
||||
chainConfig = chainConfigCopy
|
||||
if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil {
|
||||
if berlin := config.Config.Overrides.BerlinBlock; berlin != nil {
|
||||
chainConfig.BerlinBlock = berlin
|
||||
canon = false
|
||||
}
|
||||
@@ -731,7 +732,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||
writer = bufio.NewWriter(dump)
|
||||
vmConf = vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewJSONLogger(&logConfig, writer),
|
||||
Tracer: logger.NewJSONLogger(&logConfig, writer),
|
||||
EnablePreimageRecording: true,
|
||||
}
|
||||
}
|
||||
@@ -848,10 +849,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||
var traceConfig *TraceConfig
|
||||
if config != nil {
|
||||
traceConfig = &TraceConfig{
|
||||
LogConfig: config.LogConfig,
|
||||
Tracer: config.Tracer,
|
||||
Timeout: config.Timeout,
|
||||
Reexec: config.Reexec,
|
||||
Config: config.Config,
|
||||
Tracer: config.Tracer,
|
||||
Timeout: config.Timeout,
|
||||
Reexec: config.Reexec,
|
||||
}
|
||||
}
|
||||
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig)
|
||||
@@ -869,7 +870,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
)
|
||||
switch {
|
||||
case config == nil:
|
||||
tracer = vm.NewStructLogger(nil)
|
||||
tracer = logger.NewStructLogger(nil)
|
||||
case config.Tracer != nil:
|
||||
// Define a meaningful timeout of a single transaction trace
|
||||
timeout := defaultTraceTimeout
|
||||
@@ -898,7 +899,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
}
|
||||
}
|
||||
default:
|
||||
tracer = vm.NewStructLogger(config.LogConfig)
|
||||
tracer = logger.NewStructLogger(config.Config)
|
||||
}
|
||||
// Run the transaction with tracing enabled.
|
||||
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
|
||||
@@ -913,7 +914,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
|
||||
// Depending on the tracer type, format and return the output.
|
||||
switch tracer := tracer.(type) {
|
||||
case *vm.StructLogger:
|
||||
case *logger.StructLogger:
|
||||
// If the result contains a revert reason, return it.
|
||||
returnVal := fmt.Sprintf("%x", result.Return())
|
||||
if len(result.Revert()) > 0 {
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
"result": {
|
||||
"calls": [
|
||||
{
|
||||
"error": "invalid opcode: opcode 0xfe not defined",
|
||||
"error": "invalid opcode: INVALID",
|
||||
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
||||
"gas": "0x75fe3",
|
||||
"gasUsed": "0x75fe3",
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
"result": {
|
||||
"calls": [
|
||||
{
|
||||
"error": "invalid opcode: opcode 0xfe not defined",
|
||||
"error": "invalid opcode: INVALID",
|
||||
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
||||
"gas": "0x75fe3",
|
||||
"gasUsed": "0x75fe3",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,12 +71,12 @@
|
||||
opinfo["ops"] = [];
|
||||
this.stack.push(opinfo);
|
||||
break;
|
||||
case "RETURN":
|
||||
case "RETURN": case "REVERT":
|
||||
var out = log.stack.peek(0).valueOf();
|
||||
var outsize = log.stack.peek(1).valueOf();
|
||||
frame.return = log.memory.slice(out, out + outsize);
|
||||
break;
|
||||
case "STOP": case "SUICIDE":
|
||||
case "STOP": case "SELFDESTRUCT":
|
||||
frame.return = log.memory.slice(0, 0);
|
||||
break;
|
||||
case "JUMPDEST":
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
// accessList is an accumulator for the set of accounts and storage slots an EVM
|
||||
// contract execution touches.
|
||||
type accessList map[common.Address]accessListSlots
|
||||
|
||||
// accessListSlots is an accumulator for the set of storage slots within a single
|
||||
// contract that an EVM contract execution touches.
|
||||
type accessListSlots map[common.Hash]struct{}
|
||||
|
||||
// newAccessList creates a new accessList.
|
||||
func newAccessList() accessList {
|
||||
return make(map[common.Address]accessListSlots)
|
||||
}
|
||||
|
||||
// addAddress adds an address to the accesslist.
|
||||
func (al accessList) addAddress(address common.Address) {
|
||||
// Set address if not previously present
|
||||
if _, present := al[address]; !present {
|
||||
al[address] = make(map[common.Hash]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// addSlot adds a storage slot to the accesslist.
|
||||
func (al accessList) addSlot(address common.Address, slot common.Hash) {
|
||||
// Set address if not previously present
|
||||
al.addAddress(address)
|
||||
|
||||
// Set the slot on the surely existent storage set
|
||||
al[address][slot] = struct{}{}
|
||||
}
|
||||
|
||||
// equal checks if the content of the current access list is the same as the
|
||||
// content of the other one.
|
||||
func (al accessList) equal(other accessList) bool {
|
||||
// Cross reference the accounts first
|
||||
if len(al) != len(other) {
|
||||
return false
|
||||
}
|
||||
for addr := range al {
|
||||
if _, ok := other[addr]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for addr := range other {
|
||||
if _, ok := al[addr]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Accounts match, cross reference the storage slots too
|
||||
for addr, slots := range al {
|
||||
otherslots := other[addr]
|
||||
|
||||
if len(slots) != len(otherslots) {
|
||||
return false
|
||||
}
|
||||
for hash := range slots {
|
||||
if _, ok := otherslots[hash]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for hash := range otherslots {
|
||||
if _, ok := slots[hash]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// accesslist converts the accesslist to a types.AccessList.
|
||||
func (al accessList) accessList() types.AccessList {
|
||||
acl := make(types.AccessList, 0, len(al))
|
||||
for addr, slots := range al {
|
||||
tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}
|
||||
for slot := range slots {
|
||||
tuple.StorageKeys = append(tuple.StorageKeys, slot)
|
||||
}
|
||||
acl = append(acl, tuple)
|
||||
}
|
||||
return acl
|
||||
}
|
||||
|
||||
// AccessListTracer is a tracer that accumulates touched accounts and storage
|
||||
// slots into an internal set.
|
||||
type AccessListTracer struct {
|
||||
excl map[common.Address]struct{} // Set of account to exclude from the list
|
||||
list accessList // Set of accounts and storage slots touched
|
||||
}
|
||||
|
||||
// NewAccessListTracer creates a new tracer that can generate AccessLists.
|
||||
// An optional AccessList can be specified to occupy slots and addresses in
|
||||
// the resulting accesslist.
|
||||
func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
|
||||
excl := map[common.Address]struct{}{
|
||||
from: {}, to: {},
|
||||
}
|
||||
for _, addr := range precompiles {
|
||||
excl[addr] = struct{}{}
|
||||
}
|
||||
list := newAccessList()
|
||||
for _, al := range acl {
|
||||
if _, ok := excl[al.Address]; !ok {
|
||||
list.addAddress(al.Address)
|
||||
}
|
||||
for _, slot := range al.StorageKeys {
|
||||
list.addSlot(al.Address, slot)
|
||||
}
|
||||
}
|
||||
return &AccessListTracer{
|
||||
excl: excl,
|
||||
list: list,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccessListTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
|
||||
func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
stackData := stack.Data()
|
||||
stackLen := len(stackData)
|
||||
if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 {
|
||||
slot := common.Hash(stackData[stackLen-1].Bytes32())
|
||||
a.list.addSlot(scope.Contract.Address(), slot)
|
||||
}
|
||||
if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
|
||||
addr := common.Address(stackData[stackLen-1].Bytes20())
|
||||
if _, ok := a.excl[addr]; !ok {
|
||||
a.list.addAddress(addr)
|
||||
}
|
||||
}
|
||||
if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 {
|
||||
addr := common.Address(stackData[stackLen-2].Bytes20())
|
||||
if _, ok := a.excl[addr]; !ok {
|
||||
a.list.addAddress(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
|
||||
|
||||
func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// AccessList returns the current accesslist maintained by the tracer.
|
||||
func (a *AccessListTracer) AccessList() types.AccessList {
|
||||
return a.list.accessList()
|
||||
}
|
||||
|
||||
// Equal returns if the content of two access list traces are equal.
|
||||
func (a *AccessListTracer) Equal(other *AccessListTracer) bool {
|
||||
return a.list.equal(other.list)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var _ = (*structLogMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||
type StructLog struct {
|
||||
Pc uint64 `json:"pc"`
|
||||
Op vm.OpCode `json:"op"`
|
||||
Gas math.HexOrDecimal64 `json:"gas"`
|
||||
GasCost math.HexOrDecimal64 `json:"gasCost"`
|
||||
Memory hexutil.Bytes `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData hexutil.Bytes `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
OpName string `json:"opName"`
|
||||
ErrorString string `json:"error"`
|
||||
}
|
||||
var enc StructLog
|
||||
enc.Pc = s.Pc
|
||||
enc.Op = s.Op
|
||||
enc.Gas = math.HexOrDecimal64(s.Gas)
|
||||
enc.GasCost = math.HexOrDecimal64(s.GasCost)
|
||||
enc.Memory = s.Memory
|
||||
enc.MemorySize = s.MemorySize
|
||||
enc.Stack = s.Stack
|
||||
enc.ReturnData = s.ReturnData
|
||||
enc.Storage = s.Storage
|
||||
enc.Depth = s.Depth
|
||||
enc.RefundCounter = s.RefundCounter
|
||||
enc.Err = s.Err
|
||||
enc.OpName = s.OpName()
|
||||
enc.ErrorString = s.ErrorString()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||
type StructLog struct {
|
||||
Pc *uint64 `json:"pc"`
|
||||
Op *vm.OpCode `json:"op"`
|
||||
Gas *math.HexOrDecimal64 `json:"gas"`
|
||||
GasCost *math.HexOrDecimal64 `json:"gasCost"`
|
||||
Memory *hexutil.Bytes `json:"memory"`
|
||||
MemorySize *int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData *hexutil.Bytes `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth *int `json:"depth"`
|
||||
RefundCounter *uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
var dec StructLog
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Pc != nil {
|
||||
s.Pc = *dec.Pc
|
||||
}
|
||||
if dec.Op != nil {
|
||||
s.Op = *dec.Op
|
||||
}
|
||||
if dec.Gas != nil {
|
||||
s.Gas = uint64(*dec.Gas)
|
||||
}
|
||||
if dec.GasCost != nil {
|
||||
s.GasCost = uint64(*dec.GasCost)
|
||||
}
|
||||
if dec.Memory != nil {
|
||||
s.Memory = *dec.Memory
|
||||
}
|
||||
if dec.MemorySize != nil {
|
||||
s.MemorySize = *dec.MemorySize
|
||||
}
|
||||
if dec.Stack != nil {
|
||||
s.Stack = dec.Stack
|
||||
}
|
||||
if dec.ReturnData != nil {
|
||||
s.ReturnData = *dec.ReturnData
|
||||
}
|
||||
if dec.Storage != nil {
|
||||
s.Storage = dec.Storage
|
||||
}
|
||||
if dec.Depth != nil {
|
||||
s.Depth = *dec.Depth
|
||||
}
|
||||
if dec.RefundCounter != nil {
|
||||
s.RefundCounter = *dec.RefundCounter
|
||||
}
|
||||
if dec.Err != nil {
|
||||
s.Err = dec.Err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Storage represents a contract's storage.
|
||||
type Storage map[common.Hash]common.Hash
|
||||
|
||||
// Copy duplicates the current storage.
|
||||
func (s Storage) Copy() Storage {
|
||||
cpy := make(Storage)
|
||||
for key, value := range s {
|
||||
cpy[key] = value
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// Config are the configuration options for structured logger the EVM
|
||||
type Config struct {
|
||||
EnableMemory bool // enable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
DisableStorage bool // disable storage capture
|
||||
EnableReturnData bool // enable return data capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
// Chain overrides, can be used to execute a trace using future fork rules
|
||||
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||
|
||||
// StructLog is emitted to the EVM each cycle and lists information about the current internal state
|
||||
// prior to the execution of the statement.
|
||||
type StructLog struct {
|
||||
Pc uint64 `json:"pc"`
|
||||
Op vm.OpCode `json:"op"`
|
||||
Gas uint64 `json:"gas"`
|
||||
GasCost uint64 `json:"gasCost"`
|
||||
Memory []byte `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// overrides for gencodec
|
||||
type structLogMarshaling struct {
|
||||
Gas math.HexOrDecimal64
|
||||
GasCost math.HexOrDecimal64
|
||||
Memory hexutil.Bytes
|
||||
ReturnData hexutil.Bytes
|
||||
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
|
||||
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
|
||||
}
|
||||
|
||||
// OpName formats the operand name in a human-readable format.
|
||||
func (s *StructLog) OpName() string {
|
||||
return s.Op.String()
|
||||
}
|
||||
|
||||
// ErrorString formats the log's error as a string.
|
||||
func (s *StructLog) ErrorString() string {
|
||||
if s.Err != nil {
|
||||
return s.Err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StructLogger is an EVM state logger and implements EVMLogger.
|
||||
//
|
||||
// StructLogger can capture state based on the given Log configuration and also keeps
|
||||
// a track record of modified storage which is used in reporting snapshots of the
|
||||
// contract their storage.
|
||||
type StructLogger struct {
|
||||
cfg Config
|
||||
env *vm.EVM
|
||||
|
||||
storage map[common.Address]Storage
|
||||
logs []StructLog
|
||||
output []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// NewStructLogger returns a new logger
|
||||
func NewStructLogger(cfg *Config) *StructLogger {
|
||||
logger := &StructLogger{
|
||||
storage: make(map[common.Address]Storage),
|
||||
}
|
||||
if cfg != nil {
|
||||
logger.cfg = *cfg
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Reset clears the data held by the logger.
|
||||
func (l *StructLogger) Reset() {
|
||||
l.storage = make(map[common.Address]Storage)
|
||||
l.output = make([]byte, 0)
|
||||
l.logs = l.logs[:0]
|
||||
l.err = nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||
//
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
contract := scope.Contract
|
||||
// check if already accumulated the specified number of logs
|
||||
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
|
||||
return
|
||||
}
|
||||
// Copy a snapshot of the current memory state to a new buffer
|
||||
var mem []byte
|
||||
if l.cfg.EnableMemory {
|
||||
mem = make([]byte, len(memory.Data()))
|
||||
copy(mem, memory.Data())
|
||||
}
|
||||
// Copy a snapshot of the current stack state to a new buffer
|
||||
var stck []uint256.Int
|
||||
if !l.cfg.DisableStack {
|
||||
stck = make([]uint256.Int, len(stack.Data()))
|
||||
for i, item := range stack.Data() {
|
||||
stck[i] = item
|
||||
}
|
||||
}
|
||||
stackData := stack.Data()
|
||||
stackLen := len(stackData)
|
||||
// Copy a snapshot of the current storage to a new container
|
||||
var storage Storage
|
||||
if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
|
||||
// initialise new changed values storage container for this contract
|
||||
// if not present.
|
||||
if l.storage[contract.Address()] == nil {
|
||||
l.storage[contract.Address()] = make(Storage)
|
||||
}
|
||||
// capture SLOAD opcodes and record the read entry in the local storage
|
||||
if op == vm.SLOAD && stackLen >= 1 {
|
||||
var (
|
||||
address = common.Hash(stackData[stackLen-1].Bytes32())
|
||||
value = l.env.StateDB.GetState(contract.Address(), address)
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
} else if op == vm.SSTORE && stackLen >= 2 {
|
||||
// capture SSTORE opcodes and record the written entry in the local storage.
|
||||
var (
|
||||
value = common.Hash(stackData[stackLen-2].Bytes32())
|
||||
address = common.Hash(stackData[stackLen-1].Bytes32())
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
}
|
||||
}
|
||||
var rdata []byte
|
||||
if l.cfg.EnableReturnData {
|
||||
rdata = make([]byte, len(rData))
|
||||
copy(rdata, rData)
|
||||
}
|
||||
// create a new snapshot of the EVM.
|
||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
|
||||
l.logs = append(l.logs, log)
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault
|
||||
// while running an opcode.
|
||||
func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
l.output = output
|
||||
l.err = err
|
||||
if l.cfg.Debug {
|
||||
fmt.Printf("0x%x\n", output)
|
||||
if err != nil {
|
||||
fmt.Printf(" error: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// StructLogs returns the captured log entries.
|
||||
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
|
||||
|
||||
// Error returns the VM error captured by the trace.
|
||||
func (l *StructLogger) Error() error { return l.err }
|
||||
|
||||
// Output returns the VM return value captured by the trace.
|
||||
func (l *StructLogger) Output() []byte { return l.output }
|
||||
|
||||
// WriteTrace writes a formatted trace to the given writer
|
||||
func WriteTrace(writer io.Writer, logs []StructLog) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
|
||||
if log.Err != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %v", log.Err)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
|
||||
if len(log.Stack) > 0 {
|
||||
fmt.Fprintln(writer, "Stack:")
|
||||
for i := len(log.Stack) - 1; i >= 0; i-- {
|
||||
fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
|
||||
}
|
||||
}
|
||||
if len(log.Memory) > 0 {
|
||||
fmt.Fprintln(writer, "Memory:")
|
||||
fmt.Fprint(writer, hex.Dump(log.Memory))
|
||||
}
|
||||
if len(log.Storage) > 0 {
|
||||
fmt.Fprintln(writer, "Storage:")
|
||||
for h, item := range log.Storage {
|
||||
fmt.Fprintf(writer, "%x: %x\n", h, item)
|
||||
}
|
||||
}
|
||||
if len(log.ReturnData) > 0 {
|
||||
fmt.Fprintln(writer, "ReturnData:")
|
||||
fmt.Fprint(writer, hex.Dump(log.ReturnData))
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLogs writes vm logs in a readable format to the given writer
|
||||
func WriteLogs(writer io.Writer, logs []*types.Log) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
|
||||
|
||||
for i, topic := range log.Topics {
|
||||
fmt.Fprintf(writer, "%08d %x\n", i, topic)
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, hex.Dump(log.Data))
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
type mdLogger struct {
|
||||
out io.Writer
|
||||
cfg *Config
|
||||
env *vm.EVM
|
||||
}
|
||||
|
||||
// NewMarkdownLogger creates a logger which outputs information in a format adapted
|
||||
// for human readability, and is also a valid markdown table
|
||||
func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
|
||||
l := &mdLogger{out: writer, cfg: cfg}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &Config{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
if !create {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
}
|
||||
|
||||
fmt.Fprintf(t.out, `
|
||||
| Pc | Op | Cost | Stack | RStack | Refund |
|
||||
|-------|-------------|------|-----------|-----------|---------|
|
||||
`)
|
||||
}
|
||||
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
|
||||
|
||||
if !t.cfg.DisableStack {
|
||||
// format stack
|
||||
var a []string
|
||||
for _, elem := range stack.Data() {
|
||||
a = append(a, elem.Hex())
|
||||
}
|
||||
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||
fmt.Fprintf(t.out, "%10v |", b)
|
||||
}
|
||||
fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund())
|
||||
fmt.Fprintln(t.out, "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.out, "Error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
|
||||
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
type JSONLogger struct {
|
||||
encoder *json.Encoder
|
||||
cfg *Config
|
||||
env *vm.EVM
|
||||
}
|
||||
|
||||
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
||||
// into the provided stream.
|
||||
func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger {
|
||||
l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &Config{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
// TODO: Add rData to this interface as well
|
||||
l.CaptureState(pc, op, gas, cost, scope, nil, depth, err)
|
||||
}
|
||||
|
||||
// CaptureState outputs state information on the logger.
|
||||
func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
|
||||
log := StructLog{
|
||||
Pc: pc,
|
||||
Op: op,
|
||||
Gas: gas,
|
||||
GasCost: cost,
|
||||
MemorySize: memory.Len(),
|
||||
Depth: depth,
|
||||
RefundCounter: l.env.StateDB.GetRefund(),
|
||||
Err: err,
|
||||
}
|
||||
if l.cfg.EnableMemory {
|
||||
log.Memory = memory.Data()
|
||||
}
|
||||
if !l.cfg.DisableStack {
|
||||
log.Stack = stack.Data()
|
||||
}
|
||||
if l.cfg.EnableReturnData {
|
||||
log.ReturnData = rData
|
||||
}
|
||||
l.encoder.Encode(log)
|
||||
}
|
||||
|
||||
// CaptureEnd is triggered at end of execution.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
type endLog struct {
|
||||
Output string `json:"output"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
Time time.Duration `json:"time"`
|
||||
Err string `json:"error,omitempty"`
|
||||
}
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg})
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
type dummyContractRef struct {
|
||||
calledForEach bool
|
||||
}
|
||||
|
||||
func (dummyContractRef) Address() common.Address { return common.Address{} }
|
||||
func (dummyContractRef) Value() *big.Int { return new(big.Int) }
|
||||
func (dummyContractRef) SetCode(common.Hash, []byte) {}
|
||||
func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
|
||||
d.calledForEach = true
|
||||
}
|
||||
func (d *dummyContractRef) SubBalance(amount *big.Int) {}
|
||||
func (d *dummyContractRef) AddBalance(amount *big.Int) {}
|
||||
func (d *dummyContractRef) SetBalance(*big.Int) {}
|
||||
func (d *dummyContractRef) SetNonce(uint64) {}
|
||||
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
|
||||
|
||||
type dummyStatedb struct {
|
||||
state.StateDB
|
||||
}
|
||||
|
||||
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
|
||||
func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} }
|
||||
func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
|
||||
|
||||
func TestStoreCapture(t *testing.T) {
|
||||
var (
|
||||
logger = NewStructLogger(nil)
|
||||
env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: logger})
|
||||
contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
|
||||
)
|
||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||
var index common.Hash
|
||||
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
|
||||
_, err := env.Interpreter().Run(contract, []byte{}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(logger.storage[contract.Address()]) == 0 {
|
||||
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
|
||||
len(logger.storage[contract.Address()]))
|
||||
}
|
||||
exp := common.BigToHash(big.NewInt(1))
|
||||
if logger.storage[contract.Address()][index] != exp {
|
||||
t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index])
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"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/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
@@ -95,7 +96,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer := vm.NewStructLogger(&vm.LogConfig{
|
||||
tracer := logger.NewStructLogger(&logger.Config{
|
||||
Debug: false,
|
||||
//DisableStorage: true,
|
||||
//EnableMemory: false,
|
||||
|
||||
Reference in New Issue
Block a user