core, eth: split eth package, implement snap protocol (#21482)
This commit splits the eth package, separating the handling of eth and snap protocols. It also includes the capability to run snap sync (https://github.com/ethereum/devp2p/blob/master/caps/snap.md) , but does not enable it by default. Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
parent
00d10e610f
commit
017831dd5b
@ -25,7 +25,6 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
@ -143,7 +142,6 @@ func version(ctx *cli.Context) error {
|
||||
fmt.Println("Git Commit Date:", gitDate)
|
||||
}
|
||||
fmt.Println("Architecture:", runtime.GOARCH)
|
||||
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
||||
fmt.Println("Go Version:", runtime.Version())
|
||||
fmt.Println("Operating System:", runtime.GOOS)
|
||||
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
||||
|
@ -187,7 +187,7 @@ var (
|
||||
defaultSyncMode = eth.DefaultConfig.SyncMode
|
||||
SyncModeFlag = TextMarshalerFlag{
|
||||
Name: "syncmode",
|
||||
Usage: `Blockchain sync mode ("fast", "full", or "light")`,
|
||||
Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`,
|
||||
Value: &defaultSyncMode,
|
||||
}
|
||||
GCModeFlag = cli.StringFlag{
|
||||
@ -1555,8 +1555,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
|
||||
}
|
||||
if !ctx.GlobalIsSet(SnapshotFlag.Name) {
|
||||
cfg.TrieCleanCache += cfg.SnapshotCache
|
||||
cfg.SnapshotCache = 0 // Disabled
|
||||
// If snap-sync is requested, this flag is also required
|
||||
if cfg.SyncMode == downloader.SnapSync {
|
||||
log.Info("Snap sync requested, enabling --snapshot")
|
||||
ctx.Set(SnapshotFlag.Name, "true")
|
||||
} else {
|
||||
cfg.TrieCleanCache += cfg.SnapshotCache
|
||||
cfg.SnapshotCache = 0 // Disabled
|
||||
}
|
||||
}
|
||||
if ctx.GlobalIsSet(DocRootFlag.Name) {
|
||||
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
|
||||
@ -1585,16 +1591,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(NoDiscoverFlag.Name) {
|
||||
cfg.DiscoveryURLs = []string{}
|
||||
cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
|
||||
} else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
|
||||
urls := ctx.GlobalString(DNSDiscoveryFlag.Name)
|
||||
if urls == "" {
|
||||
cfg.DiscoveryURLs = []string{}
|
||||
cfg.EthDiscoveryURLs = []string{}
|
||||
} else {
|
||||
cfg.DiscoveryURLs = SplitAndTrim(urls)
|
||||
cfg.EthDiscoveryURLs = SplitAndTrim(urls)
|
||||
}
|
||||
}
|
||||
|
||||
// Override any default configs for hard coded networks.
|
||||
switch {
|
||||
case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
|
||||
@ -1676,16 +1681,20 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
|
||||
// no URLs are set.
|
||||
func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) {
|
||||
if cfg.DiscoveryURLs != nil {
|
||||
if cfg.EthDiscoveryURLs != nil {
|
||||
return // already set through flags/config
|
||||
}
|
||||
|
||||
protocol := "all"
|
||||
if cfg.SyncMode == downloader.LightSync {
|
||||
protocol = "les"
|
||||
}
|
||||
if url := params.KnownDNSNetwork(genesis, protocol); url != "" {
|
||||
cfg.DiscoveryURLs = []string{url}
|
||||
cfg.EthDiscoveryURLs = []string{url}
|
||||
}
|
||||
if cfg.SyncMode == downloader.SnapSync {
|
||||
if url := params.KnownDNSNetwork(genesis, "snap"); url != "" {
|
||||
cfg.SnapDiscoveryURLs = []string{url}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -659,12 +659,8 @@ func (bc *BlockChain) CurrentBlock() *types.Block {
|
||||
return bc.currentBlock.Load().(*types.Block)
|
||||
}
|
||||
|
||||
// Snapshot returns the blockchain snapshot tree. This method is mainly used for
|
||||
// testing, to make it possible to verify the snapshot after execution.
|
||||
//
|
||||
// Warning: There are no guarantees about the safety of using the returned 'snap' if the
|
||||
// blockchain is simultaneously importing blocks, so take care.
|
||||
func (bc *BlockChain) Snapshot() *snapshot.Tree {
|
||||
// Snapshots returns the blockchain snapshot tree.
|
||||
func (bc *BlockChain) Snapshots() *snapshot.Tree {
|
||||
return bc.snaps
|
||||
}
|
||||
|
||||
|
@ -751,7 +751,7 @@ func testSnapshot(t *testing.T, tt *snapshotTest) {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
chain.InsertChain(newBlocks)
|
||||
chain.Snapshot().Cap(newBlocks[len(newBlocks)-1].Root(), 0)
|
||||
chain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0)
|
||||
|
||||
// Simulate the blockchain crash
|
||||
// Don't call chain.Stop here, so that no snapshot
|
||||
|
@ -84,6 +84,15 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
|
||||
return ID{Hash: checksumToBytes(hash), Next: next}
|
||||
}
|
||||
|
||||
// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance.
|
||||
func NewIDWithChain(chain Blockchain) ID {
|
||||
return NewID(
|
||||
chain.Config(),
|
||||
chain.Genesis().Hash(),
|
||||
chain.CurrentHeader().Number.Uint64(),
|
||||
)
|
||||
}
|
||||
|
||||
// NewFilter creates a filter that returns if a fork ID should be rejected or not
|
||||
// based on the local chain's status.
|
||||
func NewFilter(chain Blockchain) Filter {
|
||||
|
@ -175,3 +175,24 @@ func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) {
|
||||
log.Crit("Failed to remove snapshot recovery number", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSanpshotSyncStatus retrieves the serialized sync status saved at shutdown.
|
||||
func ReadSanpshotSyncStatus(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(snapshotSyncStatusKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown.
|
||||
func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) {
|
||||
if err := db.Put(snapshotSyncStatusKey, status); err != nil {
|
||||
log.Crit("Failed to store snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last
|
||||
// shutdown
|
||||
func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(snapshotSyncStatusKey); err != nil {
|
||||
log.Crit("Failed to remove snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ var (
|
||||
// snapshotRecoveryKey tracks the snapshot recovery marker across restarts.
|
||||
snapshotRecoveryKey = []byte("SnapshotRecovery")
|
||||
|
||||
// snapshotSyncStatusKey tracks the snapshot sync status across restarts.
|
||||
snapshotSyncStatusKey = []byte("SnapshotSyncStatus")
|
||||
|
||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||
txIndexTailKey = []byte("TransactionIndexTail")
|
||||
|
||||
|
@ -241,7 +241,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||
if acc.Root != emptyRoot {
|
||||
storeTrie, err := trie.NewSecure(acc.Root, dl.triedb)
|
||||
if err != nil {
|
||||
log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err)
|
||||
log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err)
|
||||
abort := <-dl.genAbort
|
||||
abort <- stats
|
||||
return
|
||||
|
@ -314,14 +314,19 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
// GetProof returns the MerkleProof for a given Account
|
||||
func (s *StateDB) GetProof(a common.Address) ([][]byte, error) {
|
||||
// GetProof returns the Merkle proof for a given account.
|
||||
func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) {
|
||||
return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes()))
|
||||
}
|
||||
|
||||
// GetProofByHash returns the Merkle proof for a given account.
|
||||
func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof)
|
||||
err := s.trie.Prove(addrHash[:], 0, &proof)
|
||||
return proof, err
|
||||
}
|
||||
|
||||
// GetStorageProof returns the StorageProof for given key
|
||||
// GetStorageProof returns the Merkle proof for given storage slot.
|
||||
func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
trie := s.StorageTrie(a)
|
||||
@ -332,6 +337,17 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
|
||||
return proof, err
|
||||
}
|
||||
|
||||
// GetStorageProofByHash returns the Merkle proof for given storage slot.
|
||||
func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
trie := s.StorageTrie(a)
|
||||
if trie == nil {
|
||||
return proof, errors.New("storage trie for requested address does not exist")
|
||||
}
|
||||
err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||
return proof, err
|
||||
}
|
||||
|
||||
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
||||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
|
@ -56,7 +56,7 @@ func (b *EthAPIBackend) CurrentBlock() *types.Block {
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) SetHead(number uint64) {
|
||||
b.eth.protocolManager.downloader.Cancel()
|
||||
b.eth.handler.downloader.Cancel()
|
||||
b.eth.blockchain.SetHead(number)
|
||||
}
|
||||
|
||||
@ -272,10 +272,6 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader {
|
||||
return b.eth.Downloader()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) ProtocolVersion() int {
|
||||
return b.eth.EthVersion()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
||||
return b.gpo.SuggestPrice(ctx)
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 }
|
||||
|
||||
func TestAccountRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil)
|
||||
state, _ = state.New(common.Hash{}, statedb, nil)
|
||||
@ -126,6 +128,8 @@ func TestAccountRange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEmptyAccountRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
statedb = state.NewDatabase(rawdb.NewMemoryDatabase())
|
||||
state, _ = state.New(common.Hash{}, statedb, nil)
|
||||
@ -142,6 +146,8 @@ func TestEmptyAccountRange(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStorageRangeAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a state where account 0x010000... has a few storage entries.
|
||||
var (
|
||||
state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
|
@ -40,6 +40,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
@ -48,7 +50,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
@ -59,10 +60,11 @@ type Ethereum struct {
|
||||
config *Config
|
||||
|
||||
// Handlers
|
||||
txPool *core.TxPool
|
||||
blockchain *core.BlockChain
|
||||
protocolManager *ProtocolManager
|
||||
dialCandidates enode.Iterator
|
||||
txPool *core.TxPool
|
||||
blockchain *core.BlockChain
|
||||
handler *handler
|
||||
ethDialCandidates enode.Iterator
|
||||
snapDialCandidates enode.Iterator
|
||||
|
||||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
@ -145,7 +147,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
|
||||
if bcVersion != nil {
|
||||
dbVer = fmt.Sprintf("%d", *bcVersion)
|
||||
}
|
||||
log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer)
|
||||
log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer)
|
||||
|
||||
if !config.SkipBcVersionCheck {
|
||||
if bcVersion != nil && *bcVersion > core.BlockChainVersion {
|
||||
@ -196,7 +198,17 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
|
||||
if checkpoint == nil {
|
||||
checkpoint = params.TrustedCheckpoints[genesisHash]
|
||||
}
|
||||
if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil {
|
||||
if eth.handler, err = newHandler(&handlerConfig{
|
||||
Database: chainDb,
|
||||
Chain: eth.blockchain,
|
||||
TxPool: eth.txPool,
|
||||
Network: config.NetworkId,
|
||||
Sync: config.SyncMode,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
EventMux: eth.eventMux,
|
||||
Checkpoint: checkpoint,
|
||||
Whitelist: config.Whitelist,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
|
||||
@ -209,13 +221,16 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
|
||||
}
|
||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||
|
||||
eth.dialCandidates, err = eth.setupDiscovery()
|
||||
eth.ethDialCandidates, err = setupDiscovery(eth.config.EthDiscoveryURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eth.snapDialCandidates, err = setupDiscovery(eth.config.SnapDiscoveryURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start the RPC service
|
||||
eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion())
|
||||
eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer)
|
||||
|
||||
// Register the backend on the node
|
||||
stack.RegisterAPIs(eth.APIs())
|
||||
@ -310,7 +325,7 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux),
|
||||
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "miner",
|
||||
@ -473,7 +488,7 @@ func (s *Ethereum) StartMining(threads int) error {
|
||||
}
|
||||
// If mining is started, we can disable the transaction rejection mechanism
|
||||
// introduced to speed sync times.
|
||||
atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
|
||||
atomic.StoreUint32(&s.handler.acceptTxs, 1)
|
||||
|
||||
go s.miner.Start(eb)
|
||||
}
|
||||
@ -504,21 +519,17 @@ func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
||||
func (s *Ethereum) Engine() consensus.Engine { return s.engine }
|
||||
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
||||
func (s *Ethereum) IsListening() bool { return true } // Always listening
|
||||
func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) }
|
||||
func (s *Ethereum) NetVersion() uint64 { return s.networkID }
|
||||
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
|
||||
func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 }
|
||||
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) ArchiveMode() bool { return s.config.NoPruning }
|
||||
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
||||
|
||||
// Protocols returns all the currently configured
|
||||
// network protocols to start.
|
||||
func (s *Ethereum) Protocols() []p2p.Protocol {
|
||||
protos := make([]p2p.Protocol, len(ProtocolVersions))
|
||||
for i, vsn := range ProtocolVersions {
|
||||
protos[i] = s.protocolManager.makeProtocol(vsn)
|
||||
protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
|
||||
protos[i].DialCandidates = s.dialCandidates
|
||||
protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates)
|
||||
if s.config.SnapshotCache > 0 {
|
||||
protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...)
|
||||
}
|
||||
return protos
|
||||
}
|
||||
@ -526,7 +537,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol {
|
||||
// Start implements node.Lifecycle, starting all internal goroutines needed by the
|
||||
// Ethereum protocol implementation.
|
||||
func (s *Ethereum) Start() error {
|
||||
s.startEthEntryUpdate(s.p2pServer.LocalNode())
|
||||
eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
|
||||
|
||||
// Start the bloom bits servicing goroutines
|
||||
s.startBloomHandlers(params.BloomBitsBlocks)
|
||||
@ -540,7 +551,7 @@ func (s *Ethereum) Start() error {
|
||||
maxPeers -= s.config.LightPeers
|
||||
}
|
||||
// Start the networking layer and the light server if requested
|
||||
s.protocolManager.Start(maxPeers)
|
||||
s.handler.Start(maxPeers)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -548,7 +559,7 @@ func (s *Ethereum) Start() error {
|
||||
// Ethereum protocol.
|
||||
func (s *Ethereum) Stop() error {
|
||||
// Stop all the peer-related stuff first.
|
||||
s.protocolManager.Stop()
|
||||
s.handler.Stop()
|
||||
|
||||
// Then stop everything else.
|
||||
s.bloomIndexer.Close()
|
||||
@ -560,5 +571,6 @@ func (s *Ethereum) Stop() error {
|
||||
rawdb.PopUncleanShutdownMarker(s.chainDb)
|
||||
s.chainDb.Close()
|
||||
s.eventMux.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -115,7 +115,8 @@ type Config struct {
|
||||
|
||||
// This can be set to list of enrtree:// URLs which will be queried for
|
||||
// for nodes to connect to.
|
||||
DiscoveryURLs []string
|
||||
EthDiscoveryURLs []string
|
||||
SnapDiscoveryURLs []string
|
||||
|
||||
NoPruning bool // Whether to disable pruning and flush everything to disk
|
||||
NoPrefetch bool // Whether to disable prefetching and only load state on demand
|
||||
|
@ -63,11 +63,12 @@ func (eth *Ethereum) currentEthEntry() *ethEntry {
|
||||
eth.blockchain.CurrentHeader().Number.Uint64())}
|
||||
}
|
||||
|
||||
// setupDiscovery creates the node discovery source for the eth protocol.
|
||||
func (eth *Ethereum) setupDiscovery() (enode.Iterator, error) {
|
||||
if len(eth.config.DiscoveryURLs) == 0 {
|
||||
// setupDiscovery creates the node discovery source for the `eth` and `snap`
|
||||
// protocols.
|
||||
func setupDiscovery(urls []string) (enode.Iterator, error) {
|
||||
if len(urls) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
client := dnsdisc.NewClient(dnsdisc.Config{})
|
||||
return client.NewIterator(eth.config.DiscoveryURLs...)
|
||||
return client.NewIterator(urls...)
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@ -38,7 +39,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
|
||||
MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request
|
||||
MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
|
||||
MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly
|
||||
@ -89,7 +89,7 @@ var (
|
||||
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
||||
errCanceled = errors.New("syncing canceled (requested)")
|
||||
errNoSyncActive = errors.New("no sync active")
|
||||
errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 63)")
|
||||
errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 64)")
|
||||
)
|
||||
|
||||
type Downloader struct {
|
||||
@ -131,20 +131,22 @@ type Downloader struct {
|
||||
ancientLimit uint64 // The maximum block number which can be regarded as ancient data.
|
||||
|
||||
// Channels
|
||||
headerCh chan dataPack // [eth/62] Channel receiving inbound block headers
|
||||
bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies
|
||||
receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts
|
||||
bodyWakeCh chan bool // [eth/62] Channel to signal the block body fetcher of new tasks
|
||||
receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks
|
||||
headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks
|
||||
headerCh chan dataPack // Channel receiving inbound block headers
|
||||
bodyCh chan dataPack // Channel receiving inbound block bodies
|
||||
receiptCh chan dataPack // Channel receiving inbound receipts
|
||||
bodyWakeCh chan bool // Channel to signal the block body fetcher of new tasks
|
||||
receiptWakeCh chan bool // Channel to signal the receipt fetcher of new tasks
|
||||
headerProcCh chan []*types.Header // Channel to feed the header processor new tasks
|
||||
|
||||
// State sync
|
||||
pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root
|
||||
pivotLock sync.RWMutex // Lock protecting pivot header reads from updates
|
||||
|
||||
snapSync bool // Whether to run state sync over the snap protocol
|
||||
SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now
|
||||
stateSyncStart chan *stateSync
|
||||
trackStateReq chan *stateReq
|
||||
stateCh chan dataPack // [eth/63] Channel receiving inbound node state data
|
||||
stateCh chan dataPack // Channel receiving inbound node state data
|
||||
|
||||
// Cancellation and termination
|
||||
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
|
||||
@ -237,6 +239,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom,
|
||||
headerProcCh: make(chan []*types.Header, 1),
|
||||
quitCh: make(chan struct{}),
|
||||
stateCh: make(chan dataPack),
|
||||
SnapSyncer: snap.NewSyncer(stateDb, stateBloom),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
syncStatsState: stateSyncStats{
|
||||
processed: rawdb.ReadFastTrieProgress(stateDb),
|
||||
@ -286,19 +289,16 @@ func (d *Downloader) Synchronising() bool {
|
||||
return atomic.LoadInt32(&d.synchronising) > 0
|
||||
}
|
||||
|
||||
// SyncBloomContains tests if the syncbloom filter contains the given hash:
|
||||
// - false: the bloom definitely does not contain hash
|
||||
// - true: the bloom maybe contains hash
|
||||
//
|
||||
// While the bloom is being initialized (or is closed), all queries will return true.
|
||||
func (d *Downloader) SyncBloomContains(hash []byte) bool {
|
||||
return d.stateBloom == nil || d.stateBloom.Contains(hash)
|
||||
}
|
||||
|
||||
// RegisterPeer injects a new download peer into the set of block source to be
|
||||
// used for fetching hashes and blocks from.
|
||||
func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error {
|
||||
logger := log.New("peer", id)
|
||||
func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error {
|
||||
var logger log.Logger
|
||||
if len(id) < 16 {
|
||||
// Tests use short IDs, don't choke on them
|
||||
logger = log.New("peer", id)
|
||||
} else {
|
||||
logger = log.New("peer", id[:16])
|
||||
}
|
||||
logger.Trace("Registering sync peer")
|
||||
if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil {
|
||||
logger.Error("Failed to register sync peer", "err", err)
|
||||
@ -310,7 +310,7 @@ func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error {
|
||||
}
|
||||
|
||||
// RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer.
|
||||
func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) error {
|
||||
func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error {
|
||||
return d.RegisterPeer(id, version, &lightPeerWrapper{peer})
|
||||
}
|
||||
|
||||
@ -319,7 +319,13 @@ func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) e
|
||||
// the queue.
|
||||
func (d *Downloader) UnregisterPeer(id string) error {
|
||||
// Unregister the peer from the active peer set and revoke any fetch tasks
|
||||
logger := log.New("peer", id)
|
||||
var logger log.Logger
|
||||
if len(id) < 16 {
|
||||
// Tests use short IDs, don't choke on them
|
||||
logger = log.New("peer", id)
|
||||
} else {
|
||||
logger = log.New("peer", id[:16])
|
||||
}
|
||||
logger.Trace("Unregistering sync peer")
|
||||
if err := d.peers.Unregister(id); err != nil {
|
||||
logger.Error("Failed to unregister sync peer", "err", err)
|
||||
@ -381,6 +387,16 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
|
||||
if mode == FullSync && d.stateBloom != nil {
|
||||
d.stateBloom.Close()
|
||||
}
|
||||
// If snap sync was requested, create the snap scheduler and switch to fast
|
||||
// sync mode. Long term we could drop fast sync or merge the two together,
|
||||
// but until snap becomes prevalent, we should support both. TODO(karalabe).
|
||||
if mode == SnapSync {
|
||||
if !d.snapSync {
|
||||
log.Warn("Enabling snapshot sync prototype")
|
||||
d.snapSync = true
|
||||
}
|
||||
mode = FastSync
|
||||
}
|
||||
// Reset the queue, peer set and wake channels to clean any internal leftover state
|
||||
d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems)
|
||||
d.peers.Reset()
|
||||
@ -443,8 +459,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
|
||||
d.mux.Post(DoneEvent{latest})
|
||||
}
|
||||
}()
|
||||
if p.version < 63 {
|
||||
return errTooOld
|
||||
if p.version < 64 {
|
||||
return fmt.Errorf("%w, peer version: %d", errTooOld, p.version)
|
||||
}
|
||||
mode := d.getMode()
|
||||
|
||||
@ -1910,27 +1926,53 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
||||
|
||||
// DeliverHeaders injects a new batch of block headers received from a remote
|
||||
// node into the download schedule.
|
||||
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) {
|
||||
return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
|
||||
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error {
|
||||
return d.deliver(d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
|
||||
}
|
||||
|
||||
// DeliverBodies injects a new batch of block bodies received from a remote node.
|
||||
func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) (err error) {
|
||||
return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
|
||||
func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error {
|
||||
return d.deliver(d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
|
||||
}
|
||||
|
||||
// DeliverReceipts injects a new batch of receipts received from a remote node.
|
||||
func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) (err error) {
|
||||
return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
|
||||
func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error {
|
||||
return d.deliver(d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
|
||||
}
|
||||
|
||||
// DeliverNodeData injects a new batch of node state data received from a remote node.
|
||||
func (d *Downloader) DeliverNodeData(id string, data [][]byte) (err error) {
|
||||
return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
|
||||
func (d *Downloader) DeliverNodeData(id string, data [][]byte) error {
|
||||
return d.deliver(d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
|
||||
}
|
||||
|
||||
// DeliverSnapPacket is invoked from a peer's message handler when it transmits a
|
||||
// data packet for the local node to consume.
|
||||
func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error {
|
||||
switch packet := packet.(type) {
|
||||
case *snap.AccountRangePacket:
|
||||
hashes, accounts, err := packet.Unpack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof)
|
||||
|
||||
case *snap.StorageRangesPacket:
|
||||
hashset, slotset := packet.Unpack()
|
||||
return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof)
|
||||
|
||||
case *snap.ByteCodesPacket:
|
||||
return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes)
|
||||
|
||||
case *snap.TrieNodesPacket:
|
||||
return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected snap packet type: %T", packet)
|
||||
}
|
||||
}
|
||||
|
||||
// deliver injects a new batch of data received from a remote node.
|
||||
func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
|
||||
func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
|
||||
// Update the delivery metrics for both good and failed deliveries
|
||||
inMeter.Mark(int64(packet.Items()))
|
||||
defer func() {
|
||||
|
@ -390,7 +390,7 @@ func (dl *downloadTester) Rollback(hashes []common.Hash) {
|
||||
}
|
||||
|
||||
// newPeer registers a new block download source into the downloader.
|
||||
func (dl *downloadTester) newPeer(id string, version int, chain *testChain) error {
|
||||
func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) error {
|
||||
dl.lock.Lock()
|
||||
defer dl.lock.Unlock()
|
||||
|
||||
@ -518,8 +518,6 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
|
||||
// Tests that simple synchronization against a canonical chain works correctly.
|
||||
// In this test common ancestor lookup should be short circuited and not require
|
||||
// binary searching.
|
||||
func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) }
|
||||
func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) }
|
||||
func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) }
|
||||
func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) }
|
||||
func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonicalSynchronisation(t, 65, FullSync) }
|
||||
@ -528,7 +526,7 @@ func TestCanonicalSynchronisation65Light(t *testing.T) {
|
||||
testCanonicalSynchronisation(t, 65, LightSync)
|
||||
}
|
||||
|
||||
func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -547,14 +545,12 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that if a large batch of blocks are being downloaded, it is throttled
|
||||
// until the cached blocks are retrieved.
|
||||
func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) }
|
||||
func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) }
|
||||
func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) }
|
||||
func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) }
|
||||
func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) }
|
||||
func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) }
|
||||
|
||||
func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
tester := newTester()
|
||||
|
||||
@ -632,15 +628,13 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||
// Tests that simple synchronization against a forked chain works correctly. In
|
||||
// this test common ancestor lookup should *not* be short circuited, and a full
|
||||
// binary search should be executed.
|
||||
func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) }
|
||||
func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) }
|
||||
func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) }
|
||||
func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) }
|
||||
func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) }
|
||||
func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) }
|
||||
func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) }
|
||||
|
||||
func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -665,15 +659,13 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that synchronising against a much shorter but much heavyer fork works
|
||||
// corrently and is not dropped.
|
||||
func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) }
|
||||
func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastSync) }
|
||||
func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) }
|
||||
func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) }
|
||||
func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) }
|
||||
func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) }
|
||||
func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) }
|
||||
|
||||
func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -700,15 +692,13 @@ func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
// Tests that chain forks are contained within a certain interval of the current
|
||||
// chain head, ensuring that malicious peers cannot waste resources by feeding
|
||||
// long dead chains.
|
||||
func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) }
|
||||
func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, FastSync) }
|
||||
func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) }
|
||||
func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) }
|
||||
func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) }
|
||||
func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) }
|
||||
func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) }
|
||||
|
||||
func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -734,15 +724,13 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
// Tests that chain forks are contained within a certain interval of the current
|
||||
// chain head for short but heavy forks too. These are a bit special because they
|
||||
// take different ancestor lookup paths.
|
||||
func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) }
|
||||
func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) }
|
||||
func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) }
|
||||
func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) }
|
||||
func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) }
|
||||
func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) }
|
||||
func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) }
|
||||
|
||||
func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
tester := newTester()
|
||||
|
||||
@ -786,15 +774,13 @@ func TestInactiveDownloader63(t *testing.T) {
|
||||
}
|
||||
|
||||
// Tests that a canceled download wipes all previously accumulated state.
|
||||
func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) }
|
||||
func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) }
|
||||
func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) }
|
||||
func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) }
|
||||
func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) }
|
||||
func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) }
|
||||
func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) }
|
||||
|
||||
func testCancel(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -819,15 +805,13 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) {
|
||||
}
|
||||
|
||||
// Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
|
||||
func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) }
|
||||
func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) }
|
||||
func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) }
|
||||
func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) }
|
||||
func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) }
|
||||
func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) }
|
||||
func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) }
|
||||
|
||||
func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -849,15 +833,13 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that synchronisations behave well in multi-version protocol environments
|
||||
// and not wreak havoc on other nodes in the network.
|
||||
func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) }
|
||||
func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) }
|
||||
func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) }
|
||||
func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) }
|
||||
func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) }
|
||||
func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) }
|
||||
func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) }
|
||||
|
||||
func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -888,15 +870,13 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that if a block is empty (e.g. header only), no body request should be
|
||||
// made, and instead the header should be assembled into a whole block in itself.
|
||||
func TestEmptyShortCircuit63Full(t *testing.T) { testEmptyShortCircuit(t, 63, FullSync) }
|
||||
func TestEmptyShortCircuit63Fast(t *testing.T) { testEmptyShortCircuit(t, 63, FastSync) }
|
||||
func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) }
|
||||
func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) }
|
||||
func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) }
|
||||
func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) }
|
||||
func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) }
|
||||
|
||||
func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -942,15 +922,13 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that headers are enqueued continuously, preventing malicious nodes from
|
||||
// stalling the downloader by feeding gapped header chains.
|
||||
func TestMissingHeaderAttack63Full(t *testing.T) { testMissingHeaderAttack(t, 63, FullSync) }
|
||||
func TestMissingHeaderAttack63Fast(t *testing.T) { testMissingHeaderAttack(t, 63, FastSync) }
|
||||
func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) }
|
||||
func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) }
|
||||
func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) }
|
||||
func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) }
|
||||
func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) }
|
||||
|
||||
func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -974,15 +952,13 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that if requested headers are shifted (i.e. first is missing), the queue
|
||||
// detects the invalid numbering.
|
||||
func TestShiftedHeaderAttack63Full(t *testing.T) { testShiftedHeaderAttack(t, 63, FullSync) }
|
||||
func TestShiftedHeaderAttack63Fast(t *testing.T) { testShiftedHeaderAttack(t, 63, FastSync) }
|
||||
func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) }
|
||||
func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) }
|
||||
func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) }
|
||||
func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) }
|
||||
func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) }
|
||||
|
||||
func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1011,11 +987,10 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
// Tests that upon detecting an invalid header, the recent ones are rolled back
|
||||
// for various failure scenarios. Afterwards a full sync is attempted to make
|
||||
// sure no state was corrupted.
|
||||
func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) }
|
||||
func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) }
|
||||
func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) }
|
||||
|
||||
func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1103,15 +1078,13 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that a peer advertising a high TD doesn't get to stall the downloader
|
||||
// afterwards by not sending any useful hashes.
|
||||
func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) }
|
||||
func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) }
|
||||
func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) }
|
||||
func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) }
|
||||
func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) }
|
||||
func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) }
|
||||
func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) }
|
||||
|
||||
func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1125,11 +1098,10 @@ func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||
}
|
||||
|
||||
// Tests that misbehaving peers are disconnected, whilst behaving ones are not.
|
||||
func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) }
|
||||
func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) }
|
||||
func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) }
|
||||
|
||||
func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
|
||||
func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Define the disconnection requirement for individual hash fetch errors
|
||||
@ -1179,15 +1151,13 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
|
||||
|
||||
// Tests that synchronisation progress (origin block number, current block number
|
||||
// and highest block number) is tracked and updated correctly.
|
||||
func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) }
|
||||
func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) }
|
||||
func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) }
|
||||
func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) }
|
||||
func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) }
|
||||
func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) }
|
||||
func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) }
|
||||
|
||||
func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1263,21 +1233,19 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync
|
||||
// Tests that synchronisation progress (origin block number and highest block
|
||||
// number) is tracked and updated correctly in case of a fork (or manual head
|
||||
// revertal).
|
||||
func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) }
|
||||
func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) }
|
||||
func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) }
|
||||
func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) }
|
||||
func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) }
|
||||
func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) }
|
||||
func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) }
|
||||
|
||||
func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
defer tester.terminate()
|
||||
chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHashFetch)
|
||||
chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHashFetch)
|
||||
chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch)
|
||||
chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch)
|
||||
|
||||
// Set a sync init hook to catch progress changes
|
||||
starting := make(chan struct{})
|
||||
@ -1339,15 +1307,13 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
// Tests that if synchronisation is aborted due to some failure, then the progress
|
||||
// origin is not updated in the next sync cycle, as it should be considered the
|
||||
// continuation of the previous sync and not a new instance.
|
||||
func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) }
|
||||
func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) }
|
||||
func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) }
|
||||
func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) }
|
||||
func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) }
|
||||
func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) }
|
||||
func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) }
|
||||
|
||||
func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1412,15 +1378,13 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// Tests that if an attacker fakes a chain height, after the attack is detected,
|
||||
// the progress height is successfully reduced at the next sync invocation.
|
||||
func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) }
|
||||
func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) }
|
||||
func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) }
|
||||
func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) }
|
||||
func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) }
|
||||
func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) }
|
||||
func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) }
|
||||
|
||||
func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
tester := newTester()
|
||||
@ -1489,31 +1453,15 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||
|
||||
// This test reproduces an issue where unexpected deliveries would
|
||||
// block indefinitely if they arrived at the right time.
|
||||
func TestDeliverHeadersHang(t *testing.T) {
|
||||
func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) }
|
||||
func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) }
|
||||
func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) }
|
||||
func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) }
|
||||
func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) }
|
||||
|
||||
func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
protocol int
|
||||
syncMode SyncMode
|
||||
}{
|
||||
{63, FullSync},
|
||||
{63, FastSync},
|
||||
{64, FullSync},
|
||||
{64, FastSync},
|
||||
{64, LightSync},
|
||||
{65, FullSync},
|
||||
{65, FastSync},
|
||||
{65, LightSync},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDeliverHeadersHang(t, tc.protocol, tc.syncMode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) {
|
||||
master := newTester()
|
||||
defer master.terminate()
|
||||
chain := testChainBase.shorten(15)
|
||||
@ -1664,15 +1612,13 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
||||
|
||||
// Tests that peers below a pre-configured checkpoint block are prevented from
|
||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||
func TestCheckpointEnforcement63Full(t *testing.T) { testCheckpointEnforcement(t, 63, FullSync) }
|
||||
func TestCheckpointEnforcement63Fast(t *testing.T) { testCheckpointEnforcement(t, 63, FastSync) }
|
||||
func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) }
|
||||
func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) }
|
||||
func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) }
|
||||
func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) }
|
||||
func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) }
|
||||
|
||||
func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) {
|
||||
func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a new tester with a particular hard coded checkpoint block
|
||||
|
@ -24,7 +24,8 @@ 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 head
|
||||
FastSync // Quickly download the headers, full sync only at the chain
|
||||
SnapSync // Download the chain and the state via compact snashots
|
||||
LightSync // Download only the headers and terminate afterwards
|
||||
)
|
||||
|
||||
@ -39,6 +40,8 @@ func (mode SyncMode) String() string {
|
||||
return "full"
|
||||
case FastSync:
|
||||
return "fast"
|
||||
case SnapSync:
|
||||
return "snap"
|
||||
case LightSync:
|
||||
return "light"
|
||||
default:
|
||||
@ -52,6 +55,8 @@ func (mode SyncMode) MarshalText() ([]byte, error) {
|
||||
return []byte("full"), nil
|
||||
case FastSync:
|
||||
return []byte("fast"), nil
|
||||
case SnapSync:
|
||||
return []byte("snap"), nil
|
||||
case LightSync:
|
||||
return []byte("light"), nil
|
||||
default:
|
||||
@ -65,6 +70,8 @@ func (mode *SyncMode) UnmarshalText(text []byte) error {
|
||||
*mode = FullSync
|
||||
case "fast":
|
||||
*mode = FastSync
|
||||
case "snap":
|
||||
*mode = SnapSync
|
||||
case "light":
|
||||
*mode = LightSync
|
||||
default:
|
||||
|
@ -69,7 +69,7 @@ type peerConnection struct {
|
||||
|
||||
peer Peer
|
||||
|
||||
version int // Eth protocol version number to switch strategies
|
||||
version uint // Eth protocol version number to switch strategies
|
||||
log log.Logger // Contextual logger to add extra infos to peer logs
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@ -112,7 +112,7 @@ func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error {
|
||||
}
|
||||
|
||||
// newPeerConnection creates a new downloader peer.
|
||||
func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection {
|
||||
func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection {
|
||||
return &peerConnection{
|
||||
id: id,
|
||||
lacking: make(map[common.Hash]struct{}),
|
||||
@ -457,7 +457,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.headerThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
return ps.idlePeers(64, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
|
||||
@ -471,7 +471,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.blockThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
return ps.idlePeers(64, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
|
||||
@ -485,7 +485,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.receiptThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
return ps.idlePeers(64, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
|
||||
@ -499,13 +499,13 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.stateThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
return ps.idlePeers(64, 65, 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 measure throughput.
|
||||
func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) {
|
||||
func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
|
@ -113,24 +113,24 @@ type queue struct {
|
||||
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
||||
|
||||
// Headers are "special", they download in batches, supported by a skeleton chain
|
||||
headerHead common.Hash // [eth/62] Hash of the last queued header to verify order
|
||||
headerTaskPool map[uint64]*types.Header // [eth/62] Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
||||
headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for
|
||||
headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable
|
||||
headerPendPool map[string]*fetchRequest // [eth/62] Currently pending header retrieval operations
|
||||
headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers
|
||||
headerProced int // [eth/62] Number of headers already processed from the results
|
||||
headerOffset uint64 // [eth/62] Number of the first header in the result cache
|
||||
headerContCh chan bool // [eth/62] Channel to notify when header download finishes
|
||||
headerHead common.Hash // Hash of the last queued header to verify order
|
||||
headerTaskPool map[uint64]*types.Header // Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
||||
headerTaskQueue *prque.Prque // Priority queue of the skeleton indexes to fetch the filling headers for
|
||||
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
|
||||
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
|
||||
|
||||
// All data retrievals below are based on an already assembles header chain
|
||||
blockTaskPool map[common.Hash]*types.Header // [eth/62] Pending block (body) retrieval tasks, mapping hashes to headers
|
||||
blockTaskQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the blocks (bodies) for
|
||||
blockPendPool map[string]*fetchRequest // [eth/62] Currently pending block (body) retrieval operations
|
||||
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
|
||||
|
||||
receiptTaskPool map[common.Hash]*types.Header // [eth/63] Pending receipt retrieval tasks, mapping hashes to headers
|
||||
receiptTaskQueue *prque.Prque // [eth/63] Priority queue of the headers to fetch the receipts for
|
||||
receiptPendPool map[string]*fetchRequest // [eth/63] Currently pending receipt retrieval operations
|
||||
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
|
||||
|
||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||
@ -690,6 +690,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
var logger log.Logger
|
||||
if len(id) < 16 {
|
||||
// Tests use short IDs, don't choke on them
|
||||
logger = log.New("peer", id)
|
||||
} else {
|
||||
logger = log.New("peer", id[:16])
|
||||
}
|
||||
// Short circuit if the data was never requested
|
||||
request := q.headerPendPool[id]
|
||||
if request == nil {
|
||||
@ -704,10 +711,10 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
accepted := len(headers) == MaxHeaderFetch
|
||||
if accepted {
|
||||
if headers[0].Number.Uint64() != request.From {
|
||||
log.Trace("First header broke chain ordering", "peer", id, "number", headers[0].Number, "hash", headers[0].Hash(), request.From)
|
||||
logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From)
|
||||
accepted = false
|
||||
} else if headers[len(headers)-1].Hash() != target {
|
||||
log.Trace("Last header broke skeleton structure ", "peer", id, "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target)
|
||||
logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target)
|
||||
accepted = false
|
||||
}
|
||||
}
|
||||
@ -716,12 +723,12 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
for i, header := range headers[1:] {
|
||||
hash := header.Hash()
|
||||
if want := request.From + 1 + uint64(i); header.Number.Uint64() != want {
|
||||
log.Warn("Header broke chain ordering", "peer", id, "number", header.Number, "hash", hash, "expected", want)
|
||||
logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want)
|
||||
accepted = false
|
||||
break
|
||||
}
|
||||
if parentHash != header.ParentHash {
|
||||
log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash)
|
||||
logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash)
|
||||
accepted = false
|
||||
break
|
||||
}
|
||||
@ -731,7 +738,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
}
|
||||
// If the batch of headers wasn't accepted, mark as unavailable
|
||||
if !accepted {
|
||||
log.Trace("Skeleton filling not accepted", "peer", id, "from", request.From)
|
||||
logger.Trace("Skeleton filling not accepted", "from", request.From)
|
||||
|
||||
miss := q.headerPeerMiss[id]
|
||||
if miss == nil {
|
||||
@ -758,7 +765,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||
|
||||
select {
|
||||
case headerProcCh <- process:
|
||||
log.Trace("Pre-scheduled new headers", "peer", id, "count", len(process), "from", process[0].Number)
|
||||
logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number)
|
||||
q.headerProced += len(process)
|
||||
default:
|
||||
}
|
||||
|
@ -101,8 +101,16 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
||||
finished []*stateReq // Completed or failed requests
|
||||
timeout = make(chan *stateReq) // Timed out active requests
|
||||
)
|
||||
// Run the state sync.
|
||||
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()
|
||||
|
||||
@ -252,8 +260,9 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*
|
||||
type stateSync struct {
|
||||
d *Downloader // Downloader instance to access and manage current peerset
|
||||
|
||||
sched *trie.Sync // State trie sync scheduler defining the tasks
|
||||
keccak hash.Hash // Keccak256 hasher to verify deliveries with
|
||||
root common.Hash // State root currently being synced
|
||||
sched *trie.Sync // State trie sync scheduler defining the tasks
|
||||
keccak hash.Hash // 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
|
||||
@ -268,8 +277,6 @@ type stateSync struct {
|
||||
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)
|
||||
|
||||
root common.Hash
|
||||
}
|
||||
|
||||
// trieTask represents a single trie node download task, containing a set of
|
||||
@ -290,6 +297,7 @@ type codeTask struct {
|
||||
func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
return &stateSync{
|
||||
d: d,
|
||||
root: root,
|
||||
sched: state.NewStateSync(root, d.stateDB, d.stateBloom),
|
||||
keccak: sha3.NewLegacyKeccak256(),
|
||||
trieTasks: make(map[common.Hash]*trieTask),
|
||||
@ -298,7 +306,6 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
cancel: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
root: root,
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,7 +313,12 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
// it finishes, and finally notifying any goroutines waiting for the loop to
|
||||
// finish.
|
||||
func (s *stateSync) run() {
|
||||
s.err = s.loop()
|
||||
close(s.started)
|
||||
if s.d.snapSync {
|
||||
s.err = s.d.SnapSyncer.Sync(s.root, s.cancel)
|
||||
} else {
|
||||
s.err = s.loop()
|
||||
}
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
@ -318,7 +330,9 @@ func (s *stateSync) Wait() error {
|
||||
|
||||
// Cancel cancels the sync and waits until it has shut down.
|
||||
func (s *stateSync) Cancel() error {
|
||||
s.cancelOnce.Do(func() { close(s.cancel) })
|
||||
s.cancelOnce.Do(func() {
|
||||
close(s.cancel)
|
||||
})
|
||||
return s.Wait()
|
||||
}
|
||||
|
||||
@ -329,7 +343,6 @@ func (s *stateSync) Cancel() error {
|
||||
// pushed here async. The reason is to decouple processing from data receipt
|
||||
// and timeouts.
|
||||
func (s *stateSync) loop() (err error) {
|
||||
close(s.started)
|
||||
// Listen for new peer events to assign tasks to them
|
||||
newPeer := make(chan *peerConnection, 1024)
|
||||
peerSub := s.d.peers.SubscribeNewPeers(newPeer)
|
||||
|
@ -20,7 +20,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId uint64
|
||||
SyncMode downloader.SyncMode
|
||||
DiscoveryURLs []string
|
||||
EthDiscoveryURLs []string
|
||||
NoPruning bool
|
||||
NoPrefetch bool
|
||||
TxLookupLimit uint64 `toml:",omitempty"`
|
||||
@ -61,7 +61,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.Genesis = c.Genesis
|
||||
enc.NetworkId = c.NetworkId
|
||||
enc.SyncMode = c.SyncMode
|
||||
enc.DiscoveryURLs = c.DiscoveryURLs
|
||||
enc.EthDiscoveryURLs = c.EthDiscoveryURLs
|
||||
enc.NoPruning = c.NoPruning
|
||||
enc.NoPrefetch = c.NoPrefetch
|
||||
enc.TxLookupLimit = c.TxLookupLimit
|
||||
@ -106,7 +106,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId *uint64
|
||||
SyncMode *downloader.SyncMode
|
||||
DiscoveryURLs []string
|
||||
EthDiscoveryURLs []string
|
||||
NoPruning *bool
|
||||
NoPrefetch *bool
|
||||
TxLookupLimit *uint64 `toml:",omitempty"`
|
||||
@ -156,8 +156,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.SyncMode != nil {
|
||||
c.SyncMode = *dec.SyncMode
|
||||
}
|
||||
if dec.DiscoveryURLs != nil {
|
||||
c.DiscoveryURLs = dec.DiscoveryURLs
|
||||
if dec.EthDiscoveryURLs != nil {
|
||||
c.EthDiscoveryURLs = dec.EthDiscoveryURLs
|
||||
}
|
||||
if dec.NoPruning != nil {
|
||||
c.NoPruning = *dec.NoPruning
|
||||
|
879
eth/handler.go
879
eth/handler.go
File diff suppressed because it is too large
Load Diff
218
eth/handler_eth.go
Normal file
218
eth/handler_eth.go
Normal file
@ -0,0 +1,218 @@
|
||||
// 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 eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/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 }
|
||||
|
||||
// RunPeer is invoked when a peer joins on the `eth` protocol.
|
||||
func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error {
|
||||
return (*handler)(h).runEthPeer(peer, hand)
|
||||
}
|
||||
|
||||
// PeerInfo retrieves all known `eth` information about a peer.
|
||||
func (h *ethHandler) PeerInfo(id enode.ID) interface{} {
|
||||
if p := h.peers.ethPeer(id.String()); p != nil {
|
||||
return p.info()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptTxs retrieves whether transaction processing is enabled on the node
|
||||
// or if inbound transactions should simply be dropped.
|
||||
func (h *ethHandler) AcceptTxs() bool {
|
||||
return atomic.LoadUint32(&h.acceptTxs) == 1
|
||||
}
|
||||
|
||||
// Handle is invoked from a peer's message handler when it receives a new remote
|
||||
// message that the handler couldn't consume and serve itself.
|
||||
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)
|
||||
|
||||
case *eth.NewBlockPacket:
|
||||
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
return h.txFetcher.Notify(peer.ID(), *packet)
|
||||
|
||||
case *eth.TransactionsPacket:
|
||||
return h.txFetcher.Enqueue(peer.ID(), *packet, false)
|
||||
|
||||
case *eth.PooledTransactionsPacket:
|
||||
return h.txFetcher.Enqueue(peer.ID(), *packet, true)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected eth packet type: %T", packet)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.ethPeer(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 {
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
var (
|
||||
unknownHashes = make([]common.Hash, 0, len(hashes))
|
||||
unknownNumbers = make([]uint64, 0, len(numbers))
|
||||
)
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
if !h.chain.HasBlock(hashes[i], numbers[i]) {
|
||||
unknownHashes = append(unknownHashes, hashes[i])
|
||||
unknownNumbers = append(unknownNumbers, numbers[i])
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(unknownHashes); i++ {
|
||||
h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Schedule the block for import
|
||||
h.blockFetcher.Enqueue(peer.ID(), block)
|
||||
|
||||
// Assuming the block is importable by the peer, but possibly not yet done so,
|
||||
// calculate the head hash and TD that the peer truly must have.
|
||||
var (
|
||||
trueHead = block.ParentHash()
|
||||
trueTD = new(big.Int).Sub(td, block.Difficulty())
|
||||
)
|
||||
// Update the peer's total difficulty if better than the previous
|
||||
if _, td := peer.Head(); trueTD.Cmp(td) > 0 {
|
||||
peer.SetHead(trueHead, trueTD)
|
||||
h.chainSync.handlePeerEvent(peer)
|
||||
}
|
||||
return nil
|
||||
}
|
740
eth/handler_eth_test.go
Normal file
740
eth/handler_eth_test.go
Normal file
@ -0,0 +1,740 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"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/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"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"
|
||||
)
|
||||
|
||||
// testEthHandler is a mock event handler to listen for inbound network requests
|
||||
// on the `eth` protocol and convert them into a more easily testable form.
|
||||
type testEthHandler struct {
|
||||
blockBroadcasts event.Feed
|
||||
txAnnounces event.Feed
|
||||
txBroadcasts event.Feed
|
||||
}
|
||||
|
||||
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") }
|
||||
func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") }
|
||||
|
||||
func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||
switch packet := packet.(type) {
|
||||
case *eth.NewBlockPacket:
|
||||
h.blockBroadcasts.Send(packet.Block)
|
||||
return nil
|
||||
|
||||
case *eth.NewPooledTransactionHashesPacket:
|
||||
h.txAnnounces.Send(([]common.Hash)(*packet))
|
||||
return nil
|
||||
|
||||
case *eth.TransactionsPacket:
|
||||
h.txBroadcasts.Send(([]*types.Transaction)(*packet))
|
||||
return nil
|
||||
|
||||
case *eth.PooledTransactionsPacket:
|
||||
h.txBroadcasts.Send(([]*types.Transaction)(*packet))
|
||||
return nil
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet))
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that peers are correctly accepted (or rejected) based on the advertised
|
||||
// fork IDs in the protocol handshake.
|
||||
func TestForkIDSplit64(t *testing.T) { testForkIDSplit(t, 64) }
|
||||
func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, 65) }
|
||||
|
||||
func testForkIDSplit(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
engine = ethash.NewFaker()
|
||||
|
||||
configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}
|
||||
configProFork = ¶ms.ChainConfig{
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
EIP150Block: big.NewInt(2),
|
||||
EIP155Block: big.NewInt(2),
|
||||
EIP158Block: big.NewInt(2),
|
||||
ByzantiumBlock: big.NewInt(3),
|
||||
}
|
||||
dbNoFork = rawdb.NewMemoryDatabase()
|
||||
dbProFork = rawdb.NewMemoryDatabase()
|
||||
|
||||
gspecNoFork = &core.Genesis{Config: configNoFork}
|
||||
gspecProFork = &core.Genesis{Config: configProFork}
|
||||
|
||||
genesisNoFork = gspecNoFork.MustCommit(dbNoFork)
|
||||
genesisProFork = gspecProFork.MustCommit(dbProFork)
|
||||
|
||||
chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil)
|
||||
chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil)
|
||||
|
||||
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
|
||||
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
|
||||
|
||||
ethNoFork, _ = newHandler(&handlerConfig{
|
||||
Database: dbNoFork,
|
||||
Chain: chainNoFork,
|
||||
TxPool: newTestTxPool(),
|
||||
Network: 1,
|
||||
Sync: downloader.FullSync,
|
||||
BloomCache: 1,
|
||||
})
|
||||
ethProFork, _ = newHandler(&handlerConfig{
|
||||
Database: dbProFork,
|
||||
Chain: chainProFork,
|
||||
TxPool: newTestTxPool(),
|
||||
Network: 1,
|
||||
Sync: downloader.FullSync,
|
||||
BloomCache: 1,
|
||||
})
|
||||
)
|
||||
ethNoFork.Start(1000)
|
||||
ethProFork.Start(1000)
|
||||
|
||||
// Clean up everything after ourselves
|
||||
defer chainNoFork.Stop()
|
||||
defer chainProFork.Stop()
|
||||
|
||||
defer ethNoFork.Stop()
|
||||
defer ethProFork.Stop()
|
||||
|
||||
// Both nodes should allow the other to connect (same genesis, next fork is the same)
|
||||
p2pNoFork, p2pProFork := p2p.MsgPipe()
|
||||
defer p2pNoFork.Close()
|
||||
defer p2pProFork.Close()
|
||||
|
||||
peerNoFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
defer peerNoFork.Close()
|
||||
defer peerProFork.Close()
|
||||
|
||||
errc := make(chan error, 2)
|
||||
go func(errc chan error) {
|
||||
errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
go func(errc chan error) {
|
||||
errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
t.Fatalf("frontier nofork <-> profork failed: %v", err)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatalf("frontier nofork <-> profork handler timeout")
|
||||
}
|
||||
}
|
||||
// Progress into Homestead. Fork's match, so we don't care what the future holds
|
||||
chainNoFork.InsertChain(blocksNoFork[:1])
|
||||
chainProFork.InsertChain(blocksProFork[:1])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
defer p2pNoFork.Close()
|
||||
defer p2pProFork.Close()
|
||||
|
||||
peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
defer peerNoFork.Close()
|
||||
defer peerProFork.Close()
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func(errc chan error) {
|
||||
errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
go func(errc chan error) {
|
||||
errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
t.Fatalf("homestead nofork <-> profork failed: %v", err)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatalf("homestead nofork <-> profork handler timeout")
|
||||
}
|
||||
}
|
||||
// Progress into Spurious. Forks mismatch, signalling differing chains, reject
|
||||
chainNoFork.InsertChain(blocksNoFork[1:2])
|
||||
chainProFork.InsertChain(blocksProFork[1:2])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
defer p2pNoFork.Close()
|
||||
defer p2pProFork.Close()
|
||||
|
||||
peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
defer peerNoFork.Close()
|
||||
defer peerProFork.Close()
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func(errc chan error) {
|
||||
errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
go func(errc chan error) {
|
||||
errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
|
||||
}(errc)
|
||||
|
||||
var successes int
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err == nil {
|
||||
successes++
|
||||
if successes == 2 { // Only one side disconnects
|
||||
t.Fatalf("fork ID rejection didn't happen")
|
||||
}
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatalf("split peers not rejected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that received transactions are added to the local pool.
|
||||
func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }
|
||||
func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) }
|
||||
|
||||
func testRecvTransactions(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a message handler, configure it to accept transactions and watch them
|
||||
handler := newTestHandler()
|
||||
defer handler.close()
|
||||
|
||||
handler.handler.acceptTxs = 1 // mark synced to accept transactions
|
||||
|
||||
txs := make(chan core.NewTxsEvent)
|
||||
sub := handler.txpool.SubscribeNewTxsEvent(txs)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a source peer to send messages through and a sink handler to receive them
|
||||
p2pSrc, p2pSink := p2p.MsgPipe()
|
||||
defer p2pSrc.Close()
|
||||
defer p2pSink.Close()
|
||||
|
||||
src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool)
|
||||
sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool)
|
||||
defer src.Close()
|
||||
defer sink.Close()
|
||||
|
||||
go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(handler.handler), peer)
|
||||
})
|
||||
// Run the handshake locally to avoid spinning up a source handler
|
||||
var (
|
||||
genesis = handler.chain.Genesis()
|
||||
head = handler.chain.CurrentBlock()
|
||||
td = handler.chain.GetTd(head.Hash(), head.NumberU64())
|
||||
)
|
||||
if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
// Send the transaction to the sink and verify that it's added to the tx pool
|
||||
tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||
|
||||
if err := src.SendTransactions([]*types.Transaction{tx}); err != nil {
|
||||
t.Fatalf("failed to send transaction: %v", err)
|
||||
}
|
||||
select {
|
||||
case event := <-txs:
|
||||
if len(event.Txs) != 1 {
|
||||
t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs))
|
||||
} else if event.Txs[0].Hash() != tx.Hash() {
|
||||
t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash())
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("no NewTxsEvent received within 2 seconds")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }
|
||||
func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) }
|
||||
|
||||
func testSendTransactions(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a message handler and fill the pool with big transactions
|
||||
handler := newTestHandler()
|
||||
defer handler.close()
|
||||
|
||||
insert := make([]*types.Transaction, 100)
|
||||
for nonce := range insert {
|
||||
tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, txsyncPackSize/10))
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||
|
||||
insert[nonce] = tx
|
||||
}
|
||||
go handler.txpool.AddRemotes(insert) // Need goroutine to not block on feed
|
||||
time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join)
|
||||
|
||||
// Create a source handler to send messages through and a sink peer to receive them
|
||||
p2pSrc, p2pSink := p2p.MsgPipe()
|
||||
defer p2pSrc.Close()
|
||||
defer p2pSink.Close()
|
||||
|
||||
src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool)
|
||||
sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool)
|
||||
defer src.Close()
|
||||
defer sink.Close()
|
||||
|
||||
go handler.handler.runEthPeer(src, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(handler.handler), peer)
|
||||
})
|
||||
// Run the handshake locally to avoid spinning up a source handler
|
||||
var (
|
||||
genesis = handler.chain.Genesis()
|
||||
head = handler.chain.CurrentBlock()
|
||||
td = handler.chain.GetTd(head.Hash(), head.NumberU64())
|
||||
)
|
||||
if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
// After the handshake completes, the source handler should stream the sink
|
||||
// the transactions, subscribe to all inbound network events
|
||||
backend := new(testEthHandler)
|
||||
|
||||
anns := make(chan []common.Hash)
|
||||
annSub := backend.txAnnounces.Subscribe(anns)
|
||||
defer annSub.Unsubscribe()
|
||||
|
||||
bcasts := make(chan []*types.Transaction)
|
||||
bcastSub := backend.txBroadcasts.Subscribe(bcasts)
|
||||
defer bcastSub.Unsubscribe()
|
||||
|
||||
go eth.Handle(backend, sink)
|
||||
|
||||
// Make sure we get all the transactions on the correct channels
|
||||
seen := make(map[common.Hash]struct{})
|
||||
for len(seen) < len(insert) {
|
||||
switch protocol {
|
||||
case 63, 64:
|
||||
select {
|
||||
case <-anns:
|
||||
t.Errorf("tx announce received on pre eth/65")
|
||||
case txs := <-bcasts:
|
||||
for _, tx := range txs {
|
||||
if _, ok := seen[tx.Hash()]; ok {
|
||||
t.Errorf("duplicate transaction announced: %x", tx.Hash())
|
||||
}
|
||||
seen[tx.Hash()] = struct{}{}
|
||||
}
|
||||
}
|
||||
case 65:
|
||||
select {
|
||||
case hashes := <-anns:
|
||||
for _, hash := range hashes {
|
||||
if _, ok := seen[hash]; ok {
|
||||
t.Errorf("duplicate transaction announced: %x", hash)
|
||||
}
|
||||
seen[hash] = struct{}{}
|
||||
}
|
||||
case <-bcasts:
|
||||
t.Errorf("initial tx broadcast received on post eth/65")
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unsupported protocol, please extend test")
|
||||
}
|
||||
}
|
||||
for _, tx := range insert {
|
||||
if _, ok := seen[tx.Hash()]; !ok {
|
||||
t.Errorf("missing transaction: %x", tx.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that transactions get propagated to all attached peers, either via direct
|
||||
// broadcasts or via announcements/retrievals.
|
||||
func TestTransactionPropagation64(t *testing.T) { testTransactionPropagation(t, 64) }
|
||||
func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, 65) }
|
||||
|
||||
func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a source handler to send transactions from and a number of sinks
|
||||
// to receive them. We need multiple sinks since a one-to-one peering would
|
||||
// broadcast all transactions without announcement.
|
||||
source := newTestHandler()
|
||||
defer source.close()
|
||||
|
||||
sinks := make([]*testHandler, 10)
|
||||
for i := 0; i < len(sinks); i++ {
|
||||
sinks[i] = newTestHandler()
|
||||
defer sinks[i].close()
|
||||
|
||||
sinks[i].handler.acceptTxs = 1 // mark synced to accept transactions
|
||||
}
|
||||
// Interconnect all the sink handlers with the source handler
|
||||
for i, sink := range sinks {
|
||||
sink := sink // Closure for gorotuine below
|
||||
|
||||
sourcePipe, sinkPipe := p2p.MsgPipe()
|
||||
defer sourcePipe.Close()
|
||||
defer sinkPipe.Close()
|
||||
|
||||
sourcePeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, source.txpool)
|
||||
sinkPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, sink.txpool)
|
||||
defer sourcePeer.Close()
|
||||
defer sinkPeer.Close()
|
||||
|
||||
go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(source.handler), peer)
|
||||
})
|
||||
go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(sink.handler), peer)
|
||||
})
|
||||
}
|
||||
// Subscribe to all the transaction pools
|
||||
txChs := make([]chan core.NewTxsEvent, len(sinks))
|
||||
for i := 0; i < len(sinks); i++ {
|
||||
txChs[i] = make(chan core.NewTxsEvent, 1024)
|
||||
|
||||
sub := sinks[i].txpool.SubscribeNewTxsEvent(txChs[i])
|
||||
defer sub.Unsubscribe()
|
||||
}
|
||||
// Fill the source pool with transactions and wait for them at the sinks
|
||||
txs := make([]*types.Transaction, 1024)
|
||||
for nonce := range txs {
|
||||
tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||
|
||||
txs[nonce] = tx
|
||||
}
|
||||
source.txpool.AddRemotes(txs)
|
||||
|
||||
// Iterate through all the sinks and ensure they all got the transactions
|
||||
for i := range sinks {
|
||||
for arrived := 0; arrived < len(txs); {
|
||||
select {
|
||||
case event := <-txChs[i]:
|
||||
arrived += len(event.Txs)
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that post eth protocol handshake, clients perform a mutual checkpoint
|
||||
// challenge to validate each other's chains. Hash mismatches, or missing ones
|
||||
// during a fast sync should lead to the peer getting dropped.
|
||||
func TestCheckpointChallenge(t *testing.T) {
|
||||
tests := []struct {
|
||||
syncmode downloader.SyncMode
|
||||
checkpoint bool
|
||||
timeout bool
|
||||
empty bool
|
||||
match bool
|
||||
drop bool
|
||||
}{
|
||||
// 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},
|
||||
|
||||
// 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
|
||||
|
||||
// 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},
|
||||
|
||||
// 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},
|
||||
|
||||
// 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},
|
||||
}
|
||||
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) {
|
||||
testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
|
||||
// Reduce the checkpoint handshake challenge timeout
|
||||
defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
|
||||
syncChallengeTimeout = 250 * time.Millisecond
|
||||
|
||||
// Create a test handler and inject a CHT into it. The injection is a bit
|
||||
// ugly, but it beats creating everything manually just to avoid reaching
|
||||
// into the internals a bit.
|
||||
handler := newTestHandler()
|
||||
defer handler.close()
|
||||
|
||||
if syncmode == downloader.FastSync {
|
||||
atomic.StoreUint32(&handler.handler.fastSync, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&handler.handler.fastSync, 0)
|
||||
}
|
||||
var response *types.Header
|
||||
if checkpoint {
|
||||
number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
|
||||
response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
|
||||
|
||||
handler.handler.checkpointNumber = number
|
||||
handler.handler.checkpointHash = response.Hash()
|
||||
}
|
||||
// Create a challenger peer and a challenged one
|
||||
p2pLocal, p2pRemote := p2p.MsgPipe()
|
||||
defer p2pLocal.Close()
|
||||
defer p2pRemote.Close()
|
||||
|
||||
local := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool)
|
||||
remote := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool)
|
||||
defer local.Close()
|
||||
defer remote.Close()
|
||||
|
||||
go handler.handler.runEthPeer(local, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(handler.handler), peer)
|
||||
})
|
||||
// Run the handshake locally to avoid spinning up a remote handler
|
||||
var (
|
||||
genesis = handler.chain.Genesis()
|
||||
head = handler.chain.CurrentBlock()
|
||||
td = handler.chain.GetTd(head.Hash(), head.NumberU64())
|
||||
)
|
||||
if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
// Connect a new peer and check that we receive the checkpoint challenge
|
||||
if checkpoint {
|
||||
if err := remote.ExpectRequestHeadersByNumber(response.Number.Uint64(), 1, 0, false); err != nil {
|
||||
t.Fatalf("challenge mismatch: %v", err)
|
||||
}
|
||||
// Create a block to reply to the challenge if no timeout is simulated
|
||||
if !timeout {
|
||||
if empty {
|
||||
if err := remote.SendBlockHeaders([]*types.Header{}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else if match {
|
||||
if err := remote.SendBlockHeaders([]*types.Header{response}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := remote.SendBlockHeaders([]*types.Header{{Number: response.Number}}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wait until the test timeout passes to ensure proper cleanup
|
||||
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
||||
|
||||
// Verify that the remote peer is maintained or dropped
|
||||
if drop {
|
||||
if peers := handler.handler.peers.Len(); peers != 0 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
|
||||
}
|
||||
} else {
|
||||
if peers := handler.handler.peers.Len(); peers != 1 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that blocks are broadcast to a sqrt number of peers only.
|
||||
func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) }
|
||||
func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) }
|
||||
func TestBroadcastBlock3Peers(t *testing.T) { testBroadcastBlock(t, 3, 1) }
|
||||
func TestBroadcastBlock4Peers(t *testing.T) { testBroadcastBlock(t, 4, 2) }
|
||||
func TestBroadcastBlock5Peers(t *testing.T) { testBroadcastBlock(t, 5, 2) }
|
||||
func TestBroadcastBlock8Peers(t *testing.T) { testBroadcastBlock(t, 9, 3) }
|
||||
func TestBroadcastBlock12Peers(t *testing.T) { testBroadcastBlock(t, 12, 3) }
|
||||
func TestBroadcastBlock16Peers(t *testing.T) { testBroadcastBlock(t, 16, 4) }
|
||||
func TestBroadcastBloc26Peers(t *testing.T) { testBroadcastBlock(t, 26, 5) }
|
||||
func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) }
|
||||
|
||||
func testBroadcastBlock(t *testing.T, peers, bcasts int) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a source handler to broadcast blocks from and a number of sinks
|
||||
// to receive them.
|
||||
source := newTestHandlerWithBlocks(1)
|
||||
defer source.close()
|
||||
|
||||
sinks := make([]*testEthHandler, peers)
|
||||
for i := 0; i < len(sinks); i++ {
|
||||
sinks[i] = new(testEthHandler)
|
||||
}
|
||||
// Interconnect all the sink handlers with the source handler
|
||||
var (
|
||||
genesis = source.chain.Genesis()
|
||||
td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
|
||||
)
|
||||
for i, sink := range sinks {
|
||||
sink := sink // Closure for gorotuine below
|
||||
|
||||
sourcePipe, sinkPipe := p2p.MsgPipe()
|
||||
defer sourcePipe.Close()
|
||||
defer sinkPipe.Close()
|
||||
|
||||
sourcePeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil)
|
||||
sinkPeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil)
|
||||
defer sourcePeer.Close()
|
||||
defer sinkPeer.Close()
|
||||
|
||||
go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(source.handler), peer)
|
||||
})
|
||||
if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
go eth.Handle(sink, sinkPeer)
|
||||
}
|
||||
// Subscribe to all the transaction pools
|
||||
blockChs := make([]chan *types.Block, len(sinks))
|
||||
for i := 0; i < len(sinks); i++ {
|
||||
blockChs[i] = make(chan *types.Block, 1)
|
||||
defer close(blockChs[i])
|
||||
|
||||
sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i])
|
||||
defer sub.Unsubscribe()
|
||||
}
|
||||
// Initiate a block propagation across the peers
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
source.handler.BroadcastBlock(source.chain.CurrentBlock(), true)
|
||||
|
||||
// Iterate through all the sinks and ensure the correct number got the block
|
||||
done := make(chan struct{}, peers)
|
||||
for _, ch := range blockChs {
|
||||
ch := ch
|
||||
go func() {
|
||||
<-ch
|
||||
done <- struct{}{}
|
||||
}()
|
||||
}
|
||||
var received int
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
received++
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
if received != bcasts {
|
||||
t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that a propagated malformed block (uncles or transactions don't match
|
||||
// with the hashes in the header) gets discarded and not broadcast forward.
|
||||
func TestBroadcastMalformedBlock64(t *testing.T) { testBroadcastMalformedBlock(t, 64) }
|
||||
func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, 65) }
|
||||
|
||||
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a source handler to broadcast blocks from and a number of sinks
|
||||
// to receive them.
|
||||
source := newTestHandlerWithBlocks(1)
|
||||
defer source.close()
|
||||
|
||||
// Create a source handler to send messages through and a sink peer to receive them
|
||||
p2pSrc, p2pSink := p2p.MsgPipe()
|
||||
defer p2pSrc.Close()
|
||||
defer p2pSink.Close()
|
||||
|
||||
src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, source.txpool)
|
||||
sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, source.txpool)
|
||||
defer src.Close()
|
||||
defer sink.Close()
|
||||
|
||||
go source.handler.runEthPeer(src, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(source.handler), peer)
|
||||
})
|
||||
// Run the handshake locally to avoid spinning up a sink handler
|
||||
var (
|
||||
genesis = source.chain.Genesis()
|
||||
td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
|
||||
)
|
||||
if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
|
||||
t.Fatalf("failed to run protocol handshake")
|
||||
}
|
||||
// After the handshake completes, the source handler should stream the sink
|
||||
// the blocks, subscribe to inbound network events
|
||||
backend := new(testEthHandler)
|
||||
|
||||
blocks := make(chan *types.Block, 1)
|
||||
sub := backend.blockBroadcasts.Subscribe(blocks)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
go eth.Handle(backend, sink)
|
||||
|
||||
// Create various combinations of malformed blocks
|
||||
head := source.chain.CurrentBlock()
|
||||
|
||||
malformedUncles := head.Header()
|
||||
malformedUncles.UncleHash[0]++
|
||||
malformedTransactions := head.Header()
|
||||
malformedTransactions.TxHash[0]++
|
||||
malformedEverything := head.Header()
|
||||
malformedEverything.UncleHash[0]++
|
||||
malformedEverything.TxHash[0]++
|
||||
|
||||
// Try to broadcast all malformations and ensure they all get discarded
|
||||
for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
|
||||
block := types.NewBlockWithHeader(header).WithBody(head.Transactions(), head.Uncles())
|
||||
if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil {
|
||||
t.Fatalf("failed to broadcast block: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-blocks:
|
||||
t.Fatalf("malformed block forwarded")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
48
eth/handler_snap.go
Normal file
48
eth/handler_snap.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// snapHandler implements the snap.Backend interface to handle the various network
|
||||
// packets that are sent as replies or broadcasts.
|
||||
type snapHandler handler
|
||||
|
||||
func (h *snapHandler) Chain() *core.BlockChain { return h.chain }
|
||||
|
||||
// RunPeer is invoked when a peer joins on the `snap` protocol.
|
||||
func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error {
|
||||
return (*handler)(h).runSnapPeer(peer, hand)
|
||||
}
|
||||
|
||||
// PeerInfo retrieves all known `snap` information about a peer.
|
||||
func (h *snapHandler) PeerInfo(id enode.ID) interface{} {
|
||||
if p := h.peers.snapPeer(id.String()); p != nil {
|
||||
return p.info()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle is invoked from a peer's message handler when it receives a new remote
|
||||
// message that the handler couldn't consume and serve itself.
|
||||
func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error {
|
||||
return h.downloader.DeliverSnapPacket(peer, packet)
|
||||
}
|
@ -17,678 +17,154 @@
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"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/state"
|
||||
"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/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
||||
func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) }
|
||||
func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) }
|
||||
var (
|
||||
// testKey is a private key to use for funding a tester account.
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
// testAddr is the Ethereum address of the tester account.
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
)
|
||||
|
||||
// Create a "random" unknown hash for testing
|
||||
var unknown common.Hash
|
||||
for i := range unknown {
|
||||
unknown[i] = byte(i)
|
||||
}
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := uint64(downloader.MaxHeaderFetch)
|
||||
tests := []struct {
|
||||
query *getBlockHeadersData // 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
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
|
||||
[]common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
|
||||
[]common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()},
|
||||
},
|
||||
// Multiple headers should be retrievable in both directions
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 + 1).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 + 2).Hash(),
|
||||
},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 - 1).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 - 2).Hash(),
|
||||
},
|
||||
},
|
||||
// Multiple headers with skip lists should be retrievable
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 + 4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 + 8).Hash(),
|
||||
},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 - 4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(limit/2 - 8).Hash(),
|
||||
},
|
||||
},
|
||||
// The chain endpoints should be retrievable
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
|
||||
[]common.Hash{pm.blockchain.GetBlockByNumber(0).Hash()},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64()}, Amount: 1},
|
||||
[]common.Hash{pm.blockchain.CurrentBlock().Hash()},
|
||||
},
|
||||
// Ensure protocol limits are honored
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
|
||||
pm.blockchain.GetBlockHashesFromHash(pm.blockchain.CurrentBlock().Hash(), limit),
|
||||
},
|
||||
// Check that requesting more than available is handled gracefully
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()).Hash(),
|
||||
},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(0).Hash(),
|
||||
},
|
||||
},
|
||||
// Check that requesting more than available is handled gracefully, even if mid skip
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 1).Hash(),
|
||||
},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(4).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where requesting more can iterate past the endpoints
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(2).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||
pm.blockchain.GetBlockByNumber(0).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where skipping overflow loops back into the chain start
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(3).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where skipping overflow loops back to the same header
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64},
|
||||
[]common.Hash{
|
||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||
},
|
||||
},
|
||||
// Check that non existing headers aren't returned
|
||||
{
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
|
||||
[]common.Hash{},
|
||||
}, {
|
||||
&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() + 1}, Amount: 1},
|
||||
[]common.Hash{},
|
||||
},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Collect the headers to expect in the response
|
||||
headers := []*types.Header{}
|
||||
for _, hash := range tt.expect {
|
||||
headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header())
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
// If the test used number origins, repeat with hashes as the too
|
||||
if tt.query.Origin.Hash == (common.Hash{}) {
|
||||
if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
||||
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
||||
// testTxPool is a mock transaction pool that blindly accepts all transactions.
|
||||
// Its goal is to get around setting up a valid statedb for the balance and nonce
|
||||
// checks.
|
||||
type testTxPool struct {
|
||||
pool map[common.Hash]*types.Transaction // Hash map of collected transactions
|
||||
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
txFeed event.Feed // Notification feed to allow waiting for inclusion
|
||||
lock sync.RWMutex // Protects the transaction pool
|
||||
}
|
||||
|
||||
// newTestTxPool creates a mock transaction pool.
|
||||
func newTestTxPool() *testTxPool {
|
||||
return &testTxPool{
|
||||
pool: make(map[common.Hash]*types.Transaction),
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that block contents can be retrieved from a remote chain based on their hashes.
|
||||
func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) }
|
||||
func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) }
|
||||
// Has returns an indicator whether txpool has a transaction
|
||||
// cached with the given hash.
|
||||
func (p *testTxPool) Has(hash common.Hash) bool {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
func testGetBlockBodies(t *testing.T, protocol int) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
return p.pool[hash] != nil
|
||||
}
|
||||
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := downloader.MaxBlockFetch
|
||||
tests := []struct {
|
||||
random int // Number of blocks to fetch randomly from the chain
|
||||
explicit []common.Hash // Explicitly requested blocks
|
||||
available []bool // Availability of explicitly requested blocks
|
||||
expected int // Total number of existing blocks to expect
|
||||
}{
|
||||
{1, nil, nil, 1}, // A single random block should be retrievable
|
||||
{10, nil, nil, 10}, // Multiple random blocks should be retrievable
|
||||
{limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
|
||||
{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
|
||||
{0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
|
||||
{0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
|
||||
{0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
|
||||
// Get retrieves the transaction from local txpool with given
|
||||
// tx hash.
|
||||
func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
// Existing and non-existing blocks interleaved should not cause problems
|
||||
{0, []common.Hash{
|
||||
{},
|
||||
pm.blockchain.GetBlockByNumber(1).Hash(),
|
||||
{},
|
||||
pm.blockchain.GetBlockByNumber(10).Hash(),
|
||||
{},
|
||||
pm.blockchain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{false, true, false, true, false, true, false}, 3},
|
||||
return p.pool[hash]
|
||||
}
|
||||
|
||||
// AddRemotes appends a batch of transactions to the pool, and notifies any
|
||||
// listeners if the addition channel is non nil
|
||||
func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
for _, tx := range txs {
|
||||
p.pool[tx.Hash()] = tx
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Collect the hashes to request, and the response to expect
|
||||
hashes, seen := []common.Hash{}, make(map[int64]bool)
|
||||
bodies := []*blockBody{}
|
||||
p.txFeed.Send(core.NewTxsEvent{Txs: txs})
|
||||
return make([]error, len(txs))
|
||||
}
|
||||
|
||||
for j := 0; j < tt.random; j++ {
|
||||
for {
|
||||
num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64()))
|
||||
if !seen[num] {
|
||||
seen[num] = true
|
||||
// Pending returns all the transactions known to the pool
|
||||
func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
block := pm.blockchain.GetBlockByNumber(uint64(num))
|
||||
hashes = append(hashes, block.Hash())
|
||||
if len(bodies) < tt.expected {
|
||||
bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for j, hash := range tt.explicit {
|
||||
hashes = append(hashes, hash)
|
||||
if tt.available[j] && len(bodies) < tt.expected {
|
||||
block := pm.blockchain.GetBlockByHash(hash)
|
||||
bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
|
||||
}
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x05, hashes)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil {
|
||||
t.Errorf("test %d: bodies mismatch: %v", i, err)
|
||||
}
|
||||
batches := make(map[common.Address]types.Transactions)
|
||||
for _, tx := range p.pool {
|
||||
from, _ := types.Sender(types.HomesteadSigner{}, tx)
|
||||
batches[from] = append(batches[from], tx)
|
||||
}
|
||||
for _, batch := range batches {
|
||||
sort.Sort(types.TxByNonce(batch))
|
||||
}
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and
|
||||
// send events to the given channel.
|
||||
func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||
return p.txFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
// testHandler is a live implementation of the Ethereum protocol handler, just
|
||||
// preinitialized with some sane testing defaults and the transaction pool mocked
|
||||
// out.
|
||||
type testHandler struct {
|
||||
db ethdb.Database
|
||||
chain *core.BlockChain
|
||||
txpool *testTxPool
|
||||
handler *handler
|
||||
}
|
||||
|
||||
// newTestHandler creates a new handler for testing purposes with no blocks.
|
||||
func newTestHandler() *testHandler {
|
||||
return newTestHandlerWithBlocks(0)
|
||||
}
|
||||
|
||||
// newTestHandlerWithBlocks creates a new handler for testing purposes, with a
|
||||
// given number of initial blocks.
|
||||
func newTestHandlerWithBlocks(blocks int) *testHandler {
|
||||
// Create a database pre-initialize with a genesis block
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
(&core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
|
||||
}).MustCommit(db)
|
||||
|
||||
chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
|
||||
bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, nil)
|
||||
if _, err := chain.InsertChain(bs); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txpool := newTestTxPool()
|
||||
|
||||
handler, _ := newHandler(&handlerConfig{
|
||||
Database: db,
|
||||
Chain: chain,
|
||||
TxPool: txpool,
|
||||
Network: 1,
|
||||
Sync: downloader.FastSync,
|
||||
BloomCache: 1,
|
||||
})
|
||||
handler.Start(1000)
|
||||
|
||||
return &testHandler{
|
||||
db: db,
|
||||
chain: chain,
|
||||
txpool: txpool,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the node state database can be retrieved based on hashes.
|
||||
func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) }
|
||||
func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) }
|
||||
|
||||
func testGetNodeData(t *testing.T, protocol int) {
|
||||
// Define three accounts to simulate transactions with
|
||||
acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
|
||||
acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
|
||||
|
||||
signer := types.HomesteadSigner{}
|
||||
// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
|
||||
generator := func(i int, block *core.BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, the test bank sends account #1 some ether.
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
|
||||
block.AddTx(tx)
|
||||
case 1:
|
||||
// In block 2, the test bank sends some more ether to account #1.
|
||||
// acc1Addr passes it on to account #2.
|
||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
|
||||
block.AddTx(tx1)
|
||||
block.AddTx(tx2)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(acc2Addr)
|
||||
block.SetExtra([]byte("yeehaw"))
|
||||
case 3:
|
||||
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
|
||||
b2 := block.PrevBlock(1).Header()
|
||||
b2.Extra = []byte("foo")
|
||||
block.AddUncle(b2)
|
||||
b3 := block.PrevBlock(2).Header()
|
||||
b3.Extra = []byte("foo")
|
||||
block.AddUncle(b3)
|
||||
}
|
||||
}
|
||||
// Assemble the test environment
|
||||
pm, db := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
// Fetch for now the entire chain db
|
||||
hashes := []common.Hash{}
|
||||
|
||||
it := db.NewIterator(nil, nil)
|
||||
for it.Next() {
|
||||
if key := it.Key(); len(key) == common.HashLength {
|
||||
hashes = append(hashes, common.BytesToHash(key))
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
p2p.Send(peer.app, 0x0d, hashes)
|
||||
msg, err := peer.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read node data response: %v", err)
|
||||
}
|
||||
if msg.Code != 0x0e {
|
||||
t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c)
|
||||
}
|
||||
var data [][]byte
|
||||
if err := msg.Decode(&data); err != nil {
|
||||
t.Fatalf("failed to decode response node data: %v", err)
|
||||
}
|
||||
// Verify that all hashes correspond to the requested data, and reconstruct a state tree
|
||||
for i, want := range hashes {
|
||||
if hash := crypto.Keccak256Hash(data[i]); hash != want {
|
||||
t.Errorf("data hash mismatch: have %x, want %x", hash, want)
|
||||
}
|
||||
}
|
||||
statedb := rawdb.NewMemoryDatabase()
|
||||
for i := 0; i < len(data); i++ {
|
||||
statedb.Put(hashes[i].Bytes(), data[i])
|
||||
}
|
||||
accounts := []common.Address{testBank, acc1Addr, acc2Addr}
|
||||
for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ {
|
||||
trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil)
|
||||
|
||||
for j, acc := range accounts {
|
||||
state, _ := pm.blockchain.State()
|
||||
bw := state.GetBalance(acc)
|
||||
bh := trie.GetBalance(acc)
|
||||
|
||||
if (bw != nil && bh == nil) || (bw == nil && bh != nil) {
|
||||
t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
|
||||
}
|
||||
if bw != nil && bh != nil && bw.Cmp(bw) != 0 {
|
||||
t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the transaction receipts can be retrieved based on hashes.
|
||||
func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) }
|
||||
func TestGetReceipt64(t *testing.T) { testGetReceipt(t, 64) }
|
||||
|
||||
func testGetReceipt(t *testing.T, protocol int) {
|
||||
// Define three accounts to simulate transactions with
|
||||
acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
|
||||
acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
|
||||
|
||||
signer := types.HomesteadSigner{}
|
||||
// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
|
||||
generator := func(i int, block *core.BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, the test bank sends account #1 some ether.
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
|
||||
block.AddTx(tx)
|
||||
case 1:
|
||||
// In block 2, the test bank sends some more ether to account #1.
|
||||
// acc1Addr passes it on to account #2.
|
||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
|
||||
block.AddTx(tx1)
|
||||
block.AddTx(tx2)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(acc2Addr)
|
||||
block.SetExtra([]byte("yeehaw"))
|
||||
case 3:
|
||||
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
|
||||
b2 := block.PrevBlock(1).Header()
|
||||
b2.Extra = []byte("foo")
|
||||
block.AddUncle(b2)
|
||||
b3 := block.PrevBlock(2).Header()
|
||||
b3.Extra = []byte("foo")
|
||||
block.AddUncle(b3)
|
||||
}
|
||||
}
|
||||
// Assemble the test environment
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil)
|
||||
peer, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
// Collect the hashes to request, and the response to expect
|
||||
hashes, receipts := []common.Hash{}, []types.Receipts{}
|
||||
for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ {
|
||||
block := pm.blockchain.GetBlockByNumber(i)
|
||||
|
||||
hashes = append(hashes, block.Hash())
|
||||
receipts = append(receipts, pm.blockchain.GetReceiptsByHash(block.Hash()))
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x0f, hashes)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil {
|
||||
t.Errorf("receipts mismatch: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that post eth protocol handshake, clients perform a mutual checkpoint
|
||||
// challenge to validate each other's chains. Hash mismatches, or missing ones
|
||||
// during a fast sync should lead to the peer getting dropped.
|
||||
func TestCheckpointChallenge(t *testing.T) {
|
||||
tests := []struct {
|
||||
syncmode downloader.SyncMode
|
||||
checkpoint bool
|
||||
timeout bool
|
||||
empty bool
|
||||
match bool
|
||||
drop bool
|
||||
}{
|
||||
// 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},
|
||||
|
||||
// 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
|
||||
|
||||
// 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},
|
||||
|
||||
// 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},
|
||||
|
||||
// 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},
|
||||
}
|
||||
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) {
|
||||
testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
|
||||
// Reduce the checkpoint handshake challenge timeout
|
||||
defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
|
||||
syncChallengeTimeout = 250 * time.Millisecond
|
||||
|
||||
// Initialize a chain and generate a fake CHT if checkpointing is enabled
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
config = new(params.ChainConfig)
|
||||
)
|
||||
(&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block
|
||||
// If checkpointing is enabled, create and inject a fake CHT and the corresponding
|
||||
// chllenge response.
|
||||
var response *types.Header
|
||||
var cht *params.TrustedCheckpoint
|
||||
if checkpoint {
|
||||
index := uint64(rand.Intn(500))
|
||||
number := (index+1)*params.CHTFrequency - 1
|
||||
response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
|
||||
|
||||
cht = ¶ms.TrustedCheckpoint{
|
||||
SectionIndex: index,
|
||||
SectionHead: response.Hash(),
|
||||
}
|
||||
}
|
||||
// Create a checkpoint aware protocol manager
|
||||
blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new blockchain: %v", err)
|
||||
}
|
||||
pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||
}
|
||||
pm.Start(1000)
|
||||
defer pm.Stop()
|
||||
|
||||
// Connect a new peer and check that we receive the checkpoint challenge
|
||||
peer, _ := newTestPeer("peer", eth63, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
if checkpoint {
|
||||
challenge := &getBlockHeadersData{
|
||||
Origin: hashOrNumber{Number: response.Number.Uint64()},
|
||||
Amount: 1,
|
||||
Skip: 0,
|
||||
Reverse: false,
|
||||
}
|
||||
if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil {
|
||||
t.Fatalf("challenge mismatch: %v", err)
|
||||
}
|
||||
// Create a block to reply to the challenge if no timeout is simulated
|
||||
if !timeout {
|
||||
if empty {
|
||||
if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else if match {
|
||||
if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{response}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{{Number: response.Number}}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wait until the test timeout passes to ensure proper cleanup
|
||||
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
||||
|
||||
// Verify that the remote peer is maintained or dropped
|
||||
if drop {
|
||||
if peers := pm.peers.Len(); peers != 0 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
|
||||
}
|
||||
} else {
|
||||
if peers := pm.peers.Len(); peers != 1 {
|
||||
t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBroadcastBlock(t *testing.T) {
|
||||
var tests = []struct {
|
||||
totalPeers int
|
||||
broadcastExpected int
|
||||
}{
|
||||
{1, 1},
|
||||
{2, 1},
|
||||
{3, 1},
|
||||
{4, 2},
|
||||
{5, 2},
|
||||
{9, 3},
|
||||
{12, 3},
|
||||
{16, 4},
|
||||
{26, 5},
|
||||
{100, 10},
|
||||
}
|
||||
for _, test := range tests {
|
||||
testBroadcastBlock(t, test.totalPeers, test.broadcastExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
|
||||
var (
|
||||
evmux = new(event.TypeMux)
|
||||
pow = ethash.NewFaker()
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
config = ¶ms.ChainConfig{}
|
||||
gspec = &core.Genesis{Config: config}
|
||||
genesis = gspec.MustCommit(db)
|
||||
)
|
||||
blockchain, err := core.NewBlockChain(db, nil, config, pow, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new blockchain: %v", err)
|
||||
}
|
||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||
}
|
||||
pm.Start(1000)
|
||||
defer pm.Stop()
|
||||
var peers []*testPeer
|
||||
for i := 0; i < totalPeers; i++ {
|
||||
peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
|
||||
pm.BroadcastBlock(chain[0], true /*propagate*/)
|
||||
|
||||
errCh := make(chan error, totalPeers)
|
||||
doneCh := make(chan struct{}, totalPeers)
|
||||
for _, peer := range peers {
|
||||
go func(p *testPeer) {
|
||||
if err := p2p.ExpectMsg(p.app, NewBlockMsg, &newBlockData{Block: chain[0], TD: big.NewInt(131136)}); err != nil {
|
||||
errCh <- err
|
||||
} else {
|
||||
doneCh <- struct{}{}
|
||||
}
|
||||
}(peer)
|
||||
}
|
||||
var received int
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
received++
|
||||
if received > broadcastExpected {
|
||||
// We can bail early here
|
||||
t.Errorf("broadcast count mismatch: have %d > want %d", received, broadcastExpected)
|
||||
return
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
if received != broadcastExpected {
|
||||
t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected)
|
||||
}
|
||||
return
|
||||
case err = <-errCh:
|
||||
t.Fatalf("broadcast failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Tests that a propagated malformed block (uncles or transactions don't match
|
||||
// with the hashes in the header) gets discarded and not broadcast forward.
|
||||
func TestBroadcastMalformedBlock(t *testing.T) {
|
||||
// Create a live node to test propagation with
|
||||
var (
|
||||
engine = ethash.NewFaker()
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
config = ¶ms.ChainConfig{}
|
||||
gspec = &core.Genesis{Config: config}
|
||||
genesis = gspec.MustCommit(db)
|
||||
)
|
||||
blockchain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new blockchain: %v", err)
|
||||
}
|
||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||
}
|
||||
pm.Start(2)
|
||||
defer pm.Stop()
|
||||
|
||||
// Create two peers, one to send the malformed block with and one to check
|
||||
// propagation
|
||||
source, _ := newTestPeer("source", eth63, pm, true)
|
||||
defer source.close()
|
||||
|
||||
sink, _ := newTestPeer("sink", eth63, pm, true)
|
||||
defer sink.close()
|
||||
|
||||
// Create various combinations of malformed blocks
|
||||
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
|
||||
|
||||
malformedUncles := chain[0].Header()
|
||||
malformedUncles.UncleHash[0]++
|
||||
malformedTransactions := chain[0].Header()
|
||||
malformedTransactions.TxHash[0]++
|
||||
malformedEverything := chain[0].Header()
|
||||
malformedEverything.UncleHash[0]++
|
||||
malformedEverything.TxHash[0]++
|
||||
|
||||
// Keep listening to broadcasts and notify if any arrives
|
||||
notify := make(chan struct{}, 1)
|
||||
go func() {
|
||||
if _, err := sink.app.ReadMsg(); err == nil {
|
||||
notify <- struct{}{}
|
||||
}
|
||||
}()
|
||||
// Try to broadcast all malformations and ensure they all get discarded
|
||||
for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
|
||||
block := types.NewBlockWithHeader(header).WithBody(chain[0].Transactions(), chain[0].Uncles())
|
||||
if err := p2p.Send(source.app, NewBlockMsg, []interface{}{block, big.NewInt(131136)}); err != nil {
|
||||
t.Fatalf("failed to broadcast block: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-notify:
|
||||
t.Fatalf("malformed block forwarded")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
// close tears down the handler and all its internal constructs.
|
||||
func (b *testHandler) close() {
|
||||
b.handler.Stop()
|
||||
b.chain.Stop()
|
||||
}
|
||||
|
@ -1,231 +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/>.
|
||||
|
||||
// This file contains some shares testing functionality, common to multiple
|
||||
// different files and modules being tested.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"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/forkid"
|
||||
"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/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var (
|
||||
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testBank = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||
)
|
||||
|
||||
// newTestProtocolManager creates a new protocol manager for testing purposes,
|
||||
// with the given number of blocks already known, and potential notification
|
||||
// channels for different events.
|
||||
func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database, error) {
|
||||
var (
|
||||
evmux = new(event.TypeMux)
|
||||
engine = ethash.NewFaker()
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
|
||||
)
|
||||
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator)
|
||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pm.Start(1000)
|
||||
return pm, db, nil
|
||||
}
|
||||
|
||||
// newTestProtocolManagerMust creates a new protocol manager for testing purposes,
|
||||
// with the given number of blocks already known, and potential notification
|
||||
// channels for different events. In case of an error, the constructor force-
|
||||
// fails the test.
|
||||
func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database) {
|
||||
pm, db, err := newTestProtocolManager(mode, blocks, generator, newtx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create protocol manager: %v", err)
|
||||
}
|
||||
return pm, db
|
||||
}
|
||||
|
||||
// testTxPool is a fake, helper transaction pool for testing purposes
|
||||
type testTxPool struct {
|
||||
txFeed event.Feed
|
||||
pool map[common.Hash]*types.Transaction // Hash map of collected transactions
|
||||
added chan<- []*types.Transaction // Notification channel for new transactions
|
||||
|
||||
lock sync.RWMutex // Protects the transaction pool
|
||||
}
|
||||
|
||||
// Has returns an indicator whether txpool has a transaction
|
||||
// cached with the given hash.
|
||||
func (p *testTxPool) Has(hash common.Hash) bool {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.pool[hash] != nil
|
||||
}
|
||||
|
||||
// Get retrieves the transaction from local txpool with given
|
||||
// tx hash.
|
||||
func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.pool[hash]
|
||||
}
|
||||
|
||||
// AddRemotes appends a batch of transactions to the pool, and notifies any
|
||||
// listeners if the addition channel is non nil
|
||||
func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
for _, tx := range txs {
|
||||
p.pool[tx.Hash()] = tx
|
||||
}
|
||||
if p.added != nil {
|
||||
p.added <- txs
|
||||
}
|
||||
p.txFeed.Send(core.NewTxsEvent{Txs: txs})
|
||||
return make([]error, len(txs))
|
||||
}
|
||||
|
||||
// Pending returns all the transactions known to the pool
|
||||
func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
batches := make(map[common.Address]types.Transactions)
|
||||
for _, tx := range p.pool {
|
||||
from, _ := types.Sender(types.HomesteadSigner{}, tx)
|
||||
batches[from] = append(batches[from], tx)
|
||||
}
|
||||
for _, batch := range batches {
|
||||
sort.Sort(types.TxByNonce(batch))
|
||||
}
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||
return p.txFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
// newTestTransaction create a new dummy transaction.
|
||||
func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction {
|
||||
tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize))
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, from)
|
||||
return tx
|
||||
}
|
||||
|
||||
// testPeer is a simulated peer to allow testing direct network calls.
|
||||
type testPeer struct {
|
||||
net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
|
||||
app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
|
||||
*peer
|
||||
}
|
||||
|
||||
// newTestPeer creates a new peer registered at the given protocol manager.
|
||||
func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) {
|
||||
// Create a message pipe to communicate through
|
||||
app, net := p2p.MsgPipe()
|
||||
|
||||
// Start the peer on a new thread
|
||||
var id enode.ID
|
||||
rand.Read(id[:])
|
||||
peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get)
|
||||
errc := make(chan error, 1)
|
||||
go func() { errc <- pm.runPeer(peer) }()
|
||||
tp := &testPeer{app: app, net: net, peer: peer}
|
||||
|
||||
// Execute any implicitly requested handshakes and return
|
||||
if shake {
|
||||
var (
|
||||
genesis = pm.blockchain.Genesis()
|
||||
head = pm.blockchain.CurrentHeader()
|
||||
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
)
|
||||
forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64())
|
||||
tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(pm.blockchain))
|
||||
}
|
||||
return tp, errc
|
||||
}
|
||||
|
||||
// handshake simulates a trivial handshake that expects the same state from the
|
||||
// remote side as we are simulating locally.
|
||||
func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) {
|
||||
var msg interface{}
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
msg = &statusData63{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkId: DefaultConfig.NetworkId,
|
||||
TD: td,
|
||||
CurrentBlock: head,
|
||||
GenesisBlock: genesis,
|
||||
}
|
||||
case p.version >= eth64:
|
||||
msg = &statusData{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkID: DefaultConfig.NetworkId,
|
||||
TD: td,
|
||||
Head: head,
|
||||
Genesis: genesis,
|
||||
ForkID: forkID,
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
}
|
||||
if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil {
|
||||
t.Fatalf("status recv: %v", err)
|
||||
}
|
||||
if err := p2p.Send(p.app, StatusMsg, msg); err != nil {
|
||||
t.Fatalf("status send: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// close terminates the local side of the peer, notifying the remote protocol
|
||||
// manager of termination.
|
||||
func (p *testPeer) close() {
|
||||
p.app.Close()
|
||||
}
|
806
eth/peer.go
806
eth/peer.go
@ -17,806 +17,58 @@
|
||||
package eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
)
|
||||
|
||||
var (
|
||||
errClosed = errors.New("peer set is closed")
|
||||
errAlreadyRegistered = errors.New("peer is already registered")
|
||||
errNotRegistered = errors.New("peer is not registered")
|
||||
)
|
||||
|
||||
const (
|
||||
maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
|
||||
|
||||
// maxQueuedTxs is the maximum number of transactions to queue up before dropping
|
||||
// older broadcasts.
|
||||
maxQueuedTxs = 4096
|
||||
|
||||
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
|
||||
// before dropping older announcements.
|
||||
maxQueuedTxAnns = 4096
|
||||
|
||||
// maxQueuedBlocks is the maximum number of block propagations to queue up before
|
||||
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
|
||||
// that might cover uncles should be enough.
|
||||
maxQueuedBlocks = 4
|
||||
|
||||
// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
|
||||
// dropping broadcasts. Similarly to block propagations, there's no point to queue
|
||||
// above some healthy uncle limit, so use that.
|
||||
maxQueuedBlockAnns = 4
|
||||
|
||||
handshakeTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// max is a helper function which returns the larger of the two given integers.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known
|
||||
// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known
|
||||
// about a connected peer.
|
||||
type PeerInfo struct {
|
||||
Version int `json:"version"` // Ethereum protocol version negotiated
|
||||
type ethPeerInfo struct {
|
||||
Version uint `json:"version"` // Ethereum protocol version negotiated
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
|
||||
Head string `json:"head"` // SHA3 hash of the peer's best owned block
|
||||
Head string `json:"head"` // Hex hash of the peer's best owned block
|
||||
}
|
||||
|
||||
// propEvent is a block propagation, waiting for its turn in the broadcast queue.
|
||||
type propEvent struct {
|
||||
block *types.Block
|
||||
td *big.Int
|
||||
// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
|
||||
type ethPeer struct {
|
||||
*eth.Peer
|
||||
|
||||
syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time
|
||||
lock sync.RWMutex // Mutex protecting the internal fields
|
||||
}
|
||||
|
||||
type peer struct {
|
||||
id string
|
||||
|
||||
*p2p.Peer
|
||||
rw p2p.MsgReadWriter
|
||||
|
||||
version int // Protocol version negotiated
|
||||
syncDrop *time.Timer // Timed connection dropper if sync progress isn't validated in time
|
||||
|
||||
head common.Hash
|
||||
td *big.Int
|
||||
lock sync.RWMutex
|
||||
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer
|
||||
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
|
||||
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
|
||||
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
|
||||
getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool
|
||||
|
||||
term chan struct{} // Termination channel to stop the broadcaster
|
||||
}
|
||||
|
||||
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer {
|
||||
return &peer{
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
id: fmt.Sprintf("%x", p.ID().Bytes()[:8]),
|
||||
knownTxs: mapset.NewSet(),
|
||||
knownBlocks: mapset.NewSet(),
|
||||
queuedBlocks: make(chan *propEvent, maxQueuedBlocks),
|
||||
queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
|
||||
txBroadcast: make(chan []common.Hash),
|
||||
txAnnounce: make(chan []common.Hash),
|
||||
getPooledTx: getPooledTx,
|
||||
term: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) broadcastBlocks(removePeer func(string)) {
|
||||
for {
|
||||
select {
|
||||
case prop := <-p.queuedBlocks:
|
||||
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
|
||||
removePeer(p.id)
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
|
||||
|
||||
case block := <-p.queuedBlockAnns:
|
||||
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
|
||||
removePeer(p.id)
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) broadcastTransactions(removePeer func(string)) {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to broadcast as full transactions
|
||||
done chan struct{} // Non-nil if background broadcaster is running
|
||||
fail = make(chan error, 1) // Channel used to receive network error
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight broadcast running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
txs []*types.Transaction
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
|
||||
if tx := p.getPooledTx(queue[i]); tx != nil {
|
||||
txs = append(txs, tx)
|
||||
size += tx.Size()
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(txs) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.sendTransactions(txs); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transactions", "count", len(txs))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txBroadcast:
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxs {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
removePeer(p.id)
|
||||
return
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// announceTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) announceTransactions(removePeer func(string)) {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to announce as transaction stubs
|
||||
done chan struct{} // Non-nil if background announcer is running
|
||||
fail = make(chan error, 1) // Channel used to receive network error
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight announce running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction hashes until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
pending []common.Hash
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
|
||||
if p.getPooledTx(queue[i]) != nil {
|
||||
pending = append(pending, queue[i])
|
||||
size += common.HashLength
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(pending) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.sendPooledTransactionHashes(pending); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transaction announcements", "count", len(pending))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txAnnounce:
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxAnns {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
removePeer(p.id)
|
||||
return
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close signals the broadcast goroutine to terminate.
|
||||
func (p *peer) close() {
|
||||
close(p.term)
|
||||
}
|
||||
|
||||
// Info gathers and returns a collection of metadata known about a peer.
|
||||
func (p *peer) Info() *PeerInfo {
|
||||
// info gathers and returns some `eth` protocol metadata known about a peer.
|
||||
func (p *ethPeer) info() *ethPeerInfo {
|
||||
hash, td := p.Head()
|
||||
|
||||
return &PeerInfo{
|
||||
Version: p.version,
|
||||
return ðPeerInfo{
|
||||
Version: p.Version(),
|
||||
Difficulty: td,
|
||||
Head: hash.Hex(),
|
||||
}
|
||||
}
|
||||
|
||||
// Head retrieves a copy of the current head hash and total difficulty of the
|
||||
// peer.
|
||||
func (p *peer) Head() (hash common.Hash, td *big.Int) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
copy(hash[:], p.head[:])
|
||||
return hash, new(big.Int).Set(p.td)
|
||||
// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known
|
||||
// about a connected peer.
|
||||
type snapPeerInfo struct {
|
||||
Version uint `json:"version"` // Snapshot protocol version negotiated
|
||||
}
|
||||
|
||||
// SetHead updates the head hash and total difficulty of the peer.
|
||||
func (p *peer) SetHead(hash common.Hash, td *big.Int) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata.
|
||||
type snapPeer struct {
|
||||
*snap.Peer
|
||||
|
||||
copy(p.head[:], hash[:])
|
||||
p.td.Set(td)
|
||||
ethDrop *time.Timer // Connection dropper if `eth` doesn't connect in time
|
||||
lock sync.RWMutex // Mutex protecting the internal fields
|
||||
}
|
||||
|
||||
// MarkBlock marks a block as known for the peer, ensuring that the block will
|
||||
// never be propagated to this particular peer.
|
||||
func (p *peer) MarkBlock(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known block hash
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
|
||||
// MarkTransaction marks a transaction as known for the peer, ensuring that it
|
||||
// will never be propagated to this particular peer.
|
||||
func (p *peer) MarkTransaction(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known transaction hash
|
||||
for p.knownTxs.Cardinality() >= maxKnownTxs {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
|
||||
// SendTransactions64 sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is legacy support for initial transaction exchange in eth/64 and
|
||||
// prior. For eth/65 and higher use SendPooledTransactionHashes.
|
||||
func (p *peer) SendTransactions64(txs types.Transactions) error {
|
||||
return p.sendTransactions(txs)
|
||||
}
|
||||
|
||||
// sendTransactions sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction sender. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *peer) sendTransactions(txs types.Transactions) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, tx := range txs {
|
||||
p.knownTxs.Add(tx.Hash())
|
||||
}
|
||||
return p2p.Send(p.rw, TransactionMsg, txs)
|
||||
}
|
||||
|
||||
// AsyncSendTransactions queues a list of transactions (by hash) to eventually
|
||||
// propagate to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *peer) AsyncSendTransactions(hashes []common.Hash) {
|
||||
select {
|
||||
case p.txBroadcast <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
|
||||
// info gathers and returns some `snap` protocol metadata known about a peer.
|
||||
func (p *snapPeer) info() *snapPeerInfo {
|
||||
return &snapPeerInfo{
|
||||
Version: p.Version(),
|
||||
}
|
||||
}
|
||||
|
||||
// sendPooledTransactionHashes sends transaction hashes to the peer and includes
|
||||
// them in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction announcer. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes)
|
||||
}
|
||||
|
||||
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
|
||||
// announce to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
|
||||
select {
|
||||
case p.txAnnounce <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
|
||||
// hashes in its transaction hash set for future reference.
|
||||
//
|
||||
// Note, the method assumes the hashes are correct and correspond to the list of
|
||||
// transactions being sent.
|
||||
func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, PooledTransactionsMsg, txs)
|
||||
}
|
||||
|
||||
// SendNewBlockHashes announces the availability of a number of blocks through
|
||||
// a hash notification.
|
||||
func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
|
||||
// Mark all the block hashes as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
request := make(newBlockHashesData, len(hashes))
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
request[i].Hash = hashes[i]
|
||||
request[i].Number = numbers[i]
|
||||
}
|
||||
return p2p.Send(p.rw, NewBlockHashesMsg, request)
|
||||
}
|
||||
|
||||
// AsyncSendNewBlockHash queues the availability of a block for propagation to a
|
||||
// remote peer. If the peer's broadcast queue is full, the event is silently
|
||||
// dropped.
|
||||
func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
|
||||
select {
|
||||
case p.queuedBlockAnns <- block:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNewBlock propagates an entire block to a remote peer.
|
||||
func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
|
||||
}
|
||||
|
||||
// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
|
||||
// the peer's broadcast queue is full, the event is silently dropped.
|
||||
func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
||||
select {
|
||||
case p.queuedBlocks <- &propEvent{block: block, td: td}:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// SendBlockHeaders sends a batch of block headers to the remote peer.
|
||||
func (p *peer) SendBlockHeaders(headers []*types.Header) error {
|
||||
return p2p.Send(p.rw, BlockHeadersMsg, headers)
|
||||
}
|
||||
|
||||
// SendBlockBodies sends a batch of block contents to the remote peer.
|
||||
func (p *peer) SendBlockBodies(bodies []*blockBody) error {
|
||||
return p2p.Send(p.rw, BlockBodiesMsg, blockBodiesData(bodies))
|
||||
}
|
||||
|
||||
// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
|
||||
// an already RLP encoded format.
|
||||
func (p *peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, BlockBodiesMsg, bodies)
|
||||
}
|
||||
|
||||
// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the
|
||||
// hashes requested.
|
||||
func (p *peer) SendNodeData(data [][]byte) error {
|
||||
return p2p.Send(p.rw, NodeDataMsg, data)
|
||||
}
|
||||
|
||||
// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
|
||||
// ones requested from an already RLP encoded format.
|
||||
func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, ReceiptsMsg, receipts)
|
||||
}
|
||||
|
||||
// RequestOneHeader is a wrapper around the header query functions to fetch a
|
||||
// single header. It is used solely by the fetcher.
|
||||
func (p *peer) RequestOneHeader(hash common.Hash) error {
|
||||
p.Log().Debug("Fetching single header", "hash", hash)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
|
||||
}
|
||||
|
||||
// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
|
||||
// specified.
|
||||
func (p *peer) RequestBodies(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetBlockBodiesMsg, hashes)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of state data", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetNodeDataMsg, hashes)
|
||||
}
|
||||
|
||||
// RequestReceipts fetches a batch of transaction receipts from a remote node.
|
||||
func (p *peer) RequestReceipts(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetReceiptsMsg, hashes)
|
||||
}
|
||||
|
||||
// RequestTxs fetches a batch of transactions from a remote node.
|
||||
func (p *peer) RequestTxs(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes)
|
||||
}
|
||||
|
||||
// Handshake executes the eth protocol handshake, negotiating version number,
|
||||
// network IDs, difficulties, head and genesis blocks.
|
||||
func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
|
||||
// Send out own handshake in a new thread
|
||||
errc := make(chan error, 2)
|
||||
|
||||
var (
|
||||
status63 statusData63 // safe to read after two values have been received from errc
|
||||
status statusData // safe to read after two values have been received from errc
|
||||
)
|
||||
go func() {
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
errc <- p2p.Send(p.rw, StatusMsg, &statusData63{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkId: network,
|
||||
TD: td,
|
||||
CurrentBlock: head,
|
||||
GenesisBlock: genesis,
|
||||
})
|
||||
case p.version >= eth64:
|
||||
errc <- p2p.Send(p.rw, StatusMsg, &statusData{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkID: network,
|
||||
TD: td,
|
||||
Head: head,
|
||||
Genesis: genesis,
|
||||
ForkID: forkID,
|
||||
})
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
errc <- p.readStatusLegacy(network, &status63, genesis)
|
||||
case p.version >= eth64:
|
||||
errc <- p.readStatus(network, &status, genesis, forkFilter)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
}
|
||||
}()
|
||||
timeout := time.NewTimer(handshakeTimeout)
|
||||
defer timeout.Stop()
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-timeout.C:
|
||||
return p2p.DiscReadTimeout
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
p.td, p.head = status63.TD, status63.CurrentBlock
|
||||
case p.version >= eth64:
|
||||
p.td, p.head = status.TD, status.Head
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis common.Hash) error {
|
||||
msg, err := p.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Code != StatusMsg {
|
||||
return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
|
||||
}
|
||||
if msg.Size > protocolMaxMsgSize {
|
||||
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
|
||||
}
|
||||
// Decode the handshake and make sure everything matches
|
||||
if err := msg.Decode(&status); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
if status.GenesisBlock != genesis {
|
||||
return errResp(ErrGenesisMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8])
|
||||
}
|
||||
if status.NetworkId != network {
|
||||
return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkId, network)
|
||||
}
|
||||
if int(status.ProtocolVersion) != p.version {
|
||||
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash, forkFilter forkid.Filter) error {
|
||||
msg, err := p.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Code != StatusMsg {
|
||||
return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
|
||||
}
|
||||
if msg.Size > protocolMaxMsgSize {
|
||||
return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
|
||||
}
|
||||
// Decode the handshake and make sure everything matches
|
||||
if err := msg.Decode(&status); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
if status.NetworkID != network {
|
||||
return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkID, network)
|
||||
}
|
||||
if int(status.ProtocolVersion) != p.version {
|
||||
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
|
||||
}
|
||||
if status.Genesis != genesis {
|
||||
return errResp(ErrGenesisMismatch, "%x (!= %x)", status.Genesis, genesis)
|
||||
}
|
||||
if err := forkFilter(status.ForkID); err != nil {
|
||||
return errResp(ErrForkIDRejected, "%v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (p *peer) String() string {
|
||||
return fmt.Sprintf("Peer %s [%s]", p.id,
|
||||
fmt.Sprintf("eth/%2d", p.version),
|
||||
)
|
||||
}
|
||||
|
||||
// peerSet represents the collection of active peers currently participating in
|
||||
// the Ethereum sub-protocol.
|
||||
type peerSet struct {
|
||||
peers map[string]*peer
|
||||
lock sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// newPeerSet creates a new peer set to track the active participants.
|
||||
func newPeerSet() *peerSet {
|
||||
return &peerSet{
|
||||
peers: make(map[string]*peer),
|
||||
}
|
||||
}
|
||||
|
||||
// Register injects a new peer into the working set, or returns an error if the
|
||||
// peer is already known. If a new peer it registered, its broadcast loop is also
|
||||
// started.
|
||||
func (ps *peerSet) Register(p *peer, removePeer func(string)) error {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
|
||||
if ps.closed {
|
||||
return errClosed
|
||||
}
|
||||
if _, ok := ps.peers[p.id]; ok {
|
||||
return errAlreadyRegistered
|
||||
}
|
||||
ps.peers[p.id] = p
|
||||
|
||||
go p.broadcastBlocks(removePeer)
|
||||
go p.broadcastTransactions(removePeer)
|
||||
if p.version >= eth65 {
|
||||
go p.announceTransactions(removePeer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister removes a remote peer from the active set, disabling any further
|
||||
// actions to/from that particular entity.
|
||||
func (ps *peerSet) Unregister(id string) error {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
|
||||
p, ok := ps.peers[id]
|
||||
if !ok {
|
||||
return errNotRegistered
|
||||
}
|
||||
delete(ps.peers, id)
|
||||
p.close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Peer retrieves the registered peer with the given id.
|
||||
func (ps *peerSet) Peer(id string) *peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return ps.peers[id]
|
||||
}
|
||||
|
||||
// Len returns if the current number of peers in the set.
|
||||
func (ps *peerSet) Len() int {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return len(ps.peers)
|
||||
}
|
||||
|
||||
// PeersWithoutBlock retrieves a list of peers that do not have a given block in
|
||||
// their set of known hashes.
|
||||
func (ps *peerSet) PeersWithoutBlock(hash common.Hash) []*peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peer, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
if !p.knownBlocks.Contains(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PeersWithoutTx retrieves a list of peers that do not have a given transaction
|
||||
// in their set of known hashes.
|
||||
func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peer, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
if !p.knownTxs.Contains(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// BestPeer retrieves the known peer with the currently highest total difficulty.
|
||||
func (ps *peerSet) BestPeer() *peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
var (
|
||||
bestPeer *peer
|
||||
bestTd *big.Int
|
||||
)
|
||||
for _, p := range ps.peers {
|
||||
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
||||
bestPeer, bestTd = p, td
|
||||
}
|
||||
}
|
||||
return bestPeer
|
||||
}
|
||||
|
||||
// Close disconnects all peers.
|
||||
// No new peers can be registered after Close has returned.
|
||||
func (ps *peerSet) Close() {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
|
||||
for _, p := range ps.peers {
|
||||
p.Disconnect(p2p.DiscQuitting)
|
||||
}
|
||||
ps.closed = true
|
||||
}
|
||||
|
301
eth/peerset.go
Normal file
301
eth/peerset.go
Normal file
@ -0,0 +1,301 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
var (
|
||||
// errPeerSetClosed is returned if a peer is attempted to be added or removed
|
||||
// from the peer set after it has been terminated.
|
||||
errPeerSetClosed = errors.New("peerset closed")
|
||||
|
||||
// errPeerAlreadyRegistered is returned if a peer is attempted to be added
|
||||
// to the peer set, but one with the same id already exists.
|
||||
errPeerAlreadyRegistered = errors.New("peer already registered")
|
||||
|
||||
// errPeerNotRegistered is returned if a peer is attempted to be removed from
|
||||
// a peer set, but no peer with the given id exists.
|
||||
errPeerNotRegistered = errors.New("peer not registered")
|
||||
|
||||
// ethConnectTimeout is the `snap` timeout for `eth` to connect too.
|
||||
ethConnectTimeout = 3 * time.Second
|
||||
)
|
||||
|
||||
// peerSet represents the collection of active peers currently participating in
|
||||
// the `eth` or `snap` protocols.
|
||||
type peerSet struct {
|
||||
ethPeers map[string]*ethPeer // Peers connected on the `eth` protocol
|
||||
snapPeers map[string]*snapPeer // Peers connected on the `snap` protocol
|
||||
|
||||
ethJoinFeed event.Feed // Events when an `eth` peer successfully joins
|
||||
ethDropFeed event.Feed // Events when an `eth` peer gets dropped
|
||||
snapJoinFeed event.Feed // Events when a `snap` peer joins on both `eth` and `snap`
|
||||
snapDropFeed event.Feed // Events when a `snap` peer gets dropped (only if fully joined)
|
||||
|
||||
scope event.SubscriptionScope // Subscription group to unsubscribe everyone at once
|
||||
|
||||
lock sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// newPeerSet creates a new peer set to track the active participants.
|
||||
func newPeerSet() *peerSet {
|
||||
return &peerSet{
|
||||
ethPeers: make(map[string]*ethPeer),
|
||||
snapPeers: make(map[string]*snapPeer),
|
||||
}
|
||||
}
|
||||
|
||||
// subscribeEthJoin registers a subscription for peers joining (and completing
|
||||
// the handshake) on the `eth` protocol.
|
||||
func (ps *peerSet) subscribeEthJoin(ch chan<- *eth.Peer) event.Subscription {
|
||||
return ps.scope.Track(ps.ethJoinFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// subscribeEthDrop registers a subscription for peers being dropped from the
|
||||
// `eth` protocol.
|
||||
func (ps *peerSet) subscribeEthDrop(ch chan<- *eth.Peer) event.Subscription {
|
||||
return ps.scope.Track(ps.ethDropFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// subscribeSnapJoin registers a subscription for peers joining (and completing
|
||||
// the `eth` join) on the `snap` protocol.
|
||||
func (ps *peerSet) subscribeSnapJoin(ch chan<- *snap.Peer) event.Subscription {
|
||||
return ps.scope.Track(ps.snapJoinFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// subscribeSnapDrop registers a subscription for peers being dropped from the
|
||||
// `snap` protocol.
|
||||
func (ps *peerSet) subscribeSnapDrop(ch chan<- *snap.Peer) event.Subscription {
|
||||
return ps.scope.Track(ps.snapDropFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// registerEthPeer injects a new `eth` peer into the working set, or returns an
|
||||
// error if the peer is already known. The peer is announced on the `eth` join
|
||||
// feed and if it completes a pending `snap` peer, also on that feed.
|
||||
func (ps *peerSet) registerEthPeer(peer *eth.Peer) error {
|
||||
ps.lock.Lock()
|
||||
if ps.closed {
|
||||
ps.lock.Unlock()
|
||||
return errPeerSetClosed
|
||||
}
|
||||
id := peer.ID()
|
||||
if _, ok := ps.ethPeers[id]; ok {
|
||||
ps.lock.Unlock()
|
||||
return errPeerAlreadyRegistered
|
||||
}
|
||||
ps.ethPeers[id] = ðPeer{Peer: peer}
|
||||
|
||||
snap, ok := ps.snapPeers[id]
|
||||
ps.lock.Unlock()
|
||||
|
||||
if ok {
|
||||
// Previously dangling `snap` peer, stop it's timer since `eth` connected
|
||||
snap.lock.Lock()
|
||||
if snap.ethDrop != nil {
|
||||
snap.ethDrop.Stop()
|
||||
snap.ethDrop = nil
|
||||
}
|
||||
snap.lock.Unlock()
|
||||
}
|
||||
ps.ethJoinFeed.Send(peer)
|
||||
if ok {
|
||||
ps.snapJoinFeed.Send(snap.Peer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unregisterEthPeer removes a remote peer from the active set, disabling any further
|
||||
// actions to/from that particular entity. The drop is announced on the `eth` drop
|
||||
// feed and also on the `snap` feed if the eth/snap duality was broken just now.
|
||||
func (ps *peerSet) unregisterEthPeer(id string) error {
|
||||
ps.lock.Lock()
|
||||
eth, ok := ps.ethPeers[id]
|
||||
if !ok {
|
||||
ps.lock.Unlock()
|
||||
return errPeerNotRegistered
|
||||
}
|
||||
delete(ps.ethPeers, id)
|
||||
|
||||
snap, ok := ps.snapPeers[id]
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.ethDropFeed.Send(eth)
|
||||
if ok {
|
||||
ps.snapDropFeed.Send(snap)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerSnapPeer injects a new `snap` peer into the working set, or returns
|
||||
// an error if the peer is already known. The peer is announced on the `snap`
|
||||
// join feed if it completes an existing `eth` peer.
|
||||
//
|
||||
// If the peer isn't yet connected on `eth` and fails to do so within a given
|
||||
// amount of time, it is dropped. This enforces that `snap` is an extension to
|
||||
// `eth`, not a standalone leeching protocol.
|
||||
func (ps *peerSet) registerSnapPeer(peer *snap.Peer) error {
|
||||
ps.lock.Lock()
|
||||
if ps.closed {
|
||||
ps.lock.Unlock()
|
||||
return errPeerSetClosed
|
||||
}
|
||||
id := peer.ID()
|
||||
if _, ok := ps.snapPeers[id]; ok {
|
||||
ps.lock.Unlock()
|
||||
return errPeerAlreadyRegistered
|
||||
}
|
||||
ps.snapPeers[id] = &snapPeer{Peer: peer}
|
||||
|
||||
_, ok := ps.ethPeers[id]
|
||||
if !ok {
|
||||
// Dangling `snap` peer, start a timer to drop if `eth` doesn't connect
|
||||
ps.snapPeers[id].ethDrop = time.AfterFunc(ethConnectTimeout, func() {
|
||||
peer.Log().Warn("Snapshot peer missing eth, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
|
||||
peer.Disconnect(p2p.DiscUselessPeer)
|
||||
})
|
||||
}
|
||||
ps.lock.Unlock()
|
||||
|
||||
if ok {
|
||||
ps.snapJoinFeed.Send(peer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unregisterSnapPeer removes a remote peer from the active set, disabling any
|
||||
// further actions to/from that particular entity. The drop is announced on the
|
||||
// `snap` drop feed.
|
||||
func (ps *peerSet) unregisterSnapPeer(id string) error {
|
||||
ps.lock.Lock()
|
||||
peer, ok := ps.snapPeers[id]
|
||||
if !ok {
|
||||
ps.lock.Unlock()
|
||||
return errPeerNotRegistered
|
||||
}
|
||||
delete(ps.snapPeers, id)
|
||||
ps.lock.Unlock()
|
||||
|
||||
peer.lock.Lock()
|
||||
if peer.ethDrop != nil {
|
||||
peer.ethDrop.Stop()
|
||||
peer.ethDrop = nil
|
||||
}
|
||||
peer.lock.Unlock()
|
||||
|
||||
ps.snapDropFeed.Send(peer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ethPeer retrieves the registered `eth` peer with the given id.
|
||||
func (ps *peerSet) ethPeer(id string) *ethPeer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return ps.ethPeers[id]
|
||||
}
|
||||
|
||||
// snapPeer retrieves the registered `snap` peer with the given id.
|
||||
func (ps *peerSet) snapPeer(id string) *snapPeer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return ps.snapPeers[id]
|
||||
}
|
||||
|
||||
// ethPeersWithoutBlock retrieves a list of `eth` peers that do not have a given
|
||||
// block in their set of known hashes so it might be propagated to them.
|
||||
func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*ethPeer, 0, len(ps.ethPeers))
|
||||
for _, p := range ps.ethPeers {
|
||||
if !p.KnownBlock(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ethPeersWithoutTransacion retrieves a list of `eth` peers that do not have a
|
||||
// given transaction in their set of known hashes.
|
||||
func (ps *peerSet) ethPeersWithoutTransacion(hash common.Hash) []*ethPeer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*ethPeer, 0, len(ps.ethPeers))
|
||||
for _, p := range ps.ethPeers {
|
||||
if !p.KnownTransaction(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Len returns if the current number of `eth` peers in the set. Since the `snap`
|
||||
// peers are tied to the existnce of an `eth` connection, that will always be a
|
||||
// subset of `eth`.
|
||||
func (ps *peerSet) Len() int {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return len(ps.ethPeers)
|
||||
}
|
||||
|
||||
// ethPeerWithHighestTD retrieves the known peer with the currently highest total
|
||||
// difficulty.
|
||||
func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
var (
|
||||
bestPeer *eth.Peer
|
||||
bestTd *big.Int
|
||||
)
|
||||
for _, p := range ps.ethPeers {
|
||||
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
||||
bestPeer, bestTd = p.Peer, td
|
||||
}
|
||||
}
|
||||
return bestPeer
|
||||
}
|
||||
|
||||
// close disconnects all peers.
|
||||
func (ps *peerSet) close() {
|
||||
ps.lock.Lock()
|
||||
defer ps.lock.Unlock()
|
||||
|
||||
for _, p := range ps.ethPeers {
|
||||
p.Disconnect(p2p.DiscQuitting)
|
||||
}
|
||||
for _, p := range ps.snapPeers {
|
||||
p.Disconnect(p2p.DiscQuitting)
|
||||
}
|
||||
ps.closed = true
|
||||
}
|
221
eth/protocol.go
221
eth/protocol.go
@ -1,221 +0,0 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
eth63 = 63
|
||||
eth64 = 64
|
||||
eth65 = 65
|
||||
)
|
||||
|
||||
// protocolName is the official short name of the protocol used during capability negotiation.
|
||||
const protocolName = "eth"
|
||||
|
||||
// ProtocolVersions are the supported versions of the eth protocol (first is primary).
|
||||
var ProtocolVersions = []uint{eth65, eth64, eth63}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{eth65: 17, eth64: 17, eth63: 17}
|
||||
|
||||
const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
|
||||
|
||||
// eth protocol message codes
|
||||
const (
|
||||
StatusMsg = 0x00
|
||||
NewBlockHashesMsg = 0x01
|
||||
TransactionMsg = 0x02
|
||||
GetBlockHeadersMsg = 0x03
|
||||
BlockHeadersMsg = 0x04
|
||||
GetBlockBodiesMsg = 0x05
|
||||
BlockBodiesMsg = 0x06
|
||||
NewBlockMsg = 0x07
|
||||
GetNodeDataMsg = 0x0d
|
||||
NodeDataMsg = 0x0e
|
||||
GetReceiptsMsg = 0x0f
|
||||
ReceiptsMsg = 0x10
|
||||
|
||||
// New protocol message codes introduced in eth65
|
||||
//
|
||||
// Previously these message ids were used by some legacy and unsupported
|
||||
// eth protocols, reown them here.
|
||||
NewPooledTransactionHashesMsg = 0x08
|
||||
GetPooledTransactionsMsg = 0x09
|
||||
PooledTransactionsMsg = 0x0a
|
||||
)
|
||||
|
||||
type errCode int
|
||||
|
||||
const (
|
||||
ErrMsgTooLarge = iota
|
||||
ErrDecode
|
||||
ErrInvalidMsgCode
|
||||
ErrProtocolVersionMismatch
|
||||
ErrNetworkIDMismatch
|
||||
ErrGenesisMismatch
|
||||
ErrForkIDRejected
|
||||
ErrNoStatusMsg
|
||||
ErrExtraStatusMsg
|
||||
)
|
||||
|
||||
func (e errCode) String() string {
|
||||
return errorToString[int(e)]
|
||||
}
|
||||
|
||||
// XXX change once legacy code is out
|
||||
var errorToString = map[int]string{
|
||||
ErrMsgTooLarge: "Message too long",
|
||||
ErrDecode: "Invalid message",
|
||||
ErrInvalidMsgCode: "Invalid message code",
|
||||
ErrProtocolVersionMismatch: "Protocol version mismatch",
|
||||
ErrNetworkIDMismatch: "Network ID mismatch",
|
||||
ErrGenesisMismatch: "Genesis mismatch",
|
||||
ErrForkIDRejected: "Fork ID rejected",
|
||||
ErrNoStatusMsg: "No status message",
|
||||
ErrExtraStatusMsg: "Extra status message",
|
||||
}
|
||||
|
||||
type txPool interface {
|
||||
// Has returns an indicator whether txpool has a transaction
|
||||
// cached with the given hash.
|
||||
Has(hash common.Hash) bool
|
||||
|
||||
// Get retrieves the transaction from local txpool with given
|
||||
// tx hash.
|
||||
Get(hash common.Hash) *types.Transaction
|
||||
|
||||
// AddRemotes should add the given transactions to the pool.
|
||||
AddRemotes([]*types.Transaction) []error
|
||||
|
||||
// Pending should return pending transactions.
|
||||
// The slice should be modifiable by the caller.
|
||||
Pending() (map[common.Address]types.Transactions, error)
|
||||
|
||||
// SubscribeNewTxsEvent should return an event subscription of
|
||||
// NewTxsEvent and send events to the given channel.
|
||||
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
||||
}
|
||||
|
||||
// statusData63 is the network packet for the status message for eth/63.
|
||||
type statusData63 struct {
|
||||
ProtocolVersion uint32
|
||||
NetworkId uint64
|
||||
TD *big.Int
|
||||
CurrentBlock common.Hash
|
||||
GenesisBlock common.Hash
|
||||
}
|
||||
|
||||
// statusData is the network packet for the status message for eth/64 and later.
|
||||
type statusData struct {
|
||||
ProtocolVersion uint32
|
||||
NetworkID uint64
|
||||
TD *big.Int
|
||||
Head common.Hash
|
||||
Genesis common.Hash
|
||||
ForkID forkid.ID
|
||||
}
|
||||
|
||||
// newBlockHashesData is the network packet for the block announcements.
|
||||
type newBlockHashesData []struct {
|
||||
Hash common.Hash // Hash of one particular block being announced
|
||||
Number uint64 // Number of one particular block being announced
|
||||
}
|
||||
|
||||
// getBlockHeadersData represents a block header query.
|
||||
type getBlockHeadersData struct {
|
||||
Origin hashOrNumber // Block from which to retrieve headers
|
||||
Amount uint64 // Maximum number of headers to retrieve
|
||||
Skip uint64 // Blocks to skip between consecutive headers
|
||||
Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
|
||||
}
|
||||
|
||||
// hashOrNumber is a combined field for specifying an origin block.
|
||||
type hashOrNumber struct {
|
||||
Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
|
||||
Number uint64 // Block hash from which to retrieve headers (excludes Hash)
|
||||
}
|
||||
|
||||
// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
|
||||
// two contained union fields.
|
||||
func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
|
||||
if hn.Hash == (common.Hash{}) {
|
||||
return rlp.Encode(w, hn.Number)
|
||||
}
|
||||
if hn.Number != 0 {
|
||||
return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
|
||||
}
|
||||
return rlp.Encode(w, hn.Hash)
|
||||
}
|
||||
|
||||
// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
|
||||
// into either a block hash or a block number.
|
||||
func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
|
||||
_, size, _ := s.Kind()
|
||||
origin, err := s.Raw()
|
||||
if err == nil {
|
||||
switch {
|
||||
case size == 32:
|
||||
err = rlp.DecodeBytes(origin, &hn.Hash)
|
||||
case size <= 8:
|
||||
err = rlp.DecodeBytes(origin, &hn.Number)
|
||||
default:
|
||||
err = fmt.Errorf("invalid input size %d for origin", size)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newBlockData is the network packet for the block propagation message.
|
||||
type newBlockData struct {
|
||||
Block *types.Block
|
||||
TD *big.Int
|
||||
}
|
||||
|
||||
// sanityCheck verifies that the values are reasonable, as a DoS protection
|
||||
func (request *newBlockData) sanityCheck() error {
|
||||
if err := request.Block.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
|
||||
// larger, it will still fit within 100 bits
|
||||
if tdlen := request.TD.BitLen(); tdlen > 100 {
|
||||
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockBody represents the data content of a single block.
|
||||
type blockBody struct {
|
||||
Transactions []*types.Transaction // Transactions contained within a block
|
||||
Uncles []*types.Header // Uncles contained within a block
|
||||
}
|
||||
|
||||
// blockBodiesData is the network packet for block content distribution.
|
||||
type blockBodiesData []*blockBody
|
@ -1,459 +0,0 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"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/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
}
|
||||
|
||||
var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
// Tests that handshake failures are detected and reported correctly.
|
||||
func TestStatusMsgErrors63(t *testing.T) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
|
||||
var (
|
||||
genesis = pm.blockchain.Genesis()
|
||||
head = pm.blockchain.CurrentHeader()
|
||||
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
)
|
||||
defer pm.Stop()
|
||||
|
||||
tests := []struct {
|
||||
code uint64
|
||||
data interface{}
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
code: TransactionMsg, data: []interface{}{},
|
||||
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData63{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash()},
|
||||
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 63),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData63{63, 999, td, head.Hash(), genesis.Hash()},
|
||||
wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData63{63, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}},
|
||||
wantError: errResp(ErrGenesisMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]),
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
p, errc := newTestPeer("peer", 63, pm, false)
|
||||
// The send call might hang until reset because
|
||||
// the protocol might not read the payload.
|
||||
go p2p.Send(p.app, test.code, test.data)
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err == nil {
|
||||
t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError)
|
||||
} else if err.Error() != test.wantError.Error() {
|
||||
t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("protocol did not shut down within 2 seconds")
|
||||
}
|
||||
p.close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusMsgErrors64(t *testing.T) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
|
||||
var (
|
||||
genesis = pm.blockchain.Genesis()
|
||||
head = pm.blockchain.CurrentHeader()
|
||||
td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
forkID = forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64())
|
||||
)
|
||||
defer pm.Stop()
|
||||
|
||||
tests := []struct {
|
||||
code uint64
|
||||
data interface{}
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
code: TransactionMsg, data: []interface{}{},
|
||||
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkID},
|
||||
wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 64),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{64, 999, td, head.Hash(), genesis.Hash(), forkID},
|
||||
wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}, forkID},
|
||||
wantError: errResp(ErrGenesisMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis.Hash()),
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
|
||||
wantError: errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()),
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
p, errc := newTestPeer("peer", 64, pm, false)
|
||||
// The send call might hang until reset because
|
||||
// the protocol might not read the payload.
|
||||
go p2p.Send(p.app, test.code, test.data)
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err == nil {
|
||||
t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError)
|
||||
} else if err.Error() != test.wantError.Error() {
|
||||
t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("protocol did not shut down within 2 seconds")
|
||||
}
|
||||
p.close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkIDSplit(t *testing.T) {
|
||||
var (
|
||||
engine = ethash.NewFaker()
|
||||
|
||||
configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}
|
||||
configProFork = ¶ms.ChainConfig{
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
EIP150Block: big.NewInt(2),
|
||||
EIP155Block: big.NewInt(2),
|
||||
EIP158Block: big.NewInt(2),
|
||||
ByzantiumBlock: big.NewInt(3),
|
||||
}
|
||||
dbNoFork = rawdb.NewMemoryDatabase()
|
||||
dbProFork = rawdb.NewMemoryDatabase()
|
||||
|
||||
gspecNoFork = &core.Genesis{Config: configNoFork}
|
||||
gspecProFork = &core.Genesis{Config: configProFork}
|
||||
|
||||
genesisNoFork = gspecNoFork.MustCommit(dbNoFork)
|
||||
genesisProFork = gspecProFork.MustCommit(dbProFork)
|
||||
|
||||
chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil)
|
||||
chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil)
|
||||
|
||||
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
|
||||
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
|
||||
|
||||
ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil)
|
||||
ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil)
|
||||
)
|
||||
ethNoFork.Start(1000)
|
||||
ethProFork.Start(1000)
|
||||
|
||||
// Both nodes should allow the other to connect (same genesis, next fork is the same)
|
||||
p2pNoFork, p2pProFork := p2p.MsgPipe()
|
||||
peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc := make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
go func() { errc <- ethProFork.handle(peerNoFork) }()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
t.Fatalf("frontier nofork <-> profork failed: %v", err)
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
p2pNoFork.Close()
|
||||
p2pProFork.Close()
|
||||
}
|
||||
// Progress into Homestead. Fork's match, so we don't care what the future holds
|
||||
chainNoFork.InsertChain(blocksNoFork[:1])
|
||||
chainProFork.InsertChain(blocksProFork[:1])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
go func() { errc <- ethProFork.handle(peerNoFork) }()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
t.Fatalf("homestead nofork <-> profork failed: %v", err)
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
p2pNoFork.Close()
|
||||
p2pProFork.Close()
|
||||
}
|
||||
// Progress into Spurious. Forks mismatch, signalling differing chains, reject
|
||||
chainNoFork.InsertChain(blocksNoFork[1:2])
|
||||
chainProFork.InsertChain(blocksProFork[1:2])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
go func() { errc <- ethProFork.handle(peerNoFork) }()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
if want := errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()); err.Error() != want.Error() {
|
||||
t.Fatalf("fork ID rejection error mismatch: have %v, want %v", err, want)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatalf("split peers not rejected")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that received transactions are added to the local pool.
|
||||
func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
|
||||
func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }
|
||||
func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) }
|
||||
|
||||
func testRecvTransactions(t *testing.T, protocol int) {
|
||||
txAdded := make(chan []*types.Transaction)
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, txAdded)
|
||||
pm.acceptTxs = 1 // mark synced to accept transactions
|
||||
p, _ := newTestPeer("peer", protocol, pm, true)
|
||||
defer pm.Stop()
|
||||
defer p.close()
|
||||
|
||||
tx := newTestTransaction(testAccount, 0, 0)
|
||||
if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil {
|
||||
t.Fatalf("send error: %v", err)
|
||||
}
|
||||
select {
|
||||
case added := <-txAdded:
|
||||
if len(added) != 1 {
|
||||
t.Errorf("wrong number of added transactions: got %d, want 1", len(added))
|
||||
} else if added[0].Hash() != tx.Hash() {
|
||||
t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash())
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("no NewTxsEvent received within 2 seconds")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
|
||||
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }
|
||||
func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) }
|
||||
|
||||
func testSendTransactions(t *testing.T, protocol int) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
|
||||
defer pm.Stop()
|
||||
|
||||
// Fill the pool with big transactions (use a subscription to wait until all
|
||||
// the transactions are announced to avoid spurious events causing extra
|
||||
// broadcasts).
|
||||
const txsize = txsyncPackSize / 10
|
||||
alltxs := make([]*types.Transaction, 100)
|
||||
for nonce := range alltxs {
|
||||
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
|
||||
}
|
||||
pm.txpool.AddRemotes(alltxs)
|
||||
time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame)
|
||||
|
||||
// Connect several peers. They should all receive the pending transactions.
|
||||
var wg sync.WaitGroup
|
||||
checktxs := func(p *testPeer) {
|
||||
defer wg.Done()
|
||||
defer p.close()
|
||||
seen := make(map[common.Hash]bool)
|
||||
for _, tx := range alltxs {
|
||||
seen[tx.Hash()] = false
|
||||
}
|
||||
for n := 0; n < len(alltxs) && !t.Failed(); {
|
||||
var forAllHashes func(callback func(hash common.Hash))
|
||||
switch protocol {
|
||||
case 63:
|
||||
fallthrough
|
||||
case 64:
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
continue
|
||||
} else if msg.Code != TransactionMsg {
|
||||
t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
|
||||
continue
|
||||
}
|
||||
var txs []*types.Transaction
|
||||
if err := msg.Decode(&txs); err != nil {
|
||||
t.Errorf("%v: %v", p.Peer, err)
|
||||
continue
|
||||
}
|
||||
forAllHashes = func(callback func(hash common.Hash)) {
|
||||
for _, tx := range txs {
|
||||
callback(tx.Hash())
|
||||
}
|
||||
}
|
||||
case 65:
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
continue
|
||||
} else if msg.Code != NewPooledTransactionHashesMsg {
|
||||
t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code)
|
||||
continue
|
||||
}
|
||||
var hashes []common.Hash
|
||||
if err := msg.Decode(&hashes); err != nil {
|
||||
t.Errorf("%v: %v", p.Peer, err)
|
||||
continue
|
||||
}
|
||||
forAllHashes = func(callback func(hash common.Hash)) {
|
||||
for _, h := range hashes {
|
||||
callback(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
forAllHashes(func(hash common.Hash) {
|
||||
seentx, want := seen[hash]
|
||||
if seentx {
|
||||
t.Errorf("%v: got tx more than once: %x", p.Peer, hash)
|
||||
}
|
||||
if !want {
|
||||
t.Errorf("%v: got unexpected tx: %x", p.Peer, hash)
|
||||
}
|
||||
seen[hash] = true
|
||||
n++
|
||||
})
|
||||
}
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
p, _ := newTestPeer(fmt.Sprintf("peer #%d", i), protocol, pm, true)
|
||||
wg.Add(1)
|
||||
go checktxs(p)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestTransactionPropagation(t *testing.T) { testSyncTransaction(t, true) }
|
||||
func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) }
|
||||
|
||||
func testSyncTransaction(t *testing.T, propagtion bool) {
|
||||
// Create a protocol manager for transaction fetcher and sender
|
||||
pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
|
||||
defer pmFetcher.Stop()
|
||||
pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
|
||||
pmSender.broadcastTxAnnouncesOnly = !propagtion
|
||||
defer pmSender.Stop()
|
||||
|
||||
// Sync up the two peers
|
||||
io1, io2 := p2p.MsgPipe()
|
||||
|
||||
go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get))
|
||||
go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get))
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer()))
|
||||
atomic.StoreUint32(&pmFetcher.acceptTxs, 1)
|
||||
|
||||
newTxs := make(chan core.NewTxsEvent, 1024)
|
||||
sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Fill the pool with new transactions
|
||||
alltxs := make([]*types.Transaction, 1024)
|
||||
for nonce := range alltxs {
|
||||
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0)
|
||||
}
|
||||
pmSender.txpool.AddRemotes(alltxs)
|
||||
|
||||
var got int
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case ev := <-newTxs:
|
||||
got += len(ev.Txs)
|
||||
if got == 1024 {
|
||||
break loop
|
||||
}
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Fatal("Failed to retrieve all transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the custom union field encoder and decoder works correctly.
|
||||
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
|
||||
// Create a "random" hash for testing
|
||||
var hash common.Hash
|
||||
for i := range hash {
|
||||
hash[i] = byte(i)
|
||||
}
|
||||
// Assemble some table driven tests
|
||||
tests := []struct {
|
||||
packet *getBlockHeadersData
|
||||
fail bool
|
||||
}{
|
||||
// Providing the origin as either a hash or a number should both work
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}}},
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}}},
|
||||
|
||||
// Providing arbitrary query field should also work
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
|
||||
// Providing both the origin hash and origin number must fail
|
||||
{fail: true, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash, Number: 314}}},
|
||||
}
|
||||
// Iterate over each of the tests and try to encode and then decode
|
||||
for i, tt := range tests {
|
||||
bytes, err := rlp.EncodeToBytes(tt.packet)
|
||||
if err != nil && !tt.fail {
|
||||
t.Fatalf("test %d: failed to encode packet: %v", i, err)
|
||||
} else if err == nil && tt.fail {
|
||||
t.Fatalf("test %d: encode should have failed", i)
|
||||
}
|
||||
if !tt.fail {
|
||||
packet := new(getBlockHeadersData)
|
||||
if err := rlp.DecodeBytes(bytes, packet); err != nil {
|
||||
t.Fatalf("test %d: failed to decode packet: %v", i, err)
|
||||
}
|
||||
if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
|
||||
packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
|
||||
t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
195
eth/protocols/eth/broadcast.go
Normal file
195
eth/protocols/eth/broadcast.go
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright 2019 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 (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// This is the target size for the packs of transactions or announcements. A
|
||||
// pack can get larger than this if a single transactions exceeds this size.
|
||||
maxTxPacketSize = 100 * 1024
|
||||
)
|
||||
|
||||
// blockPropagation is a block propagation event, waiting for its turn in the
|
||||
// broadcast queue.
|
||||
type blockPropagation struct {
|
||||
block *types.Block
|
||||
td *big.Int
|
||||
}
|
||||
|
||||
// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *Peer) broadcastBlocks() {
|
||||
for {
|
||||
select {
|
||||
case prop := <-p.queuedBlocks:
|
||||
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
|
||||
|
||||
case block := <-p.queuedBlockAnns:
|
||||
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *Peer) broadcastTransactions() {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to broadcast as full transactions
|
||||
done chan struct{} // Non-nil if background broadcaster is running
|
||||
fail = make(chan error, 1) // Channel used to receive network error
|
||||
failed bool // Flag whether a send failed, discard everything onward
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight broadcast running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
txs []*types.Transaction
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < maxTxPacketSize; i++ {
|
||||
if tx := p.txpool.Get(queue[i]); tx != nil {
|
||||
txs = append(txs, tx)
|
||||
size += tx.Size()
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(txs) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.SendTransactions(txs); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transactions", "count", len(txs))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txBroadcast:
|
||||
// If the connection failed, discard all transaction events
|
||||
if failed {
|
||||
continue
|
||||
}
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxs {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
failed = true
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// announceTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *Peer) announceTransactions() {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to announce as transaction stubs
|
||||
done chan struct{} // Non-nil if background announcer is running
|
||||
fail = make(chan error, 1) // Channel used to receive network error
|
||||
failed bool // Flag whether a send failed, discard everything onward
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight announce running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction hashes until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
pending []common.Hash
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < maxTxPacketSize; i++ {
|
||||
if p.txpool.Get(queue[i]) != nil {
|
||||
pending = append(pending, queue[i])
|
||||
size += common.HashLength
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(pending) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.sendPooledTransactionHashes(pending); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transaction announcements", "count", len(pending))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txAnnounce:
|
||||
// If the connection failed, discard all transaction events
|
||||
if failed {
|
||||
continue
|
||||
}
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxAnns {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
failed = true
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
65
eth/protocols/eth/discovery.go
Normal file
65
eth/protocols/eth/discovery.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2019 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 (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// enrEntry is the ENR entry which advertises `eth` protocol on the discovery.
|
||||
type enrEntry struct {
|
||||
ForkID forkid.ID // Fork identifier per EIP-2124
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// ENRKey implements enr.Entry.
|
||||
func (e enrEntry) ENRKey() string {
|
||||
return "eth"
|
||||
}
|
||||
|
||||
// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain
|
||||
// head events and updates the requested node record whenever a fork is passed.
|
||||
func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) {
|
||||
var newHead = make(chan core.ChainHeadEvent, 10)
|
||||
sub := chain.SubscribeChainHeadEvent(newHead)
|
||||
|
||||
go func() {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case <-newHead:
|
||||
ln.Set(currentENREntry(chain))
|
||||
case <-sub.Err():
|
||||
// Would be nice to sync with Stop, but there is no
|
||||
// good way to do that.
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// currentENREntry constructs an `eth` ENR entry based on the current state of the chain.
|
||||
func currentENREntry(chain *core.BlockChain) *enrEntry {
|
||||
return &enrEntry{
|
||||
ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64()),
|
||||
}
|
||||
}
|
512
eth/protocols/eth/handler.go
Normal file
512
eth/protocols/eth/handler.go
Normal file
@ -0,0 +1,512 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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/p2p"
|
||||
"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/rlp"
|
||||
"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
|
||||
|
||||
// maxBodiesServe is the maximum number of block bodies to serve. This number
|
||||
// is mostly there to limit the number of disk lookups. With 24KB block sizes
|
||||
// nowadays, the practical limit will always be softResponseLimit.
|
||||
maxBodiesServe = 1024
|
||||
|
||||
// maxNodeDataServe is the maximum number of state trie nodes to serve. This
|
||||
// number is there to limit the number of disk lookups.
|
||||
maxNodeDataServe = 1024
|
||||
|
||||
// maxReceiptsServe is the maximum number of block receipts to serve. This
|
||||
// number is mostly there to limit the number of disk lookups. With block
|
||||
// containing 200+ transactions nowadays, the practical limit will always
|
||||
// be softResponseLimit.
|
||||
maxReceiptsServe = 1024
|
||||
)
|
||||
|
||||
// Handler is a callback to invoke from an outside runner after the boilerplate
|
||||
// exchanges have passed.
|
||||
type Handler func(peer *Peer) error
|
||||
|
||||
// Backend defines the data retrieval methods to serve remote requests and the
|
||||
// callback methods to invoke on remote deliveries.
|
||||
type Backend interface {
|
||||
// Chain retrieves the blockchain object to serve data.
|
||||
Chain() *core.BlockChain
|
||||
|
||||
// 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
|
||||
|
||||
// AcceptTxs retrieves whether transaction processing is enabled on the node
|
||||
// or if inbound transactions should simply be dropped.
|
||||
AcceptTxs() bool
|
||||
|
||||
// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
|
||||
// should do any peer maintenance work, handshakes and validations. If all
|
||||
// is passed, control should be given back to the `handler` to process the
|
||||
// inbound messages going forward.
|
||||
RunPeer(peer *Peer, handler Handler) error
|
||||
|
||||
// PeerInfo retrieves all known `eth` information about a peer.
|
||||
PeerInfo(id enode.ID) interface{}
|
||||
|
||||
// Handle is a callback to be invoked when a data packet is received from
|
||||
// the remote peer. Only packets not consumed by the protocol handler will
|
||||
// be forwarded to the backend.
|
||||
Handle(peer *Peer, packet Packet) error
|
||||
}
|
||||
|
||||
// TxPool defines the methods needed by the protocol handler to serve transactions.
|
||||
type TxPool interface {
|
||||
// Get retrieves the the transaction from the local txpool with the given hash.
|
||||
Get(hash common.Hash) *types.Transaction
|
||||
}
|
||||
|
||||
// MakeProtocols constructs the P2P protocol definitions for `eth`.
|
||||
func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||
protocols := make([]p2p.Protocol, len(protocolVersions))
|
||||
for i, version := range protocolVersions {
|
||||
version := version // Closure
|
||||
|
||||
protocols[i] = p2p.Protocol{
|
||||
Name: protocolName,
|
||||
Version: version,
|
||||
Length: protocolLengths[version],
|
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
peer := NewPeer(version, p, rw, backend.TxPool())
|
||||
defer peer.Close()
|
||||
|
||||
return backend.RunPeer(peer, func(peer *Peer) error {
|
||||
return Handle(backend, peer)
|
||||
})
|
||||
},
|
||||
NodeInfo: func() interface{} {
|
||||
return nodeInfo(backend.Chain(), network)
|
||||
},
|
||||
PeerInfo: func(id enode.ID) interface{} {
|
||||
return backend.PeerInfo(id)
|
||||
},
|
||||
Attributes: []enr.Entry{currentENREntry(backend.Chain())},
|
||||
DialCandidates: dnsdisc,
|
||||
}
|
||||
}
|
||||
return protocols
|
||||
}
|
||||
|
||||
// NodeInfo represents a short summary of the `eth` sub-protocol metadata
|
||||
// known about the host peer.
|
||||
type NodeInfo struct {
|
||||
Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
||||
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
||||
Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
|
||||
Head common.Hash `json:"head"` // Hex hash of the host's best owned block
|
||||
}
|
||||
|
||||
// nodeInfo retrieves some `eth` protocol metadata about the running host node.
|
||||
func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo {
|
||||
head := chain.CurrentBlock()
|
||||
return &NodeInfo{
|
||||
Network: network,
|
||||
Difficulty: chain.GetTd(head.Hash(), head.NumberU64()),
|
||||
Genesis: chain.Genesis().Hash(),
|
||||
Config: chain.Config(),
|
||||
Head: head.Hash(),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is invoked whenever an `eth` connection is made that successfully passes
|
||||
// the protocol handshake. This method will keep processing messages until the
|
||||
// connection is torn down.
|
||||
func Handle(backend Backend, peer *Peer) error {
|
||||
for {
|
||||
if err := handleMessage(backend, peer); err != nil {
|
||||
peer.Log().Debug("Message handling failed in `eth`", "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessage is invoked whenever an inbound message is received from a remote
|
||||
// peer. The remote connection is torn down upon returning any error.
|
||||
func handleMessage(backend Backend, peer *Peer) error {
|
||||
// Read the next message from the remote peer, and ensure it's fully consumed
|
||||
msg, err := peer.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Size > maxMessageSize {
|
||||
return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
|
||||
}
|
||||
defer msg.Discard()
|
||||
|
||||
// Handle the message depending on its contents
|
||||
switch {
|
||||
case msg.Code == StatusMsg:
|
||||
// Status messages should never arrive after the handshake
|
||||
return fmt.Errorf("%w: uncontrolled status message", errExtraStatusMsg)
|
||||
|
||||
// Block header query, collect the requested headers and reply
|
||||
case msg.Code == GetBlockHeadersMsg:
|
||||
// Decode the complex header query
|
||||
var query GetBlockHeadersPacket
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
hashMode := query.Origin.Hash != (common.Hash{})
|
||||
first := true
|
||||
maxNonCanonical := uint64(100)
|
||||
|
||||
// Gather headers until the fetch or network limits is reached
|
||||
var (
|
||||
bytes common.StorageSize
|
||||
headers []*types.Header
|
||||
unknown bool
|
||||
lookups int
|
||||
)
|
||||
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit &&
|
||||
len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe {
|
||||
lookups++
|
||||
// Retrieve the next header satisfying the query
|
||||
var origin *types.Header
|
||||
if hashMode {
|
||||
if first {
|
||||
first = false
|
||||
origin = backend.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)
|
||||
}
|
||||
} else {
|
||||
origin = backend.Chain().GetHeaderByNumber(query.Origin.Number)
|
||||
}
|
||||
if origin == nil {
|
||||
break
|
||||
}
|
||||
headers = append(headers, origin)
|
||||
bytes += estHeaderSize
|
||||
|
||||
// Advance to the next header of the query
|
||||
switch {
|
||||
case hashMode && query.Reverse:
|
||||
// Hash based traversal towards the genesis block
|
||||
ancestor := query.Skip + 1
|
||||
if ancestor == 0 {
|
||||
unknown = true
|
||||
} else {
|
||||
query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
|
||||
unknown = (query.Origin.Hash == common.Hash{})
|
||||
}
|
||||
case hashMode && !query.Reverse:
|
||||
// Hash based traversal towards the leaf block
|
||||
var (
|
||||
current = origin.Number.Uint64()
|
||||
next = current + query.Skip + 1
|
||||
)
|
||||
if next <= current {
|
||||
infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ")
|
||||
peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
|
||||
unknown = true
|
||||
} else {
|
||||
if header := backend.Chain().GetHeaderByNumber(next); header != nil {
|
||||
nextHash := header.Hash()
|
||||
expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
|
||||
if expOldHash == query.Origin.Hash {
|
||||
query.Origin.Hash, query.Origin.Number = nextHash, next
|
||||
} else {
|
||||
unknown = true
|
||||
}
|
||||
} else {
|
||||
unknown = true
|
||||
}
|
||||
}
|
||||
case query.Reverse:
|
||||
// Number based traversal towards the genesis block
|
||||
if query.Origin.Number >= query.Skip+1 {
|
||||
query.Origin.Number -= query.Skip + 1
|
||||
} else {
|
||||
unknown = true
|
||||
}
|
||||
|
||||
case !query.Reverse:
|
||||
// Number based traversal towards the leaf block
|
||||
query.Origin.Number += query.Skip + 1
|
||||
}
|
||||
}
|
||||
return peer.SendBlockHeaders(headers)
|
||||
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
// A batch of headers arrived to one of our previous requests
|
||||
res := new(BlockHeadersPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetBlockBodiesMsg:
|
||||
// Decode the block body retrieval message
|
||||
var query GetBlockBodiesPacket
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Gather blocks until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
bodies []rlp.RawValue
|
||||
)
|
||||
for lookups, hash := range query {
|
||||
if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe ||
|
||||
lookups >= 2*maxBodiesServe {
|
||||
break
|
||||
}
|
||||
if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 {
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
}
|
||||
}
|
||||
return peer.SendBlockBodiesRLP(bodies)
|
||||
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
// A batch of block bodies arrived to one of our previous requests
|
||||
res := new(BlockBodiesPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetNodeDataMsg:
|
||||
// Decode the trie node data retrieval message
|
||||
var query GetNodeDataPacket
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Gather state data until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
nodes [][]byte
|
||||
)
|
||||
for lookups, hash := range query {
|
||||
if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe ||
|
||||
lookups >= 2*maxNodeDataServe {
|
||||
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)
|
||||
if len(entry) == 0 || err != nil {
|
||||
// Read the contract code with prefix only to save unnecessary lookups.
|
||||
entry, err = backend.Chain().ContractCodeWithPrefix(hash)
|
||||
}
|
||||
if err == nil && len(entry) > 0 {
|
||||
nodes = append(nodes, entry)
|
||||
bytes += len(entry)
|
||||
}
|
||||
}
|
||||
return peer.SendNodeData(nodes)
|
||||
|
||||
case msg.Code == NodeDataMsg:
|
||||
// A batch of node state data arrived to one of our previous requests
|
||||
res := new(NodeDataPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetReceiptsMsg:
|
||||
// Decode the block receipts retrieval message
|
||||
var query GetReceiptsPacket
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Gather state data until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
receipts []rlp.RawValue
|
||||
)
|
||||
for lookups, hash := range query {
|
||||
if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe ||
|
||||
lookups >= 2*maxReceiptsServe {
|
||||
break
|
||||
}
|
||||
// Retrieve the requested block's receipts
|
||||
results := backend.Chain().GetReceiptsByHash(hash)
|
||||
if results == nil {
|
||||
if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// If known, encode and queue for response packet
|
||||
if encoded, err := rlp.EncodeToBytes(results); err != nil {
|
||||
log.Error("Failed to encode receipt", "err", err)
|
||||
} else {
|
||||
receipts = append(receipts, encoded)
|
||||
bytes += len(encoded)
|
||||
}
|
||||
}
|
||||
return peer.SendReceiptsRLP(receipts)
|
||||
|
||||
case msg.Code == ReceiptsMsg:
|
||||
// A batch of receipts arrived to one of our previous requests
|
||||
res := new(ReceiptsPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == NewBlockHashesMsg:
|
||||
// A batch of new block announcements just arrived
|
||||
ann := new(NewBlockHashesPacket)
|
||||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Mark the hashes as present at the remote node
|
||||
for _, block := range *ann {
|
||||
peer.markBlock(block.Hash)
|
||||
}
|
||||
// Deliver them all to the backend for queuing
|
||||
return backend.Handle(peer, ann)
|
||||
|
||||
case msg.Code == NewBlockMsg:
|
||||
// Retrieve and decode the propagated block
|
||||
ann := new(NewBlockPacket)
|
||||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() {
|
||||
log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash())
|
||||
break // TODO(karalabe): return error eventually, but wait a few releases
|
||||
}
|
||||
if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() {
|
||||
log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash())
|
||||
break // TODO(karalabe): return error eventually, but wait a few releases
|
||||
}
|
||||
if err := ann.sanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
ann.Block.ReceivedAt = msg.ReceivedAt
|
||||
ann.Block.ReceivedFrom = peer
|
||||
|
||||
// Mark the peer as owning the block
|
||||
peer.markBlock(ann.Block.Hash())
|
||||
|
||||
return backend.Handle(peer, ann)
|
||||
|
||||
case msg.Code == NewPooledTransactionHashesMsg && peer.version >= ETH65:
|
||||
// New transaction announcement arrived, make sure we have
|
||||
// a valid and fresh chain to handle them
|
||||
if !backend.AcceptTxs() {
|
||||
break
|
||||
}
|
||||
ann := new(NewPooledTransactionHashesPacket)
|
||||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
for _, hash := range *ann {
|
||||
peer.markTransaction(hash)
|
||||
}
|
||||
return backend.Handle(peer, ann)
|
||||
|
||||
case msg.Code == GetPooledTransactionsMsg && peer.version >= ETH65:
|
||||
// Decode the pooled transactions retrieval message
|
||||
var query GetPooledTransactionsPacket
|
||||
if err := msg.Decode(&query); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Gather transactions until the fetch or network limits is reached
|
||||
var (
|
||||
bytes int
|
||||
hashes []common.Hash
|
||||
txs []rlp.RawValue
|
||||
)
|
||||
for _, hash := range query {
|
||||
if bytes >= softResponseLimit {
|
||||
break
|
||||
}
|
||||
// Retrieve the requested transaction, skipping if unknown to us
|
||||
tx := backend.TxPool().Get(hash)
|
||||
if tx == nil {
|
||||
continue
|
||||
}
|
||||
// If known, encode and queue for response packet
|
||||
if encoded, err := rlp.EncodeToBytes(tx); err != nil {
|
||||
log.Error("Failed to encode transaction", "err", err)
|
||||
} else {
|
||||
hashes = append(hashes, hash)
|
||||
txs = append(txs, encoded)
|
||||
bytes += len(encoded)
|
||||
}
|
||||
}
|
||||
return peer.SendPooledTransactionsRLP(hashes, txs)
|
||||
|
||||
case msg.Code == TransactionsMsg || (msg.Code == PooledTransactionsMsg && peer.version >= ETH65):
|
||||
// Transactions arrived, make sure we have a valid and fresh chain to handle them
|
||||
if !backend.AcceptTxs() {
|
||||
break
|
||||
}
|
||||
// Transactions can be processed, parse all of them and deliver to the pool
|
||||
var txs []*types.Transaction
|
||||
if err := msg.Decode(&txs); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
for i, tx := range txs {
|
||||
// Validate and mark the remote transaction
|
||||
if tx == nil {
|
||||
return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
|
||||
}
|
||||
peer.markTransaction(tx.Hash())
|
||||
}
|
||||
if msg.Code == PooledTransactionsMsg {
|
||||
return backend.Handle(peer, (*PooledTransactionsPacket)(&txs))
|
||||
}
|
||||
return backend.Handle(peer, (*TransactionsPacket)(&txs))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
|
||||
}
|
||||
return nil
|
||||
}
|
519
eth/protocols/eth/handler_test.go
Normal file
519
eth/protocols/eth/handler_test.go
Normal file
@ -0,0 +1,519 @@
|
||||
// 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 eth
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"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/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var (
|
||||
// testKey is a private key to use for funding a tester account.
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
// testAddr is the Ethereum address of the tester account.
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
)
|
||||
|
||||
// testBackend is a mock implementation of the live Ethereum message handler. Its
|
||||
// purpose is to allow testing the request/reply workflows and wire serialization
|
||||
// in the `eth` protocol without actually doing any data processing.
|
||||
type testBackend struct {
|
||||
db ethdb.Database
|
||||
chain *core.BlockChain
|
||||
txpool *core.TxPool
|
||||
}
|
||||
|
||||
// newTestBackend creates an empty chain and wraps it into a mock backend.
|
||||
func newTestBackend(blocks int) *testBackend {
|
||||
return newTestBackendWithGenerator(blocks, nil)
|
||||
}
|
||||
|
||||
// newTestBackend creates a chain with a number of explicitly defined blocks and
|
||||
// wraps it into a mock backend.
|
||||
func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend {
|
||||
// Create a database pre-initialize with a genesis block
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
(&core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
|
||||
}).MustCommit(db)
|
||||
|
||||
chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
|
||||
bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator)
|
||||
if _, err := chain.InsertChain(bs); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
txconfig := core.DefaultTxPoolConfig
|
||||
txconfig.Journal = "" // Don't litter the disk with test journals
|
||||
|
||||
return &testBackend{
|
||||
db: db,
|
||||
chain: chain,
|
||||
txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain),
|
||||
}
|
||||
}
|
||||
|
||||
// close tears down the transaction pool and chain behind the mock backend.
|
||||
func (b *testBackend) close() {
|
||||
b.txpool.Stop()
|
||||
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) RunPeer(peer *Peer, handler Handler) error {
|
||||
// Normally the backend would do peer mainentance and handshakes. All that
|
||||
// is omitted and we will just give control back to the handler.
|
||||
return handler(peer)
|
||||
}
|
||||
func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") }
|
||||
|
||||
func (b *testBackend) AcceptTxs() bool {
|
||||
panic("data processing tests should be done in the handler package")
|
||||
}
|
||||
func (b *testBackend) Handle(*Peer, Packet) error {
|
||||
panic("data processing tests should be done in the handler package")
|
||||
}
|
||||
|
||||
// Tests that block headers can be retrieved from a remote chain based on user queries.
|
||||
func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) }
|
||||
func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, 65) }
|
||||
|
||||
func testGetBlockHeaders(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
backend := newTestBackend(maxHeadersServe + 15)
|
||||
defer backend.close()
|
||||
|
||||
peer, _ := newTestPeer("peer", protocol, backend)
|
||||
defer peer.close()
|
||||
|
||||
// Create a "random" unknown hash for testing
|
||||
var unknown common.Hash
|
||||
for i := range unknown {
|
||||
unknown[i] = byte(i)
|
||||
}
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := uint64(maxHeadersServe)
|
||||
tests := []struct {
|
||||
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
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
|
||||
[]common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1},
|
||||
[]common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
|
||||
},
|
||||
// Multiple headers should be retrievable in both directions
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(limit / 2).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 + 1).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 + 2).Hash(),
|
||||
},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(limit / 2).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 - 1).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 - 2).Hash(),
|
||||
},
|
||||
},
|
||||
// Multiple headers with skip lists should be retrievable
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(limit / 2).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 + 4).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 + 8).Hash(),
|
||||
},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(limit / 2).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 - 4).Hash(),
|
||||
backend.chain.GetBlockByNumber(limit/2 - 8).Hash(),
|
||||
},
|
||||
},
|
||||
// The chain endpoints should be retrievable
|
||||
{
|
||||
&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()},
|
||||
},
|
||||
// Ensure protocol limits are honored
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
|
||||
backend.chain.GetBlockHashesFromHash(backend.chain.CurrentBlock().Hash(), limit),
|
||||
},
|
||||
// Check that requesting more than available is handled gracefully
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(),
|
||||
backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64()).Hash(),
|
||||
},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(4).Hash(),
|
||||
backend.chain.GetBlockByNumber(0).Hash(),
|
||||
},
|
||||
},
|
||||
// Check that requesting more than available is handled gracefully, even if mid skip
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(),
|
||||
backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 1).Hash(),
|
||||
},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(4).Hash(),
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where requesting more can iterate past the endpoints
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(2).Hash(),
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
backend.chain.GetBlockByNumber(0).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where skipping overflow loops back into the chain start
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(3).Hash(),
|
||||
},
|
||||
},
|
||||
// Check a corner case where skipping overflow loops back to the same header
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64},
|
||||
[]common.Hash{
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
},
|
||||
},
|
||||
// Check that non existing headers aren't returned
|
||||
{
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: unknown}, Amount: 1},
|
||||
[]common.Hash{},
|
||||
}, {
|
||||
&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() + 1}, Amount: 1},
|
||||
[]common.Hash{},
|
||||
},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Collect the headers to expect in the response
|
||||
var headers []*types.Header
|
||||
for _, hash := range tt.expect {
|
||||
headers = append(headers, backend.chain.GetBlockByHash(hash).Header())
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
// If the test used number origins, repeat with hashes as the too
|
||||
if tt.query.Origin.Hash == (common.Hash{}) {
|
||||
if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
||||
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
||||
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that block contents can be retrieved from a remote chain based on their hashes.
|
||||
func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) }
|
||||
func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, 65) }
|
||||
|
||||
func testGetBlockBodies(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
backend := newTestBackend(maxBodiesServe + 15)
|
||||
defer backend.close()
|
||||
|
||||
peer, _ := newTestPeer("peer", protocol, backend)
|
||||
defer peer.close()
|
||||
|
||||
// Create a batch of tests for various scenarios
|
||||
limit := maxBodiesServe
|
||||
tests := []struct {
|
||||
random int // Number of blocks to fetch randomly from the chain
|
||||
explicit []common.Hash // Explicitly requested blocks
|
||||
available []bool // Availability of explicitly requested blocks
|
||||
expected int // Total number of existing blocks to expect
|
||||
}{
|
||||
{1, nil, nil, 1}, // A single random block should be retrievable
|
||||
{10, nil, nil, 10}, // Multiple random blocks should be retrievable
|
||||
{limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
|
||||
{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
|
||||
{0, []common.Hash{backend.chain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
|
||||
{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
|
||||
{0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
|
||||
|
||||
// Existing and non-existing blocks interleaved should not cause problems
|
||||
{0, []common.Hash{
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(10).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{false, true, false, true, false, true, false}, 3},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
// Collect the hashes to request, and the response to expectva
|
||||
var (
|
||||
hashes []common.Hash
|
||||
bodies []*BlockBody
|
||||
seen = make(map[int64]bool)
|
||||
)
|
||||
for j := 0; j < tt.random; j++ {
|
||||
for {
|
||||
num := rand.Int63n(int64(backend.chain.CurrentBlock().NumberU64()))
|
||||
if !seen[num] {
|
||||
seen[num] = true
|
||||
|
||||
block := backend.chain.GetBlockByNumber(uint64(num))
|
||||
hashes = append(hashes, block.Hash())
|
||||
if len(bodies) < tt.expected {
|
||||
bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for j, hash := range tt.explicit {
|
||||
hashes = append(hashes, hash)
|
||||
if tt.available[j] && len(bodies) < tt.expected {
|
||||
block := backend.chain.GetBlockByHash(hash)
|
||||
bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
|
||||
}
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x05, hashes)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil {
|
||||
t.Errorf("test %d: bodies mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the state trie nodes can be retrieved based on hashes.
|
||||
func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) }
|
||||
func TestGetNodeData65(t *testing.T) { testGetNodeData(t, 65) }
|
||||
|
||||
func testGetNodeData(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Define three accounts to simulate transactions with
|
||||
acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
|
||||
acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
|
||||
|
||||
signer := types.HomesteadSigner{}
|
||||
// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
|
||||
generator := func(i int, block *core.BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, the test bank sends account #1 some ether.
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey)
|
||||
block.AddTx(tx)
|
||||
case 1:
|
||||
// In block 2, the test bank sends some more ether to account #1.
|
||||
// acc1Addr passes it on to account #2.
|
||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
|
||||
block.AddTx(tx1)
|
||||
block.AddTx(tx2)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(acc2Addr)
|
||||
block.SetExtra([]byte("yeehaw"))
|
||||
case 3:
|
||||
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
|
||||
b2 := block.PrevBlock(1).Header()
|
||||
b2.Extra = []byte("foo")
|
||||
block.AddUncle(b2)
|
||||
b3 := block.PrevBlock(2).Header()
|
||||
b3.Extra = []byte("foo")
|
||||
block.AddUncle(b3)
|
||||
}
|
||||
}
|
||||
// Assemble the test environment
|
||||
backend := newTestBackendWithGenerator(4, generator)
|
||||
defer backend.close()
|
||||
|
||||
peer, _ := newTestPeer("peer", protocol, backend)
|
||||
defer peer.close()
|
||||
|
||||
// Fetch for now the entire chain db
|
||||
var hashes []common.Hash
|
||||
|
||||
it := backend.db.NewIterator(nil, nil)
|
||||
for it.Next() {
|
||||
if key := it.Key(); len(key) == common.HashLength {
|
||||
hashes = append(hashes, common.BytesToHash(key))
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
p2p.Send(peer.app, 0x0d, hashes)
|
||||
msg, err := peer.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read node data response: %v", err)
|
||||
}
|
||||
if msg.Code != 0x0e {
|
||||
t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c)
|
||||
}
|
||||
var data [][]byte
|
||||
if err := msg.Decode(&data); err != nil {
|
||||
t.Fatalf("failed to decode response node data: %v", err)
|
||||
}
|
||||
// Verify that all hashes correspond to the requested data, and reconstruct a state tree
|
||||
for i, want := range hashes {
|
||||
if hash := crypto.Keccak256Hash(data[i]); hash != want {
|
||||
t.Errorf("data hash mismatch: have %x, want %x", hash, want)
|
||||
}
|
||||
}
|
||||
statedb := rawdb.NewMemoryDatabase()
|
||||
for i := 0; i < len(data); i++ {
|
||||
statedb.Put(hashes[i].Bytes(), data[i])
|
||||
}
|
||||
accounts := []common.Address{testAddr, acc1Addr, acc2Addr}
|
||||
for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ {
|
||||
trie, _ := state.New(backend.chain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil)
|
||||
|
||||
for j, acc := range accounts {
|
||||
state, _ := backend.chain.State()
|
||||
bw := state.GetBalance(acc)
|
||||
bh := trie.GetBalance(acc)
|
||||
|
||||
if (bw != nil && bh == nil) || (bw == nil && bh != nil) {
|
||||
t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
|
||||
}
|
||||
if bw != nil && bh != nil && bw.Cmp(bw) != 0 {
|
||||
t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the transaction receipts can be retrieved based on hashes.
|
||||
func TestGetBlockReceipts64(t *testing.T) { testGetBlockReceipts(t, 64) }
|
||||
func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, 65) }
|
||||
|
||||
func testGetBlockReceipts(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Define three accounts to simulate transactions with
|
||||
acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
|
||||
acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
|
||||
|
||||
signer := types.HomesteadSigner{}
|
||||
// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
|
||||
generator := func(i int, block *core.BlockGen) {
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, the test bank sends account #1 some ether.
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey)
|
||||
block.AddTx(tx)
|
||||
case 1:
|
||||
// In block 2, the test bank sends some more ether to account #1.
|
||||
// acc1Addr passes it on to account #2.
|
||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
|
||||
block.AddTx(tx1)
|
||||
block.AddTx(tx2)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(acc2Addr)
|
||||
block.SetExtra([]byte("yeehaw"))
|
||||
case 3:
|
||||
// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
|
||||
b2 := block.PrevBlock(1).Header()
|
||||
b2.Extra = []byte("foo")
|
||||
block.AddUncle(b2)
|
||||
b3 := block.PrevBlock(2).Header()
|
||||
b3.Extra = []byte("foo")
|
||||
block.AddUncle(b3)
|
||||
}
|
||||
}
|
||||
// Assemble the test environment
|
||||
backend := newTestBackendWithGenerator(4, generator)
|
||||
defer backend.close()
|
||||
|
||||
peer, _ := newTestPeer("peer", protocol, backend)
|
||||
defer peer.close()
|
||||
|
||||
// Collect the hashes to request, and the response to expect
|
||||
var (
|
||||
hashes []common.Hash
|
||||
receipts []types.Receipts
|
||||
)
|
||||
for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ {
|
||||
block := backend.chain.GetBlockByNumber(i)
|
||||
|
||||
hashes = append(hashes, block.Hash())
|
||||
receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash()))
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x0f, hashes)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil {
|
||||
t.Errorf("receipts mismatch: %v", err)
|
||||
}
|
||||
}
|
107
eth/protocols/eth/handshake.go
Normal file
107
eth/protocols/eth/handshake.go
Normal file
@ -0,0 +1,107 @@
|
||||
// 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 eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
const (
|
||||
// handshakeTimeout is the maximum allowed time for the `eth` handshake to
|
||||
// complete before dropping the connection.= as malicious.
|
||||
handshakeTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// Handshake executes the eth protocol handshake, negotiating version number,
|
||||
// network IDs, difficulties, head and genesis blocks.
|
||||
func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
|
||||
// Send out own handshake in a new thread
|
||||
errc := make(chan error, 2)
|
||||
|
||||
var status StatusPacket // safe to read after two values have been received from errc
|
||||
|
||||
go func() {
|
||||
errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkID: network,
|
||||
TD: td,
|
||||
Head: head,
|
||||
Genesis: genesis,
|
||||
ForkID: forkID,
|
||||
})
|
||||
}()
|
||||
go func() {
|
||||
errc <- p.readStatus(network, &status, genesis, forkFilter)
|
||||
}()
|
||||
timeout := time.NewTimer(handshakeTimeout)
|
||||
defer timeout.Stop()
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-timeout.C:
|
||||
return p2p.DiscReadTimeout
|
||||
}
|
||||
}
|
||||
p.td, p.head = status.TD, status.Head
|
||||
|
||||
// TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
|
||||
// larger, it will still fit within 100 bits
|
||||
if tdlen := p.td.BitLen(); tdlen > 100 {
|
||||
return fmt.Errorf("too large total difficulty: bitlen %d", tdlen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readStatus reads the remote handshake message.
|
||||
func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error {
|
||||
msg, err := p.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Code != StatusMsg {
|
||||
return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg)
|
||||
}
|
||||
if msg.Size > maxMessageSize {
|
||||
return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
|
||||
}
|
||||
// Decode the handshake and make sure everything matches
|
||||
if err := msg.Decode(&status); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
if status.NetworkID != network {
|
||||
return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network)
|
||||
}
|
||||
if uint(status.ProtocolVersion) != p.version {
|
||||
return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
|
||||
}
|
||||
if status.Genesis != genesis {
|
||||
return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
|
||||
}
|
||||
if err := forkFilter(status.ForkID); err != nil {
|
||||
return fmt.Errorf("%w: %v", errForkIDRejected, err)
|
||||
}
|
||||
return nil
|
||||
}
|
91
eth/protocols/eth/handshake_test.go
Normal file
91
eth/protocols/eth/handshake_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2014 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"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// Tests that handshake failures are detected and reported correctly.
|
||||
func TestHandshake64(t *testing.T) { testHandshake(t, 64) }
|
||||
func TestHandshake65(t *testing.T) { testHandshake(t, 65) }
|
||||
|
||||
func testHandshake(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test backend only to have some valid genesis chain
|
||||
backend := newTestBackend(3)
|
||||
defer backend.close()
|
||||
|
||||
var (
|
||||
genesis = backend.chain.Genesis()
|
||||
head = backend.chain.CurrentBlock()
|
||||
td = backend.chain.GetTd(head.Hash(), head.NumberU64())
|
||||
forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64())
|
||||
)
|
||||
tests := []struct {
|
||||
code uint64
|
||||
data interface{}
|
||||
want error
|
||||
}{
|
||||
{
|
||||
code: TransactionsMsg, data: []interface{}{},
|
||||
want: errNoStatusMsg,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID},
|
||||
want: errProtocolVersionMismatch,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID},
|
||||
want: errNetworkIDMismatch,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID},
|
||||
want: errGenesisMismatch,
|
||||
},
|
||||
{
|
||||
code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
|
||||
want: errForkIDRejected,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
// Create the two peers to shake with each other
|
||||
app, net := p2p.MsgPipe()
|
||||
defer app.Close()
|
||||
defer net.Close()
|
||||
|
||||
peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil)
|
||||
defer peer.Close()
|
||||
|
||||
// Send the junk test with one peer, check the handshake failure
|
||||
go p2p.Send(app, test.code, test.data)
|
||||
|
||||
err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain))
|
||||
if err == nil {
|
||||
t.Errorf("test %d: protocol returned nil error, want %q", i, test.want)
|
||||
} else if !errors.Is(err, test.want) {
|
||||
t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want)
|
||||
}
|
||||
}
|
||||
}
|
429
eth/protocols/eth/peer.go
Normal file
429
eth/protocols/eth/peer.go
Normal file
@ -0,0 +1,429 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxKnownTxs is the maximum transactions hashes to keep in the known list
|
||||
// before starting to randomly evict them.
|
||||
maxKnownTxs = 32768
|
||||
|
||||
// maxKnownBlocks is the maximum block hashes to keep in the known list
|
||||
// before starting to randomly evict them.
|
||||
maxKnownBlocks = 1024
|
||||
|
||||
// maxQueuedTxs is the maximum number of transactions to queue up before dropping
|
||||
// older broadcasts.
|
||||
maxQueuedTxs = 4096
|
||||
|
||||
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
|
||||
// before dropping older announcements.
|
||||
maxQueuedTxAnns = 4096
|
||||
|
||||
// maxQueuedBlocks is the maximum number of block propagations to queue up before
|
||||
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
|
||||
// that might cover uncles should be enough.
|
||||
maxQueuedBlocks = 4
|
||||
|
||||
// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
|
||||
// dropping broadcasts. Similarly to block propagations, there's no point to queue
|
||||
// above some healthy uncle limit, so use that.
|
||||
maxQueuedBlockAnns = 4
|
||||
)
|
||||
|
||||
// max is a helper function which returns the larger of the two given integers.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Peer is a collection of relevant information we have about a `eth` peer.
|
||||
type Peer struct {
|
||||
id string // Unique ID for the peer, cached
|
||||
|
||||
*p2p.Peer // The embedded P2P package peer
|
||||
rw p2p.MsgReadWriter // Input/output streams for snap
|
||||
version uint // Protocol version negotiated
|
||||
|
||||
head common.Hash // Latest advertised head block hash
|
||||
td *big.Int // Latest advertised head block total difficulty
|
||||
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer
|
||||
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
|
||||
|
||||
txpool TxPool // Transaction pool used by the broadcasters for liveness checks
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
|
||||
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
|
||||
|
||||
term chan struct{} // Termination channel to stop the broadcasters
|
||||
lock sync.RWMutex // Mutex protecting the internal fields
|
||||
}
|
||||
|
||||
// NewPeer create a wrapper for a network connection and negotiated protocol
|
||||
// version.
|
||||
func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer {
|
||||
peer := &Peer{
|
||||
id: p.ID().String(),
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
knownTxs: mapset.NewSet(),
|
||||
knownBlocks: mapset.NewSet(),
|
||||
queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks),
|
||||
queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
|
||||
txBroadcast: make(chan []common.Hash),
|
||||
txAnnounce: make(chan []common.Hash),
|
||||
txpool: txpool,
|
||||
term: make(chan struct{}),
|
||||
}
|
||||
// Start up all the broadcasters
|
||||
go peer.broadcastBlocks()
|
||||
go peer.broadcastTransactions()
|
||||
if version >= ETH65 {
|
||||
go peer.announceTransactions()
|
||||
}
|
||||
return peer
|
||||
}
|
||||
|
||||
// Close signals the broadcast goroutine to terminate. Only ever call this if
|
||||
// you created the peer yourself via NewPeer. Otherwise let whoever created it
|
||||
// clean it up!
|
||||
func (p *Peer) Close() {
|
||||
close(p.term)
|
||||
}
|
||||
|
||||
// ID retrieves the peer's unique identifier.
|
||||
func (p *Peer) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// Version retrieves the peer's negoatiated `eth` protocol version.
|
||||
func (p *Peer) Version() uint {
|
||||
return p.version
|
||||
}
|
||||
|
||||
// Head retrieves the current head hash and total difficulty of the peer.
|
||||
func (p *Peer) Head() (hash common.Hash, td *big.Int) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
copy(hash[:], p.head[:])
|
||||
return hash, new(big.Int).Set(p.td)
|
||||
}
|
||||
|
||||
// SetHead updates the head hash and total difficulty of the peer.
|
||||
func (p *Peer) SetHead(hash common.Hash, td *big.Int) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
copy(p.head[:], hash[:])
|
||||
p.td.Set(td)
|
||||
}
|
||||
|
||||
// KnownBlock returns whether peer is known to already have a block.
|
||||
func (p *Peer) KnownBlock(hash common.Hash) bool {
|
||||
return p.knownBlocks.Contains(hash)
|
||||
}
|
||||
|
||||
// KnownTransaction returns whether peer is known to already have a transaction.
|
||||
func (p *Peer) KnownTransaction(hash common.Hash) bool {
|
||||
return p.knownTxs.Contains(hash)
|
||||
}
|
||||
|
||||
// markBlock marks a block as known for the peer, ensuring that the block will
|
||||
// never be propagated to this particular peer.
|
||||
func (p *Peer) markBlock(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known block hash
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
|
||||
// markTransaction marks a transaction as known for the peer, ensuring that it
|
||||
// will never be propagated to this particular peer.
|
||||
func (p *Peer) markTransaction(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known transaction hash
|
||||
for p.knownTxs.Cardinality() >= maxKnownTxs {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
|
||||
// SendTransactions sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction sender. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
//
|
||||
// The reasons this is public is to allow packages using this protocol to write
|
||||
// tests that directly send messages without having to do the asyn queueing.
|
||||
func (p *Peer) SendTransactions(txs types.Transactions) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, tx := range txs {
|
||||
p.knownTxs.Add(tx.Hash())
|
||||
}
|
||||
return p2p.Send(p.rw, TransactionsMsg, txs)
|
||||
}
|
||||
|
||||
// AsyncSendTransactions queues a list of transactions (by hash) to eventually
|
||||
// propagate to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *Peer) AsyncSendTransactions(hashes []common.Hash) {
|
||||
select {
|
||||
case p.txBroadcast <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
// sendPooledTransactionHashes sends transaction hashes to the peer and includes
|
||||
// them in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction announcer. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes))
|
||||
}
|
||||
|
||||
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
|
||||
// announce to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
|
||||
select {
|
||||
case p.txAnnounce <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
|
||||
// hashes in its transaction hash set for future reference.
|
||||
//
|
||||
// Note, the method assumes the hashes are correct and correspond to the list of
|
||||
// transactions being sent.
|
||||
func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding
|
||||
}
|
||||
|
||||
// SendNewBlockHashes announces the availability of a number of blocks through
|
||||
// a hash notification.
|
||||
func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
|
||||
// Mark all the block hashes as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
request := make(NewBlockHashesPacket, len(hashes))
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
request[i].Hash = hashes[i]
|
||||
request[i].Number = numbers[i]
|
||||
}
|
||||
return p2p.Send(p.rw, NewBlockHashesMsg, request)
|
||||
}
|
||||
|
||||
// AsyncSendNewBlockHash queues the availability of a block for propagation to a
|
||||
// remote peer. If the peer's broadcast queue is full, the event is silently
|
||||
// dropped.
|
||||
func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
|
||||
select {
|
||||
case p.queuedBlockAnns <- block:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// SendNewBlock propagates an entire block to a remote peer.
|
||||
func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{block, td})
|
||||
}
|
||||
|
||||
// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
|
||||
// the peer's broadcast queue is full, the event is silently dropped.
|
||||
func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
||||
select {
|
||||
case p.queuedBlocks <- &blockPropagation{block: block, td: td}:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// SendBlockHeaders sends a batch of block headers to the remote peer.
|
||||
func (p *Peer) SendBlockHeaders(headers []*types.Header) error {
|
||||
return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers))
|
||||
}
|
||||
|
||||
// SendBlockBodies sends a batch of block contents to the remote peer.
|
||||
func (p *Peer) SendBlockBodies(bodies []*BlockBody) error {
|
||||
return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesPacket(bodies))
|
||||
}
|
||||
|
||||
// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
|
||||
// an already RLP encoded format.
|
||||
func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding
|
||||
}
|
||||
|
||||
// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the
|
||||
// hashes requested.
|
||||
func (p *Peer) SendNodeData(data [][]byte) error {
|
||||
return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data))
|
||||
}
|
||||
|
||||
// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
|
||||
// ones requested from an already RLP encoded format.
|
||||
func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
|
||||
return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching single header", "hash", hash)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Hash: hash},
|
||||
Amount: uint64(1),
|
||||
Skip: uint64(0),
|
||||
Reverse: false,
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Hash: origin},
|
||||
Amount: uint64(amount),
|
||||
Skip: uint64(skip),
|
||||
Reverse: reverse,
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
|
||||
return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Number: origin},
|
||||
Amount: uint64(amount),
|
||||
Skip: uint64(skip),
|
||||
Reverse: reverse,
|
||||
})
|
||||
}
|
||||
|
||||
// ExpectRequestHeadersByNumber is a testing method to mirror the recipient side
|
||||
// of the RequestHeadersByNumber operation.
|
||||
func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
|
||||
req := &GetBlockHeadersPacket{
|
||||
Origin: HashOrNumber{Number: origin},
|
||||
Amount: uint64(amount),
|
||||
Skip: uint64(skip),
|
||||
Reverse: reverse,
|
||||
}
|
||||
return p2p.ExpectMsg(p.rw, GetBlockHeadersMsg, req)
|
||||
}
|
||||
|
||||
// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
|
||||
// specified.
|
||||
func (p *Peer) RequestBodies(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
p.Log().Debug("Fetching batch of state data", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes))
|
||||
}
|
||||
|
||||
// RequestReceipts fetches a batch of transaction receipts from a remote node.
|
||||
func (p *Peer) RequestReceipts(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes))
|
||||
}
|
||||
|
||||
// RequestTxs fetches a batch of transactions from a remote node.
|
||||
func (p *Peer) RequestTxs(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes))
|
||||
}
|
61
eth/protocols/eth/peer_test.go
Normal file
61
eth/protocols/eth/peer_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
// 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/>.
|
||||
|
||||
// This file contains some shares testing functionality, common to multiple
|
||||
// different files and modules being tested.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// testPeer is a simulated peer to allow testing direct network calls.
|
||||
type testPeer struct {
|
||||
*Peer
|
||||
|
||||
net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
|
||||
app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
|
||||
}
|
||||
|
||||
// newTestPeer creates a new peer registered at the given data backend.
|
||||
func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) {
|
||||
// Create a message pipe to communicate through
|
||||
app, net := p2p.MsgPipe()
|
||||
|
||||
// Start the peer on a new thread
|
||||
var id enode.ID
|
||||
rand.Read(id[:])
|
||||
|
||||
peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool())
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
errc <- backend.RunPeer(peer, func(peer *Peer) error {
|
||||
return Handle(backend, peer)
|
||||
})
|
||||
}()
|
||||
return &testPeer{app: app, net: net, Peer: peer}, errc
|
||||
}
|
||||
|
||||
// close terminates the local side of the peer, notifying the remote protocol
|
||||
// manager of termination.
|
||||
func (p *testPeer) close() {
|
||||
p.Peer.Close()
|
||||
p.app.Close()
|
||||
}
|
279
eth/protocols/eth/protocol.go
Normal file
279
eth/protocols/eth/protocol.go
Normal file
@ -0,0 +1,279 @@
|
||||
// Copyright 2014 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"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
ETH64 = 64
|
||||
ETH65 = 65
|
||||
)
|
||||
|
||||
// protocolName is the official short name of the `eth` protocol used during
|
||||
// devp2p capability negotiation.
|
||||
const protocolName = "eth"
|
||||
|
||||
// protocolVersions are the supported versions of the `eth` protocol (first
|
||||
// is primary).
|
||||
var protocolVersions = []uint{ETH65, ETH64}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to
|
||||
// different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{ETH65: 17, ETH64: 17}
|
||||
|
||||
// maxMessageSize is the maximum cap on the size of a protocol message.
|
||||
const maxMessageSize = 10 * 1024 * 1024
|
||||
|
||||
const (
|
||||
// Protocol messages in eth/64
|
||||
StatusMsg = 0x00
|
||||
NewBlockHashesMsg = 0x01
|
||||
TransactionsMsg = 0x02
|
||||
GetBlockHeadersMsg = 0x03
|
||||
BlockHeadersMsg = 0x04
|
||||
GetBlockBodiesMsg = 0x05
|
||||
BlockBodiesMsg = 0x06
|
||||
NewBlockMsg = 0x07
|
||||
GetNodeDataMsg = 0x0d
|
||||
NodeDataMsg = 0x0e
|
||||
GetReceiptsMsg = 0x0f
|
||||
ReceiptsMsg = 0x10
|
||||
|
||||
// Protocol messages overloaded in eth/65
|
||||
NewPooledTransactionHashesMsg = 0x08
|
||||
GetPooledTransactionsMsg = 0x09
|
||||
PooledTransactionsMsg = 0x0a
|
||||
)
|
||||
|
||||
var (
|
||||
errNoStatusMsg = errors.New("no status message")
|
||||
errMsgTooLarge = errors.New("message too long")
|
||||
errDecode = errors.New("invalid message")
|
||||
errInvalidMsgCode = errors.New("invalid message code")
|
||||
errProtocolVersionMismatch = errors.New("protocol version mismatch")
|
||||
errNetworkIDMismatch = errors.New("network ID mismatch")
|
||||
errGenesisMismatch = errors.New("genesis mismatch")
|
||||
errForkIDRejected = errors.New("fork ID rejected")
|
||||
errExtraStatusMsg = errors.New("extra status message")
|
||||
)
|
||||
|
||||
// Packet represents a p2p message in the `eth` protocol.
|
||||
type Packet interface {
|
||||
Name() string // Name returns a string corresponding to the message type.
|
||||
Kind() byte // Kind returns the message type.
|
||||
}
|
||||
|
||||
// StatusPacket is the network packet for the status message for eth/64 and later.
|
||||
type StatusPacket struct {
|
||||
ProtocolVersion uint32
|
||||
NetworkID uint64
|
||||
TD *big.Int
|
||||
Head common.Hash
|
||||
Genesis common.Hash
|
||||
ForkID forkid.ID
|
||||
}
|
||||
|
||||
// NewBlockHashesPacket is the network packet for the block announcements.
|
||||
type NewBlockHashesPacket []struct {
|
||||
Hash common.Hash // Hash of one particular block being announced
|
||||
Number uint64 // Number of one particular block being announced
|
||||
}
|
||||
|
||||
// Unpack retrieves the block hashes and numbers from the announcement packet
|
||||
// and returns them in a split flat format that's more consistent with the
|
||||
// internal data structures.
|
||||
func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) {
|
||||
var (
|
||||
hashes = make([]common.Hash, len(*p))
|
||||
numbers = make([]uint64, len(*p))
|
||||
)
|
||||
for i, body := range *p {
|
||||
hashes[i], numbers[i] = body.Hash, body.Number
|
||||
}
|
||||
return hashes, numbers
|
||||
}
|
||||
|
||||
// TransactionsPacket is the network packet for broadcasting new transactions.
|
||||
type TransactionsPacket []*types.Transaction
|
||||
|
||||
// GetBlockHeadersPacket represents a block header query.
|
||||
type GetBlockHeadersPacket struct {
|
||||
Origin HashOrNumber // Block from which to retrieve headers
|
||||
Amount uint64 // Maximum number of headers to retrieve
|
||||
Skip uint64 // Blocks to skip between consecutive headers
|
||||
Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
|
||||
}
|
||||
|
||||
// HashOrNumber is a combined field for specifying an origin block.
|
||||
type HashOrNumber struct {
|
||||
Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
|
||||
Number uint64 // Block hash from which to retrieve headers (excludes Hash)
|
||||
}
|
||||
|
||||
// EncodeRLP is a specialized encoder for HashOrNumber to encode only one of the
|
||||
// two contained union fields.
|
||||
func (hn *HashOrNumber) EncodeRLP(w io.Writer) error {
|
||||
if hn.Hash == (common.Hash{}) {
|
||||
return rlp.Encode(w, hn.Number)
|
||||
}
|
||||
if hn.Number != 0 {
|
||||
return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
|
||||
}
|
||||
return rlp.Encode(w, hn.Hash)
|
||||
}
|
||||
|
||||
// DecodeRLP is a specialized decoder for HashOrNumber to decode the contents
|
||||
// into either a block hash or a block number.
|
||||
func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error {
|
||||
_, size, _ := s.Kind()
|
||||
origin, err := s.Raw()
|
||||
if err == nil {
|
||||
switch {
|
||||
case size == 32:
|
||||
err = rlp.DecodeBytes(origin, &hn.Hash)
|
||||
case size <= 8:
|
||||
err = rlp.DecodeBytes(origin, &hn.Number)
|
||||
default:
|
||||
err = fmt.Errorf("invalid input size %d for origin", size)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// BlockHeadersPacket represents a block header response.
|
||||
type BlockHeadersPacket []*types.Header
|
||||
|
||||
// NewBlockPacket is the network packet for the block propagation message.
|
||||
type NewBlockPacket struct {
|
||||
Block *types.Block
|
||||
TD *big.Int
|
||||
}
|
||||
|
||||
// sanityCheck verifies that the values are reasonable, as a DoS protection
|
||||
func (request *NewBlockPacket) sanityCheck() error {
|
||||
if err := request.Block.SanityCheck(); err != nil {
|
||||
return err
|
||||
}
|
||||
//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
|
||||
// larger, it will still fit within 100 bits
|
||||
if tdlen := request.TD.BitLen(); tdlen > 100 {
|
||||
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockBodiesPacket represents a block body query.
|
||||
type GetBlockBodiesPacket []common.Hash
|
||||
|
||||
// BlockBodiesPacket is the network packet for block content distribution.
|
||||
type BlockBodiesPacket []*BlockBody
|
||||
|
||||
// BlockBody represents the data content of a single block.
|
||||
type BlockBody struct {
|
||||
Transactions []*types.Transaction // Transactions contained within a block
|
||||
Uncles []*types.Header // Uncles contained within a block
|
||||
}
|
||||
|
||||
// Unpack retrieves the transactions and uncles from the range packet and returns
|
||||
// them in a split flat format that's more consistent with the internal data structures.
|
||||
func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) {
|
||||
var (
|
||||
txset = make([][]*types.Transaction, len(*p))
|
||||
uncleset = make([][]*types.Header, len(*p))
|
||||
)
|
||||
for i, body := range *p {
|
||||
txset[i], uncleset[i] = body.Transactions, body.Uncles
|
||||
}
|
||||
return txset, uncleset
|
||||
}
|
||||
|
||||
// GetNodeDataPacket represents a trie node data query.
|
||||
type GetNodeDataPacket []common.Hash
|
||||
|
||||
// NodeDataPacket is the network packet for trie node data distribution.
|
||||
type NodeDataPacket [][]byte
|
||||
|
||||
// GetReceiptsPacket represents a block receipts query.
|
||||
type GetReceiptsPacket []common.Hash
|
||||
|
||||
// ReceiptsPacket is the network packet for block receipts distribution.
|
||||
type ReceiptsPacket [][]*types.Receipt
|
||||
|
||||
// NewPooledTransactionHashesPacket represents a transaction announcement packet.
|
||||
type NewPooledTransactionHashesPacket []common.Hash
|
||||
|
||||
// GetPooledTransactionsPacket represents a transaction query.
|
||||
type GetPooledTransactionsPacket []common.Hash
|
||||
|
||||
// PooledTransactionsPacket is the network packet for transaction distribution.
|
||||
type PooledTransactionsPacket []*types.Transaction
|
||||
|
||||
func (*StatusPacket) Name() string { return "Status" }
|
||||
func (*StatusPacket) Kind() byte { return StatusMsg }
|
||||
|
||||
func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" }
|
||||
func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg }
|
||||
|
||||
func (*TransactionsPacket) Name() string { return "Transactions" }
|
||||
func (*TransactionsPacket) Kind() byte { return TransactionsMsg }
|
||||
|
||||
func (*GetBlockHeadersPacket) Name() string { return "GetBlockHeaders" }
|
||||
func (*GetBlockHeadersPacket) Kind() byte { return GetBlockHeadersMsg }
|
||||
|
||||
func (*BlockHeadersPacket) Name() string { return "BlockHeaders" }
|
||||
func (*BlockHeadersPacket) Kind() byte { return BlockHeadersMsg }
|
||||
|
||||
func (*GetBlockBodiesPacket) Name() string { return "GetBlockBodies" }
|
||||
func (*GetBlockBodiesPacket) Kind() byte { return GetBlockBodiesMsg }
|
||||
|
||||
func (*BlockBodiesPacket) Name() string { return "BlockBodies" }
|
||||
func (*BlockBodiesPacket) Kind() byte { return BlockBodiesMsg }
|
||||
|
||||
func (*NewBlockPacket) Name() string { return "NewBlock" }
|
||||
func (*NewBlockPacket) Kind() byte { return NewBlockMsg }
|
||||
|
||||
func (*GetNodeDataPacket) Name() string { return "GetNodeData" }
|
||||
func (*GetNodeDataPacket) Kind() byte { return GetNodeDataMsg }
|
||||
|
||||
func (*NodeDataPacket) Name() string { return "NodeData" }
|
||||
func (*NodeDataPacket) Kind() byte { return NodeDataMsg }
|
||||
|
||||
func (*GetReceiptsPacket) Name() string { return "GetReceipts" }
|
||||
func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg }
|
||||
|
||||
func (*ReceiptsPacket) Name() string { return "Receipts" }
|
||||
func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg }
|
||||
|
||||
func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" }
|
||||
func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg }
|
||||
|
||||
func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" }
|
||||
func (*GetPooledTransactionsPacket) Kind() byte { return GetPooledTransactionsMsg }
|
||||
|
||||
func (*PooledTransactionsPacket) Name() string { return "PooledTransactions" }
|
||||
func (*PooledTransactionsPacket) Kind() byte { return PooledTransactionsMsg }
|
68
eth/protocols/eth/protocol_test.go
Normal file
68
eth/protocols/eth/protocol_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2014 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Tests that the custom union field encoder and decoder works correctly.
|
||||
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
|
||||
// Create a "random" hash for testing
|
||||
var hash common.Hash
|
||||
for i := range hash {
|
||||
hash[i] = byte(i)
|
||||
}
|
||||
// Assemble some table driven tests
|
||||
tests := []struct {
|
||||
packet *GetBlockHeadersPacket
|
||||
fail bool
|
||||
}{
|
||||
// Providing the origin as either a hash or a number should both work
|
||||
{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}},
|
||||
{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}},
|
||||
|
||||
// Providing arbitrary query field should also work
|
||||
{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
|
||||
|
||||
// Providing both the origin hash and origin number must fail
|
||||
{fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}},
|
||||
}
|
||||
// Iterate over each of the tests and try to encode and then decode
|
||||
for i, tt := range tests {
|
||||
bytes, err := rlp.EncodeToBytes(tt.packet)
|
||||
if err != nil && !tt.fail {
|
||||
t.Fatalf("test %d: failed to encode packet: %v", i, err)
|
||||
} else if err == nil && tt.fail {
|
||||
t.Fatalf("test %d: encode should have failed", i)
|
||||
}
|
||||
if !tt.fail {
|
||||
packet := new(GetBlockHeadersPacket)
|
||||
if err := rlp.DecodeBytes(bytes, packet); err != nil {
|
||||
t.Fatalf("test %d: failed to decode packet: %v", i, err)
|
||||
}
|
||||
if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
|
||||
packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
|
||||
t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
eth/protocols/snap/discovery.go
Normal file
32
eth/protocols/snap/discovery.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snap
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// enrEntry is the ENR entry which advertises `snap` protocol on the discovery.
|
||||
type enrEntry struct {
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// ENRKey implements enr.Entry.
|
||||
func (e enrEntry) ENRKey() string {
|
||||
return "snap"
|
||||
}
|
490
eth/protocols/snap/handler.go
Normal file
490
eth/protocols/snap/handler.go
Normal file
@ -0,0 +1,490 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
const (
|
||||
// softResponseLimit is the target maximum size of replies to data retrievals.
|
||||
softResponseLimit = 2 * 1024 * 1024
|
||||
|
||||
// maxCodeLookups is the maximum number of bytecodes to serve. This number is
|
||||
// there to limit the number of disk lookups.
|
||||
maxCodeLookups = 1024
|
||||
|
||||
// stateLookupSlack defines the ratio by how much a state response can exceed
|
||||
// the requested limit in order to try and avoid breaking up contracts into
|
||||
// multiple packages and proving them.
|
||||
stateLookupSlack = 0.1
|
||||
|
||||
// maxTrieNodeLookups is the maximum number of state trie nodes to serve. This
|
||||
// number is there to limit the number of disk lookups.
|
||||
maxTrieNodeLookups = 1024
|
||||
)
|
||||
|
||||
// Handler is a callback to invoke from an outside runner after the boilerplate
|
||||
// exchanges have passed.
|
||||
type Handler func(peer *Peer) error
|
||||
|
||||
// Backend defines the data retrieval methods to serve remote requests and the
|
||||
// callback methods to invoke on remote deliveries.
|
||||
type Backend interface {
|
||||
// Chain retrieves the blockchain object to serve data.
|
||||
Chain() *core.BlockChain
|
||||
|
||||
// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
|
||||
// should do any peer maintenance work, handshakes and validations. If all
|
||||
// is passed, control should be given back to the `handler` to process the
|
||||
// inbound messages going forward.
|
||||
RunPeer(peer *Peer, handler Handler) error
|
||||
|
||||
// PeerInfo retrieves all known `snap` information about a peer.
|
||||
PeerInfo(id enode.ID) interface{}
|
||||
|
||||
// Handle is a callback to be invoked when a data packet is received from
|
||||
// the remote peer. Only packets not consumed by the protocol handler will
|
||||
// be forwarded to the backend.
|
||||
Handle(peer *Peer, packet Packet) error
|
||||
}
|
||||
|
||||
// MakeProtocols constructs the P2P protocol definitions for `snap`.
|
||||
func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol {
|
||||
protocols := make([]p2p.Protocol, len(protocolVersions))
|
||||
for i, version := range protocolVersions {
|
||||
version := version // Closure
|
||||
|
||||
protocols[i] = p2p.Protocol{
|
||||
Name: protocolName,
|
||||
Version: version,
|
||||
Length: protocolLengths[version],
|
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error {
|
||||
return handle(backend, peer)
|
||||
})
|
||||
},
|
||||
NodeInfo: func() interface{} {
|
||||
return nodeInfo(backend.Chain())
|
||||
},
|
||||
PeerInfo: func(id enode.ID) interface{} {
|
||||
return backend.PeerInfo(id)
|
||||
},
|
||||
Attributes: []enr.Entry{&enrEntry{}},
|
||||
DialCandidates: dnsdisc,
|
||||
}
|
||||
}
|
||||
return protocols
|
||||
}
|
||||
|
||||
// handle is the callback invoked to manage the life cycle of a `snap` peer.
|
||||
// When this function terminates, the peer is disconnected.
|
||||
func handle(backend Backend, peer *Peer) error {
|
||||
for {
|
||||
if err := handleMessage(backend, peer); err != nil {
|
||||
peer.Log().Debug("Message handling failed in `snap`", "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessage is invoked whenever an inbound message is received from a
|
||||
// remote peer on the `spap` protocol. The remote connection is torn down upon
|
||||
// returning any error.
|
||||
func handleMessage(backend Backend, peer *Peer) error {
|
||||
// Read the next message from the remote peer, and ensure it's fully consumed
|
||||
msg, err := peer.rw.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Size > maxMessageSize {
|
||||
return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
|
||||
}
|
||||
defer msg.Discard()
|
||||
|
||||
// Handle the message depending on its contents
|
||||
switch {
|
||||
case msg.Code == GetAccountRangeMsg:
|
||||
// Decode the account retrieval request
|
||||
var req GetAccountRangePacket
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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())
|
||||
|
||||
// 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
|
||||
return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{
|
||||
ID: req.ID,
|
||||
Accounts: accounts,
|
||||
Proof: proofs,
|
||||
})
|
||||
|
||||
case msg.Code == AccountRangeMsg:
|
||||
// A range of accounts arrived to one of our previous requests
|
||||
res := new(AccountRangePacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Ensure the range is monotonically increasing
|
||||
for i := 1; i < len(res.Accounts); i++ {
|
||||
if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 {
|
||||
return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:])
|
||||
}
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetStorageRangesMsg:
|
||||
// Decode the storage retrieval request
|
||||
var req GetStorageRangesPacket
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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 := 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
|
||||
)
|
||||
for it.Next() && size < hardLimit {
|
||||
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{}) || size >= hardLimit {
|
||||
// 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 state.Account
|
||||
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
|
||||
return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{
|
||||
ID: req.ID,
|
||||
Slots: slots,
|
||||
Proof: proofs,
|
||||
})
|
||||
|
||||
case msg.Code == StorageRangesMsg:
|
||||
// A range of storage slots arrived to one of our previous requests
|
||||
res := new(StorageRangesPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
// Ensure the ranges ae monotonically increasing
|
||||
for i, slots := range res.Slots {
|
||||
for j := 1; j < len(slots); j++ {
|
||||
if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 {
|
||||
return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetByteCodesMsg:
|
||||
// Decode bytecode retrieval request
|
||||
var req GetByteCodesPacket
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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
|
||||
return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{
|
||||
ID: req.ID,
|
||||
Codes: codes,
|
||||
})
|
||||
|
||||
case msg.Code == ByteCodesMsg:
|
||||
// A batch of byte codes arrived to one of our previous requests
|
||||
res := new(ByteCodesPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
case msg.Code == GetTrieNodesMsg:
|
||||
// Decode trie node retrieval request
|
||||
var req GetTrieNodesPacket
|
||||
if err := msg.Decode(&req); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
// We don't have the requested state available, bail out
|
||||
return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID})
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Abort request processing if we've exceeded our limits
|
||||
if bytes > req.Bytes || loads > maxTrieNodeLookups {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Send back anything accumulated
|
||||
return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{
|
||||
ID: req.ID,
|
||||
Nodes: nodes,
|
||||
})
|
||||
|
||||
case msg.Code == TrieNodesMsg:
|
||||
// A batch of trie nodes arrived to one of our previous requests
|
||||
res := new(TrieNodesPacket)
|
||||
if err := msg.Decode(res); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
return backend.Handle(peer, res)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// NodeInfo represents a short summary of the `snap` sub-protocol metadata
|
||||
// known about the host peer.
|
||||
type NodeInfo struct{}
|
||||
|
||||
// nodeInfo retrieves some `snap` protocol metadata about the running host node.
|
||||
func nodeInfo(chain *core.BlockChain) *NodeInfo {
|
||||
return &NodeInfo{}
|
||||
}
|
111
eth/protocols/snap/peer.go
Normal file
111
eth/protocols/snap/peer.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snap
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
// Peer is a collection of relevant information we have about a `snap` peer.
|
||||
type Peer struct {
|
||||
id string // Unique ID for the peer, cached
|
||||
|
||||
*p2p.Peer // The embedded P2P package peer
|
||||
rw p2p.MsgReadWriter // Input/output streams for snap
|
||||
version uint // Protocol version negotiated
|
||||
|
||||
logger log.Logger // Contextual logger with the peer id injected
|
||||
}
|
||||
|
||||
// newPeer create a wrapper for a network connection and negotiated protocol
|
||||
// version.
|
||||
func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
id := p.ID().String()
|
||||
return &Peer{
|
||||
id: id,
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
logger: log.New("peer", id[:8]),
|
||||
}
|
||||
}
|
||||
|
||||
// ID retrieves the peer's unique identifier.
|
||||
func (p *Peer) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// Version retrieves the peer's negoatiated `snap` protocol version.
|
||||
func (p *Peer) Version() uint {
|
||||
return p.version
|
||||
}
|
||||
|
||||
// RequestAccountRange fetches a batch of accounts rooted in a specific account
|
||||
// trie, starting with the origin.
|
||||
func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error {
|
||||
p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
|
||||
return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{
|
||||
ID: id,
|
||||
Root: root,
|
||||
Origin: origin,
|
||||
Limit: limit,
|
||||
Bytes: bytes,
|
||||
})
|
||||
}
|
||||
|
||||
// RequestStorageRange fetches a batch of storage slots belonging to one or more
|
||||
// accounts. If slots from only one accout is requested, an origin marker may also
|
||||
// be used to retrieve from there.
|
||||
func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
|
||||
if len(accounts) == 1 && origin != nil {
|
||||
p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
|
||||
} else {
|
||||
p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
|
||||
}
|
||||
return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{
|
||||
ID: id,
|
||||
Root: root,
|
||||
Accounts: accounts,
|
||||
Origin: origin,
|
||||
Limit: limit,
|
||||
Bytes: bytes,
|
||||
})
|
||||
}
|
||||
|
||||
// RequestByteCodes fetches a batch of bytecodes by hash.
|
||||
func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
|
||||
p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
|
||||
return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{
|
||||
ID: id,
|
||||
Hashes: hashes,
|
||||
Bytes: bytes,
|
||||
})
|
||||
}
|
||||
|
||||
// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
|
||||
// a specificstate trie.
|
||||
func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
|
||||
p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
|
||||
return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{
|
||||
ID: id,
|
||||
Root: root,
|
||||
Paths: paths,
|
||||
Bytes: bytes,
|
||||
})
|
||||
}
|
218
eth/protocols/snap/protocol.go
Normal file
218
eth/protocols/snap/protocol.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package snap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
snap1 = 1
|
||||
)
|
||||
|
||||
// protocolName is the official short name of the `snap` protocol used during
|
||||
// devp2p capability negotiation.
|
||||
const protocolName = "snap"
|
||||
|
||||
// protocolVersions are the supported versions of the `snap` protocol (first
|
||||
// is primary).
|
||||
var protocolVersions = []uint{snap1}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to
|
||||
// different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{snap1: 8}
|
||||
|
||||
// maxMessageSize is the maximum cap on the size of a protocol message.
|
||||
const maxMessageSize = 10 * 1024 * 1024
|
||||
|
||||
const (
|
||||
GetAccountRangeMsg = 0x00
|
||||
AccountRangeMsg = 0x01
|
||||
GetStorageRangesMsg = 0x02
|
||||
StorageRangesMsg = 0x03
|
||||
GetByteCodesMsg = 0x04
|
||||
ByteCodesMsg = 0x05
|
||||
GetTrieNodesMsg = 0x06
|
||||
TrieNodesMsg = 0x07
|
||||
)
|
||||
|
||||
var (
|
||||
errMsgTooLarge = errors.New("message too long")
|
||||
errDecode = errors.New("invalid message")
|
||||
errInvalidMsgCode = errors.New("invalid message code")
|
||||
errBadRequest = errors.New("bad request")
|
||||
)
|
||||
|
||||
// Packet represents a p2p message in the `snap` protocol.
|
||||
type Packet interface {
|
||||
Name() string // Name returns a string corresponding to the message type.
|
||||
Kind() byte // Kind returns the message type.
|
||||
}
|
||||
|
||||
// GetAccountRangePacket represents an account query.
|
||||
type GetAccountRangePacket struct {
|
||||
ID uint64 // Request ID to match up responses with
|
||||
Root common.Hash // Root hash of the account trie to serve
|
||||
Origin common.Hash // Hash of the first account to retrieve
|
||||
Limit common.Hash // Hash of the last account to retrieve
|
||||
Bytes uint64 // Soft limit at which to stop returning data
|
||||
}
|
||||
|
||||
// AccountRangePacket represents an account query response.
|
||||
type AccountRangePacket struct {
|
||||
ID uint64 // ID of the request this is a response for
|
||||
Accounts []*AccountData // List of consecutive accounts from the trie
|
||||
Proof [][]byte // List of trie nodes proving the account range
|
||||
}
|
||||
|
||||
// AccountData represents a single account in a query response.
|
||||
type AccountData struct {
|
||||
Hash common.Hash // Hash of the account
|
||||
Body rlp.RawValue // Account body in slim format
|
||||
}
|
||||
|
||||
// Unpack retrieves the accounts from the range packet and converts from slim
|
||||
// wire representation to consensus format. The returned data is RLP encoded
|
||||
// since it's expected to be serialized to disk without further interpretation.
|
||||
//
|
||||
// Note, this method does a round of RLP decoding and reencoding, so only use it
|
||||
// once and cache the results if need be. Ideally discard the packet afterwards
|
||||
// to not double the memory use.
|
||||
func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) {
|
||||
var (
|
||||
hashes = make([]common.Hash, len(p.Accounts))
|
||||
accounts = make([][]byte, len(p.Accounts))
|
||||
)
|
||||
for i, acc := range p.Accounts {
|
||||
val, err := snapshot.FullAccountRLP(acc.Body)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err)
|
||||
}
|
||||
hashes[i], accounts[i] = acc.Hash, val
|
||||
}
|
||||
return hashes, accounts, nil
|
||||
}
|
||||
|
||||
// GetStorageRangesPacket represents an storage slot query.
|
||||
type GetStorageRangesPacket struct {
|
||||
ID uint64 // Request ID to match up responses with
|
||||
Root common.Hash // Root hash of the account trie to serve
|
||||
Accounts []common.Hash // Account hashes of the storage tries to serve
|
||||
Origin []byte // Hash of the first storage slot to retrieve (large contract mode)
|
||||
Limit []byte // Hash of the last storage slot to retrieve (large contract mode)
|
||||
Bytes uint64 // Soft limit at which to stop returning data
|
||||
}
|
||||
|
||||
// StorageRangesPacket represents a storage slot query response.
|
||||
type StorageRangesPacket struct {
|
||||
ID uint64 // ID of the request this is a response for
|
||||
Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts
|
||||
Proof [][]byte // Merkle proofs for the *last* slot range, if it's incomplete
|
||||
}
|
||||
|
||||
// StorageData represents a single storage slot in a query response.
|
||||
type StorageData struct {
|
||||
Hash common.Hash // Hash of the storage slot
|
||||
Body []byte // Data content of the slot
|
||||
}
|
||||
|
||||
// Unpack retrieves the storage slots from the range packet and returns them in
|
||||
// a split flat format that's more consistent with the internal data structures.
|
||||
func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) {
|
||||
var (
|
||||
hashset = make([][]common.Hash, len(p.Slots))
|
||||
slotset = make([][][]byte, len(p.Slots))
|
||||
)
|
||||
for i, slots := range p.Slots {
|
||||
hashset[i] = make([]common.Hash, len(slots))
|
||||
slotset[i] = make([][]byte, len(slots))
|
||||
for j, slot := range slots {
|
||||
hashset[i][j] = slot.Hash
|
||||
slotset[i][j] = slot.Body
|
||||
}
|
||||
}
|
||||
return hashset, slotset
|
||||
}
|
||||
|
||||
// GetByteCodesPacket represents a contract bytecode query.
|
||||
type GetByteCodesPacket struct {
|
||||
ID uint64 // Request ID to match up responses with
|
||||
Hashes []common.Hash // Code hashes to retrieve the code for
|
||||
Bytes uint64 // Soft limit at which to stop returning data
|
||||
}
|
||||
|
||||
// ByteCodesPacket represents a contract bytecode query response.
|
||||
type ByteCodesPacket struct {
|
||||
ID uint64 // ID of the request this is a response for
|
||||
Codes [][]byte // Requested contract bytecodes
|
||||
}
|
||||
|
||||
// GetTrieNodesPacket represents a state trie node query.
|
||||
type GetTrieNodesPacket struct {
|
||||
ID uint64 // Request ID to match up responses with
|
||||
Root common.Hash // Root hash of the account trie to serve
|
||||
Paths []TrieNodePathSet // Trie node hashes to retrieve the nodes for
|
||||
Bytes uint64 // Soft limit at which to stop returning data
|
||||
}
|
||||
|
||||
// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to
|
||||
// represent trie nodes would be a simple list of `account || storage` path
|
||||
// segments concatenated, but that would be very wasteful on the network.
|
||||
//
|
||||
// Instead, this array special cases the first element as the path in the
|
||||
// account trie and the remaining elements as paths in the storage trie. To
|
||||
// address an account node, the slice should have a length of 1 consisting
|
||||
// of only the account path. There's no need to be able to address both an
|
||||
// account node and a storage node in the same request as it cannot happen
|
||||
// that a slot is accessed before the account path is fully expanded.
|
||||
type TrieNodePathSet [][]byte
|
||||
|
||||
// TrieNodesPacket represents a state trie node query response.
|
||||
type TrieNodesPacket struct {
|
||||
ID uint64 // ID of the request this is a response for
|
||||
Nodes [][]byte // Requested state trie nodes
|
||||
}
|
||||
|
||||
func (*GetAccountRangePacket) Name() string { return "GetAccountRange" }
|
||||
func (*GetAccountRangePacket) Kind() byte { return GetAccountRangeMsg }
|
||||
|
||||
func (*AccountRangePacket) Name() string { return "AccountRange" }
|
||||
func (*AccountRangePacket) Kind() byte { return AccountRangeMsg }
|
||||
|
||||
func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" }
|
||||
func (*GetStorageRangesPacket) Kind() byte { return GetStorageRangesMsg }
|
||||
|
||||
func (*StorageRangesPacket) Name() string { return "StorageRanges" }
|
||||
func (*StorageRangesPacket) Kind() byte { return StorageRangesMsg }
|
||||
|
||||
func (*GetByteCodesPacket) Name() string { return "GetByteCodes" }
|
||||
func (*GetByteCodesPacket) Kind() byte { return GetByteCodesMsg }
|
||||
|
||||
func (*ByteCodesPacket) Name() string { return "ByteCodes" }
|
||||
func (*ByteCodesPacket) Kind() byte { return ByteCodesMsg }
|
||||
|
||||
func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" }
|
||||
func (*GetTrieNodesPacket) Kind() byte { return GetTrieNodesMsg }
|
||||
|
||||
func (*TrieNodesPacket) Name() string { return "TrieNodes" }
|
||||
func (*TrieNodesPacket) Kind() byte { return TrieNodesMsg }
|
2481
eth/protocols/snap/sync.go
Normal file
2481
eth/protocols/snap/sync.go
Normal file
File diff suppressed because it is too large
Load Diff
129
eth/sync.go
129
eth/sync.go
@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
@ -40,12 +41,12 @@ const (
|
||||
)
|
||||
|
||||
type txsync struct {
|
||||
p *peer
|
||||
p *eth.Peer
|
||||
txs []*types.Transaction
|
||||
}
|
||||
|
||||
// syncTransactions starts sending all currently pending transactions to the given peer.
|
||||
func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
func (h *handler) syncTransactions(p *eth.Peer) {
|
||||
// Assemble the set of transaction to broadcast or announce to the remote
|
||||
// peer. Fun fact, this is quite an expensive operation as it needs to sort
|
||||
// the transactions if the sorting is not cached yet. However, with a random
|
||||
@ -53,7 +54,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
//
|
||||
// TODO(karalabe): Figure out if we could get away with random order somehow
|
||||
var txs types.Transactions
|
||||
pending, _ := pm.txpool.Pending()
|
||||
pending, _ := h.txpool.Pending()
|
||||
for _, batch := range pending {
|
||||
txs = append(txs, batch...)
|
||||
}
|
||||
@ -63,7 +64,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
// The eth/65 protocol introduces proper transaction announcements, so instead
|
||||
// of dripping transactions across multiple peers, just send the entire list as
|
||||
// an announcement and let the remote side decide what they need (likely nothing).
|
||||
if p.version >= eth65 {
|
||||
if p.Version() >= eth.ETH65 {
|
||||
hashes := make([]common.Hash, len(txs))
|
||||
for i, tx := range txs {
|
||||
hashes[i] = tx.Hash()
|
||||
@ -73,8 +74,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
}
|
||||
// Out of luck, peer is running legacy protocols, drop the txs over
|
||||
select {
|
||||
case pm.txsyncCh <- &txsync{p: p, txs: txs}:
|
||||
case <-pm.quitSync:
|
||||
case h.txsyncCh <- &txsync{p: p, txs: txs}:
|
||||
case <-h.quitSync:
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,8 +83,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
// connection. When a new peer appears, we relay all currently pending
|
||||
// transactions. In order to minimise egress bandwidth usage, we send
|
||||
// the transactions in small packs to one peer at a time.
|
||||
func (pm *ProtocolManager) txsyncLoop64() {
|
||||
defer pm.wg.Done()
|
||||
func (h *handler) txsyncLoop64() {
|
||||
defer h.wg.Done()
|
||||
|
||||
var (
|
||||
pending = make(map[enode.ID]*txsync)
|
||||
@ -94,7 +95,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
// send starts a sending a pack of transactions from the sync.
|
||||
send := func(s *txsync) {
|
||||
if s.p.version >= eth65 {
|
||||
if s.p.Version() >= eth.ETH65 {
|
||||
panic("initial transaction syncer running on eth/65+")
|
||||
}
|
||||
// Fill pack with transactions up to the target size.
|
||||
@ -108,14 +109,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
// Remove the transactions that will be sent.
|
||||
s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])]
|
||||
if len(s.txs) == 0 {
|
||||
delete(pending, s.p.ID())
|
||||
delete(pending, s.p.Peer.ID())
|
||||
}
|
||||
// Send the pack in the background.
|
||||
s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
|
||||
sending = true
|
||||
go func() { done <- pack.p.SendTransactions64(pack.txs) }()
|
||||
go func() { done <- pack.p.SendTransactions(pack.txs) }()
|
||||
}
|
||||
|
||||
// pick chooses the next pending sync.
|
||||
pick := func() *txsync {
|
||||
if len(pending) == 0 {
|
||||
@ -132,8 +132,8 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case s := <-pm.txsyncCh:
|
||||
pending[s.p.ID()] = s
|
||||
case s := <-h.txsyncCh:
|
||||
pending[s.p.Peer.ID()] = s
|
||||
if !sending {
|
||||
send(s)
|
||||
}
|
||||
@ -142,13 +142,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
// Stop tracking peers that cause send failures.
|
||||
if err != nil {
|
||||
pack.p.Log().Debug("Transaction send failed", "err", err)
|
||||
delete(pending, pack.p.ID())
|
||||
delete(pending, pack.p.Peer.ID())
|
||||
}
|
||||
// Schedule the next send.
|
||||
if s := pick(); s != nil {
|
||||
send(s)
|
||||
}
|
||||
case <-pm.quitSync:
|
||||
case <-h.quitSync:
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -156,7 +156,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
|
||||
|
||||
// chainSyncer coordinates blockchain sync components.
|
||||
type chainSyncer struct {
|
||||
pm *ProtocolManager
|
||||
handler *handler
|
||||
force *time.Timer
|
||||
forced bool // true when force timer fired
|
||||
peerEventCh chan struct{}
|
||||
@ -166,15 +166,15 @@ type chainSyncer struct {
|
||||
// chainSyncOp is a scheduled sync operation.
|
||||
type chainSyncOp struct {
|
||||
mode downloader.SyncMode
|
||||
peer *peer
|
||||
peer *eth.Peer
|
||||
td *big.Int
|
||||
head common.Hash
|
||||
}
|
||||
|
||||
// newChainSyncer creates a chainSyncer.
|
||||
func newChainSyncer(pm *ProtocolManager) *chainSyncer {
|
||||
func newChainSyncer(handler *handler) *chainSyncer {
|
||||
return &chainSyncer{
|
||||
pm: pm,
|
||||
handler: handler,
|
||||
peerEventCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@ -182,23 +182,24 @@ func newChainSyncer(pm *ProtocolManager) *chainSyncer {
|
||||
// handlePeerEvent notifies the syncer about a change in the peer set.
|
||||
// This is called for new peers and every time a peer announces a new
|
||||
// chain head.
|
||||
func (cs *chainSyncer) handlePeerEvent(p *peer) bool {
|
||||
func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool {
|
||||
select {
|
||||
case cs.peerEventCh <- struct{}{}:
|
||||
return true
|
||||
case <-cs.pm.quitSync:
|
||||
case <-cs.handler.quitSync:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// loop runs in its own goroutine and launches the sync when necessary.
|
||||
func (cs *chainSyncer) loop() {
|
||||
defer cs.pm.wg.Done()
|
||||
defer cs.handler.wg.Done()
|
||||
|
||||
cs.pm.blockFetcher.Start()
|
||||
cs.pm.txFetcher.Start()
|
||||
defer cs.pm.blockFetcher.Stop()
|
||||
defer cs.pm.txFetcher.Stop()
|
||||
cs.handler.blockFetcher.Start()
|
||||
cs.handler.txFetcher.Start()
|
||||
defer cs.handler.blockFetcher.Stop()
|
||||
defer cs.handler.txFetcher.Stop()
|
||||
defer cs.handler.downloader.Terminate()
|
||||
|
||||
// The force timer lowers the peer count threshold down to one when it fires.
|
||||
// This ensures we'll always start sync even if there aren't enough peers.
|
||||
@ -209,7 +210,6 @@ func (cs *chainSyncer) loop() {
|
||||
if op := cs.nextSyncOp(); op != nil {
|
||||
cs.startSync(op)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-cs.peerEventCh:
|
||||
// Peer information changed, recheck.
|
||||
@ -220,14 +220,13 @@ func (cs *chainSyncer) loop() {
|
||||
case <-cs.force.C:
|
||||
cs.forced = true
|
||||
|
||||
case <-cs.pm.quitSync:
|
||||
case <-cs.handler.quitSync:
|
||||
// Disable all insertion on the blockchain. This needs to happen before
|
||||
// terminating the downloader because the downloader waits for blockchain
|
||||
// inserts, and these can take a long time to finish.
|
||||
cs.pm.blockchain.StopInsert()
|
||||
cs.pm.downloader.Terminate()
|
||||
cs.handler.chain.StopInsert()
|
||||
cs.handler.downloader.Terminate()
|
||||
if cs.doneCh != nil {
|
||||
// Wait for the current sync to end.
|
||||
<-cs.doneCh
|
||||
}
|
||||
return
|
||||
@ -245,19 +244,22 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
minPeers := defaultMinSyncPeers
|
||||
if cs.forced {
|
||||
minPeers = 1
|
||||
} else if minPeers > cs.pm.maxPeers {
|
||||
minPeers = cs.pm.maxPeers
|
||||
} else if minPeers > cs.handler.maxPeers {
|
||||
minPeers = cs.handler.maxPeers
|
||||
}
|
||||
if cs.pm.peers.Len() < minPeers {
|
||||
if cs.handler.peers.Len() < minPeers {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have enough peers, check TD.
|
||||
peer := cs.pm.peers.BestPeer()
|
||||
// We have enough peers, check TD
|
||||
peer := cs.handler.peers.ethPeerWithHighestTD()
|
||||
if peer == nil {
|
||||
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.
|
||||
@ -265,42 +267,42 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
||||
return op
|
||||
}
|
||||
|
||||
func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp {
|
||||
func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
|
||||
peerHead, peerTD := p.Head()
|
||||
return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead}
|
||||
}
|
||||
|
||||
func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
|
||||
// If we're in fast sync mode, return that directly
|
||||
if atomic.LoadUint32(&cs.pm.fastSync) == 1 {
|
||||
block := cs.pm.blockchain.CurrentFastBlock()
|
||||
td := cs.pm.blockchain.GetTdByHash(block.Hash())
|
||||
if atomic.LoadUint32(&cs.handler.fastSync) == 1 {
|
||||
block := cs.handler.chain.CurrentFastBlock()
|
||||
td := cs.handler.chain.GetTdByHash(block.Hash())
|
||||
return downloader.FastSync, td
|
||||
}
|
||||
// We are probably in full sync, but we might have rewound to before the
|
||||
// fast sync pivot, check if we should reenable
|
||||
if pivot := rawdb.ReadLastPivotNumber(cs.pm.chaindb); pivot != nil {
|
||||
if head := cs.pm.blockchain.CurrentBlock(); head.NumberU64() < *pivot {
|
||||
block := cs.pm.blockchain.CurrentFastBlock()
|
||||
td := cs.pm.blockchain.GetTdByHash(block.Hash())
|
||||
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.GetTdByHash(block.Hash())
|
||||
return downloader.FastSync, td
|
||||
}
|
||||
}
|
||||
// Nope, we're really full syncing
|
||||
head := cs.pm.blockchain.CurrentHeader()
|
||||
td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
head := cs.handler.chain.CurrentHeader()
|
||||
td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64())
|
||||
return downloader.FullSync, td
|
||||
}
|
||||
|
||||
// startSync launches doSync in a new goroutine.
|
||||
func (cs *chainSyncer) startSync(op *chainSyncOp) {
|
||||
cs.doneCh = make(chan error, 1)
|
||||
go func() { cs.doneCh <- cs.pm.doSync(op) }()
|
||||
go func() { cs.doneCh <- cs.handler.doSync(op) }()
|
||||
}
|
||||
|
||||
// doSync synchronizes the local blockchain with a remote peer.
|
||||
func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
if op.mode == downloader.FastSync {
|
||||
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
|
||||
// txlookup limit.
|
||||
// The main concern here is: during the fast sync Geth won't index the
|
||||
@ -310,35 +312,33 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
// 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
|
||||
// will just blindly use the original one.
|
||||
limit := pm.blockchain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(pm.chaindb); stored == nil {
|
||||
rawdb.WriteFastTxLookupLimit(pm.chaindb, limit)
|
||||
limit := h.chain.TxLookupLimit()
|
||||
if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
|
||||
rawdb.WriteFastTxLookupLimit(h.database, limit)
|
||||
} else if *stored != limit {
|
||||
pm.blockchain.SetTxLookupLimit(*stored)
|
||||
h.chain.SetTxLookupLimit(*stored)
|
||||
log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
|
||||
}
|
||||
}
|
||||
// Run the sync cycle, and disable fast sync if we're past the pivot block
|
||||
err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode)
|
||||
err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if atomic.LoadUint32(&pm.fastSync) == 1 {
|
||||
if atomic.LoadUint32(&h.fastSync) == 1 {
|
||||
log.Info("Fast sync complete, auto disabling")
|
||||
atomic.StoreUint32(&pm.fastSync, 0)
|
||||
atomic.StoreUint32(&h.fastSync, 0)
|
||||
}
|
||||
|
||||
// If we've successfully finished a sync cycle and passed any required checkpoint,
|
||||
// enable accepting transactions from the network.
|
||||
head := pm.blockchain.CurrentBlock()
|
||||
if head.NumberU64() >= pm.checkpointNumber {
|
||||
head := h.chain.CurrentBlock()
|
||||
if head.NumberU64() >= h.checkpointNumber {
|
||||
// Checkpoint passed, sanity check the timestamp to have a fallback mechanism
|
||||
// for non-checkpointed (number = 0) private networks.
|
||||
if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
|
||||
atomic.StoreUint32(&pm.acceptTxs, 1)
|
||||
atomic.StoreUint32(&h.acceptTxs, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if head.NumberU64() > 0 {
|
||||
// We've completed a sync cycle, notify all peers of new state. This path is
|
||||
// essential in star-topology networks where a gateway node needs to notify
|
||||
@ -346,8 +346,7 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
|
||||
// scenario will most often crop up in private and hackathon networks with
|
||||
// degenerate connectivity, but it should be healthy for the mainnet too to
|
||||
// more reliably update peers or the local TD state.
|
||||
pm.BroadcastBlock(head, false)
|
||||
h.BroadcastBlock(head, false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -22,43 +22,59 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) }
|
||||
// Tests that fast sync is disabled after a successful sync cycle.
|
||||
func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) }
|
||||
func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) }
|
||||
|
||||
// Tests that fast sync gets disabled as soon as a real block is successfully
|
||||
// imported into the blockchain.
|
||||
func testFastSyncDisabling(t *testing.T, protocol int) {
|
||||
func testFastSyncDisabling(t *testing.T, protocol uint) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a pristine protocol manager, check that fast sync is left enabled
|
||||
pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
|
||||
if atomic.LoadUint32(&pmEmpty.fastSync) == 0 {
|
||||
// Create an empty handler and ensure it's in fast sync mode
|
||||
empty := newTestHandler()
|
||||
if atomic.LoadUint32(&empty.handler.fastSync) == 0 {
|
||||
t.Fatalf("fast sync disabled on pristine blockchain")
|
||||
}
|
||||
// Create a full protocol manager, check that fast sync gets disabled
|
||||
pmFull, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
|
||||
if atomic.LoadUint32(&pmFull.fastSync) == 1 {
|
||||
defer empty.close()
|
||||
|
||||
// Create a full handler and ensure fast sync ends up disabled
|
||||
full := newTestHandlerWithBlocks(1024)
|
||||
if atomic.LoadUint32(&full.handler.fastSync) == 1 {
|
||||
t.Fatalf("fast sync not disabled on non-empty blockchain")
|
||||
}
|
||||
defer full.close()
|
||||
|
||||
// Sync up the two peers
|
||||
io1, io2 := p2p.MsgPipe()
|
||||
go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get))
|
||||
go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get))
|
||||
// Sync up the two handlers
|
||||
emptyPipe, fullPipe := p2p.MsgPipe()
|
||||
defer emptyPipe.Close()
|
||||
defer fullPipe.Close()
|
||||
|
||||
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()
|
||||
|
||||
go empty.handler.runEthPeer(emptyPeer, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(empty.handler), peer)
|
||||
})
|
||||
go full.handler.runEthPeer(fullPeer, func(peer *eth.Peer) error {
|
||||
return eth.Handle((*ethHandler)(full.handler), peer)
|
||||
})
|
||||
// Wait a bit for the above handlers to start
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer())
|
||||
if err := pmEmpty.doSync(op); err != nil {
|
||||
t.Fatal("sync failed:", err)
|
||||
}
|
||||
|
||||
// Check that fast sync was disabled
|
||||
if atomic.LoadUint32(&pmEmpty.fastSync) == 1 {
|
||||
op := peerToSyncOp(downloader.FastSync, empty.handler.peers.ethPeerWithHighestTD())
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@ -444,13 +444,15 @@ func (s *Service) login(conn *connWrapper) error {
|
||||
// Construct and send the login authentication
|
||||
infos := s.server.NodeInfo()
|
||||
|
||||
var network, protocol string
|
||||
var protocols []string
|
||||
for _, proto := range s.server.Protocols {
|
||||
protocols = append(protocols, fmt.Sprintf("%s/%d", proto.Name, proto.Version))
|
||||
}
|
||||
var network string
|
||||
if info := infos.Protocols["eth"]; info != nil {
|
||||
network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network)
|
||||
protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
|
||||
network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network)
|
||||
} else {
|
||||
network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network)
|
||||
protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])
|
||||
}
|
||||
auth := &authMsg{
|
||||
ID: s.node,
|
||||
@ -459,7 +461,7 @@ func (s *Service) login(conn *connWrapper) error {
|
||||
Node: infos.Name,
|
||||
Port: infos.Ports.Listener,
|
||||
Network: network,
|
||||
Protocol: protocol,
|
||||
Protocol: strings.Join(protocols, ", "),
|
||||
API: "No",
|
||||
Os: runtime.GOOS,
|
||||
OsVer: runtime.GOARCH,
|
||||
|
@ -1040,10 +1040,6 @@ func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) {
|
||||
return hexutil.Big(*price), err
|
||||
}
|
||||
|
||||
func (r *Resolver) ProtocolVersion(ctx context.Context) (int32, error) {
|
||||
return int32(r.backend.ProtocolVersion()), nil
|
||||
}
|
||||
|
||||
func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) {
|
||||
return hexutil.Big(*r.backend.ChainConfig().ChainID), nil
|
||||
}
|
||||
|
@ -310,8 +310,6 @@ const schema string = `
|
||||
# GasPrice returns the node's estimate of a gas price sufficient to
|
||||
# ensure a transaction is mined in a timely fashion.
|
||||
gasPrice: BigInt!
|
||||
# ProtocolVersion returns the current wire protocol version number.
|
||||
protocolVersion: Int!
|
||||
# Syncing returns information on the current synchronisation state.
|
||||
syncing: SyncState
|
||||
# ChainID returns the current chain ID for transaction replay protection.
|
||||
|
@ -64,11 +64,6 @@ func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error)
|
||||
return (*hexutil.Big)(price), err
|
||||
}
|
||||
|
||||
// ProtocolVersion returns the current Ethereum protocol version this node supports
|
||||
func (s *PublicEthereumAPI) ProtocolVersion() hexutil.Uint {
|
||||
return hexutil.Uint(s.b.ProtocolVersion())
|
||||
}
|
||||
|
||||
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
|
||||
// yet received the latest block headers from its pears. In case it is synchronizing:
|
||||
// - startingBlock: block number this node started to synchronise from
|
||||
@ -1905,13 +1900,12 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) {
|
||||
|
||||
// PublicNetAPI offers network related RPC methods
|
||||
type PublicNetAPI struct {
|
||||
net *p2p.Server
|
||||
networkVersion uint64
|
||||
net *p2p.Server
|
||||
}
|
||||
|
||||
// NewPublicNetAPI creates a new net API instance.
|
||||
func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI {
|
||||
return &PublicNetAPI{net, networkVersion}
|
||||
func NewPublicNetAPI(net *p2p.Server) *PublicNetAPI {
|
||||
return &PublicNetAPI{net}
|
||||
}
|
||||
|
||||
// Listening returns an indication if the node is listening for network connections.
|
||||
@ -1924,11 +1918,6 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
|
||||
return hexutil.Uint(s.net.PeerCount())
|
||||
}
|
||||
|
||||
// Version returns the current ethereum protocol version.
|
||||
func (s *PublicNetAPI) Version() string {
|
||||
return fmt.Sprintf("%d", s.networkVersion)
|
||||
}
|
||||
|
||||
// checkTxFee is an internal function used to check whether the fee of
|
||||
// the given transaction is _reasonable_(under the cap).
|
||||
func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
||||
|
@ -41,7 +41,6 @@ import (
|
||||
type Backend interface {
|
||||
// General Ethereum API
|
||||
Downloader() *downloader.Downloader
|
||||
ProtocolVersion() int
|
||||
SuggestPrice(ctx context.Context) (*big.Int, error)
|
||||
ChainDb() ethdb.Database
|
||||
AccountManager() *accounts.Manager
|
||||
|
@ -171,7 +171,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) {
|
||||
leth.blockchain.DisableCheckFreq()
|
||||
}
|
||||
|
||||
leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId)
|
||||
leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer)
|
||||
|
||||
// Register the backend on the node
|
||||
stack.RegisterAPIs(leth.APIs())
|
||||
|
@ -35,9 +35,9 @@ func (e lesEntry) ENRKey() string {
|
||||
|
||||
// setupDiscovery creates the node discovery source for the eth protocol.
|
||||
func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) {
|
||||
if len(eth.config.DiscoveryURLs) == 0 {
|
||||
if len(eth.config.EthDiscoveryURLs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
client := dnsdisc.NewClient(dnsdisc.Config{})
|
||||
return client.NewIterator(eth.config.DiscoveryURLs...)
|
||||
return client.NewIterator(eth.config.EthDiscoveryURLs...)
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
|
||||
func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
|
||||
|
||||
func testGetBlockHeaders(t *testing.T, protocol int) {
|
||||
server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0)
|
||||
server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0)
|
||||
defer tearDown()
|
||||
|
||||
bc := server.handler.blockchain
|
||||
|
13
les/peer.go
13
les/peer.go
@ -31,7 +31,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||
lpc "github.com/ethereum/go-ethereum/les/lespay/client"
|
||||
lps "github.com/ethereum/go-ethereum/les/lespay/server"
|
||||
@ -162,9 +161,17 @@ func (p *peerCommons) String() string {
|
||||
return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version))
|
||||
}
|
||||
|
||||
// PeerInfo represents a short summary of the `eth` sub-protocol metadata known
|
||||
// about a connected peer.
|
||||
type PeerInfo struct {
|
||||
Version int `json:"version"` // Ethereum protocol version negotiated
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
|
||||
Head string `json:"head"` // SHA3 hash of the peer's best owned block
|
||||
}
|
||||
|
||||
// Info gathers and returns a collection of metadata known about a peer.
|
||||
func (p *peerCommons) Info() *eth.PeerInfo {
|
||||
return ð.PeerInfo{
|
||||
func (p *peerCommons) Info() *PeerInfo {
|
||||
return &PeerInfo{
|
||||
Version: p.version,
|
||||
Difficulty: p.Td(),
|
||||
Head: fmt.Sprintf("%x", p.Head()),
|
||||
|
@ -47,7 +47,7 @@ import (
|
||||
const (
|
||||
softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
|
||||
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
|
||||
ethVersion = 63 // equivalent eth version for the downloader
|
||||
ethVersion = 64 // equivalent eth version for the downloader
|
||||
|
||||
MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
|
||||
MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request
|
||||
|
@ -147,7 +147,7 @@ func (t *BlockTest) Run(snapshotter bool) error {
|
||||
}
|
||||
// Cross-check the snapshot-to-hash against the trie hash
|
||||
if snapshotter {
|
||||
if err := snapshot.VerifyState(chain.Snapshot(), chain.CurrentBlock().Root()); err != nil {
|
||||
if err := snapshot.VerifyState(chain.Snapshots(), chain.CurrentBlock().Root()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/fuzzers/rangeproof/corpus/random.dat
Normal file
BIN
tests/fuzzers/rangeproof/corpus/random.dat
Normal file
Binary file not shown.
41
tests/fuzzers/rangeproof/debug/main.go
Normal file
41
tests/fuzzers/rangeproof/debug/main.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/tests/fuzzers/rangeproof"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: debug <file>\n")
|
||||
fmt.Fprintf(os.Stderr, "Example\n")
|
||||
fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
crasher := os.Args[1]
|
||||
data, err := ioutil.ReadFile(crasher)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rangeproof.Fuzz(data)
|
||||
}
|
218
tests/fuzzers/rangeproof/rangeproof-fuzzer.go
Normal file
218
tests/fuzzers/rangeproof/rangeproof-fuzzer.go
Normal file
@ -0,0 +1,218 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rangeproof
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
type kv struct {
|
||||
k, v []byte
|
||||
t bool
|
||||
}
|
||||
|
||||
type entrySlice []*kv
|
||||
|
||||
func (p entrySlice) Len() int { return len(p) }
|
||||
func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 }
|
||||
func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
type fuzzer struct {
|
||||
input io.Reader
|
||||
exhausted bool
|
||||
}
|
||||
|
||||
func (f *fuzzer) randBytes(n int) []byte {
|
||||
r := make([]byte, n)
|
||||
if _, err := f.input.Read(r); err != nil {
|
||||
f.exhausted = true
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *fuzzer) readInt() uint64 {
|
||||
var x uint64
|
||||
if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil {
|
||||
f.exhausted = true
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) {
|
||||
|
||||
trie := new(trie.Trie)
|
||||
vals := make(map[string]*kv)
|
||||
size := f.readInt()
|
||||
// Fill it with some fluff
|
||||
for i := byte(0); i < byte(size); i++ {
|
||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false}
|
||||
trie.Update(value.k, value.v)
|
||||
trie.Update(value2.k, value2.v)
|
||||
vals[string(value.k)] = value
|
||||
vals[string(value2.k)] = value2
|
||||
}
|
||||
if f.exhausted {
|
||||
return nil, nil
|
||||
}
|
||||
// And now fill with some random
|
||||
for i := 0; i < n; i++ {
|
||||
k := f.randBytes(32)
|
||||
v := f.randBytes(20)
|
||||
value := &kv{k, v, false}
|
||||
trie.Update(k, v)
|
||||
vals[string(k)] = value
|
||||
if f.exhausted {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return trie, vals
|
||||
}
|
||||
|
||||
func (f *fuzzer) fuzz() int {
|
||||
maxSize := 200
|
||||
tr, vals := f.randomTrie(1 + int(f.readInt())%maxSize)
|
||||
if f.exhausted {
|
||||
return 0 // input too short
|
||||
}
|
||||
var entries entrySlice
|
||||
for _, kv := range vals {
|
||||
entries = append(entries, kv)
|
||||
}
|
||||
if len(entries) <= 1 {
|
||||
return 0
|
||||
}
|
||||
sort.Sort(entries)
|
||||
|
||||
var ok = 0
|
||||
for {
|
||||
start := int(f.readInt() % uint64(len(entries)))
|
||||
end := 1 + int(f.readInt()%uint64(len(entries)-1))
|
||||
testcase := int(f.readInt() % uint64(6))
|
||||
index := int(f.readInt() & 0xFFFFFFFF)
|
||||
index2 := int(f.readInt() & 0xFFFFFFFF)
|
||||
if f.exhausted {
|
||||
break
|
||||
}
|
||||
proof := memorydb.New()
|
||||
if err := tr.Prove(entries[start].k, 0, proof); err != nil {
|
||||
panic(fmt.Sprintf("Failed to prove the first node %v", err))
|
||||
}
|
||||
if err := tr.Prove(entries[end-1].k, 0, proof); err != nil {
|
||||
panic(fmt.Sprintf("Failed to prove the last node %v", err))
|
||||
}
|
||||
var keys [][]byte
|
||||
var vals [][]byte
|
||||
for i := start; i < end; i++ {
|
||||
keys = append(keys, entries[i].k)
|
||||
vals = append(vals, entries[i].v)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return 0
|
||||
}
|
||||
var first, last = keys[0], keys[len(keys)-1]
|
||||
testcase %= 6
|
||||
switch testcase {
|
||||
case 0:
|
||||
// Modified key
|
||||
keys[index%len(keys)] = f.randBytes(32) // In theory it can't be same
|
||||
case 1:
|
||||
// Modified val
|
||||
vals[index%len(vals)] = f.randBytes(20) // In theory it can't be same
|
||||
case 2:
|
||||
// Gapped entry slice
|
||||
index = index % len(keys)
|
||||
keys = append(keys[:index], keys[index+1:]...)
|
||||
vals = append(vals[:index], vals[index+1:]...)
|
||||
case 3:
|
||||
// Out of order
|
||||
index1 := index % len(keys)
|
||||
index2 := index2 % len(keys)
|
||||
keys[index1], keys[index2] = keys[index2], keys[index1]
|
||||
vals[index1], vals[index2] = vals[index2], vals[index1]
|
||||
case 4:
|
||||
// Set random key to nil, do nothing
|
||||
keys[index%len(keys)] = nil
|
||||
case 5:
|
||||
// Set random value to nil, deletion
|
||||
vals[index%len(vals)] = nil
|
||||
|
||||
// Other cases:
|
||||
// Modify something in the proof db
|
||||
// add stuff to proof db
|
||||
// drop stuff from proof db
|
||||
|
||||
}
|
||||
if f.exhausted {
|
||||
break
|
||||
}
|
||||
ok = 1
|
||||
//nodes, subtrie
|
||||
nodes, subtrie, notary, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof)
|
||||
if err != nil {
|
||||
if nodes != nil {
|
||||
panic("err != nil && nodes != nil")
|
||||
}
|
||||
if subtrie != nil {
|
||||
panic("err != nil && subtrie != nil")
|
||||
}
|
||||
if notary != nil {
|
||||
panic("err != nil && notary != nil")
|
||||
}
|
||||
if hasMore {
|
||||
panic("err != nil && hasMore == true")
|
||||
}
|
||||
} else {
|
||||
if nodes == nil {
|
||||
panic("err == nil && nodes == nil")
|
||||
}
|
||||
if subtrie == nil {
|
||||
panic("err == nil && subtrie == nil")
|
||||
}
|
||||
if notary == nil {
|
||||
panic("err == nil && subtrie == nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// The function must return
|
||||
// 1 if the fuzzer should increase priority of the
|
||||
// given input during subsequent fuzzing (for example, the input is lexically
|
||||
// correct and was parsed successfully);
|
||||
// -1 if the input must not be added to corpus even if gives new coverage; and
|
||||
// 0 otherwise; other values are reserved for future use.
|
||||
func Fuzz(input []byte) int {
|
||||
if len(input) < 100 {
|
||||
return 0
|
||||
}
|
||||
r := bytes.NewReader(input)
|
||||
f := fuzzer{
|
||||
input: r,
|
||||
exhausted: false,
|
||||
}
|
||||
return f.fuzz()
|
||||
}
|
57
trie/notary.go
Normal file
57
trie/notary.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
)
|
||||
|
||||
// KeyValueNotary tracks which keys have been accessed through a key-value reader
|
||||
// with te scope of verifying if certain proof datasets are maliciously bloated.
|
||||
type KeyValueNotary struct {
|
||||
ethdb.KeyValueReader
|
||||
reads map[string]struct{}
|
||||
}
|
||||
|
||||
// NewKeyValueNotary wraps a key-value database with an access notary to track
|
||||
// which items have bene accessed.
|
||||
func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary {
|
||||
return &KeyValueNotary{
|
||||
KeyValueReader: db,
|
||||
reads: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves an item from the underlying database, but also tracks it as an
|
||||
// accessed slot for bloat checks.
|
||||
func (k *KeyValueNotary) Get(key []byte) ([]byte, error) {
|
||||
k.reads[string(key)] = struct{}{}
|
||||
return k.KeyValueReader.Get(key)
|
||||
}
|
||||
|
||||
// Accessed returns s snapshot of the original key-value store containing only the
|
||||
// data accessed through the notary.
|
||||
func (k *KeyValueNotary) Accessed() ethdb.KeyValueStore {
|
||||
db := memorydb.New()
|
||||
for keystr := range k.reads {
|
||||
key := []byte(keystr)
|
||||
val, _ := k.KeyValueReader.Get(key)
|
||||
db.Put(key, val)
|
||||
}
|
||||
return db
|
||||
}
|
104
trie/proof.go
104
trie/proof.go
@ -426,7 +426,7 @@ func hasRightElement(node node, key []byte) bool {
|
||||
|
||||
// VerifyRangeProof checks whether the given leaf nodes and edge proof
|
||||
// can prove the given trie leaves range is matched with the specific root.
|
||||
// Besides, the range should be consecutive(no gap inside) and monotonic
|
||||
// Besides, the range should be consecutive (no gap inside) and monotonic
|
||||
// increasing.
|
||||
//
|
||||
// Note the given proof actually contains two edge proofs. Both of them can
|
||||
@ -454,96 +454,136 @@ func hasRightElement(node node, key []byte) bool {
|
||||
//
|
||||
// Except returning the error to indicate the proof is valid or not, the function will
|
||||
// also return a flag to indicate whether there exists more accounts/slots in the trie.
|
||||
func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (error, bool) {
|
||||
func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, *Trie, *KeyValueNotary, bool, error) {
|
||||
if len(keys) != len(values) {
|
||||
return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false
|
||||
return nil, nil, nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
|
||||
}
|
||||
// Ensure the received batch is monotonic increasing.
|
||||
for i := 0; i < len(keys)-1; i++ {
|
||||
if bytes.Compare(keys[i], keys[i+1]) >= 0 {
|
||||
return errors.New("range is not monotonically increasing"), false
|
||||
return nil, nil, nil, false, errors.New("range is not monotonically increasing")
|
||||
}
|
||||
}
|
||||
// Create a key-value notary to track which items from the given proof the
|
||||
// range prover actually needed to verify the data
|
||||
notary := NewKeyValueNotary(proof)
|
||||
|
||||
// Special case, there is no edge proof at all. The given range is expected
|
||||
// to be the whole leaf-set in the trie.
|
||||
if proof == nil {
|
||||
emptytrie, err := New(common.Hash{}, NewDatabase(memorydb.New()))
|
||||
var (
|
||||
diskdb = memorydb.New()
|
||||
triedb = NewDatabase(diskdb)
|
||||
)
|
||||
tr, err := New(common.Hash{}, triedb)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
for index, key := range keys {
|
||||
emptytrie.TryUpdate(key, values[index])
|
||||
tr.TryUpdate(key, values[index])
|
||||
}
|
||||
if emptytrie.Hash() != rootHash {
|
||||
return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, emptytrie.Hash()), false
|
||||
if tr.Hash() != rootHash {
|
||||
return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
|
||||
}
|
||||
return nil, false // no more element.
|
||||
// Proof seems valid, serialize all the nodes into the database
|
||||
if _, err := tr.Commit(nil); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
if err := triedb.Commit(rootHash, false, nil); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
return diskdb, tr, notary, false, nil // No more elements
|
||||
}
|
||||
// Special case, there is a provided edge proof but zero key/value
|
||||
// pairs, ensure there are no more accounts / slots in the trie.
|
||||
if len(keys) == 0 {
|
||||
root, val, err := proofToPath(rootHash, nil, firstKey, proof, true)
|
||||
root, val, err := proofToPath(rootHash, nil, firstKey, notary, true)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
if val != nil || hasRightElement(root, firstKey) {
|
||||
return errors.New("more entries available"), false
|
||||
return nil, nil, nil, false, errors.New("more entries available")
|
||||
}
|
||||
return nil, false
|
||||
// Since the entire proof is a single path, we can construct a trie and a
|
||||
// node database directly out of the inputs, no need to generate them
|
||||
diskdb := notary.Accessed()
|
||||
tr := &Trie{
|
||||
db: NewDatabase(diskdb),
|
||||
root: root,
|
||||
}
|
||||
return diskdb, tr, notary, hasRightElement(root, firstKey), nil
|
||||
}
|
||||
// Special case, there is only one element and two edge keys are same.
|
||||
// In this case, we can't construct two edge paths. So handle it here.
|
||||
if len(keys) == 1 && bytes.Equal(firstKey, lastKey) {
|
||||
root, val, err := proofToPath(rootHash, nil, firstKey, proof, false)
|
||||
root, val, err := proofToPath(rootHash, nil, firstKey, notary, false)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
if !bytes.Equal(firstKey, keys[0]) {
|
||||
return errors.New("correct proof but invalid key"), false
|
||||
return nil, nil, nil, false, errors.New("correct proof but invalid key")
|
||||
}
|
||||
if !bytes.Equal(val, values[0]) {
|
||||
return errors.New("correct proof but invalid data"), false
|
||||
return nil, nil, nil, false, errors.New("correct proof but invalid data")
|
||||
}
|
||||
return nil, hasRightElement(root, firstKey)
|
||||
// Since the entire proof is a single path, we can construct a trie and a
|
||||
// node database directly out of the inputs, no need to generate them
|
||||
diskdb := notary.Accessed()
|
||||
tr := &Trie{
|
||||
db: NewDatabase(diskdb),
|
||||
root: root,
|
||||
}
|
||||
return diskdb, tr, notary, hasRightElement(root, firstKey), nil
|
||||
}
|
||||
// Ok, in all other cases, we require two edge paths available.
|
||||
// First check the validity of edge keys.
|
||||
if bytes.Compare(firstKey, lastKey) >= 0 {
|
||||
return errors.New("invalid edge keys"), false
|
||||
return nil, nil, nil, false, errors.New("invalid edge keys")
|
||||
}
|
||||
// todo(rjl493456442) different length edge keys should be supported
|
||||
if len(firstKey) != len(lastKey) {
|
||||
return errors.New("inconsistent edge keys"), false
|
||||
return nil, nil, nil, false, errors.New("inconsistent edge keys")
|
||||
}
|
||||
// Convert the edge proofs to edge trie paths. Then we can
|
||||
// have the same tree architecture with the original one.
|
||||
// For the first edge proof, non-existent proof is allowed.
|
||||
root, _, err := proofToPath(rootHash, nil, firstKey, proof, true)
|
||||
root, _, err := proofToPath(rootHash, nil, firstKey, notary, true)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
// Pass the root node here, the second path will be merged
|
||||
// with the first one. For the last edge proof, non-existent
|
||||
// proof is also allowed.
|
||||
root, _, err = proofToPath(rootHash, root, lastKey, proof, true)
|
||||
root, _, err = proofToPath(rootHash, root, lastKey, notary, true)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
// Remove all internal references. All the removed parts should
|
||||
// be re-filled(or re-constructed) by the given leaves range.
|
||||
if err := unsetInternal(root, firstKey, lastKey); err != nil {
|
||||
return err, false
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
// Rebuild the trie with the leave stream, the shape of trie
|
||||
// Rebuild the trie with the leaf stream, the shape of trie
|
||||
// should be same with the original one.
|
||||
newtrie := &Trie{root: root, db: NewDatabase(memorydb.New())}
|
||||
var (
|
||||
diskdb = memorydb.New()
|
||||
triedb = NewDatabase(diskdb)
|
||||
)
|
||||
tr := &Trie{root: root, db: triedb}
|
||||
for index, key := range keys {
|
||||
newtrie.TryUpdate(key, values[index])
|
||||
tr.TryUpdate(key, values[index])
|
||||
}
|
||||
if newtrie.Hash() != rootHash {
|
||||
return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, newtrie.Hash()), false
|
||||
if tr.Hash() != rootHash {
|
||||
return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
|
||||
}
|
||||
return nil, hasRightElement(root, keys[len(keys)-1])
|
||||
// Proof seems valid, serialize all the nodes into the database
|
||||
if _, err := tr.Commit(nil); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
if err := triedb.Commit(rootHash, false, nil); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
return diskdb, tr, notary, hasRightElement(root, keys[len(keys)-1]), nil
|
||||
}
|
||||
|
||||
// get returns the child of the given node. Return nil if the
|
||||
|
@ -19,6 +19,7 @@ package trie
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
mrand "math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
@ -181,7 +182,7 @@ func TestRangeProof(t *testing.T) {
|
||||
keys = append(keys, entries[i].k)
|
||||
vals = append(vals, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
@ -232,7 +233,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) {
|
||||
keys = append(keys, entries[i].k)
|
||||
vals = append(vals, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
@ -253,7 +254,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to verify whole rang with non-existent edges")
|
||||
}
|
||||
@ -288,7 +289,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected to detect the error, got nil")
|
||||
}
|
||||
@ -310,7 +311,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected to detect the error, got nil")
|
||||
}
|
||||
@ -334,7 +335,7 @@ func TestOneElementRangeProof(t *testing.T) {
|
||||
if err := trie.Prove(entries[start].k, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the first node %v", err)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -349,7 +350,7 @@ func TestOneElementRangeProof(t *testing.T) {
|
||||
if err := trie.Prove(entries[start].k, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -364,7 +365,7 @@ func TestOneElementRangeProof(t *testing.T) {
|
||||
if err := trie.Prove(last, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -379,7 +380,7 @@ func TestOneElementRangeProof(t *testing.T) {
|
||||
if err := trie.Prove(last, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -401,7 +402,7 @@ func TestAllElementsProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -414,7 +415,7 @@ func TestAllElementsProof(t *testing.T) {
|
||||
if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -429,7 +430,7 @@ func TestAllElementsProof(t *testing.T) {
|
||||
if err := trie.Prove(last, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -462,7 +463,7 @@ func TestSingleSideRangeProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -498,7 +499,7 @@ func TestReverseSingleSideRangeProof(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -570,7 +571,7 @@ func TestBadRangeProof(t *testing.T) {
|
||||
index = mrand.Intn(end - start)
|
||||
vals[index] = nil
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
|
||||
if err == nil {
|
||||
t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1)
|
||||
}
|
||||
@ -604,7 +605,7 @@ func TestGappedRangeProof(t *testing.T) {
|
||||
keys = append(keys, entries[i].k)
|
||||
vals = append(vals, entries[i].v)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
|
||||
if err == nil {
|
||||
t.Fatal("expect error, got nil")
|
||||
}
|
||||
@ -631,7 +632,7 @@ func TestSameSideProofs(t *testing.T) {
|
||||
if err := trie.Prove(last, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
@ -647,7 +648,7 @@ func TestSameSideProofs(t *testing.T) {
|
||||
if err := trie.Prove(last, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the last node %v", err)
|
||||
}
|
||||
err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
|
||||
_, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
@ -715,7 +716,7 @@ func TestHasRightElement(t *testing.T) {
|
||||
k = append(k, entries[i].k)
|
||||
v = append(v, entries[i].v)
|
||||
}
|
||||
err, hasMore := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof)
|
||||
_, _, _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
@ -748,13 +749,57 @@ func TestEmptyRangeProof(t *testing.T) {
|
||||
if err := trie.Prove(first, 0, proof); err != nil {
|
||||
t.Fatalf("Failed to prove the first node %v", err)
|
||||
}
|
||||
err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof)
|
||||
db, tr, not, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof)
|
||||
if c.err && err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
if !c.err && err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
// If no error was returned, ensure the returned trie and database contains
|
||||
// the entire proof, since there's no value
|
||||
if !c.err {
|
||||
if err := tr.Prove(first, 0, memorydb.New()); err != nil {
|
||||
t.Errorf("returned trie doesn't contain original proof: %v", err)
|
||||
}
|
||||
if memdb := db.(*memorydb.Database); memdb.Len() != proof.Len() {
|
||||
t.Errorf("database entry count mismatch: have %d, want %d", memdb.Len(), proof.Len())
|
||||
}
|
||||
if not == nil {
|
||||
t.Errorf("missing notary")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBloatedProof tests a malicious proof, where the proof is more or less the
|
||||
// whole trie.
|
||||
func TestBloatedProof(t *testing.T) {
|
||||
// Use a small trie
|
||||
trie, kvs := nonRandomTrie(100)
|
||||
var entries entrySlice
|
||||
for _, kv := range kvs {
|
||||
entries = append(entries, kv)
|
||||
}
|
||||
sort.Sort(entries)
|
||||
var keys [][]byte
|
||||
var vals [][]byte
|
||||
|
||||
proof := memorydb.New()
|
||||
for i, entry := range entries {
|
||||
trie.Prove(entry.k, 0, proof)
|
||||
if i == 50 {
|
||||
keys = append(keys, entry.k)
|
||||
vals = append(vals, entry.v)
|
||||
}
|
||||
}
|
||||
want := memorydb.New()
|
||||
trie.Prove(keys[0], 0, want)
|
||||
trie.Prove(keys[len(keys)-1], 0, want)
|
||||
|
||||
_, _, notary, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
|
||||
if used := notary.Accessed().(*memorydb.Database); used.Len() != want.Len() {
|
||||
t.Fatalf("notary proof size mismatch: have %d, want %d", used.Len(), want.Len())
|
||||
}
|
||||
}
|
||||
|
||||
@ -858,7 +903,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof)
|
||||
_, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof)
|
||||
if err != nil {
|
||||
b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
|
||||
}
|
||||
@ -889,3 +934,20 @@ func randBytes(n int) []byte {
|
||||
crand.Read(r)
|
||||
return r
|
||||
}
|
||||
|
||||
func nonRandomTrie(n int) (*Trie, map[string]*kv) {
|
||||
trie := new(Trie)
|
||||
vals := make(map[string]*kv)
|
||||
max := uint64(0xffffffffffffffff)
|
||||
for i := uint64(0); i < uint64(n); i++ {
|
||||
value := make([]byte, 32)
|
||||
key := make([]byte, 32)
|
||||
binary.LittleEndian.PutUint64(key, i)
|
||||
binary.LittleEndian.PutUint64(value, i-max)
|
||||
//value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||
elem := &kv{key, value, false}
|
||||
trie.Update(elem.k, elem.v)
|
||||
vals[string(elem.k)] = elem
|
||||
}
|
||||
return trie, vals
|
||||
}
|
||||
|
@ -125,14 +125,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
|
||||
it.Release()
|
||||
it = database.NewIterator(nil, key)
|
||||
|
||||
log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
swap = time.Now()
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
// Mark the bloom filter inited and return
|
||||
log.Info("Initialized fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
atomic.StoreUint32(&b.inited, 1)
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ func (b *SyncBloom) Close() error {
|
||||
b.pend.Wait()
|
||||
|
||||
// Wipe the bloom, but mark it "uninited" just in case someone attempts an access
|
||||
log.Info("Deallocated fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate())
|
||||
log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.errorRate())
|
||||
|
||||
atomic.StoreUint32(&b.inited, 0)
|
||||
b.bloom = nil
|
||||
|
39
trie/trie.go
39
trie/trie.go
@ -19,13 +19,13 @@ package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -159,29 +159,26 @@ func (t *Trie) TryGetNode(path []byte) ([]byte, int, error) {
|
||||
if item == nil {
|
||||
return nil, resolved, nil
|
||||
}
|
||||
enc, err := rlp.EncodeToBytes(item)
|
||||
if err != nil {
|
||||
log.Error("Encoding existing trie node failed", "err", err)
|
||||
return nil, resolved, err
|
||||
}
|
||||
return enc, resolved, err
|
||||
return item, resolved, err
|
||||
}
|
||||
|
||||
func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item node, newnode node, resolved int, err error) {
|
||||
func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) {
|
||||
// If we reached the requested path, return the current node
|
||||
if pos >= len(path) {
|
||||
// Don't return collapsed hash nodes though
|
||||
if _, ok := origNode.(hashNode); !ok {
|
||||
// Short nodes have expanded keys, compact them before returning
|
||||
item := origNode
|
||||
if sn, ok := item.(*shortNode); ok {
|
||||
item = &shortNode{
|
||||
Key: hexToCompact(sn.Key),
|
||||
Val: sn.Val,
|
||||
}
|
||||
}
|
||||
return item, origNode, 0, nil
|
||||
// Although we most probably have the original node expanded, encoding
|
||||
// that into consensus form can be nasty (needs to cascade down) and
|
||||
// time consuming. Instead, just pull the hash up from disk directly.
|
||||
var hash hashNode
|
||||
if node, ok := origNode.(hashNode); ok {
|
||||
hash = node
|
||||
} else {
|
||||
hash, _ = origNode.cache()
|
||||
}
|
||||
if hash == nil {
|
||||
return nil, origNode, 0, errors.New("non-consensus node")
|
||||
}
|
||||
blob, err := t.db.Node(common.BytesToHash(hash))
|
||||
return blob, origNode, 1, err
|
||||
}
|
||||
// Path still needs to be traversed, descend into children
|
||||
switch n := (origNode).(type) {
|
||||
@ -491,7 +488,7 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
|
||||
// Hash returns the root hash of the trie. It does not write to the
|
||||
// database and can be used even if the trie doesn't have one.
|
||||
func (t *Trie) Hash() common.Hash {
|
||||
hash, cached, _ := t.hashRoot(nil)
|
||||
hash, cached, _ := t.hashRoot()
|
||||
t.root = cached
|
||||
return common.BytesToHash(hash.(hashNode))
|
||||
}
|
||||
@ -545,7 +542,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
|
||||
}
|
||||
|
||||
// hashRoot calculates the root hash of the given trie
|
||||
func (t *Trie) hashRoot(db *Database) (node, node, error) {
|
||||
func (t *Trie) hashRoot() (node, node, error) {
|
||||
if t.root == nil {
|
||||
return hashNode(emptyRoot.Bytes()), nil, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user