From 8e80a3eba70fa4762f8d37a3c2179458f8154ed4 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 26 Jun 2023 11:41:30 -0400 Subject: [PATCH 01/32] add key file flag --- cmd/p2p/sensor/sensor.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index ea5bcefc..5b98c33d 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -1,6 +1,7 @@ package sensor import ( + "crypto/ecdsa" "errors" "fmt" "time" @@ -36,6 +37,8 @@ type ( revalidationInterval time.Duration ShouldRunPprof bool PprofPort uint + KeyFile string + privateKey *ecdsa.PrivateKey } ) @@ -69,6 +72,27 @@ var SensorCmd = &cobra.Command{ }() } + inputSensorParams.privateKey, err = crypto.GenerateKey() + if err != nil { + return err + } + + if len(inputSensorParams.KeyFile) > 0 { + var privateKey *ecdsa.PrivateKey + privateKey, err = crypto.LoadECDSA(inputSensorParams.KeyFile) + + if err != nil { + log.Warn().Err(err).Msg("Key file was not found, generating a new key file") + + err = crypto.SaveECDSA(inputSensorParams.KeyFile, inputSensorParams.privateKey) + if err != nil { + return err + } + } else { + inputSensorParams.privateKey = privateKey + } + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -78,7 +102,7 @@ var SensorCmd = &cobra.Command{ } var cfg discover.Config - cfg.PrivateKey, _ = crypto.GenerateKey() + cfg.PrivateKey = inputSensorParams.privateKey bn, err := p2p.ParseBootnodes(inputSensorParams.Bootnodes) if err != nil { return fmt.Errorf("unable to parse bootnodes: %w", err) @@ -105,7 +129,7 @@ var SensorCmd = &cobra.Command{ c := newSensor(inputSet, disc, disc.RandomNodes()) c.revalidateInterval = inputSensorParams.revalidationInterval - log.Info().Msg("Starting sensor") + log.Info().Str("enode", disc.Self().URLv4()).Msg("Starting sensor") c.run(inputSensorParams.Threads) return nil @@ -146,4 +170,5 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.RevalidationInterval, "revalidation-interval", "r", "10m", "The amount of time it takes to retry connecting to a failed peer.") SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof.") SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") } From 25fc9991f3e9eeba8ed14777d83fbf4be2c8e0da Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Tue, 27 Jun 2023 10:59:48 -0400 Subject: [PATCH 02/32] fix shadowed error --- cmd/p2p/sensor/sensor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 5b98c33d..ae81d305 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -66,8 +66,8 @@ var SensorCmd = &cobra.Command{ if inputSensorParams.ShouldRunPprof { go func() { - if err := http.ListenAndServe(fmt.Sprintf("localhost:%v", inputSensorParams.PprofPort), nil); err != nil { - log.Error().Err(err).Msg("Failed to start pprof") + if pprofErr := http.ListenAndServe(fmt.Sprintf("localhost:%v", inputSensorParams.PprofPort), nil); pprofErr != nil { + log.Error().Err(pprofErr).Msg("Failed to start pprof") } }() } From d17df57e3a3576117c570c615ad48cb1ab2bf75a Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 28 Jun 2023 09:51:51 -0400 Subject: [PATCH 03/32] fix database nil --- p2p/rlpx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/rlpx.go b/p2p/rlpx.go index fc910fdd..d49fd7d4 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -347,7 +347,7 @@ func (c *Conn) processNewPooledTransactionHashes(db database.Database, count *Me atomic.AddInt32(&count.TransactionHashes, int32(len(hashes))) c.logger.Trace().Msgf("Received %v NewPooledTransactionHashes", len(hashes)) - if !db.ShouldWriteTransactions() { + if db == nil || !db.ShouldWriteTransactions() { return nil } From bbab2976e02a72a2f661b6ce51a06daadb457e02 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 29 Jun 2023 11:34:43 -0400 Subject: [PATCH 04/32] add ip and port flags --- cmd/p2p/crawl/crawl.go | 2 +- cmd/p2p/sensor/sensor.go | 8 +++++++- p2p/p2p.go | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 71e2c393..330a2006 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -73,7 +73,7 @@ var CrawlCmd = &cobra.Command{ } ln := enode.NewLocalNode(db, cfg.PrivateKey) - socket, err := p2p.Listen(ln) + socket, err := p2p.Listen(ln, 0) if err != nil { return err } diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index ae81d305..d77eb0f3 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "errors" "fmt" + "net" "time" "net/http" @@ -39,6 +40,8 @@ type ( PprofPort uint KeyFile string privateKey *ecdsa.PrivateKey + IP net.IP + Port int } ) @@ -115,7 +118,8 @@ var SensorCmd = &cobra.Command{ } ln := enode.NewLocalNode(db, cfg.PrivateKey) - socket, err := p2p.Listen(ln) + ln.SetStaticIP(inputSensorParams.IP) + socket, err := p2p.Listen(ln, inputSensorParams.Port) if err != nil { return err } @@ -171,4 +175,6 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof.") SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") + SensorCmd.PersistentFlags().IPVarP(&inputSensorParams.IP, "ip", "i", net.IP{127, 0, 0, 1}, "The sensor's IP address.") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's discovery port.") } diff --git a/p2p/p2p.go b/p2p/p2p.go index 2b59635a..1ba7399d 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -13,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -func Listen(ln *enode.LocalNode) (*net.UDPConn, error) { - addr := "0.0.0.0:0" +func Listen(ln *enode.LocalNode, port int) (*net.UDPConn, error) { + addr := fmt.Sprintf("0.0.0.0:%v", port) socket, err := net.ListenPacket("udp4", addr) if err != nil { From c58fca964d0a81b4afa34a4c76c38b5b3ff6be7b Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Tue, 1 Aug 2023 11:34:47 -0400 Subject: [PATCH 05/32] add server --- cmd/p2p/sensor/sensor.go | 98 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index d77eb0f3..e1718c5c 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -2,15 +2,25 @@ package sensor import ( "crypto/ecdsa" + "encoding/json" "errors" "fmt" + "io/ioutil" "net" + "os" + "os/signal" + "syscall" "time" "net/http" _ "net/http/pprof" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/rs/zerolog/log" @@ -99,6 +109,60 @@ var SensorCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { + eth66 := ethp2p.Protocol{ + Name: "eth", + Version: 66, + Length: 17, + Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { + log.Info().Interface("peer", p.Info().Enode).Send() + + genesis, _ := loadGenesis("genesis.json") + genesisHash := common.HexToHash("0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") + + err := ethp2p.Send(rw, 0, ð.StatusPacket{ + ProtocolVersion: 66, + NetworkID: 137, + Genesis: genesisHash, + ForkID: forkid.NewID(genesis.Config, genesisHash, 45629536), + }) + if err != nil { + log.Error().Err(err).Send() + } + + msg, err := rw.ReadMsg() + var status eth.StatusPacket + err = msg.Decode(&status) + log.Info().Interface("status", status).Err(err).Send() + + for { + msg, err := rw.ReadMsg() + if err != nil { + return err + } + switch msg.Code { + case 2: + var txs eth.TransactionsPacket + err = msg.Decode(&txs) + log.Info().Interface("txs", txs).Err(err).Send() + case 3: + var request eth.GetBlockHeadersPacket66 + err = msg.Decode(&request) + log.Info().Interface("request", request).Err(err).Send() + case 7: + var block eth.NewBlockPacket + err = msg.Decode(&block) + log.Info().Interface("block", block.Block.Header()).Err(err).Send() + case 8: + var txs eth.NewPooledTransactionHashesPacket + err = msg.Decode(&txs) + log.Info().Interface("txs", txs).Err(err).Send() + default: + log.Info().Interface("msg", msg).Send() + } + } + }, + } + inputSet, err := p2p.LoadNodesJSON(inputSensorParams.NodesFile) if err != nil { return err @@ -112,6 +176,28 @@ var SensorCmd = &cobra.Command{ } cfg.Bootnodes = bn + server := ethp2p.Server{ + Config: ethp2p.Config{ + PrivateKey: inputSensorParams.privateKey, + NoDial: true, + NoDiscovery: true, + MaxPeers: inputSensorParams.MaxPeers, + ListenAddr: fmt.Sprintf("%v:%v", inputSensorParams.IP.String(), inputSensorParams.Port), + Protocols: []ethp2p.Protocol{eth66}, + EnableMsgEvents: true, + }, + } + if err = server.Start(); err != nil { + return err + } + + log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") + + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) + <-done + return nil + db, err := enode.OpenDB(inputSensorParams.Database) if err != nil { return err @@ -178,3 +264,15 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().IPVarP(&inputSensorParams.IP, "ip", "i", net.IP{127, 0, 0, 1}, "The sensor's IP address.") SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's discovery port.") } + +func loadGenesis(genesisFile string) (core.Genesis, error) { + chainConfig, err := ioutil.ReadFile(genesisFile) + if err != nil { + return core.Genesis{}, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return core.Genesis{}, err + } + return gen, nil +} From 8c6e6b93a5289b9bc31638ef133423cf92f8cd12 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Tue, 1 Aug 2023 14:42:27 -0400 Subject: [PATCH 06/32] fetch latest block --- cmd/p2p/sensor/sensor.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index e1718c5c..db2838c3 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -18,15 +18,19 @@ import ( "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/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/maticnetwork/polygon-cli/p2p" + "github.com/maticnetwork/polygon-cli/rpctypes" ) type ( @@ -109,6 +113,7 @@ var SensorCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { + eth66 := ethp2p.Protocol{ Name: "eth", Version: 66, @@ -119,14 +124,29 @@ var SensorCmd = &cobra.Command{ genesis, _ := loadGenesis("genesis.json") genesisHash := common.HexToHash("0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") - err := ethp2p.Send(rw, 0, ð.StatusPacket{ + client, err := rpc.Dial("https://polygon-rpc.com") + defer client.Close() + if err != nil { + return err + } + + var block rpctypes.RawBlockResponse + err = client.Call(&block, "eth_getBlockByNumber", "latest", true) + if err != nil { + return err + } + log.Info().Interface("block", block).Send() + + err = ethp2p.Send(rw, 0, ð.StatusPacket{ ProtocolVersion: 66, NetworkID: 137, Genesis: genesisHash, - ForkID: forkid.NewID(genesis.Config, genesisHash, 45629536), + ForkID: forkid.NewID(genesis.Config, genesisHash, block.Number.ToUint64()), + Head: block.Hash.ToHash(), + TD: block.TotalDifficulty.ToBigInt(), }) if err != nil { - log.Error().Err(err).Send() + return err } msg, err := rw.ReadMsg() From 84a4e21598af1138f992ff4df779afdf6b5c1609 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Tue, 1 Aug 2023 18:58:09 -0400 Subject: [PATCH 07/32] update message codes --- cmd/p2p/sensor/sensor.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index db2838c3..9f12292d 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -137,7 +137,7 @@ var SensorCmd = &cobra.Command{ } log.Info().Interface("block", block).Send() - err = ethp2p.Send(rw, 0, ð.StatusPacket{ + err = ethp2p.Send(rw, eth.StatusMsg, ð.StatusPacket{ ProtocolVersion: 66, NetworkID: 137, Genesis: genesisHash, @@ -160,19 +160,19 @@ var SensorCmd = &cobra.Command{ return err } switch msg.Code { - case 2: + case eth.TransactionsMsg: var txs eth.TransactionsPacket err = msg.Decode(&txs) log.Info().Interface("txs", txs).Err(err).Send() - case 3: + case eth.BlockHeadersMsg: var request eth.GetBlockHeadersPacket66 err = msg.Decode(&request) log.Info().Interface("request", request).Err(err).Send() - case 7: + case eth.NewBlockMsg: var block eth.NewBlockPacket err = msg.Decode(&block) log.Info().Interface("block", block.Block.Header()).Err(err).Send() - case 8: + case eth.NewPooledTransactionHashesMsg: var txs eth.NewPooledTransactionHashesPacket err = msg.Decode(&txs) log.Info().Interface("txs", txs).Err(err).Send() From e93abbad1d3ff719f99797fd102dd4f944afb719 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 2 Aug 2023 12:44:57 -0400 Subject: [PATCH 08/32] update params --- cmd/p2p/sensor/sensor.go | 92 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 9f12292d..dc905a7d 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -7,9 +7,6 @@ import ( "fmt" "io/ioutil" "net" - "os" - "os/signal" - "syscall" "time" "net/http" @@ -19,12 +16,12 @@ 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/p2p/nat" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -56,6 +53,10 @@ type ( privateKey *ecdsa.PrivateKey IP net.IP Port int + RPC string + genesis core.Genesis + GenesisFile string + GenesisHash string } ) @@ -110,10 +111,15 @@ var SensorCmd = &cobra.Command{ } } + inputSensorParams.genesis, err = loadGenesis(inputSensorParams.GenesisFile) + if err != nil { + log.Error().Err(err).Msg("Failed to load genesis file") + return err + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { - eth66 := ethp2p.Protocol{ Name: "eth", Version: 66, @@ -121,10 +127,9 @@ var SensorCmd = &cobra.Command{ Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { log.Info().Interface("peer", p.Info().Enode).Send() - genesis, _ := loadGenesis("genesis.json") - genesisHash := common.HexToHash("0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") + genesisHash := common.HexToHash(inputSensorParams.GenesisHash) - client, err := rpc.Dial("https://polygon-rpc.com") + client, err := rpc.Dial(inputSensorParams.RPC) defer client.Close() if err != nil { return err @@ -141,9 +146,13 @@ var SensorCmd = &cobra.Command{ ProtocolVersion: 66, NetworkID: 137, Genesis: genesisHash, - ForkID: forkid.NewID(genesis.Config, genesisHash, block.Number.ToUint64()), - Head: block.Hash.ToHash(), - TD: block.TotalDifficulty.ToBigInt(), + ForkID: forkid.NewID( + inputSensorParams.genesis.Config, + genesisHash, + block.Number.ToUint64(), + ), + Head: block.Hash.ToHash(), + TD: block.TotalDifficulty.ToBigInt(), }) if err != nil { return err @@ -171,7 +180,7 @@ var SensorCmd = &cobra.Command{ case eth.NewBlockMsg: var block eth.NewBlockPacket err = msg.Decode(&block) - log.Info().Interface("block", block.Block.Header()).Err(err).Send() + log.Info().Interface("block", block.Block.Number()).Err(err).Send() case eth.NewPooledTransactionHashesMsg: var txs eth.NewPooledTransactionHashesPacket err = msg.Decode(&txs) @@ -198,33 +207,19 @@ var SensorCmd = &cobra.Command{ server := ethp2p.Server{ Config: ethp2p.Config{ - PrivateKey: inputSensorParams.privateKey, - NoDial: true, - NoDiscovery: true, - MaxPeers: inputSensorParams.MaxPeers, - ListenAddr: fmt.Sprintf("%v:%v", inputSensorParams.IP.String(), inputSensorParams.Port), - Protocols: []ethp2p.Protocol{eth66}, - EnableMsgEvents: true, + PrivateKey: inputSensorParams.privateKey, + MaxPeers: inputSensorParams.MaxPeers, + ListenAddr: fmt.Sprintf("%v:%v", inputSensorParams.IP.String(), inputSensorParams.Port), + Protocols: []ethp2p.Protocol{eth66}, + NoDiscovery: true, }, } if err = server.Start(); err != nil { return err } + defer server.Stop() - log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") - - done := make(chan os.Signal, 1) - signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) - <-done - return nil - - db, err := enode.OpenDB(inputSensorParams.Database) - if err != nil { - return err - } - - ln := enode.NewLocalNode(db, cfg.PrivateKey) - ln.SetStaticIP(inputSensorParams.IP) + ln := server.LocalNode() socket, err := p2p.Listen(ln, inputSensorParams.Port) if err != nil { return err @@ -246,6 +241,18 @@ var SensorCmd = &cobra.Command{ }, } +func loadGenesis(genesisFile string) (core.Genesis, error) { + chainConfig, err := ioutil.ReadFile(genesisFile) + if err != nil { + return core.Genesis{}, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return core.Genesis{}, err + } + return gen, nil +} + func init() { SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", `Comma separated nodes used for bootstrapping. At least one bootnode is @@ -282,17 +289,8 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") SensorCmd.PersistentFlags().IPVarP(&inputSensorParams.IP, "ip", "i", net.IP{127, 0, 0, 1}, "The sensor's IP address.") - SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's discovery port.") -} - -func loadGenesis(genesisFile string) (core.Genesis, error) { - chainConfig, err := ioutil.ReadFile(genesisFile) - if err != nil { - return core.Genesis{}, err - } - var gen core.Genesis - if err := json.Unmarshal(chainConfig, &gen); err != nil { - return core.Genesis{}, err - } - return gen, nil + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's TCP and discovery port.") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "The RPC endpoint.") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisFile, "genesis", "genesis.json", "The genesis file.") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisHash, "genesis-hash", "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", "The genesis block hash.") } From b6bcdf4fdc92cc1cec12216e9ff210c0bdcf22ed Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 2 Aug 2023 15:11:57 -0400 Subject: [PATCH 09/32] add nat --- cmd/p2p/sensor/sensor.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index dc905a7d..7f307428 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -15,13 +15,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" - - // "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -36,7 +34,6 @@ type ( Threads int NetworkID uint64 NodesFile string - Database string ProjectID string SensorID string MaxPeers int @@ -140,11 +137,10 @@ var SensorCmd = &cobra.Command{ if err != nil { return err } - log.Info().Interface("block", block).Send() err = ethp2p.Send(rw, eth.StatusMsg, ð.StatusPacket{ ProtocolVersion: 66, - NetworkID: 137, + NetworkID: inputSensorParams.NetworkID, Genesis: genesisHash, ForkID: forkid.NewID( inputSensorParams.genesis.Config, @@ -161,7 +157,7 @@ var SensorCmd = &cobra.Command{ msg, err := rw.ReadMsg() var status eth.StatusPacket err = msg.Decode(&status) - log.Info().Interface("status", status).Err(err).Send() + log.Info().Interface("status", status).Err(err).Msg("New peer") for { msg, err := rw.ReadMsg() @@ -204,14 +200,20 @@ var SensorCmd = &cobra.Command{ return fmt.Errorf("unable to parse bootnodes: %w", err) } cfg.Bootnodes = bn + ip, err := nat.Parse("any") + if err != nil { + return err + } server := ethp2p.Server{ Config: ethp2p.Config{ PrivateKey: inputSensorParams.privateKey, MaxPeers: inputSensorParams.MaxPeers, - ListenAddr: fmt.Sprintf("%v:%v", inputSensorParams.IP.String(), inputSensorParams.Port), + ListenAddr: fmt.Sprintf(":%v", inputSensorParams.Port), Protocols: []ethp2p.Protocol{eth66}, + NoDial: true, NoDiscovery: true, + NAT: ip, }, } if err = server.Start(); err != nil { @@ -234,7 +236,7 @@ var SensorCmd = &cobra.Command{ c := newSensor(inputSet, disc, disc.RandomNodes()) c.revalidateInterval = inputSensorParams.revalidationInterval - log.Info().Str("enode", disc.Self().URLv4()).Msg("Starting sensor") + log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") c.run(inputSensorParams.Threads) return nil @@ -265,7 +267,6 @@ required, so other nodes in the network can discover each other.`) if err := SensorCmd.MarkPersistentFlagRequired("network-id"); err != nil { log.Error().Err(err).Msg("Failed to mark network-id as required persistent flag") } - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Database, "database", "d", "", "Node database for updating and storing client information.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.ProjectID, "project-id", "P", "", "GCP project ID.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.SensorID, "sensor-id", "s", "", "Sensor ID.") if err := SensorCmd.MarkPersistentFlagRequired("sensor-id"); err != nil { From f6a527b8b9c3ceb7b53e245e7f493ba251d34ab6 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 2 Aug 2023 15:28:12 -0400 Subject: [PATCH 10/32] remove ip flag --- cmd/p2p/sensor/sensor.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 7f307428..21ebc212 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io/ioutil" - "net" "time" "net/http" @@ -48,7 +47,6 @@ type ( PprofPort uint KeyFile string privateKey *ecdsa.PrivateKey - IP net.IP Port int RPC string genesis core.Genesis @@ -200,6 +198,7 @@ var SensorCmd = &cobra.Command{ return fmt.Errorf("unable to parse bootnodes: %w", err) } cfg.Bootnodes = bn + ip, err := nat.Parse("any") if err != nil { return err @@ -289,7 +288,6 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof.") SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") - SensorCmd.PersistentFlags().IPVarP(&inputSensorParams.IP, "ip", "i", net.IP{127, 0, 0, 1}, "The sensor's IP address.") SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's TCP and discovery port.") SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "The RPC endpoint.") SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisFile, "genesis", "genesis.json", "The genesis file.") From b503cfe37ca7ba8ee5a2b7cd21b99aadf009618d Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 2 Aug 2023 15:33:34 -0400 Subject: [PATCH 11/32] fix ci --- cmd/p2p/sensor/sensor.go | 17 +++++++++++++---- doc/polycli_p2p_sensor.md | 6 +++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 21ebc212..f5ffc6b4 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "time" "net/http" @@ -125,10 +125,10 @@ var SensorCmd = &cobra.Command{ genesisHash := common.HexToHash(inputSensorParams.GenesisHash) client, err := rpc.Dial(inputSensorParams.RPC) - defer client.Close() if err != nil { return err } + defer client.Close() var block rpctypes.RawBlockResponse err = client.Call(&block, "eth_getBlockByNumber", "latest", true) @@ -153,9 +153,17 @@ var SensorCmd = &cobra.Command{ } msg, err := rw.ReadMsg() + if err != nil { + return err + } + var status eth.StatusPacket err = msg.Decode(&status) - log.Info().Interface("status", status).Err(err).Msg("New peer") + if err != nil { + return err + } + + log.Info().Interface("status", status).Msg("New peer") for { msg, err := rw.ReadMsg() @@ -243,7 +251,8 @@ var SensorCmd = &cobra.Command{ } func loadGenesis(genesisFile string) (core.Genesis, error) { - chainConfig, err := ioutil.ReadFile(genesisFile) + chainConfig, err := os.ReadFile(genesisFile) + if err != nil { return core.Genesis{}, err } diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index fa36c620..4fdeeaed 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -25,18 +25,22 @@ If no nodes.json file exists, run `echo "{}" >> nodes.json` to get started. ```bash -b, --bootnodes string Comma separated nodes used for bootstrapping. At least one bootnode is required, so other nodes in the network can discover each other. - -d, --database string Node database for updating and storing client information. + --genesis string The genesis file. (default "genesis.json") + --genesis-hash string The genesis block hash. (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") -h, --help help for sensor + -k, --key-file string The file of the private key. If no key file is found then a key file will be generated. -D, --max-db-writes int The maximum number of concurrent database writes to perform. Increasing this will result in less chance of missing data (i.e. broken pipes) but can significantly increase memory usage. (default 100) -m, --max-peers int Maximum number of peers to connect to. (default 200) -n, --network-id uint Filter discovered nodes by this network ID. -p, --parallel int How many parallel discoveries to attempt. (default 16) + --port int The sensor's TCP and discovery port. (default 30303) --pprof Whether to run pprof. --pprof-port uint The port to run pprof on. (default 6060) -P, --project-id string GCP project ID. -r, --revalidation-interval string The amount of time it takes to retry connecting to a failed peer. (default "10m") + --rpc string The RPC endpoint. (default "https://polygon-rpc.com") -s, --sensor-id string Sensor ID. --write-block-events Whether to write block events to the database. (default true) -B, --write-blocks Whether to write blocks to the database. (default true) From 6d93205bef74af97ac283c900c4bf306ef9559c6 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 3 Aug 2023 11:16:09 -0400 Subject: [PATCH 12/32] move logic to protocol.go --- cmd/p2p/sensor/sensor.go | 96 +++--------------------------- p2p/protocol.go | 122 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 87 deletions(-) create mode 100644 p2p/protocol.go diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index f5ffc6b4..dcbc42b6 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -13,18 +13,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/protocols/eth" ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/maticnetwork/polygon-cli/p2p" - "github.com/maticnetwork/polygon-cli/rpctypes" ) type ( @@ -115,85 +111,6 @@ var SensorCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { - eth66 := ethp2p.Protocol{ - Name: "eth", - Version: 66, - Length: 17, - Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { - log.Info().Interface("peer", p.Info().Enode).Send() - - genesisHash := common.HexToHash(inputSensorParams.GenesisHash) - - client, err := rpc.Dial(inputSensorParams.RPC) - if err != nil { - return err - } - defer client.Close() - - var block rpctypes.RawBlockResponse - err = client.Call(&block, "eth_getBlockByNumber", "latest", true) - if err != nil { - return err - } - - err = ethp2p.Send(rw, eth.StatusMsg, ð.StatusPacket{ - ProtocolVersion: 66, - NetworkID: inputSensorParams.NetworkID, - Genesis: genesisHash, - ForkID: forkid.NewID( - inputSensorParams.genesis.Config, - genesisHash, - block.Number.ToUint64(), - ), - Head: block.Hash.ToHash(), - TD: block.TotalDifficulty.ToBigInt(), - }) - if err != nil { - return err - } - - msg, err := rw.ReadMsg() - if err != nil { - return err - } - - var status eth.StatusPacket - err = msg.Decode(&status) - if err != nil { - return err - } - - log.Info().Interface("status", status).Msg("New peer") - - for { - msg, err := rw.ReadMsg() - if err != nil { - return err - } - switch msg.Code { - case eth.TransactionsMsg: - var txs eth.TransactionsPacket - err = msg.Decode(&txs) - log.Info().Interface("txs", txs).Err(err).Send() - case eth.BlockHeadersMsg: - var request eth.GetBlockHeadersPacket66 - err = msg.Decode(&request) - log.Info().Interface("request", request).Err(err).Send() - case eth.NewBlockMsg: - var block eth.NewBlockPacket - err = msg.Decode(&block) - log.Info().Interface("block", block.Block.Number()).Err(err).Send() - case eth.NewPooledTransactionHashesMsg: - var txs eth.NewPooledTransactionHashesPacket - err = msg.Decode(&txs) - log.Info().Interface("txs", txs).Err(err).Send() - default: - log.Info().Interface("msg", msg).Send() - } - } - }, - } - inputSet, err := p2p.LoadNodesJSON(inputSensorParams.NodesFile) if err != nil { return err @@ -214,10 +131,15 @@ var SensorCmd = &cobra.Command{ server := ethp2p.Server{ Config: ethp2p.Config{ - PrivateKey: inputSensorParams.privateKey, - MaxPeers: inputSensorParams.MaxPeers, - ListenAddr: fmt.Sprintf(":%v", inputSensorParams.Port), - Protocols: []ethp2p.Protocol{eth66}, + PrivateKey: inputSensorParams.privateKey, + MaxPeers: inputSensorParams.MaxPeers, + ListenAddr: fmt.Sprintf(":%v", inputSensorParams.Port), + Protocols: []ethp2p.Protocol{p2p.NewEth66Protocol( + &inputSensorParams.genesis, + common.HexToHash(inputSensorParams.GenesisHash), + inputSensorParams.RPC, + inputSensorParams.NetworkID, + )}, NoDial: true, NoDiscovery: true, NAT: ip, diff --git a/p2p/protocol.go b/p2p/protocol.go new file mode 100644 index 00000000..aeb6acdd --- /dev/null +++ b/p2p/protocol.go @@ -0,0 +1,122 @@ +package p2p + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + ethp2p "github.com/ethereum/go-ethereum/p2p" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/rpc" + "github.com/rs/zerolog/log" + + "github.com/maticnetwork/polygon-cli/rpctypes" +) + +func NewEth66Protocol(genesis *core.Genesis, genesisHash common.Hash, url string, networkID uint64) ethp2p.Protocol { + return ethp2p.Protocol{ + Name: "eth", + Version: 66, + Length: 17, + Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { + log.Info().Interface("peer", p.Info().Enode).Send() + + block, err := getLatestBlock(url) + if err != nil { + log.Error().Err(err).Msg("Failed to get latest block") + return err + } + + err = statusExchange(rw, ð.StatusPacket{ + ProtocolVersion: 66, + NetworkID: networkID, + Genesis: genesisHash, + ForkID: forkid.NewID(genesis.Config, genesisHash, block.Number.ToUint64()), + Head: block.Hash.ToHash(), + TD: block.TotalDifficulty.ToBigInt(), + }) + if err != nil { + return err + } + + for { + handleMessage(rw) + } + }, + } +} + +func getLatestBlock(url string) (*rpctypes.RawBlockResponse, error) { + client, err := rpc.Dial(url) + if err != nil { + return nil, err + } + defer client.Close() + + var block rpctypes.RawBlockResponse + err = client.Call(&block, "eth_getBlockByNumber", "latest", true) + if err != nil { + return nil, err + } + + return &block, nil +} + +func statusExchange(rw ethp2p.MsgReadWriter, packet *eth.StatusPacket) error { + err := ethp2p.Send(rw, eth.StatusMsg, &packet) + if err != nil { + return err + } + + msg, err := rw.ReadMsg() + if err != nil { + return err + } + + var status eth.StatusPacket + err = msg.Decode(&status) + if err != nil { + return err + } + + if status.NetworkID != packet.NetworkID { + return errors.New("network IDs mismatch") + } + + log.Info().Interface("status", status).Msg("New peer") + + return nil +} + +func handleMessage(rw ethp2p.MsgReadWriter) error { + msg, err := rw.ReadMsg() + if err != nil { + return err + } + defer msg.Discard() + + switch msg.Code { + case eth.TransactionsMsg: + var txs eth.TransactionsPacket + err = msg.Decode(&txs) + log.Info().Interface("txs", txs).Err(err).Send() + case eth.BlockHeadersMsg: + var request eth.GetBlockHeadersPacket66 + err = msg.Decode(&request) + log.Info().Interface("request", request).Err(err).Send() + case eth.NewBlockMsg: + var block eth.NewBlockPacket + err = msg.Decode(&block) + log.Info().Interface("block", block.Block.Number()).Err(err).Send() + case eth.NewPooledTransactionHashesMsg: + var txs eth.NewPooledTransactionHashesPacket + err = msg.Decode(&txs) + log.Info().Interface("txs", txs).Err(err).Send() + default: + log.Info().Interface("msg", msg).Send() + } + + return nil +} From f2da6e51d196ddac29d8685aa514f608f2c8a539 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 3 Aug 2023 11:52:23 -0400 Subject: [PATCH 13/32] use any nat --- cmd/p2p/sensor/sensor.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index dcbc42b6..ba9a7168 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -124,11 +124,6 @@ var SensorCmd = &cobra.Command{ } cfg.Bootnodes = bn - ip, err := nat.Parse("any") - if err != nil { - return err - } - server := ethp2p.Server{ Config: ethp2p.Config{ PrivateKey: inputSensorParams.privateKey, @@ -142,7 +137,7 @@ var SensorCmd = &cobra.Command{ )}, NoDial: true, NoDiscovery: true, - NAT: ip, + NAT: nat.Any(), }, } if err = server.Start(); err != nil { From 877ef2094bde2a7916d99eb4c815720ad2f2b1ed Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 9 Aug 2023 15:10:19 -0400 Subject: [PATCH 14/32] behold! --- cmd/p2p/crawl/crawl.go | 6 +- cmd/p2p/crawl/crawl_util.go | 1 + cmd/p2p/ping/ping.go | 4 +- cmd/p2p/sensor/sensor.go | 136 ++++++++----- cmd/p2p/sensor/sensor_util.go | 254 ------------------------ go.mod | 2 +- go.sum | 1 + p2p/database/database.go | 4 +- p2p/database/datastore.go | 264 +++++++++++++++---------- p2p/log.go | 26 ++- p2p/{nodeset.go => node.go} | 51 ++++- p2p/protocol.go | 357 ++++++++++++++++++++++++++++++---- p2p/rlpx.go | 192 ++---------------- p2p/types.go | 23 +-- 14 files changed, 658 insertions(+), 663 deletions(-) delete mode 100644 cmd/p2p/sensor/sensor_util.go rename p2p/{nodeset.go => node.go} (55%) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 330a2006..0a26e49a 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -54,7 +54,7 @@ var CrawlCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { - inputSet, err := p2p.LoadNodesJSON(inputCrawlParams.NodesFile) + nodes, err := p2p.ReadNodeSet(inputCrawlParams.NodesFile) if err != nil { return err } @@ -84,13 +84,13 @@ var CrawlCmd = &cobra.Command{ } defer disc.Close() - c := newCrawler(inputSet, disc, disc.RandomNodes()) + c := newCrawler(nodes, disc, disc.RandomNodes()) c.revalidateInterval = inputCrawlParams.revalidationInterval log.Info().Msg("Starting crawl") output := c.run(inputCrawlParams.timeout, inputCrawlParams.Threads) - return p2p.WriteNodesJSON(inputCrawlParams.NodesFile, output) + return p2p.WriteNodeSet(inputCrawlParams.NodesFile, output) }, } diff --git a/cmd/p2p/crawl/crawl_util.go b/cmd/p2p/crawl/crawl_util.go index d55331a2..5793b98f 100644 --- a/cmd/p2p/crawl/crawl_util.go +++ b/cmd/p2p/crawl/crawl_util.go @@ -218,6 +218,7 @@ func (c *crawler) updateNode(n *enode.Node) int { status = nodeAdded } node.LastResponse = node.LastCheck + node.URL = n.URLv4() } // Store/update node in output set. diff --git a/cmd/p2p/ping/ping.go b/cmd/p2p/ping/ping.go index 57dde7f5..e971f30a 100644 --- a/cmd/p2p/ping/ping.go +++ b/cmd/p2p/ping/ping.go @@ -45,7 +45,7 @@ other messages the peer sends (e.g. blocks, transactions, etc.).`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { nodes := []*enode.Node{} - if inputSet, err := p2p.LoadNodesJSON(args[0]); err == nil { + if inputSet, err := p2p.ReadNodeSet(args[0]); err == nil { nodes = inputSet.Nodes() inputPingParams.Listen = false } else if node, err := p2p.ParseNode(args[0]); err == nil { @@ -98,7 +98,7 @@ other messages the peer sends (e.g. blocks, transactions, etc.).`, errStr = err.Error() } else if inputPingParams.Listen { // If the dial and peering were successful, listen to the peer for messages. - if err := conn.ReadAndServe(nil, count); err != nil { + if err := conn.ReadAndServe(count); err != nil { log.Error().Err(err).Msg("Received error") } } diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index ba9a7168..f7df27a6 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -6,6 +6,8 @@ import ( "errors" "fmt" "os" + "os/signal" + "syscall" "time" "net/http" @@ -15,18 +17,18 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" ethp2p "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/maticnetwork/polygon-cli/p2p" + "github.com/maticnetwork/polygon-cli/p2p/database" ) type ( sensorParams struct { Bootnodes string - Threads int NetworkID uint64 NodesFile string ProjectID string @@ -37,17 +39,21 @@ type ( ShouldWriteBlockEvents bool ShouldWriteTransactions bool ShouldWriteTransactionEvents bool - RevalidationInterval string - revalidationInterval time.Duration ShouldRunPprof bool PprofPort uint KeyFile string - privateKey *ecdsa.PrivateKey Port int + DiscoveryPort int RPC string - genesis core.Genesis GenesisFile string GenesisHash string + DialRatio int + NAT string + + nodes []*enode.Node + privateKey *ecdsa.PrivateKey + genesis core.Genesis + nat nat.Interface } ) @@ -59,20 +65,20 @@ var ( // sensor and transmitting blocks and transactions to a database. var SensorCmd = &cobra.Command{ Use: "sensor [nodes file]", - Short: "Start a devp2p sensor that discovers other peers and will receive blocks and transactions. ", + Short: "Start a devp2p sensor that discovers other peers and will receive blocks and transactions.", Long: "If no nodes.json file exists, run `echo \"{}\" >> nodes.json` to get started.", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) (err error) { inputSensorParams.NodesFile = args[0] - if inputSensorParams.NetworkID == 0 { - return errors.New("network ID must be greater than zero") - } - - inputSensorParams.revalidationInterval, err = time.ParseDuration(inputSensorParams.RevalidationInterval) + inputSensorParams.nodes, err = p2p.ReadStaticNodes(inputSensorParams.NodesFile) if err != nil { return err } + if inputSensorParams.NetworkID == 0 { + return errors.New("network ID must be greater than zero") + } + if inputSensorParams.ShouldRunPprof { go func() { if pprofErr := http.ListenAndServe(fmt.Sprintf("localhost:%v", inputSensorParams.PprofPort), nil); pprofErr != nil { @@ -108,62 +114,83 @@ var SensorCmd = &cobra.Command{ return err } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - inputSet, err := p2p.LoadNodesJSON(inputSensorParams.NodesFile) + inputSensorParams.nat, err = nat.Parse(inputSensorParams.NAT) if err != nil { + log.Error().Err(err).Msg("Failed to parse NAT") return err } - var cfg discover.Config - cfg.PrivateKey = inputSensorParams.privateKey - bn, err := p2p.ParseBootnodes(inputSensorParams.Bootnodes) + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + db := database.NewDatastore(cmd.Context(), database.DatastoreOptions{ + ProjectID: inputSensorParams.ProjectID, + SensorID: inputSensorParams.SensorID, + MaxConcurrentWrites: inputSensorParams.MaxConcurrentDatabaseWrites, + ShouldWriteBlocks: inputSensorParams.ShouldWriteBlocks, + ShouldWriteBlockEvents: inputSensorParams.ShouldWriteBlockEvents, + ShouldWriteTransactions: inputSensorParams.ShouldWriteTransactions, + ShouldWriteTransactionEvents: inputSensorParams.ShouldWriteTransactionEvents, + }) + + bootnodes, err := p2p.ParseBootnodes(inputSensorParams.Bootnodes) if err != nil { return fmt.Errorf("unable to parse bootnodes: %w", err) } - cfg.Bootnodes = bn + + opts := p2p.Eth66ProtocolOptions{ + Context: cmd.Context(), + Database: db, + Genesis: &inputSensorParams.genesis, + GenesisHash: common.HexToHash(inputSensorParams.GenesisHash), + RPC: inputSensorParams.RPC, + SensorID: inputSensorParams.SensorID, + NetworkID: inputSensorParams.NetworkID, + Peers: make(chan *enode.Node), + } server := ethp2p.Server{ Config: ethp2p.Config{ - PrivateKey: inputSensorParams.privateKey, - MaxPeers: inputSensorParams.MaxPeers, - ListenAddr: fmt.Sprintf(":%v", inputSensorParams.Port), - Protocols: []ethp2p.Protocol{p2p.NewEth66Protocol( - &inputSensorParams.genesis, - common.HexToHash(inputSensorParams.GenesisHash), - inputSensorParams.RPC, - inputSensorParams.NetworkID, - )}, - NoDial: true, - NoDiscovery: true, - NAT: nat.Any(), + PrivateKey: inputSensorParams.privateKey, + BootstrapNodes: bootnodes, + StaticNodes: inputSensorParams.nodes, + MaxPeers: inputSensorParams.MaxPeers, + ListenAddr: fmt.Sprintf(":%d", inputSensorParams.Port), + DiscAddr: fmt.Sprintf(":%d", inputSensorParams.DiscoveryPort), + Protocols: []ethp2p.Protocol{p2p.NewEth66Protocol(opts)}, + DialRatio: inputSensorParams.DialRatio, + NAT: inputSensorParams.nat, }, } + + log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") if err = server.Start(); err != nil { return err } defer server.Stop() - ln := server.LocalNode() - socket, err := p2p.Listen(ln, inputSensorParams.Port) - if err != nil { - return err - } + ticker := time.NewTicker(2 * time.Second) + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) - disc, err := discover.ListenV4(socket, ln, cfg) - if err != nil { - return err - } - defer disc.Close() - - c := newSensor(inputSet, disc, disc.RandomNodes()) - c.revalidateInterval = inputSensorParams.revalidationInterval + peers := make(p2p.StaticNodes) - log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") + for { + select { + case <-ticker.C: + log.Info().Interface("peers", server.PeerCount()).Send() - c.run(inputSensorParams.Threads) - return nil + p2p.WriteStaticNodes(inputSensorParams.NodesFile, peers) + case peer := <-opts.Peers: + if _, ok := peers[peer.ID()]; !ok { + peers[peer.ID()] = peer.URLv4() + } + case <-signals: + ticker.Stop() + log.Info().Msg("Stopping sever...") + return nil + } + } }, } @@ -187,7 +214,6 @@ required, so other nodes in the network can discover each other.`) if err := SensorCmd.MarkPersistentFlagRequired("bootnodes"); err != nil { log.Error().Err(err).Msg("Failed to mark bootnodes as required persistent flag") } - SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.Threads, "parallel", "p", 16, "How many parallel discoveries to attempt.") SensorCmd.PersistentFlags().Uint64VarP(&inputSensorParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network ID.") if err := SensorCmd.MarkPersistentFlagRequired("network-id"); err != nil { log.Error().Err(err).Msg("Failed to mark network-id as required persistent flag") @@ -198,7 +224,7 @@ required, so other nodes in the network can discover each other.`) log.Error().Err(err).Msg("Failed to mark sensor-id as required persistent flag") } SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxPeers, "max-peers", "m", 200, "Maximum number of peers to connect to.") - SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxConcurrentDatabaseWrites, "max-db-writes", "D", 100, + SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxConcurrentDatabaseWrites, "max-db-writes", "D", 10000, `The maximum number of concurrent database writes to perform. Increasing this will result in less chance of missing data (i.e. broken pipes) but can significantly increase memory usage.`) @@ -210,12 +236,16 @@ increase CPU and memory usage.`) SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldWriteTransactionEvents, "write-tx-events", true, `Whether to write transaction events to the database. This option could significantly increase CPU and memory usage.`) - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.RevalidationInterval, "revalidation-interval", "r", "10m", "The amount of time it takes to retry connecting to a failed peer.") SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof.") SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") - SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The sensor's TCP and discovery port.") - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "The RPC endpoint.") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The TCP network listening port.") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DiscoveryPort, "discovery-port", 30303, "The UDP P2P discovery port.") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "The RPC endpoint used to fetch the latest block.") SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisFile, "genesis", "genesis.json", "The genesis file.") SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisHash, "genesis-hash", "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", "The genesis block hash.") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DialRatio, "dial-ratio", 0, + `The ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of +connections to be dialed. Setting this to 0 defaults it to 3.`) + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.NAT, "nat", "any", "The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:).") } diff --git a/cmd/p2p/sensor/sensor_util.go b/cmd/p2p/sensor/sensor_util.go deleted file mode 100644 index 6275aecd..00000000 --- a/cmd/p2p/sensor/sensor_util.go +++ /dev/null @@ -1,254 +0,0 @@ -package sensor - -import ( - "context" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/rs/zerolog/log" - - "github.com/maticnetwork/polygon-cli/p2p" - "github.com/maticnetwork/polygon-cli/p2p/database" -) - -type sensor struct { - input p2p.NodeSet - output p2p.NodeSet - disc resolver - iters []enode.Iterator - inputIter enode.Iterator - nodeCh chan *enode.Node - db database.Database - peers map[string]struct{} - count *p2p.MessageCount - - // settings - revalidateInterval time.Duration - outputMutex sync.RWMutex - peersMutex sync.RWMutex -} - -const ( - nodeRemoved = iota - nodeSkipRecent - nodeSkipIncompat - nodeAdded - nodeUpdated -) - -type resolver interface { - RequestENR(*enode.Node) (*enode.Node, error) -} - -// newSensor creates the new sensor and establishes the connection. If you want -// to change the database, this is where you should do it. -func newSensor(input p2p.NodeSet, disc resolver, iters ...enode.Iterator) *sensor { - s := &sensor{ - input: input, - output: make(p2p.NodeSet, len(input)), - disc: disc, - iters: iters, - inputIter: enode.IterNodes(input.Nodes()), - nodeCh: make(chan *enode.Node), - db: database.NewDatastore(context.Background(), database.DatastoreOptions{ - ProjectID: inputSensorParams.ProjectID, - SensorID: inputSensorParams.SensorID, - MaxConcurrentWrites: inputSensorParams.MaxConcurrentDatabaseWrites, - ShouldWriteBlocks: inputSensorParams.ShouldWriteBlocks, - ShouldWriteBlockEvents: inputSensorParams.ShouldWriteBlockEvents, - ShouldWriteTransactions: inputSensorParams.ShouldWriteTransactions, - ShouldWriteTransactionEvents: inputSensorParams.ShouldWriteTransactionEvents, - }), - peers: make(map[string]struct{}), - count: &p2p.MessageCount{}, - } - s.iters = append(s.iters, s.inputIter) - // Copy input to output initially. Any nodes that fail validation - // will be dropped from output during the run. - for id, n := range input { - s.output[id] = n - } - return s -} - -// run will start nthreads number of goroutines for discovery and a goroutine -// for logging. -func (s *sensor) run(nthreads int) { - statusTicker := time.NewTicker(time.Second * 8) - - if nthreads < 1 { - nthreads = 1 - } - - for _, it := range s.iters { - go s.runIterator(it) - } - - var ( - added uint64 - updated uint64 - skipped uint64 - recent uint64 - removed uint64 - ) - - // This will start the goroutines responsible for discovery. - for i := 0; i < nthreads; i++ { - go func() { - for { - switch s.updateNode(<-s.nodeCh) { - case nodeSkipIncompat: - atomic.AddUint64(&skipped, 1) - case nodeSkipRecent: - atomic.AddUint64(&recent, 1) - case nodeRemoved: - atomic.AddUint64(&removed, 1) - case nodeAdded: - atomic.AddUint64(&added, 1) - default: - atomic.AddUint64(&updated, 1) - } - } - }() - } - - // Start logging message counts and peer status. - go p2p.LogMessageCount(s.count, time.NewTicker(time.Second)) - - for { - <-statusTicker.C - s.peersMutex.RLock() - log.Info(). - Uint64("added", atomic.LoadUint64(&added)). - Uint64("updated", atomic.LoadUint64(&updated)). - Uint64("removed", atomic.LoadUint64(&removed)). - Uint64("ignored(recent)", atomic.LoadUint64(&removed)). - Uint64("ignored(incompatible)", atomic.LoadUint64(&skipped)). - Int("peers", len(s.peers)). - Msg("Discovery in progress") - s.peersMutex.RUnlock() - } -} - -func (s *sensor) runIterator(it enode.Iterator) { - for it.Next() { - s.nodeCh <- it.Node() - } -} - -// peerNode peers with nodes with matching network IDs. Nodes will exchange -// hello and status messages to determine the network ID. If the network IDs -// match then a connection will be opened with the node to receive blocks and -// transactions. -func (s *sensor) peerNode(n *enode.Node) bool { - conn, err := p2p.Dial(n) - if err != nil { - log.Debug().Err(err).Msg("Dial failed") - return true - } - conn.SensorID = inputSensorParams.SensorID - - hello, status, err := conn.Peer() - if err != nil { - log.Debug().Err(err).Msg("Peer failed") - conn.Close() - return true - } - - log.Debug().Interface("hello", hello).Interface("status", status).Msg("Peering messages received") - - skip := inputSensorParams.NetworkID != status.NetworkID - if !skip { - s.peersMutex.Lock() - defer s.peersMutex.Unlock() - - // Don't open duplicate connections with peers. The enode is used here - // rather than the enr because the enr can change. Ensure the maximum - // number of peers is not exceeded to prevent memory overuse. - peer := n.URLv4() - if _, ok := s.peers[peer]; !ok && len(s.peers) < inputSensorParams.MaxPeers { - s.peers[peer] = struct{}{} - - go func() { - defer conn.Close() - if err := conn.ReadAndServe(s.db, s.count); err != nil { - log.Debug().Err(err).Msg("Received error") - } - - s.peersMutex.Lock() - defer s.peersMutex.Unlock() - delete(s.peers, peer) - }() - } - } - - return skip -} - -// updateNode updates the info about the given node, and returns a status about -// what changed. If the node is compatible, then it will peer with the node and -// start receiving block and transaction data. -func (s *sensor) updateNode(n *enode.Node) int { - s.outputMutex.RLock() - node, ok := s.output[n.ID()] - s.outputMutex.RUnlock() - - // Skip validation of recently-seen nodes. - if ok && time.Since(node.LastCheck) < s.revalidateInterval { - log.Debug().Str("node", n.String()).Msg("Skipping node") - return nodeSkipRecent - } - - // Skip the node if unable to peer. - if s.peerNode(n) { - return nodeSkipIncompat - } - - // Request the node record. - status := nodeUpdated - node.LastCheck = truncNow() - - if nn, err := s.disc.RequestENR(n); err != nil { - if node.Score == 0 { - // Node doesn't implement EIP-868. - log.Debug().Str("node", n.String()).Msg("Skipping node") - return nodeSkipIncompat - } - node.Score /= 2 - } else { - node.N = nn - node.Seq = nn.Seq() - node.Score++ - if node.FirstResponse.IsZero() { - node.FirstResponse = node.LastCheck - status = nodeAdded - } - node.LastResponse = node.LastCheck - } - - // Store/update node in output set. - s.outputMutex.Lock() - defer s.outputMutex.Unlock() - - if node.Score <= 0 { - log.Debug().Str("node", n.String()).Msg("Removing node") - delete(s.output, n.ID()) - return nodeRemoved - } - - log.Debug().Str("node", n.String()).Uint64("seq", n.Seq()).Int("score", node.Score).Msg("Updating node") - s.output[n.ID()] = node - - // Update the nodes file at the end of each iteration. - if err := p2p.WriteNodesJSON(inputSensorParams.NodesFile, s.output); err != nil { - log.Error().Err(err).Msg("Failed to write nodes json") - } - - return status -} - -func truncNow() time.Time { - return time.Now().UTC().Truncate(1 * time.Second) -} diff --git a/go.mod b/go.mod index f2cbae0a..255f9b40 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/google/gofuzz v1.2.0 - github.com/schollz/progressbar/v3 v3.13.1 github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/schollz/progressbar/v3 v3.13.1 github.com/xeipuuv/gojsonschema v1.2.0 ) diff --git a/go.sum b/go.sum index 4d5a01da..9c1c9764 100644 --- a/go.sum +++ b/go.sum @@ -555,6 +555,7 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= diff --git a/p2p/database/database.go b/p2p/database/database.go index 0eb781d6..de461eaa 100644 --- a/p2p/database/database.go +++ b/p2p/database/database.go @@ -35,7 +35,9 @@ type Database interface { // ShouldWriteTransactionEvents return true, respectively. WriteTransactions(context.Context, *enode.Node, []*types.Transaction) - HasParentBlock(context.Context, common.Hash) bool + // HasBlock will return whether the block is in the database. If the database + // client has not been initialized this will always return true. + HasBlock(context.Context, common.Hash) bool MaxConcurrentWrites() int ShouldWriteBlocks() bool diff --git a/p2p/database/datastore.go b/p2p/database/datastore.go index 1ca1645e..90a48fca 100644 --- a/p2p/database/datastore.go +++ b/p2p/database/datastore.go @@ -32,6 +32,8 @@ type Datastore struct { shouldWriteBlockEvents bool shouldWriteTransactions bool shouldWriteTransactionEvents bool + writes chan struct{} + oldestBlock *types.Header } // DatastoreEvent can represent a peer sending the sensor a transaction hash or @@ -109,7 +111,6 @@ func NewDatastore(ctx context.Context, opts DatastoreOptions) Database { client, err := datastore.NewClient(ctx, opts.ProjectID) if err != nil { log.Error().Err(err).Msg("Could not connect to Datastore") - return nil } return &Datastore{ @@ -120,70 +121,30 @@ func NewDatastore(ctx context.Context, opts DatastoreOptions) Database { shouldWriteBlockEvents: opts.ShouldWriteBlockEvents, shouldWriteTransactions: opts.ShouldWriteTransactions, shouldWriteTransactionEvents: opts.ShouldWriteTransactionEvents, + writes: make(chan struct{}, opts.MaxConcurrentWrites), } } // WriteBlock writes the block and the block event to datastore. func (d *Datastore) WriteBlock(ctx context.Context, peer *enode.Node, block *types.Block, td *big.Int) { - if d.ShouldWriteBlockEvents() { - d.writeEvent(peer, blockEventsKind, block.Hash(), blocksKind) - } - - if !d.ShouldWriteBlocks() { + if d.client == nil { return } - key := datastore.NameKey(blocksKind, block.Hash().Hex(), nil) - - _, err := d.client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { - var dsBlock DatastoreBlock - // Fetch the block. We don't check the error because if some of the fields - // are nil we will just set them. - _ = tx.Get(key, &dsBlock) - - shouldWrite := false - - if dsBlock.DatastoreHeader == nil { - shouldWrite = true - dsBlock.DatastoreHeader = newDatastoreHeader(block.Header()) - } - - if len(dsBlock.TotalDifficulty) == 0 { - shouldWrite = true - dsBlock.TotalDifficulty = td.String() - } - - if dsBlock.Transactions == nil && len(block.Transactions()) > 0 { - shouldWrite = true - if d.shouldWriteTransactions { - d.writeTransactions(ctx, block.Transactions()) - } - - dsBlock.Transactions = make([]*datastore.Key, 0, len(block.Transactions())) - for _, tx := range block.Transactions() { - dsBlock.Transactions = append(dsBlock.Transactions, datastore.NameKey(transactionsKind, tx.Hash().Hex(), nil)) - } - } - - if dsBlock.Uncles == nil && len(block.Uncles()) > 0 { - shouldWrite = true - dsBlock.Uncles = make([]*datastore.Key, 0, len(block.Uncles())) - for _, uncle := range block.Uncles() { - d.writeBlockHeader(ctx, uncle) - dsBlock.Uncles = append(dsBlock.Uncles, datastore.NameKey(blocksKind, uncle.Hash().Hex(), nil)) - } - } - - if shouldWrite { - _, err := tx.Put(key, &dsBlock) - return err - } - - return nil - }) + if d.ShouldWriteBlockEvents() { + d.writes <- struct{}{} + go func() { + d.writeEvent(peer, blockEventsKind, block.Hash(), blocksKind) + <-d.writes + }() + } - if err != nil { - log.Error().Err(err).Msg("Failed to write new block") + if d.ShouldWriteBlocks() { + d.writes <- struct{}{} + go func() { + d.writeBlock(ctx, block, td) + <-d.writes + }() } } @@ -192,12 +153,16 @@ func (d *Datastore) WriteBlock(ctx context.Context, peer *enode.Node, block *typ // requested. The block events will be written when the hash is received // instead. func (d *Datastore) WriteBlockHeaders(ctx context.Context, headers []*types.Header) { - if !d.ShouldWriteBlocks() { + if d.client == nil || !d.ShouldWriteBlocks() { return } - for _, header := range headers { - d.writeBlockHeader(ctx, header) + for _, h := range headers { + d.writes <- struct{}{} + go func(header *types.Header) { + d.writeBlockHeader(ctx, header) + <-d.writes + }(h) } } @@ -207,65 +172,42 @@ func (d *Datastore) WriteBlockHeaders(ctx context.Context, headers []*types.Head // instead. It will write the uncles and transactions to datastore if they // don't already exist. func (d *Datastore) WriteBlockBody(ctx context.Context, body *eth.BlockBody, hash common.Hash) { - if !d.ShouldWriteBlocks() { + if d.client == nil || !d.ShouldWriteBlocks() { return } - key := datastore.NameKey(blocksKind, hash.Hex(), nil) - - _, err := d.client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { - var block DatastoreBlock - if err := tx.Get(key, &block); err != nil { - log.Debug().Err(err).Str("hash", hash.Hex()).Msg("Failed to fetch block when writing block body") - } - - shouldWrite := false - - if block.Transactions == nil && len(body.Transactions) > 0 { - shouldWrite = true - if d.shouldWriteTransactions { - d.writeTransactions(ctx, body.Transactions) - } - - block.Transactions = make([]*datastore.Key, 0, len(body.Transactions)) - for _, tx := range body.Transactions { - block.Transactions = append(block.Transactions, datastore.NameKey(transactionsKind, tx.Hash().Hex(), nil)) - } - } - - if block.Uncles == nil && len(body.Uncles) > 0 { - shouldWrite = true - block.Uncles = make([]*datastore.Key, 0, len(body.Uncles)) - for _, uncle := range body.Uncles { - d.writeBlockHeader(ctx, uncle) - block.Uncles = append(block.Uncles, datastore.NameKey(blocksKind, uncle.Hash().Hex(), nil)) - } - } - - if shouldWrite { - _, err := tx.Put(key, &block) - return err - } - - return nil - }) - - if err != nil { - log.Error().Err(err).Msg("Failed to write block body") - } + d.writes <- struct{}{} + go func() { + d.writeBlockBody(ctx, body, hash) + <-d.writes + }() } // WriteBlockHashes will write the block events to datastore. func (d *Datastore) WriteBlockHashes(ctx context.Context, peer *enode.Node, hashes []common.Hash) { - if d.ShouldWriteBlockEvents() { - d.writeEvents(ctx, peer, blockEventsKind, hashes, blocksKind) + if d.client == nil || !d.ShouldWriteBlockEvents() || len(hashes) == 0 { + return } + + d.writes <- struct{}{} + go func() { + d.writeEvents(ctx, peer, blockEventsKind, hashes, blocksKind) + <-d.writes + }() } // WriteTransactions will write the transactions and transaction events to datastore. func (d *Datastore) WriteTransactions(ctx context.Context, peer *enode.Node, txs []*types.Transaction) { + if d.client == nil { + return + } + if d.ShouldWriteTransactions() { - d.writeTransactions(ctx, txs) + d.writes <- struct{}{} + go func() { + d.writeTransactions(ctx, txs) + <-d.writes + }() } if d.ShouldWriteTransactionEvents() { @@ -274,7 +216,11 @@ func (d *Datastore) WriteTransactions(ctx context.Context, peer *enode.Node, txs hashes = append(hashes, tx.Hash()) } - d.writeEvents(ctx, peer, transactionEventsKind, hashes, transactionsKind) + d.writes <- struct{}{} + go func() { + d.writeEvents(ctx, peer, transactionEventsKind, hashes, transactionsKind) + <-d.writes + }() } } @@ -298,7 +244,11 @@ func (d *Datastore) ShouldWriteTransactionEvents() bool { return d.shouldWriteTransactionEvents } -func (d *Datastore) HasParentBlock(ctx context.Context, hash common.Hash) bool { +func (d *Datastore) HasBlock(ctx context.Context, hash common.Hash) bool { + if d.client == nil { + return true + } + key := datastore.NameKey(blocksKind, hash.Hex(), nil) var block DatastoreBlock err := d.client.Get(ctx, key, &block) @@ -362,6 +312,61 @@ func newDatastoreTransaction(tx *types.Transaction) *DatastoreTransaction { } } +func (d *Datastore) writeBlock(ctx context.Context, block *types.Block, td *big.Int) { + key := datastore.NameKey(blocksKind, block.Hash().Hex(), nil) + + _, err := d.client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var dsBlock DatastoreBlock + // Fetch the block. We don't check the error because if some of the fields + // are nil we will just set them. + _ = tx.Get(key, &dsBlock) + + shouldWrite := false + + if dsBlock.DatastoreHeader == nil { + shouldWrite = true + dsBlock.DatastoreHeader = newDatastoreHeader(block.Header()) + } + + if len(dsBlock.TotalDifficulty) == 0 { + shouldWrite = true + dsBlock.TotalDifficulty = td.String() + } + + if dsBlock.Transactions == nil && len(block.Transactions()) > 0 { + shouldWrite = true + if d.shouldWriteTransactions { + d.writeTransactions(ctx, block.Transactions()) + } + + dsBlock.Transactions = make([]*datastore.Key, 0, len(block.Transactions())) + for _, tx := range block.Transactions() { + dsBlock.Transactions = append(dsBlock.Transactions, datastore.NameKey(transactionsKind, tx.Hash().Hex(), nil)) + } + } + + if dsBlock.Uncles == nil && len(block.Uncles()) > 0 { + shouldWrite = true + dsBlock.Uncles = make([]*datastore.Key, 0, len(block.Uncles())) + for _, uncle := range block.Uncles() { + d.writeBlockHeader(ctx, uncle) + dsBlock.Uncles = append(dsBlock.Uncles, datastore.NameKey(blocksKind, uncle.Hash().Hex(), nil)) + } + } + + if shouldWrite { + _, err := tx.Put(key, &dsBlock) + return err + } + + return nil + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to write new block") + } +} + // writeEvent writes either a block or transaction event to datastore depending // on the provided eventKind and hashKind. func (d *Datastore) writeEvent(peer *enode.Node, eventKind string, hash common.Hash, hashKind string) { @@ -423,6 +428,51 @@ func (d *Datastore) writeBlockHeader(ctx context.Context, header *types.Header) } } +func (d *Datastore) writeBlockBody(ctx context.Context, body *eth.BlockBody, hash common.Hash) { + key := datastore.NameKey(blocksKind, hash.Hex(), nil) + + _, err := d.client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var block DatastoreBlock + if err := tx.Get(key, &block); err != nil { + log.Debug().Err(err).Str("hash", hash.Hex()).Msg("Failed to fetch block when writing block body") + } + + shouldWrite := false + + if block.Transactions == nil && len(body.Transactions) > 0 { + shouldWrite = true + if d.shouldWriteTransactions { + d.writeTransactions(ctx, body.Transactions) + } + + block.Transactions = make([]*datastore.Key, 0, len(body.Transactions)) + for _, tx := range body.Transactions { + block.Transactions = append(block.Transactions, datastore.NameKey(transactionsKind, tx.Hash().Hex(), nil)) + } + } + + if block.Uncles == nil && len(body.Uncles) > 0 { + shouldWrite = true + block.Uncles = make([]*datastore.Key, 0, len(body.Uncles)) + for _, uncle := range body.Uncles { + d.writeBlockHeader(ctx, uncle) + block.Uncles = append(block.Uncles, datastore.NameKey(blocksKind, uncle.Hash().Hex(), nil)) + } + } + + if shouldWrite { + _, err := tx.Put(key, &block) + return err + } + + return nil + }) + + if err != nil { + log.Error().Err(err).Msg("Failed to write block body") + } +} + // writeTransactions will write the transactions to datastore and return the // transaction hashes. func (d *Datastore) writeTransactions(ctx context.Context, txs []*types.Transaction) { diff --git a/p2p/log.go b/p2p/log.go index 94fb2e24..75f50b2a 100644 --- a/p2p/log.go +++ b/p2p/log.go @@ -50,10 +50,19 @@ func LogMessageCount(count *MessageCount, ticker *time.Ticker) { Disconnects: atomic.LoadInt32(&count.Disconnects), } - if c.BlockHeaders+c.BlockBodies+c.Blocks+c.BlockHashes+ - c.BlockHeaderRequests+c.BlockBodiesRequests+c.Transactions+ - c.TransactionHashes+c.TransactionRequests+c.Pings+c.Errors+ - c.Disconnects == 0 { + if sum( + c.BlockHeaders, + c.BlockBodies, + c.BlockHashes, + c.BlockHeaderRequests, + c.BlockBodiesRequests, + c.Transactions, + c.TransactionHashes, + c.TransactionRequests, + c.Pings, + c.Errors, + c.Disconnects, + ) == 0 { continue } @@ -73,3 +82,12 @@ func LogMessageCount(count *MessageCount, ticker *time.Ticker) { atomic.StoreInt32(&count.Disconnects, 0) } } + +func sum(ints ...int32) int32 { + var sum int32 = 0 + for _, i := range ints { + sum += i + } + + return sum +} diff --git a/p2p/nodeset.go b/p2p/node.go similarity index 55% rename from p2p/nodeset.go rename to p2p/node.go index c5a1989f..d2de3c85 100644 --- a/p2p/nodeset.go +++ b/p2p/node.go @@ -3,12 +3,14 @@ package p2p import ( "bytes" "encoding/json" + "fmt" "os" "sort" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/rs/zerolog/log" ) const jsonIndent = " " @@ -16,10 +18,12 @@ const jsonIndent = " " // NodeSet is the nodes.json file format. It holds a set of node records // as a JSON object. type NodeSet map[enode.ID]NodeJSON +type StaticNodes map[enode.ID]string type NodeJSON struct { Seq uint64 `json:"seq"` N *enode.Node `json:"record"` + URL string `json:"url"` // The score tracks how many liveness checks were performed. It is incremented by one // every time the node passes a check, and halved every time it doesn't. @@ -31,7 +35,7 @@ type NodeJSON struct { LastCheck time.Time `json:"lastCheck,omitempty"` } -func LoadNodesJSON(file string) (NodeSet, error) { +func ReadNodeSet(file string) (NodeSet, error) { var nodes NodeSet if err := common.LoadJSON(file, &nodes); err != nil { return nil, err @@ -39,7 +43,7 @@ func LoadNodesJSON(file string) (NodeSet, error) { return nodes, nil } -func WriteNodesJSON(file string, nodes NodeSet) error { +func WriteNodeSet(file string, nodes NodeSet) error { nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent) if err != nil { return err @@ -63,3 +67,46 @@ func (ns NodeSet) Nodes() []*enode.Node { }) return result } + +// ReadStaticNodes parses a list of discovery node URLs loaded from a JSON file +// from within the data directory. +func ReadStaticNodes(file string) ([]*enode.Node, error) { + // Load the nodes from the config file. + var nodelist []string + if err := common.LoadJSON(file, &nodelist); err != nil { + return nil, fmt.Errorf("failed to load node list file: %w", err) + } + + // Interpret the list as a discovery node array + var nodes []*enode.Node + for _, url := range nodelist { + if url == "" { + continue + } + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + log.Warn().Err(err).Str("url", url).Msg("Failed to parse enode") + continue + } + nodes = append(nodes, node) + } + + return nodes, nil +} + +func WriteStaticNodes(file string, nodes StaticNodes) error { + urls := make([]string, 0, len(nodes)) + for _, url := range nodes { + urls = append(urls, url) + } + + bytes, err := json.MarshalIndent(urls, "", jsonIndent) + if err != nil { + return err + } + if file == "-" { + _, err = os.Stdout.Write(bytes) + return err + } + return os.WriteFile(file, bytes, 0644) +} diff --git a/p2p/protocol.go b/p2p/protocol.go index aeb6acdd..8380d76e 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -1,48 +1,132 @@ package p2p import ( + "container/list" + "context" "errors" + "math/big" "github.com/ethereum/go-ethereum/common" ethp2p "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "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/protocols/eth" "github.com/ethereum/go-ethereum/rpc" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/maticnetwork/polygon-cli/p2p/database" "github.com/maticnetwork/polygon-cli/rpctypes" ) -func NewEth66Protocol(genesis *core.Genesis, genesisHash common.Hash, url string, networkID uint64) ethp2p.Protocol { +// conn represents an individual connection with a peer. +type conn struct { + sensorID string + node *enode.Node + logger zerolog.Logger + rw ethp2p.MsgReadWriter + db database.Database + + // requests is used to store the request ID and the block hash. This is used + // when fetching block bodies because the eth protocol block bodies do not + // contain information about the block hash. + requests *list.List + requestNum uint64 + + // oldestBlock stores the first block the sensor has seen so when fetching + // parent blocks, it does not request blocks older than this. + oldestBlock *types.Header +} + +type Eth66ProtocolOptions struct { + Context context.Context + Database database.Database + Genesis *core.Genesis + GenesisHash common.Hash + RPC string + SensorID string + NetworkID uint64 + Peers chan *enode.Node +} + +func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { return ethp2p.Protocol{ Name: "eth", Version: 66, Length: 17, Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { - log.Info().Interface("peer", p.Info().Enode).Send() - - block, err := getLatestBlock(url) + block, err := getLatestBlock(opts.RPC) if err != nil { log.Error().Err(err).Msg("Failed to get latest block") return err } - err = statusExchange(rw, ð.StatusPacket{ + c := conn{ + sensorID: opts.SensorID, + node: p.Node(), + logger: log.With().Str("peer", p.Node().URLv4()).Logger(), + rw: rw, + db: opts.Database, + requests: list.New(), + requestNum: 0, + } + + status := eth.StatusPacket{ ProtocolVersion: 66, - NetworkID: networkID, - Genesis: genesisHash, - ForkID: forkid.NewID(genesis.Config, genesisHash, block.Number.ToUint64()), + NetworkID: opts.NetworkID, + Genesis: opts.GenesisHash, + ForkID: forkid.NewID(opts.Genesis.Config, opts.GenesisHash, block.Number.ToUint64()), Head: block.Hash.ToHash(), TD: block.TotalDifficulty.ToBigInt(), - }) - if err != nil { + } + if err = c.statusExchange(&status); err != nil { return err } + opts.Peers <- p.Node() + ctx := opts.Context + for { - handleMessage(rw) + msg, err := rw.ReadMsg() + if err != nil { + return err + } + + switch msg.Code { + case eth.NewBlockHashesMsg: + err = c.handleNewBlockHashes(ctx, msg) + case eth.TransactionsMsg: + err = c.handleTransactions(ctx, msg) + case eth.GetBlockHeadersMsg: + err = c.handleGetBlockHeaders(msg) + case eth.BlockHeadersMsg: + err = c.handleBlockHeaders(ctx, msg) + case eth.GetBlockBodiesMsg: + err = c.handleGetBlockBodies(msg) + case eth.BlockBodiesMsg: + err = c.handleBlockBodies(ctx, msg) + case eth.NewBlockMsg: + err = c.handleNewBlock(ctx, msg) + case eth.NewPooledTransactionHashesMsg: + err = c.handleNewPooledTransactionHashes(ctx, msg) + case eth.GetPooledTransactionsMsg: + err = c.handleGetPooledTransactions(msg) + case eth.PooledTransactionsMsg: + err = c.handlePooledTransactions(ctx, msg) + case eth.GetReceiptsMsg: + err = c.handleGetReceipts(msg) + default: + log.Trace().Interface("msg", msg).Send() + } + + msg.Discard() + if err != nil { + c.logger.Error().Err(err).Send() + return err + } } }, } @@ -64,13 +148,13 @@ func getLatestBlock(url string) (*rpctypes.RawBlockResponse, error) { return &block, nil } -func statusExchange(rw ethp2p.MsgReadWriter, packet *eth.StatusPacket) error { - err := ethp2p.Send(rw, eth.StatusMsg, &packet) +func (c *conn) statusExchange(packet *eth.StatusPacket) error { + err := ethp2p.Send(c.rw, eth.StatusMsg, &packet) if err != nil { return err } - msg, err := rw.ReadMsg() + msg, err := c.rw.ReadMsg() if err != nil { return err } @@ -85,38 +169,233 @@ func statusExchange(rw ethp2p.MsgReadWriter, packet *eth.StatusPacket) error { return errors.New("network IDs mismatch") } - log.Info().Interface("status", status).Msg("New peer") + c.logger.Info().Interface("status", status).Msg("New peer") return nil } -func handleMessage(rw ethp2p.MsgReadWriter) error { - msg, err := rw.ReadMsg() - if err != nil { +// getBlockData will send a GetBlockHeaders and GetBlockBodies request to the +// peer. It will return an error if the sending either of the requests failed. +func (c *conn) getBlockData(hash common.Hash) error { + headersRequest := &GetBlockHeaders{ + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + // Providing both the hash and number will result in a `both origin + // hash and number` error. + Origin: eth.HashOrNumber{Hash: hash}, + Amount: 1, + }, + } + + if err := ethp2p.Send(c.rw, eth.GetBlockHeadersMsg, headersRequest); err != nil { + return err + } + + c.requestNum++ + c.requests.PushBack(request{ + requestID: c.requestNum, + hash: hash, + }) + bodiesRequest := &GetBlockBodies{ + RequestId: c.requestNum, + GetBlockBodiesPacket: []common.Hash{hash}, + } + + return ethp2p.Send(c.rw, eth.GetBlockBodiesMsg, bodiesRequest) +} + +// getParentBlock will send a request to the peer if the parent of the header +// does not exist in the database. +func (c *conn) getParentBlock(ctx context.Context, header *types.Header) error { + if !c.db.ShouldWriteBlocks() || !c.db.ShouldWriteBlockEvents() { + return nil + } + + if c.oldestBlock == nil { + c.logger.Info().Interface("block", header).Msg("Setting oldest block") + c.oldestBlock = header + return nil + } + + if c.db.HasBlock(ctx, header.ParentHash) || header.Number.Cmp(c.oldestBlock.Number) != 1 { + return nil + } + + c.logger.Info(). + Str("hash", header.ParentHash.Hex()). + Str("number", new(big.Int).Sub(header.Number, big.NewInt(1)).String()). + Msg("Fetching missing parent block") + + return c.getBlockData(header.ParentHash) +} + +func (c *conn) handleNewBlockHashes(ctx context.Context, msg ethp2p.Msg) error { + var packet eth.NewBlockHashesPacket + if err := msg.Decode(&packet); err != nil { return err } - defer msg.Discard() - - switch msg.Code { - case eth.TransactionsMsg: - var txs eth.TransactionsPacket - err = msg.Decode(&txs) - log.Info().Interface("txs", txs).Err(err).Send() - case eth.BlockHeadersMsg: - var request eth.GetBlockHeadersPacket66 - err = msg.Decode(&request) - log.Info().Interface("request", request).Err(err).Send() - case eth.NewBlockMsg: - var block eth.NewBlockPacket - err = msg.Decode(&block) - log.Info().Interface("block", block.Block.Number()).Err(err).Send() - case eth.NewPooledTransactionHashesMsg: - var txs eth.NewPooledTransactionHashesPacket - err = msg.Decode(&txs) - log.Info().Interface("txs", txs).Err(err).Send() - default: - log.Info().Interface("msg", msg).Send() + + hashes := make([]common.Hash, 0, len(packet)) + for _, hash := range packet { + hashes = append(hashes, hash.Hash) + if err := c.getBlockData(hash.Hash); err != nil { + return err + } } + c.db.WriteBlockHashes(ctx, c.node, hashes) + return nil } + +func (c *conn) handleTransactions(ctx context.Context, msg ethp2p.Msg) error { + var txs eth.TransactionsPacket + if err := msg.Decode(&txs); err != nil { + return err + } + + c.db.WriteTransactions(ctx, c.node, txs) + + return nil +} + +func (c *conn) handleGetBlockHeaders(msg ethp2p.Msg) error { + var request eth.GetBlockHeadersPacket66 + if err := msg.Decode(&request); err != nil { + return err + } + return ethp2p.Send( + c.rw, + eth.GetBlockHeadersMsg, + ð.BlockHeadersPacket66{RequestId: request.RequestId}, + ) +} + +func (c *conn) handleBlockHeaders(ctx context.Context, msg ethp2p.Msg) error { + var packet eth.BlockHeadersPacket66 + if err := msg.Decode(&packet); err != nil { + return err + } + + headers := packet.BlockHeadersPacket + for _, header := range headers { + if err := c.getParentBlock(ctx, header); err != nil { + return err + } + } + + c.db.WriteBlockHeaders(ctx, headers) + + return nil +} + +func (c *conn) handleGetBlockBodies(msg ethp2p.Msg) error { + var request eth.GetBlockBodiesPacket66 + if err := msg.Decode(&request); err != nil { + return err + } + return ethp2p.Send( + c.rw, + eth.GetBlockHeadersMsg, + ð.BlockBodiesPacket66{RequestId: request.RequestId}, + ) +} + +func (c *conn) handleBlockBodies(ctx context.Context, msg ethp2p.Msg) error { + var packet eth.BlockBodiesPacket66 + if err := msg.Decode(&packet); err != nil { + return err + } + + if len(packet.BlockBodiesPacket) == 0 { + return nil + } + + var hash *common.Hash + for e := c.requests.Front(); e != nil; e = e.Next() { + r, ok := e.Value.(request) + if !ok { + log.Error().Msg("Request type assertion failed") + continue + } + + if r.requestID == packet.RequestId { + hash = &r.hash + c.requests.Remove(e) + break + } + } + + if hash == nil { + c.logger.Warn().Msg("No block hash found for block body") + return nil + } + + c.db.WriteBlockBody(ctx, packet.BlockBodiesPacket[0], *hash) + + return nil +} + +func (c *conn) handleNewBlock(ctx context.Context, msg ethp2p.Msg) error { + var block eth.NewBlockPacket + if err := msg.Decode(&block); err != nil { + return err + } + + if err := c.getParentBlock(ctx, block.Block.Header()); err != nil { + return err + } + + c.db.WriteBlock(ctx, c.node, block.Block, block.TD) + + return nil +} + +func (c *conn) handleGetPooledTransactions(msg ethp2p.Msg) error { + var request eth.GetPooledTransactionsPacket66 + if err := msg.Decode(&request); err != nil { + return err + } + return ethp2p.Send(c.rw, eth.GetPooledTransactionsMsg, ð.PooledTransactionsPacket66{ + RequestId: request.RequestId, + }) +} + +func (c *conn) handleNewPooledTransactionHashes(ctx context.Context, msg ethp2p.Msg) error { + var txs eth.NewPooledTransactionHashesPacket + if err := msg.Decode(&txs); err != nil { + return err + } + + if !c.db.ShouldWriteTransactions() || !c.db.ShouldWriteTransactionEvents() { + return nil + } + + var hashes []common.Hash = txs + + return ethp2p.Send( + c.rw, + eth.GetPooledTransactionsMsg, + ð.GetPooledTransactionsPacket66{GetPooledTransactionsPacket: hashes}, + ) +} + +func (c *conn) handlePooledTransactions(ctx context.Context, msg ethp2p.Msg) error { + var packet eth.PooledTransactionsPacket66 + if err := msg.Decode(&packet); err != nil { + return err + } + + c.db.WriteTransactions(ctx, c.node, packet.PooledTransactionsPacket) + + return nil +} + +func (c *conn) handleGetReceipts(msg ethp2p.Msg) error { + var request eth.GetReceiptsPacket66 + if err := msg.Decode(&request); err != nil { + return err + } + return ethp2p.Send(c.rw, eth.GetBlockHeadersMsg, ð.ReceiptsPacket66{ + RequestId: request.RequestId, + }) +} diff --git a/p2p/rlpx.go b/p2p/rlpx.go index d49fd7d4..6f930d44 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -1,10 +1,7 @@ package p2p import ( - "container/list" - "context" "fmt" - "math/big" "math/rand" "net" "strings" @@ -12,13 +9,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" - "github.com/maticnetwork/polygon-cli/p2p/database" "github.com/rs/zerolog/log" ) @@ -28,18 +22,17 @@ var ( // Dial attempts to Dial the given node and perform a handshake, // returning the created Conn if successful. -func Dial(n *enode.Node) (*Conn, error) { +func Dial(n *enode.Node) (*rlpxConn, error) { fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP())) if err != nil { return nil, err } - conn := Conn{ - Conn: rlpx.NewConn(fd, n.Pubkey()), - node: n, - logger: log.With().Str("peer", n.URLv4()).Logger(), - requests: list.New(), - requestNum: 0, + conn := rlpxConn{ + Conn: rlpx.NewConn(fd, n.Pubkey()), + caps: []p2p.Cap{{Name: "eth", Version: 66}}, + node: n, + logger: log.With().Str("peer", n.URLv4()).Logger(), } if conn.ourKey, err = crypto.GenerateKey(); err != nil { @@ -55,16 +48,12 @@ func Dial(n *enode.Node) (*Conn, error) { return nil, err } - conn.caps = []p2p.Cap{ - {Name: "eth", Version: 66}, - } - return &conn, nil } // Peer performs both the protocol handshake and the status message // exchange with the node in order to Peer with it. -func (c *Conn) Peer() (*Hello, *Status, error) { +func (c *rlpxConn) Peer() (*Hello, *Status, error) { hello, err := c.handshake() if err != nil { return nil, nil, fmt.Errorf("handshake failed: %v", err) @@ -77,7 +66,7 @@ func (c *Conn) Peer() (*Hello, *Status, error) { } // handshake performs a protocol handshake with the node. -func (c *Conn) handshake() (*Hello, error) { +func (c *rlpxConn) handshake() (*Hello, error) { defer func() { _ = c.SetDeadline(time.Time{}) }() if err := c.SetDeadline(time.Now().Add(10 * time.Second)); err != nil { return nil, err @@ -111,7 +100,7 @@ func (c *Conn) handshake() (*Hello, error) { } // statusExchange gets the Status message from the given node. -func (c *Conn) statusExchange() (*Status, error) { +func (c *rlpxConn) statusExchange() (*Status, error) { defer func() { _ = c.SetDeadline(time.Time{}) }() if err := c.SetDeadline(time.Now().Add(20 * time.Second)); err != nil { return nil, err @@ -151,17 +140,7 @@ type request struct { } // ReadAndServe reads messages from peers and writes it to a database. -func (c *Conn) ReadAndServe(db database.Database, count *MessageCount) error { - // dbCh is used to limit the number of database goroutines running at one - // time with a buffered channel. Without this, a large influx of messages can - // bog down the system and leak memory. - var dbCh chan struct{} - if db != nil { - dbCh = make(chan struct{}, db.MaxConcurrentWrites()) - } - - ctx := context.Background() - +func (c *rlpxConn) ReadAndServe(count *MessageCount) error { for { start := time.Now() @@ -181,20 +160,6 @@ func (c *Conn) ReadAndServe(db database.Database, count *MessageCount) error { case *BlockHeaders: atomic.AddInt32(&count.BlockHeaders, int32(len(msg.BlockHeadersPacket))) c.logger.Trace().Msgf("Received %v BlockHeaders", len(msg.BlockHeadersPacket)) - - if db != nil && db.ShouldWriteBlocks() { - for _, header := range msg.BlockHeadersPacket { - if err := c.getParentBlock(ctx, db, header); err != nil { - return err - } - } - - dbCh <- struct{}{} - go func() { - db.WriteBlockHeaders(ctx, msg.BlockHeadersPacket) - <-dbCh - }() - } case *GetBlockHeaders: atomic.AddInt32(&count.BlockHeaderRequests, 1) c.logger.Trace().Msgf("Received GetBlockHeaders request") @@ -209,34 +174,6 @@ func (c *Conn) ReadAndServe(db database.Database, count *MessageCount) error { case *BlockBodies: atomic.AddInt32(&count.BlockBodies, int32(len(msg.BlockBodiesPacket))) c.logger.Trace().Msgf("Received %v BlockBodies", len(msg.BlockBodiesPacket)) - - var hash *common.Hash - for e := c.requests.Front(); e != nil; e = e.Next() { - r, ok := e.Value.(request) - if !ok { - log.Error().Msg("Request type assertion failed") - continue - } - - if r.requestID == msg.ReqID() { - hash = &r.hash - c.requests.Remove(e) - break - } - } - - if hash == nil { - c.logger.Warn().Msg("No block hash found for block body") - break - } - - if db != nil && len(msg.BlockBodiesPacket) > 0 && db.ShouldWriteBlocks() { - dbCh <- struct{}{} - go func() { - db.WriteBlockBody(ctx, msg.BlockBodiesPacket[0], *hash) - <-dbCh - }() - } case *GetBlockBodies: atomic.AddInt32(&count.BlockBodiesRequests, int32(len(msg.GetBlockBodiesPacket))) c.logger.Trace().Msgf("Received %v GetBlockBodies request", len(msg.GetBlockBodiesPacket)) @@ -250,65 +187,21 @@ func (c *Conn) ReadAndServe(db database.Database, count *MessageCount) error { case *NewBlockHashes: atomic.AddInt32(&count.BlockHashes, int32(len(*msg))) c.logger.Trace().Msgf("Received %v NewBlockHashes", len(*msg)) - - hashes := make([]common.Hash, 0, len(*msg)) - for _, hash := range *msg { - hashes = append(hashes, hash.Hash) - if err := c.getBlockData(hash.Hash); err != nil { - return err - } - } - - if db != nil && db.ShouldWriteBlockEvents() && len(hashes) > 0 { - dbCh <- struct{}{} - go func() { - db.WriteBlockHashes(ctx, c.node, hashes) - <-dbCh - }() - } case *NewBlock: atomic.AddInt32(&count.Blocks, 1) c.logger.Trace().Str("hash", msg.Block.Hash().Hex()).Msg("Received NewBlock") - - if db != nil && (db.ShouldWriteBlocks() || db.ShouldWriteBlockEvents()) { - if err := c.getParentBlock(ctx, db, msg.Block.Header()); err != nil { - return err - } - - dbCh <- struct{}{} - go func() { - db.WriteBlock(ctx, c.node, msg.Block, msg.TD) - <-dbCh - }() - } case *Transactions: atomic.AddInt32(&count.Transactions, int32(len(*msg))) c.logger.Trace().Msgf("Received %v Transactions", len(*msg)) - - if db != nil && (db.ShouldWriteTransactions() || db.ShouldWriteTransactionEvents()) { - dbCh <- struct{}{} - go func() { - db.WriteTransactions(ctx, c.node, *msg) - <-dbCh - }() - } case *PooledTransactions: atomic.AddInt32(&count.Transactions, int32(len(msg.PooledTransactionsPacket))) c.logger.Trace().Msgf("Received %v PooledTransactions", len(msg.PooledTransactionsPacket)) - - if db != nil && (db.ShouldWriteTransactions() || db.ShouldWriteTransactionEvents()) { - dbCh <- struct{}{} - go func() { - db.WriteTransactions(ctx, c.node, msg.PooledTransactionsPacket) - <-dbCh - }() - } case *NewPooledTransactionHashes: - if err := c.processNewPooledTransactionHashes(db, count, msg.Hashes); err != nil { + if err := c.processNewPooledTransactionHashes(count, msg.Hashes); err != nil { return err } case *NewPooledTransactionHashes66: - if err := c.processNewPooledTransactionHashes(db, count, *msg); err != nil { + if err := c.processNewPooledTransactionHashes(count, *msg); err != nil { return err } case *GetPooledTransactions: @@ -343,14 +236,10 @@ func (c *Conn) ReadAndServe(db database.Database, count *MessageCount) error { // processNewPooledTransactionHashes processes NewPooledTransactionHashes // messages by requesting the transaction bodies. -func (c *Conn) processNewPooledTransactionHashes(db database.Database, count *MessageCount, hashes []common.Hash) error { +func (c *rlpxConn) processNewPooledTransactionHashes(count *MessageCount, hashes []common.Hash) error { atomic.AddInt32(&count.TransactionHashes, int32(len(hashes))) c.logger.Trace().Msgf("Received %v NewPooledTransactionHashes", len(hashes)) - if db == nil || !db.ShouldWriteTransactions() { - return nil - } - req := &GetPooledTransactions{ RequestId: rand.Uint64(), GetPooledTransactionsPacket: hashes, @@ -362,58 +251,3 @@ func (c *Conn) processNewPooledTransactionHashes(db database.Database, count *Me return nil } - -// getBlockData will send a GetBlockHeaders and GetBlockBodies request to the -// peer. It will return an error if the sending either of the requests failed. -func (c *Conn) getBlockData(hash common.Hash) error { - headersRequest := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - // Providing both the hash and number will result in a `both origin - // hash and number` error. - Origin: eth.HashOrNumber{Hash: hash}, - Amount: 1, - }, - } - - if err := c.Write(headersRequest); err != nil { - c.logger.Error().Err(err).Msg("Failed to write GetBlockHeaders request") - return err - } - - c.requestNum++ - c.requests.PushBack(request{ - requestID: c.requestNum, - hash: hash, - }) - bodiesRequest := &GetBlockBodies{ - RequestId: c.requestNum, - GetBlockBodiesPacket: []common.Hash{hash}, - } - if err := c.Write(bodiesRequest); err != nil { - c.logger.Error().Err(err).Msg("Failed to write GetBlockBodies request") - return err - } - - return nil -} - -// getParentBlock will send a request to the peer if the parent of the header -// does not exist in the database. -func (c *Conn) getParentBlock(ctx context.Context, db database.Database, header *types.Header) error { - if c.oldestBlock == nil { - c.logger.Info().Interface("block", header).Msg("Setting oldest block") - c.oldestBlock = header - return nil - } - - if !db.HasParentBlock(ctx, header.ParentHash) && header.Number.Cmp(c.oldestBlock.Number) == 1 { - c.logger.Info(). - Str("hash", header.ParentHash.Hex()). - Str("number", new(big.Int).Sub(header.Number, big.NewInt(1)).String()). - Msg("Fetching missing parent block") - - return c.getBlockData(header.ParentHash) - } - - return nil -} diff --git a/p2p/types.go b/p2p/types.go index 99445c10..e622d1cc 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -1,13 +1,11 @@ package p2p import ( - "container/list" "crypto/ecdsa" "fmt" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/p2p" @@ -148,29 +146,18 @@ type PooledTransactions eth.PooledTransactionsPacket66 func (msg PooledTransactions) Code() int { return 26 } func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId } -// Conn represents an individual connection with a peer -type Conn struct { +// rlpxConn represents an individual connection with a peer. +type rlpxConn struct { *rlpx.Conn - SensorID string ourKey *ecdsa.PrivateKey caps []p2p.Cap node *enode.Node logger zerolog.Logger - - // requests is used to store the request ID and the block hash. This is used - // when fetching block bodies because the eth protocol block bodies do not - // contain information about the block hash. - requests *list.List - requestNum uint64 - - // oldestBlock stores the first block the sensor has seen so when fetching - // parent blocks, it does not request blocks older than this. - oldestBlock *types.Header } // Read reads an eth66 packet from the connection. -func (c *Conn) Read() Message { +func (c *rlpxConn) Read() Message { code, rawData, _, err := c.Conn.Read() if err != nil { return errorf("could not read from connection: %v", err) @@ -256,7 +243,7 @@ func (c *Conn) Read() Message { } // Write writes a eth packet to the connection. -func (c *Conn) Write(msg Message) error { +func (c *rlpxConn) Write(msg Message) error { payload, err := rlp.EncodeToBytes(msg) if err != nil { return err @@ -266,7 +253,7 @@ func (c *Conn) Write(msg Message) error { } // ReadSnap reads a snap/1 response with the given id from the connection. -func (c *Conn) ReadSnap(id uint64) (Message, error) { +func (c *rlpxConn) ReadSnap(id uint64) (Message, error) { respId := id + 1 start := time.Now() for respId != id && time.Since(start) < timeout { From 11a0ba2ecb258ac9e89a7e53ff6c4e3f45109d13 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 9 Aug 2023 15:14:35 -0400 Subject: [PATCH 15/32] remove port for p2p.Listen --- cmd/p2p/crawl/crawl.go | 2 +- p2p/p2p.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 0a26e49a..7eeb4968 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -73,7 +73,7 @@ var CrawlCmd = &cobra.Command{ } ln := enode.NewLocalNode(db, cfg.PrivateKey) - socket, err := p2p.Listen(ln, 0) + socket, err := p2p.Listen(ln) if err != nil { return err } diff --git a/p2p/p2p.go b/p2p/p2p.go index 1ba7399d..f37e5c14 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -13,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -func Listen(ln *enode.LocalNode, port int) (*net.UDPConn, error) { - addr := fmt.Sprintf("0.0.0.0:%v", port) +func Listen(ln *enode.LocalNode) (*net.UDPConn, error) { + addr := fmt.Sprintf("0.0.0.0:0") socket, err := net.ListenPacket("udp4", addr) if err != nil { From 5dd2861bb9f10c631c66c5dff5208901af490dbc Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 9 Aug 2023 15:18:26 -0400 Subject: [PATCH 16/32] fix lint and docs --- doc/polycli_p2p.md | 2 +- doc/polycli_p2p_sensor.md | 54 ++++++++++++++++++++------------------- p2p/protocol.go | 5 +++- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/doc/polycli_p2p.md b/doc/polycli_p2p.md index 299d5e76..9e2944f2 100644 --- a/doc/polycli_p2p.md +++ b/doc/polycli_p2p.md @@ -60,5 +60,5 @@ The command also inherits flags from parent commands. - [polycli p2p ping](polycli_p2p_ping.md) - Ping node(s) and return the output. -- [polycli p2p sensor](polycli_p2p_sensor.md) - Start a devp2p sensor that discovers other peers and will receive blocks and transactions. +- [polycli p2p sensor](polycli_p2p_sensor.md) - Start a devp2p sensor that discovers other peers and will receive blocks and transactions. diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index 4fdeeaed..10aa633f 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -11,7 +11,7 @@ ## Description -Start a devp2p sensor that discovers other peers and will receive blocks and transactions. +Start a devp2p sensor that discovers other peers and will receive blocks and transactions. ```bash polycli p2p sensor [nodes file] [flags] @@ -23,31 +23,33 @@ If no nodes.json file exists, run `echo "{}" >> nodes.json` to get started. ## Flags ```bash - -b, --bootnodes string Comma separated nodes used for bootstrapping. At least one bootnode is - required, so other nodes in the network can discover each other. - --genesis string The genesis file. (default "genesis.json") - --genesis-hash string The genesis block hash. (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") - -h, --help help for sensor - -k, --key-file string The file of the private key. If no key file is found then a key file will be generated. - -D, --max-db-writes int The maximum number of concurrent database writes to perform. Increasing - this will result in less chance of missing data (i.e. broken pipes) but - can significantly increase memory usage. (default 100) - -m, --max-peers int Maximum number of peers to connect to. (default 200) - -n, --network-id uint Filter discovered nodes by this network ID. - -p, --parallel int How many parallel discoveries to attempt. (default 16) - --port int The sensor's TCP and discovery port. (default 30303) - --pprof Whether to run pprof. - --pprof-port uint The port to run pprof on. (default 6060) - -P, --project-id string GCP project ID. - -r, --revalidation-interval string The amount of time it takes to retry connecting to a failed peer. (default "10m") - --rpc string The RPC endpoint. (default "https://polygon-rpc.com") - -s, --sensor-id string Sensor ID. - --write-block-events Whether to write block events to the database. (default true) - -B, --write-blocks Whether to write blocks to the database. (default true) - --write-tx-events Whether to write transaction events to the database. This option could significantly - increase CPU and memory usage. (default true) - -t, --write-txs Whether to write transactions to the database. This option could significantly - increase CPU and memory usage. (default true) + -b, --bootnodes string Comma separated nodes used for bootstrapping. At least one bootnode is + required, so other nodes in the network can discover each other. + --dial-ratio int The ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of + connections to be dialed. Setting this to 0 defaults it to 3. + --discovery-port int The UDP P2P discovery port. (default 30303) + --genesis string The genesis file. (default "genesis.json") + --genesis-hash string The genesis block hash. (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") + -h, --help help for sensor + -k, --key-file string The file of the private key. If no key file is found then a key file will be generated. + -D, --max-db-writes int The maximum number of concurrent database writes to perform. Increasing + this will result in less chance of missing data (i.e. broken pipes) but + can significantly increase memory usage. (default 10000) + -m, --max-peers int Maximum number of peers to connect to. (default 200) + --nat string The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:). (default "any") + -n, --network-id uint Filter discovered nodes by this network ID. + --port int The TCP network listening port. (default 30303) + --pprof Whether to run pprof. + --pprof-port uint The port to run pprof on. (default 6060) + -P, --project-id string GCP project ID. + --rpc string The RPC endpoint used to fetch the latest block. (default "https://polygon-rpc.com") + -s, --sensor-id string Sensor ID. + --write-block-events Whether to write block events to the database. (default true) + -B, --write-blocks Whether to write blocks to the database. (default true) + --write-tx-events Whether to write transaction events to the database. This option could significantly + increase CPU and memory usage. (default true) + -t, --write-txs Whether to write transactions to the database. This option could significantly + increase CPU and memory usage. (default true) ``` The command also inherits flags from parent commands. diff --git a/p2p/protocol.go b/p2p/protocol.go index 8380d76e..85c136b0 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -122,11 +122,14 @@ func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { log.Trace().Interface("msg", msg).Send() } - msg.Discard() if err != nil { c.logger.Error().Err(err).Send() return err } + + if err = msg.Discard(); err != nil { + return err + } } }, } From ff5748fb8079153f8002379bfba451343f0152fd Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Wed, 9 Aug 2023 15:27:14 -0400 Subject: [PATCH 17/32] fix lint --- cmd/p2p/crawl/crawl_util.go | 10 +++++----- cmd/p2p/sensor/sensor.go | 5 ++++- p2p/database/datastore.go | 1 - p2p/node.go | 18 +++++++++--------- p2p/p2p.go | 4 +--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/p2p/crawl/crawl_util.go b/cmd/p2p/crawl/crawl_util.go index 5793b98f..0d5ce614 100644 --- a/cmd/p2p/crawl/crawl_util.go +++ b/cmd/p2p/crawl/crawl_util.go @@ -12,8 +12,8 @@ import ( ) type crawler struct { - input p2p.NodeSet - output p2p.NodeSet + input p2p.CrawlNodeSet + output p2p.CrawlNodeSet disc resolver iters []enode.Iterator inputIter enode.Iterator @@ -37,10 +37,10 @@ type resolver interface { RequestENR(*enode.Node) (*enode.Node, error) } -func newCrawler(input p2p.NodeSet, disc resolver, iters ...enode.Iterator) *crawler { +func newCrawler(input p2p.CrawlNodeSet, disc resolver, iters ...enode.Iterator) *crawler { c := &crawler{ input: input, - output: make(p2p.NodeSet, len(input)), + output: make(p2p.CrawlNodeSet, len(input)), disc: disc, iters: iters, inputIter: enode.IterNodes(input.Nodes()), @@ -56,7 +56,7 @@ func newCrawler(input p2p.NodeSet, disc resolver, iters ...enode.Iterator) *craw return c } -func (c *crawler) run(timeout time.Duration, nthreads int) p2p.NodeSet { +func (c *crawler) run(timeout time.Duration, nthreads int) p2p.CrawlNodeSet { var ( timeoutTimer = time.NewTimer(timeout) timeoutCh <-chan time.Time diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index f7df27a6..b73fd071 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -180,7 +180,10 @@ var SensorCmd = &cobra.Command{ case <-ticker.C: log.Info().Interface("peers", server.PeerCount()).Send() - p2p.WriteStaticNodes(inputSensorParams.NodesFile, peers) + err = p2p.WriteStaticNodes(inputSensorParams.NodesFile, peers) + if err != nil { + log.Error().Err(err).Msg("Failed to write static nodes to file") + } case peer := <-opts.Peers: if _, ok := peers[peer.ID()]; !ok { peers[peer.ID()] = peer.URLv4() diff --git a/p2p/database/datastore.go b/p2p/database/datastore.go index 90a48fca..2a3720ff 100644 --- a/p2p/database/datastore.go +++ b/p2p/database/datastore.go @@ -33,7 +33,6 @@ type Datastore struct { shouldWriteTransactions bool shouldWriteTransactionEvents bool writes chan struct{} - oldestBlock *types.Header } // DatastoreEvent can represent a peer sending the sensor a transaction hash or diff --git a/p2p/node.go b/p2p/node.go index d2de3c85..ff32c256 100644 --- a/p2p/node.go +++ b/p2p/node.go @@ -15,12 +15,10 @@ import ( const jsonIndent = " " -// NodeSet is the nodes.json file format. It holds a set of node records +// CrawlNodeSet is the nodes.json file format. It holds a set of node records // as a JSON object. -type NodeSet map[enode.ID]NodeJSON -type StaticNodes map[enode.ID]string - -type NodeJSON struct { +type CrawlNodeSet map[enode.ID]CrawlNode +type CrawlNode struct { Seq uint64 `json:"seq"` N *enode.Node `json:"record"` URL string `json:"url"` @@ -35,15 +33,17 @@ type NodeJSON struct { LastCheck time.Time `json:"lastCheck,omitempty"` } -func ReadNodeSet(file string) (NodeSet, error) { - var nodes NodeSet +type StaticNodes map[enode.ID]string + +func ReadNodeSet(file string) (CrawlNodeSet, error) { + var nodes CrawlNodeSet if err := common.LoadJSON(file, &nodes); err != nil { return nil, err } return nodes, nil } -func WriteNodeSet(file string, nodes NodeSet) error { +func WriteNodeSet(file string, nodes CrawlNodeSet) error { nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent) if err != nil { return err @@ -56,7 +56,7 @@ func WriteNodeSet(file string, nodes NodeSet) error { } // Nodes returns the node records contained in the set. -func (ns NodeSet) Nodes() []*enode.Node { +func (ns CrawlNodeSet) Nodes() []*enode.Node { result := make([]*enode.Node, 0, len(ns)) for _, n := range ns { result = append(result, n.N) diff --git a/p2p/p2p.go b/p2p/p2p.go index f37e5c14..9ec258f0 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -14,9 +14,7 @@ import ( ) func Listen(ln *enode.LocalNode) (*net.UDPConn, error) { - addr := fmt.Sprintf("0.0.0.0:0") - - socket, err := net.ListenPacket("udp4", addr) + socket, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { return nil, err } From 278ad9dd4306d1a0e1a6374e10ece1286bf6e81b Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 10 Aug 2023 12:46:37 -0400 Subject: [PATCH 18/32] update nodeset --- cmd/p2p/crawl/crawl.go | 3 +- cmd/p2p/crawl/crawl_util.go | 69 ++++++---------------- cmd/p2p/ping/ping.go | 18 +++--- cmd/p2p/sensor/sensor.go | 13 +++-- p2p/node.go | 112 ------------------------------------ p2p/nodeset.go | 59 +++++++++++++++++++ 6 files changed, 97 insertions(+), 177 deletions(-) delete mode 100644 p2p/node.go create mode 100644 p2p/nodeset.go diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 7eeb4968..58124cfc 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -23,6 +23,7 @@ type ( NodesFile string Database string RevalidationInterval string + revalidationInterval time.Duration } ) @@ -36,7 +37,7 @@ var ( var CrawlCmd = &cobra.Command{ Use: "crawl [nodes file]", Short: "Crawl a network on the devp2p layer and generate a nodes JSON file.", - Long: "If no nodes.json file exists, run `echo \"{}\" >> nodes.json` to get started.", + Long: "If no nodes.json file exists, run `echo \"[]\" >> nodes.json` to get started.", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) (err error) { inputCrawlParams.NodesFile = args[0] diff --git a/cmd/p2p/crawl/crawl_util.go b/cmd/p2p/crawl/crawl_util.go index 0d5ce614..73e0edb7 100644 --- a/cmd/p2p/crawl/crawl_util.go +++ b/cmd/p2p/crawl/crawl_util.go @@ -12,8 +12,8 @@ import ( ) type crawler struct { - input p2p.CrawlNodeSet - output p2p.CrawlNodeSet + input []*enode.Node + output p2p.NodeSet disc resolver iters []enode.Iterator inputIter enode.Iterator @@ -37,26 +37,26 @@ type resolver interface { RequestENR(*enode.Node) (*enode.Node, error) } -func newCrawler(input p2p.CrawlNodeSet, disc resolver, iters ...enode.Iterator) *crawler { +func newCrawler(input []*enode.Node, disc resolver, iters ...enode.Iterator) *crawler { c := &crawler{ input: input, - output: make(p2p.CrawlNodeSet, len(input)), + output: make(p2p.NodeSet, len(input)), disc: disc, iters: iters, - inputIter: enode.IterNodes(input.Nodes()), + inputIter: enode.IterNodes(input), ch: make(chan *enode.Node), closed: make(chan struct{}), } c.iters = append(c.iters, c.inputIter) // Copy input to output initially. Any nodes that fail validation // will be dropped from output during the run. - for id, n := range input { - c.output[id] = n + for _, n := range input { + c.output[n.ID()] = n.URLv4() } return c } -func (c *crawler) run(timeout time.Duration, nthreads int) p2p.CrawlNodeSet { +func (c *crawler) run(timeout time.Duration, nthreads int) p2p.NodeSet { var ( timeoutTimer = time.NewTimer(timeout) timeoutCh <-chan time.Time @@ -74,10 +74,8 @@ func (c *crawler) run(timeout time.Duration, nthreads int) p2p.CrawlNodeSet { } var ( added uint64 - updated uint64 skipped uint64 recent uint64 - removed uint64 wg sync.WaitGroup ) wg.Add(nthreads) @@ -92,12 +90,8 @@ func (c *crawler) run(timeout time.Duration, nthreads int) p2p.CrawlNodeSet { atomic.AddUint64(&skipped, 1) case nodeSkipRecent: atomic.AddUint64(&recent, 1) - case nodeRemoved: - atomic.AddUint64(&removed, 1) case nodeAdded: atomic.AddUint64(&added, 1) - default: - atomic.AddUint64(&updated, 1) } case <-c.closed: return @@ -125,9 +119,7 @@ loop: case <-statusTicker.C: log.Info(). Uint64("added", atomic.LoadUint64(&added)). - Uint64("updated", atomic.LoadUint64(&updated)). - Uint64("removed", atomic.LoadUint64(&removed)). - Uint64("ignored(recent)", atomic.LoadUint64(&removed)). + Uint64("ignored(recent)", atomic.LoadUint64(&recent)). Uint64("ignored(incompatible)", atomic.LoadUint64(&skipped)). Msg("Crawling in progress") } @@ -184,12 +176,10 @@ func shouldSkipNode(n *enode.Node) bool { // what changed. func (c *crawler) updateNode(n *enode.Node) int { c.mu.RLock() - node, ok := c.output[n.ID()] + _, ok := c.output[n.ID()] c.mu.RUnlock() - // Skip validation of recently-seen nodes. - if ok && time.Since(node.LastCheck) < c.revalidateInterval { - log.Debug().Str("id", n.ID().String()).Msg("Skipping node") + if ok { return nodeSkipRecent } @@ -198,42 +188,17 @@ func (c *crawler) updateNode(n *enode.Node) int { return nodeSkipIncompat } - // Request the node record. - status := nodeUpdated - node.LastCheck = truncNow() - - if nn, err := c.disc.RequestENR(n); err != nil { - if node.Score == 0 { - // Node doesn't implement EIP-868. - log.Debug().Str("id", n.ID().String()).Msg("Skipping node") - return nodeSkipIncompat - } - node.Score /= 2 - } else { - node.N = nn - node.Seq = nn.Seq() - node.Score++ - if node.FirstResponse.IsZero() { - node.FirstResponse = node.LastCheck - status = nodeAdded - } - node.LastResponse = node.LastCheck - node.URL = n.URLv4() + nn, err := c.disc.RequestENR(n) + if err != nil { + return nodeSkipIncompat } // Store/update node in output set. c.mu.Lock() - defer c.mu.Unlock() - - if node.Score <= 0 { - log.Debug().Str("id", n.ID().String()).Msg("Removing node") - delete(c.output, n.ID()) - return nodeRemoved - } + c.output[nn.ID()] = nn.URLv4() + c.mu.Unlock() - log.Debug().Str("id", n.ID().String()).Uint64("seq", n.Seq()).Int("score", node.Score).Msg("Updating node") - c.output[n.ID()] = node - return status + return nodeAdded } func truncNow() time.Time { diff --git a/cmd/p2p/ping/ping.go b/cmd/p2p/ping/ping.go index e971f30a..e3dcfcd8 100644 --- a/cmd/p2p/ping/ping.go +++ b/cmd/p2p/ping/ping.go @@ -39,14 +39,13 @@ var PingCmd = &cobra.Command{ Long: `Ping nodes by either giving a single enode/enr or an entire nodes file. This command will establish a handshake and status exchange to get the Hello and -Status messages and output JSON. If providing a enode/enr rather than a node file, -then the connection will remain open by default (--listen=true), and you can see -other messages the peer sends (e.g. blocks, transactions, etc.).`, +Status messages and output JSON. If providing a enode/enr rather than a nodes +file, then the connection will remain open by default (--listen=true), and you +can see other messages the peer sends (e.g. blocks, transactions, etc.).`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - nodes := []*enode.Node{} - if inputSet, err := p2p.ReadNodeSet(args[0]); err == nil { - nodes = inputSet.Nodes() + nodes, err := p2p.ReadNodeSet(args[0]) + if err == nil { inputPingParams.Listen = false } else if node, err := p2p.ParseNode(args[0]); err == nil { nodes = append(nodes, node) @@ -105,7 +104,12 @@ other messages the peer sends (e.g. blocks, transactions, etc.).`, // Save the results to the output map. mutex.Lock() - output[node.ID()] = pingNodeJSON{node, hello, status, errStr} + output[node.ID()] = pingNodeJSON{ + Record: node, + Hello: hello, + Status: status, + Error: errStr, + } mutex.Unlock() }(n) } diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index b73fd071..2d2c38e4 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -66,11 +66,11 @@ var ( var SensorCmd = &cobra.Command{ Use: "sensor [nodes file]", Short: "Start a devp2p sensor that discovers other peers and will receive blocks and transactions.", - Long: "If no nodes.json file exists, run `echo \"{}\" >> nodes.json` to get started.", + Long: "If no nodes.json file exists, run `echo \"[]\" >> nodes.json` to get started.", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) (err error) { inputSensorParams.NodesFile = args[0] - inputSensorParams.nodes, err = p2p.ReadStaticNodes(inputSensorParams.NodesFile) + inputSensorParams.nodes, err = p2p.ReadNodeSet(inputSensorParams.NodesFile) if err != nil { return err } @@ -173,16 +173,19 @@ var SensorCmd = &cobra.Command{ signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) - peers := make(p2p.StaticNodes) + peers := make(p2p.NodeSet) + for _, node := range inputSensorParams.nodes { + peers[node.ID()] = node.URLv4() + } for { select { case <-ticker.C: log.Info().Interface("peers", server.PeerCount()).Send() - err = p2p.WriteStaticNodes(inputSensorParams.NodesFile, peers) + err = p2p.WriteNodeSet(inputSensorParams.NodesFile, peers) if err != nil { - log.Error().Err(err).Msg("Failed to write static nodes to file") + log.Error().Err(err).Msg("Failed to write nodes to file") } case peer := <-opts.Peers: if _, ok := peers[peer.ID()]; !ok { diff --git a/p2p/node.go b/p2p/node.go deleted file mode 100644 index ff32c256..00000000 --- a/p2p/node.go +++ /dev/null @@ -1,112 +0,0 @@ -package p2p - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "sort" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/rs/zerolog/log" -) - -const jsonIndent = " " - -// CrawlNodeSet is the nodes.json file format. It holds a set of node records -// as a JSON object. -type CrawlNodeSet map[enode.ID]CrawlNode -type CrawlNode struct { - Seq uint64 `json:"seq"` - N *enode.Node `json:"record"` - URL string `json:"url"` - - // The score tracks how many liveness checks were performed. It is incremented by one - // every time the node passes a check, and halved every time it doesn't. - Score int `json:"score,omitempty"` - // These two track the time of last successful contact. - FirstResponse time.Time `json:"firstResponse,omitempty"` - LastResponse time.Time `json:"lastResponse,omitempty"` - // This one tracks the time of our last attempt to contact the node. - LastCheck time.Time `json:"lastCheck,omitempty"` -} - -type StaticNodes map[enode.ID]string - -func ReadNodeSet(file string) (CrawlNodeSet, error) { - var nodes CrawlNodeSet - if err := common.LoadJSON(file, &nodes); err != nil { - return nil, err - } - return nodes, nil -} - -func WriteNodeSet(file string, nodes CrawlNodeSet) error { - nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent) - if err != nil { - return err - } - if file == "-" { - _, err = os.Stdout.Write(nodesJSON) - return err - } - return os.WriteFile(file, nodesJSON, 0644) -} - -// Nodes returns the node records contained in the set. -func (ns CrawlNodeSet) Nodes() []*enode.Node { - result := make([]*enode.Node, 0, len(ns)) - for _, n := range ns { - result = append(result, n.N) - } - // Sort by ID. - sort.Slice(result, func(i, j int) bool { - return bytes.Compare(result[i].ID().Bytes(), result[j].ID().Bytes()) < 0 - }) - return result -} - -// ReadStaticNodes parses a list of discovery node URLs loaded from a JSON file -// from within the data directory. -func ReadStaticNodes(file string) ([]*enode.Node, error) { - // Load the nodes from the config file. - var nodelist []string - if err := common.LoadJSON(file, &nodelist); err != nil { - return nil, fmt.Errorf("failed to load node list file: %w", err) - } - - // Interpret the list as a discovery node array - var nodes []*enode.Node - for _, url := range nodelist { - if url == "" { - continue - } - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - log.Warn().Err(err).Str("url", url).Msg("Failed to parse enode") - continue - } - nodes = append(nodes, node) - } - - return nodes, nil -} - -func WriteStaticNodes(file string, nodes StaticNodes) error { - urls := make([]string, 0, len(nodes)) - for _, url := range nodes { - urls = append(urls, url) - } - - bytes, err := json.MarshalIndent(urls, "", jsonIndent) - if err != nil { - return err - } - if file == "-" { - _, err = os.Stdout.Write(bytes) - return err - } - return os.WriteFile(file, bytes, 0644) -} diff --git a/p2p/nodeset.go b/p2p/nodeset.go new file mode 100644 index 00000000..a7d84426 --- /dev/null +++ b/p2p/nodeset.go @@ -0,0 +1,59 @@ +package p2p + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/rs/zerolog/log" +) + +const jsonIndent = " " + +// NodeSet is the mapping of the node ID to the URL. +type NodeSet map[enode.ID]string + +// ReadNodeSet parses a list of discovery node URLs loaded from a JSON file. +func ReadNodeSet(file string) ([]*enode.Node, error) { + // Load the nodes from the config file. + var nodelist []string + if err := common.LoadJSON(file, &nodelist); err != nil { + return nil, fmt.Errorf("failed to load node list file: %w", err) + } + + // Interpret the list as a discovery node array + var nodes []*enode.Node + for _, url := range nodelist { + if url == "" { + continue + } + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + log.Warn().Err(err).Str("url", url).Msg("Failed to parse enode") + continue + } + nodes = append(nodes, node) + } + + return nodes, nil +} + +// WriteNodeSet writes the node set as a JSON list of URLs to a file. +func WriteNodeSet(file string, nodes NodeSet) error { + urls := make([]string, 0, len(nodes)) + for _, url := range nodes { + urls = append(urls, url) + } + + bytes, err := json.MarshalIndent(urls, "", jsonIndent) + if err != nil { + return err + } + if file == "-" { + _, err = os.Stdout.Write(bytes) + return err + } + return os.WriteFile(file, bytes, 0644) +} From 85aa6a64c034e05e527058568eac0032476342df Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 10 Aug 2023 12:56:13 -0400 Subject: [PATCH 19/32] fix lint --- cmd/p2p/ping/ping.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/p2p/ping/ping.go b/cmd/p2p/ping/ping.go index e3dcfcd8..e7e0bd4b 100644 --- a/cmd/p2p/ping/ping.go +++ b/cmd/p2p/ping/ping.go @@ -44,8 +44,9 @@ file, then the connection will remain open by default (--listen=true), and you can see other messages the peer sends (e.g. blocks, transactions, etc.).`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - nodes, err := p2p.ReadNodeSet(args[0]) - if err == nil { + nodes := []*enode.Node{} + if input, err := p2p.ReadNodeSet(args[0]); err == nil { + nodes = input inputPingParams.Listen = false } else if node, err := p2p.ParseNode(args[0]); err == nil { nodes = append(nodes, node) From c5158df68fcd1e2fa64519ed35585f4ca515540e Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 10 Aug 2023 12:57:33 -0400 Subject: [PATCH 20/32] make gen-doc --- doc/polycli_p2p_crawl.md | 2 +- doc/polycli_p2p_ping.md | 6 +++--- doc/polycli_p2p_sensor.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/polycli_p2p_crawl.md b/doc/polycli_p2p_crawl.md index 1837fada..0eb22f8d 100644 --- a/doc/polycli_p2p_crawl.md +++ b/doc/polycli_p2p_crawl.md @@ -19,7 +19,7 @@ polycli p2p crawl [nodes file] [flags] ## Usage -If no nodes.json file exists, run `echo "{}" >> nodes.json` to get started. +If no nodes.json file exists, run `echo "[]" >> nodes.json` to get started. ## Flags ```bash diff --git a/doc/polycli_p2p_ping.md b/doc/polycli_p2p_ping.md index 20898c71..d6070041 100644 --- a/doc/polycli_p2p_ping.md +++ b/doc/polycli_p2p_ping.md @@ -22,9 +22,9 @@ polycli p2p ping [enode/enr or nodes file] [flags] Ping nodes by either giving a single enode/enr or an entire nodes file. This command will establish a handshake and status exchange to get the Hello and -Status messages and output JSON. If providing a enode/enr rather than a node file, -then the connection will remain open by default (--listen=true), and you can see -other messages the peer sends (e.g. blocks, transactions, etc.). +Status messages and output JSON. If providing a enode/enr rather than a nodes +file, then the connection will remain open by default (--listen=true), and you +can see other messages the peer sends (e.g. blocks, transactions, etc.). ## Flags ```bash diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index 10aa633f..d3a33214 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -19,7 +19,7 @@ polycli p2p sensor [nodes file] [flags] ## Usage -If no nodes.json file exists, run `echo "{}" >> nodes.json` to get started. +If no nodes.json file exists, run `echo "[]" >> nodes.json` to get started. ## Flags ```bash From 0b2aa849aacb1046618b3c47548ff5f946596067 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Thu, 10 Aug 2023 13:01:22 -0400 Subject: [PATCH 21/32] remove truncNow --- cmd/p2p/crawl/crawl_util.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/p2p/crawl/crawl_util.go b/cmd/p2p/crawl/crawl_util.go index 73e0edb7..d0f77421 100644 --- a/cmd/p2p/crawl/crawl_util.go +++ b/cmd/p2p/crawl/crawl_util.go @@ -200,7 +200,3 @@ func (c *crawler) updateNode(n *enode.Node) int { return nodeAdded } - -func truncNow() time.Time { - return time.Now().UTC().Truncate(1 * time.Second) -} From 0f97715d469adbca9dada18f78ba424fcb72d430 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Fri, 11 Aug 2023 08:29:28 -0400 Subject: [PATCH 22/32] address comments --- cmd/p2p/crawl/crawl.go | 4 ++-- cmd/p2p/sensor/sensor.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 58124cfc..24a718d4 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -37,7 +37,7 @@ var ( var CrawlCmd = &cobra.Command{ Use: "crawl [nodes file]", Short: "Crawl a network on the devp2p layer and generate a nodes JSON file.", - Long: "If no nodes.json file exists, run `echo \"[]\" >> nodes.json` to get started.", + Long: "If no nodes.json file exists, it will be created.", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) (err error) { inputCrawlParams.NodesFile = args[0] @@ -57,7 +57,7 @@ var CrawlCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { nodes, err := p2p.ReadNodeSet(inputCrawlParams.NodesFile) if err != nil { - return err + log.Warn().Err(err).Msgf("Creating nodes file %v because it does not exist", inputCrawlParams.NodesFile) } var cfg discover.Config diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 2d2c38e4..3a77a628 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -170,6 +170,8 @@ var SensorCmd = &cobra.Command{ defer server.Stop() ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) @@ -192,7 +194,6 @@ var SensorCmd = &cobra.Command{ peers[peer.ID()] = peer.URLv4() } case <-signals: - ticker.Stop() log.Info().Msg("Stopping sever...") return nil } From eade1fb5b27013fe87ef3c5ccba17d18b73395b9 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Fri, 11 Aug 2023 14:42:19 -0400 Subject: [PATCH 23/32] keep track of head block and improve logging --- cmd/p2p/crawl/crawl.go | 3 +- cmd/p2p/ping/ping.go | 13 ++++- cmd/p2p/sensor/sensor.go | 118 +++++++++++++++++++++++++++------------ doc/polycli_p2p_crawl.md | 2 +- p2p/log.go | 105 ++++++++++++++++------------------ p2p/nodeset.go | 5 +- p2p/protocol.go | 114 ++++++++++++++++++++++++++----------- 7 files changed, 228 insertions(+), 132 deletions(-) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 24a718d4..6b802fc2 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -62,11 +62,10 @@ var CrawlCmd = &cobra.Command{ var cfg discover.Config cfg.PrivateKey, _ = crypto.GenerateKey() - bn, err := p2p.ParseBootnodes(inputCrawlParams.Bootnodes) + cfg.Bootnodes, err = p2p.ParseBootnodes(inputCrawlParams.Bootnodes) if err != nil { return fmt.Errorf("unable to parse bootnodes: %w", err) } - cfg.Bootnodes = bn db, err := enode.OpenDB(inputCrawlParams.Database) if err != nil { diff --git a/cmd/p2p/ping/ping.go b/cmd/p2p/ping/ping.go index e7e0bd4b..e591d306 100644 --- a/cmd/p2p/ping/ping.go +++ b/cmd/p2p/ping/ping.go @@ -47,7 +47,6 @@ can see other messages the peer sends (e.g. blocks, transactions, etc.).`, nodes := []*enode.Node{} if input, err := p2p.ReadNodeSet(args[0]); err == nil { nodes = input - inputPingParams.Listen = false } else if node, err := p2p.ParseNode(args[0]); err == nil { nodes = append(nodes, node) } else { @@ -65,7 +64,17 @@ can see other messages the peer sends (e.g. blocks, transactions, etc.).`, sem := make(chan bool, inputPingParams.Threads) count := &p2p.MessageCount{} - go p2p.LogMessageCount(count, time.NewTicker(time.Second)) + go func() { + ticker := time.NewTicker(2 * time.Second) + for { + <-ticker.C + c := count.Load() + if !c.IsEmpty() { + log.Info().Interface("counts", c).Send() + count.Clear() + } + } + }() // Ping each node in the slice. for _, n := range nodes { diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 3a77a628..647c0230 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/signal" + "sync" "syscall" "time" @@ -19,11 +20,13 @@ import ( ethp2p "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/maticnetwork/polygon-cli/p2p" "github.com/maticnetwork/polygon-cli/p2p/database" + "github.com/maticnetwork/polygon-cli/rpctypes" ) type ( @@ -50,6 +53,7 @@ type ( DialRatio int NAT string + bootnodes []*enode.Node nodes []*enode.Node privateKey *ecdsa.PrivateKey genesis core.Genesis @@ -66,13 +70,20 @@ var ( var SensorCmd = &cobra.Command{ Use: "sensor [nodes file]", Short: "Start a devp2p sensor that discovers other peers and will receive blocks and transactions.", - Long: "If no nodes.json file exists, run `echo \"[]\" >> nodes.json` to get started.", + Long: "If no nodes.json file exists, it will be created.", Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) (err error) { inputSensorParams.NodesFile = args[0] inputSensorParams.nodes, err = p2p.ReadNodeSet(inputSensorParams.NodesFile) if err != nil { - return err + log.Warn().Err(err).Msgf("Creating nodes file %v because it does not exist", inputSensorParams.NodesFile) + } + + if len(inputSensorParams.Bootnodes) > 0 { + inputSensorParams.bootnodes, err = p2p.ParseBootnodes(inputSensorParams.Bootnodes) + if err != nil { + return fmt.Errorf("unable to parse bootnodes: %w", err) + } } if inputSensorParams.NetworkID == 0 { @@ -133,9 +144,17 @@ var SensorCmd = &cobra.Command{ ShouldWriteTransactionEvents: inputSensorParams.ShouldWriteTransactionEvents, }) - bootnodes, err := p2p.ParseBootnodes(inputSensorParams.Bootnodes) + // Fetch the latest block which will be used later when crafting the status + // message. This call will only be made once and stored in the head field + // until the sensor receives a new block it can overwrite it with. + block, err := getLatestBlock(inputSensorParams.RPC) if err != nil { - return fmt.Errorf("unable to parse bootnodes: %w", err) + return err + } + head := p2p.HeadBlock{ + Hash: block.Hash.ToHash(), + TotalDifficulty: block.TotalDifficulty.ToBigInt(), + Number: block.Number.ToUint64(), } opts := p2p.Eth66ProtocolOptions{ @@ -147,12 +166,15 @@ var SensorCmd = &cobra.Command{ SensorID: inputSensorParams.SensorID, NetworkID: inputSensorParams.NetworkID, Peers: make(chan *enode.Node), + Head: &head, + HeadMutex: &sync.RWMutex{}, + Count: &p2p.MessageCount{}, } server := ethp2p.Server{ Config: ethp2p.Config{ PrivateKey: inputSensorParams.privateKey, - BootstrapNodes: bootnodes, + BootstrapNodes: inputSensorParams.bootnodes, StaticNodes: inputSensorParams.nodes, MaxPeers: inputSensorParams.MaxPeers, ListenAddr: fmt.Sprintf(":%d", inputSensorParams.Port), @@ -164,7 +186,11 @@ var SensorCmd = &cobra.Command{ } log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") - if err = server.Start(); err != nil { + + // Starting the server isn't actually a blocking call so the sensor needs to + // have something that waits for it. This is implemented by the for {} loop + // seen below. + if err := server.Start(); err != nil { return err } defer server.Stop() @@ -177,16 +203,19 @@ var SensorCmd = &cobra.Command{ peers := make(p2p.NodeSet) for _, node := range inputSensorParams.nodes { + // Because the node URLs can change, map them to the node ID to prevent + // duplicates. peers[node.ID()] = node.URLv4() } for { select { case <-ticker.C: - log.Info().Interface("peers", server.PeerCount()).Send() + count := opts.Count.Load() + opts.Count.Clear() + log.Info().Interface("peers", server.PeerCount()).Interface("counts", count).Send() - err = p2p.WriteNodeSet(inputSensorParams.NodesFile, peers) - if err != nil { + if err := p2p.WriteNodeSet(inputSensorParams.NodesFile, peers); err != nil { log.Error().Err(err).Msg("Failed to write nodes to file") } case peer := <-opts.Peers: @@ -194,13 +223,16 @@ var SensorCmd = &cobra.Command{ peers[peer.ID()] = peer.URLv4() } case <-signals: - log.Info().Msg("Stopping sever...") + // This gracefully stops the sensor so that the peers can be written to + // the nodes file. + log.Info().Msg("Stopping sensor...") return nil } } }, } +// loadGenesis unmarshals the genesis file into the core.Genesis struct. func loadGenesis(genesisFile string) (core.Genesis, error) { chainConfig, err := os.ReadFile(genesisFile) @@ -214,45 +246,57 @@ func loadGenesis(genesisFile string) (core.Genesis, error) { return gen, nil } -func init() { - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", - `Comma separated nodes used for bootstrapping. At least one bootnode is -required, so other nodes in the network can discover each other.`) - if err := SensorCmd.MarkPersistentFlagRequired("bootnodes"); err != nil { - log.Error().Err(err).Msg("Failed to mark bootnodes as required persistent flag") +// getLatestBlock will get the latest block from an RPC provider. +func getLatestBlock(url string) (*rpctypes.RawBlockResponse, error) { + client, err := rpc.Dial(url) + if err != nil { + return nil, err } - SensorCmd.PersistentFlags().Uint64VarP(&inputSensorParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network ID.") + defer client.Close() + + var block rpctypes.RawBlockResponse + err = client.Call(&block, "eth_getBlockByNumber", "latest", true) + if err != nil { + return nil, err + } + + return &block, nil +} + +func init() { + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", `Comma separated nodes used for bootstrapping`) + SensorCmd.PersistentFlags().Uint64VarP(&inputSensorParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network ID") if err := SensorCmd.MarkPersistentFlagRequired("network-id"); err != nil { log.Error().Err(err).Msg("Failed to mark network-id as required persistent flag") } - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.ProjectID, "project-id", "P", "", "GCP project ID.") - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.SensorID, "sensor-id", "s", "", "Sensor ID.") + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.ProjectID, "project-id", "P", "", "GCP project ID") + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.SensorID, "sensor-id", "s", "", "Sensor ID when writing block/tx events") if err := SensorCmd.MarkPersistentFlagRequired("sensor-id"); err != nil { log.Error().Err(err).Msg("Failed to mark sensor-id as required persistent flag") } - SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxPeers, "max-peers", "m", 200, "Maximum number of peers to connect to.") + SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxPeers, "max-peers", "m", 200, "Maximum number of peers to connect to") SensorCmd.PersistentFlags().IntVarP(&inputSensorParams.MaxConcurrentDatabaseWrites, "max-db-writes", "D", 10000, - `The maximum number of concurrent database writes to perform. Increasing -this will result in less chance of missing data (i.e. broken pipes) but -can significantly increase memory usage.`) - SensorCmd.PersistentFlags().BoolVarP(&inputSensorParams.ShouldWriteBlocks, "write-blocks", "B", true, "Whether to write blocks to the database.") - SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldWriteBlockEvents, "write-block-events", true, "Whether to write block events to the database.") + `Maximum number of concurrent database writes to perform. Increasing this +will result in less chance of missing data (i.e. broken pipes) but can +significantly increase memory usage.`) + SensorCmd.PersistentFlags().BoolVarP(&inputSensorParams.ShouldWriteBlocks, "write-blocks", "B", true, "Whether to write blocks to the database") + SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldWriteBlockEvents, "write-block-events", true, "Whether to write block events to the database") SensorCmd.PersistentFlags().BoolVarP(&inputSensorParams.ShouldWriteTransactions, "write-txs", "t", true, `Whether to write transactions to the database. This option could significantly increase CPU and memory usage.`) SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldWriteTransactionEvents, "write-tx-events", true, - `Whether to write transaction events to the database. This option could significantly -increase CPU and memory usage.`) - SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof.") - SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "The port to run pprof on.") - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "The file of the private key. If no key file is found then a key file will be generated.") - SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "The TCP network listening port.") - SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DiscoveryPort, "discovery-port", 30303, "The UDP P2P discovery port.") - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "The RPC endpoint used to fetch the latest block.") - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisFile, "genesis", "genesis.json", "The genesis file.") - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisHash, "genesis-hash", "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", "The genesis block hash.") + `Whether to write transaction events to the database. This option could +significantly increase CPU and memory usage.`) + SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.ShouldRunPprof, "pprof", false, "Whether to run pprof") + SensorCmd.PersistentFlags().UintVar(&inputSensorParams.PprofPort, "pprof-port", 6060, "Port pprof runs on") + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.KeyFile, "key-file", "k", "", "Private key file") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.Port, "port", 30303, "TCP network listening port") + SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DiscoveryPort, "discovery-port", 30303, "UDP P2P discovery port") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.RPC, "rpc", "https://polygon-rpc.com", "RPC endpoint used to fetch the latest block") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisFile, "genesis", "genesis.json", "Genesis file") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.GenesisHash, "genesis-hash", "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", "The genesis block hash") SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DialRatio, "dial-ratio", 0, - `The ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of + `Ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of connections to be dialed. Setting this to 0 defaults it to 3.`) - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.NAT, "nat", "any", "The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:).") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.NAT, "nat", "any", "The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)") } diff --git a/doc/polycli_p2p_crawl.md b/doc/polycli_p2p_crawl.md index 0eb22f8d..94ce0b48 100644 --- a/doc/polycli_p2p_crawl.md +++ b/doc/polycli_p2p_crawl.md @@ -19,7 +19,7 @@ polycli p2p crawl [nodes file] [flags] ## Usage -If no nodes.json file exists, run `echo "[]" >> nodes.json` to get started. +If no nodes.json file exists, it will be created. ## Flags ```bash diff --git a/p2p/log.go b/p2p/log.go index 75f50b2a..c3956531 100644 --- a/p2p/log.go +++ b/p2p/log.go @@ -2,9 +2,6 @@ package p2p import ( "sync/atomic" - "time" - - "github.com/rs/zerolog/log" ) // MessageCount is used to help the outer goroutine to receive summary of the @@ -26,61 +23,57 @@ type MessageCount struct { Disconnects int32 `json:",omitempty"` } -// LogMessageCount will log the message counts and reset them based on the -// ticker. This should be called as with a goroutine or it will block -// indefinitely. -func LogMessageCount(count *MessageCount, ticker *time.Ticker) { - for { - if _, ok := <-ticker.C; !ok { - return - } - - c := MessageCount{ - BlockHeaders: atomic.LoadInt32(&count.BlockHeaders), - BlockBodies: atomic.LoadInt32(&count.BlockBodies), - Blocks: atomic.LoadInt32(&count.Blocks), - BlockHashes: atomic.LoadInt32(&count.BlockHashes), - BlockHeaderRequests: atomic.LoadInt32(&count.BlockHeaderRequests), - BlockBodiesRequests: atomic.LoadInt32(&count.BlockBodiesRequests), - Transactions: atomic.LoadInt32(&count.Transactions), - TransactionHashes: atomic.LoadInt32(&count.TransactionHashes), - TransactionRequests: atomic.LoadInt32(&count.TransactionRequests), - Pings: atomic.LoadInt32(&count.Pings), - Errors: atomic.LoadInt32(&count.Errors), - Disconnects: atomic.LoadInt32(&count.Disconnects), - } - - if sum( - c.BlockHeaders, - c.BlockBodies, - c.BlockHashes, - c.BlockHeaderRequests, - c.BlockBodiesRequests, - c.Transactions, - c.TransactionHashes, - c.TransactionRequests, - c.Pings, - c.Errors, - c.Disconnects, - ) == 0 { - continue - } +// Load takes a snapshot of all the counts in a thread-safe manner. Make sure +// you call this before reading any data from the message counter. +func (count *MessageCount) Load() MessageCount { + return MessageCount{ + BlockHeaders: atomic.LoadInt32(&count.BlockHeaders), + BlockBodies: atomic.LoadInt32(&count.BlockBodies), + Blocks: atomic.LoadInt32(&count.Blocks), + BlockHashes: atomic.LoadInt32(&count.BlockHashes), + BlockHeaderRequests: atomic.LoadInt32(&count.BlockHeaderRequests), + BlockBodiesRequests: atomic.LoadInt32(&count.BlockBodiesRequests), + Transactions: atomic.LoadInt32(&count.Transactions), + TransactionHashes: atomic.LoadInt32(&count.TransactionHashes), + TransactionRequests: atomic.LoadInt32(&count.TransactionRequests), + Pings: atomic.LoadInt32(&count.Pings), + Errors: atomic.LoadInt32(&count.Errors), + Disconnects: atomic.LoadInt32(&count.Disconnects), + } +} - log.Info().Interface("counts", c).Msg("Received messages") +// Clear clears all of the counts from the message counter. +func (count *MessageCount) Clear() { + atomic.StoreInt32(&count.BlockHeaders, 0) + atomic.StoreInt32(&count.BlockBodies, 0) + atomic.StoreInt32(&count.Blocks, 0) + atomic.StoreInt32(&count.BlockHashes, 0) + atomic.StoreInt32(&count.BlockHeaderRequests, 0) + atomic.StoreInt32(&count.BlockBodiesRequests, 0) + atomic.StoreInt32(&count.Transactions, 0) + atomic.StoreInt32(&count.TransactionHashes, 0) + atomic.StoreInt32(&count.TransactionRequests, 0) + atomic.StoreInt32(&count.Pings, 0) + atomic.StoreInt32(&count.Errors, 0) + atomic.StoreInt32(&count.Disconnects, 0) +} - atomic.StoreInt32(&count.BlockHeaders, 0) - atomic.StoreInt32(&count.BlockBodies, 0) - atomic.StoreInt32(&count.Blocks, 0) - atomic.StoreInt32(&count.BlockHashes, 0) - atomic.StoreInt32(&count.BlockHeaderRequests, 0) - atomic.StoreInt32(&count.BlockBodiesRequests, 0) - atomic.StoreInt32(&count.Transactions, 0) - atomic.StoreInt32(&count.TransactionHashes, 0) - atomic.StoreInt32(&count.TransactionRequests, 0) - atomic.StoreInt32(&count.Pings, 0) - atomic.StoreInt32(&count.Errors, 0) - atomic.StoreInt32(&count.Disconnects, 0) - } +// IsEmpty checks whether the sum of all the counts is empty. Make sure to call +// Load before this method to get an accurate count. +func (c *MessageCount) IsEmpty() bool { + return sum( + c.BlockHeaders, + c.BlockBodies, + c.BlockHashes, + c.BlockHeaderRequests, + c.BlockBodiesRequests, + c.Transactions, + c.TransactionHashes, + c.TransactionRequests, + c.Pings, + c.Errors, + c.Disconnects, + ) == 0 } func sum(ints ...int32) int32 { diff --git a/p2p/nodeset.go b/p2p/nodeset.go index a7d84426..867795c3 100644 --- a/p2p/nodeset.go +++ b/p2p/nodeset.go @@ -12,7 +12,10 @@ import ( const jsonIndent = " " -// NodeSet is the mapping of the node ID to the URL. +// NodeSet is the mapping of the node ID to the URL. This is used in the p2p +// ping, crawl, and sensor commands. When written this should be consistent with +// the geth/bor static-nodes.json file format which is just an JSON string array +// of URLs. type NodeSet map[enode.ID]string // ReadNodeSet parses a list of discovery node URLs loaded from a JSON file. diff --git a/p2p/protocol.go b/p2p/protocol.go index 85c136b0..361f31b6 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -5,6 +5,8 @@ import ( "context" "errors" "math/big" + "sync" + "sync/atomic" "github.com/ethereum/go-ethereum/common" ethp2p "github.com/ethereum/go-ethereum/p2p" @@ -14,21 +16,22 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/maticnetwork/polygon-cli/p2p/database" - "github.com/maticnetwork/polygon-cli/rpctypes" ) // conn represents an individual connection with a peer. type conn struct { - sensorID string - node *enode.Node - logger zerolog.Logger - rw ethp2p.MsgReadWriter - db database.Database + sensorID string + node *enode.Node + logger zerolog.Logger + rw ethp2p.MsgReadWriter + db database.Database + head *HeadBlock + headMutex *sync.RWMutex + count *MessageCount // requests is used to store the request ID and the block hash. This is used // when fetching block bodies because the eth protocol block bodies do not @@ -41,6 +44,7 @@ type conn struct { oldestBlock *types.Header } +// Eth66ProtocolOptions is the options used when creating a new eth66 protocol. type Eth66ProtocolOptions struct { Context context.Context Database database.Database @@ -50,20 +54,29 @@ type Eth66ProtocolOptions struct { SensorID string NetworkID uint64 Peers chan *enode.Node + Count *MessageCount + + // Head keeps track of the current head block of the chain. This is required + // when doing the status exchange. + Head *HeadBlock + HeadMutex *sync.RWMutex +} + +// HeadBlock contains the necessary head block data for the status message. +type HeadBlock struct { + Hash common.Hash + TotalDifficulty *big.Int + Number uint64 } +// NewEth66Proctocol creates the new eth66 protocol. This will handle writing the +// status exchange, message handling, and writing blocks/txs to the database. func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { return ethp2p.Protocol{ Name: "eth", Version: 66, Length: 17, Run: func(p *ethp2p.Peer, rw ethp2p.MsgReadWriter) error { - block, err := getLatestBlock(opts.RPC) - if err != nil { - log.Error().Err(err).Msg("Failed to get latest block") - return err - } - c := conn{ sensorID: opts.SensorID, node: p.Node(), @@ -72,23 +85,32 @@ func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { db: opts.Database, requests: list.New(), requestNum: 0, + head: opts.Head, + headMutex: opts.HeadMutex, + count: opts.Count, } + c.headMutex.RLock() status := eth.StatusPacket{ ProtocolVersion: 66, NetworkID: opts.NetworkID, Genesis: opts.GenesisHash, - ForkID: forkid.NewID(opts.Genesis.Config, opts.GenesisHash, block.Number.ToUint64()), - Head: block.Hash.ToHash(), - TD: block.TotalDifficulty.ToBigInt(), + ForkID: forkid.NewID(opts.Genesis.Config, opts.GenesisHash, opts.Head.Number), + Head: opts.Head.Hash, + TD: opts.Head.TotalDifficulty, } - if err = c.statusExchange(&status); err != nil { + err := c.statusExchange(&status) + c.headMutex.RUnlock() + if err != nil { return err } + // Send the node to the peers channel. This allows the peers to be captured + // across all connections and written to the nodes.json file. opts.Peers <- p.Node() ctx := opts.Context + // Handle all the of the messages here. for { msg, err := rw.ReadMsg() if err != nil { @@ -122,6 +144,9 @@ func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { log.Trace().Interface("msg", msg).Send() } + // All the handler functions are built in a way where returning an error + // should drop the connection. If the connection shouldn't be dropped, + // then return nil and log the error instead. if err != nil { c.logger.Error().Err(err).Send() return err @@ -135,22 +160,8 @@ func NewEth66Protocol(opts Eth66ProtocolOptions) ethp2p.Protocol { } } -func getLatestBlock(url string) (*rpctypes.RawBlockResponse, error) { - client, err := rpc.Dial(url) - if err != nil { - return nil, err - } - defer client.Close() - - var block rpctypes.RawBlockResponse - err = client.Call(&block, "eth_getBlockByNumber", "latest", true) - if err != nil { - return nil, err - } - - return &block, nil -} - +// statusExchange will exchange status message between the nodes. It will return +// and error if the nodes are incompatible. func (c *conn) statusExchange(packet *eth.StatusPacket) error { err := ethp2p.Send(c.rw, eth.StatusMsg, &packet) if err != nil { @@ -162,6 +173,10 @@ func (c *conn) statusExchange(packet *eth.StatusPacket) error { return err } + if msg.Code != eth.StatusMsg { + return errors.New("expected status message code") + } + var status eth.StatusPacket err = msg.Decode(&status) if err != nil { @@ -237,6 +252,8 @@ func (c *conn) handleNewBlockHashes(ctx context.Context, msg ethp2p.Msg) error { return err } + atomic.AddInt32(&c.count.BlockHashes, int32(len(packet))) + hashes := make([]common.Hash, 0, len(packet)) for _, hash := range packet { hashes = append(hashes, hash.Hash) @@ -256,6 +273,8 @@ func (c *conn) handleTransactions(ctx context.Context, msg ethp2p.Msg) error { return err } + atomic.AddInt32(&c.count.Transactions, int32(len(txs))) + c.db.WriteTransactions(ctx, c.node, txs) return nil @@ -266,6 +285,9 @@ func (c *conn) handleGetBlockHeaders(msg ethp2p.Msg) error { if err := msg.Decode(&request); err != nil { return err } + + atomic.AddInt32(&c.count.BlockHeaderRequests, 1) + return ethp2p.Send( c.rw, eth.GetBlockHeadersMsg, @@ -280,6 +302,8 @@ func (c *conn) handleBlockHeaders(ctx context.Context, msg ethp2p.Msg) error { } headers := packet.BlockHeadersPacket + atomic.AddInt32(&c.count.BlockHeaders, int32(len(headers))) + for _, header := range headers { if err := c.getParentBlock(ctx, header); err != nil { return err @@ -296,6 +320,9 @@ func (c *conn) handleGetBlockBodies(msg ethp2p.Msg) error { if err := msg.Decode(&request); err != nil { return err } + + atomic.AddInt32(&c.count.BlockBodiesRequests, int32(len(request.GetBlockBodiesPacket))) + return ethp2p.Send( c.rw, eth.GetBlockHeadersMsg, @@ -313,6 +340,8 @@ func (c *conn) handleBlockBodies(ctx context.Context, msg ethp2p.Msg) error { return nil } + atomic.AddInt32(&c.count.BlockBodies, int32(len(packet.BlockBodiesPacket))) + var hash *common.Hash for e := c.requests.Front(); e != nil; e = e.Next() { r, ok := e.Value.(request) @@ -344,6 +373,18 @@ func (c *conn) handleNewBlock(ctx context.Context, msg ethp2p.Msg) error { return err } + atomic.AddInt32(&c.count.Blocks, 1) + + // Set the head block if newer. + c.headMutex.Lock() + if block.Block.Number().Uint64() > c.head.Number && block.TD.Cmp(c.head.TotalDifficulty) == 1 { + c.head.Hash = block.Block.Hash() + c.head.TotalDifficulty = block.TD + c.head.Number = block.Block.Number().Uint64() + c.logger.Info().Interface("head", c.head).Msg("Setting head block") + } + c.headMutex.Unlock() + if err := c.getParentBlock(ctx, block.Block.Header()); err != nil { return err } @@ -358,6 +399,9 @@ func (c *conn) handleGetPooledTransactions(msg ethp2p.Msg) error { if err := msg.Decode(&request); err != nil { return err } + + atomic.AddInt32(&c.count.TransactionRequests, int32(len(request.GetPooledTransactionsPacket))) + return ethp2p.Send(c.rw, eth.GetPooledTransactionsMsg, ð.PooledTransactionsPacket66{ RequestId: request.RequestId, }) @@ -369,6 +413,8 @@ func (c *conn) handleNewPooledTransactionHashes(ctx context.Context, msg ethp2p. return err } + atomic.AddInt32(&c.count.TransactionHashes, int32(len(txs))) + if !c.db.ShouldWriteTransactions() || !c.db.ShouldWriteTransactionEvents() { return nil } @@ -388,6 +434,8 @@ func (c *conn) handlePooledTransactions(ctx context.Context, msg ethp2p.Msg) err return err } + atomic.AddInt32(&c.count.Transactions, int32(len(packet.PooledTransactionsPacket))) + c.db.WriteTransactions(ctx, c.node, packet.PooledTransactionsPacket) return nil From a89905a626841166b8ee50ffdc17907df607bae6 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Fri, 11 Aug 2023 14:47:47 -0400 Subject: [PATCH 24/32] update docs --- doc/polycli_p2p_sensor.md | 47 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index d3a33214..0472bf7a 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -19,35 +19,34 @@ polycli p2p sensor [nodes file] [flags] ## Usage -If no nodes.json file exists, run `echo "[]" >> nodes.json` to get started. +If no nodes.json file exists, it will be created. ## Flags ```bash - -b, --bootnodes string Comma separated nodes used for bootstrapping. At least one bootnode is - required, so other nodes in the network can discover each other. - --dial-ratio int The ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of + -b, --bootnodes string Comma separated nodes used for bootstrapping + --dial-ratio int Ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of connections to be dialed. Setting this to 0 defaults it to 3. - --discovery-port int The UDP P2P discovery port. (default 30303) - --genesis string The genesis file. (default "genesis.json") - --genesis-hash string The genesis block hash. (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") + --discovery-port int UDP P2P discovery port (default 30303) + --genesis string Genesis file (default "genesis.json") + --genesis-hash string The genesis block hash (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") -h, --help help for sensor - -k, --key-file string The file of the private key. If no key file is found then a key file will be generated. - -D, --max-db-writes int The maximum number of concurrent database writes to perform. Increasing - this will result in less chance of missing data (i.e. broken pipes) but - can significantly increase memory usage. (default 10000) - -m, --max-peers int Maximum number of peers to connect to. (default 200) - --nat string The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:). (default "any") - -n, --network-id uint Filter discovered nodes by this network ID. - --port int The TCP network listening port. (default 30303) - --pprof Whether to run pprof. - --pprof-port uint The port to run pprof on. (default 6060) - -P, --project-id string GCP project ID. - --rpc string The RPC endpoint used to fetch the latest block. (default "https://polygon-rpc.com") - -s, --sensor-id string Sensor ID. - --write-block-events Whether to write block events to the database. (default true) - -B, --write-blocks Whether to write blocks to the database. (default true) - --write-tx-events Whether to write transaction events to the database. This option could significantly - increase CPU and memory usage. (default true) + -k, --key-file string Private key file + -D, --max-db-writes int Maximum number of concurrent database writes to perform. Increasing this + will result in less chance of missing data (i.e. broken pipes) but can + significantly increase memory usage. (default 10000) + -m, --max-peers int Maximum number of peers to connect to (default 200) + --nat string The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:) (default "any") + -n, --network-id uint Filter discovered nodes by this network ID + --port int TCP network listening port (default 30303) + --pprof Whether to run pprof + --pprof-port uint Port pprof runs on (default 6060) + -P, --project-id string GCP project ID + --rpc string RPC endpoint used to fetch the latest block (default "https://polygon-rpc.com") + -s, --sensor-id string Sensor ID when writing block/tx events + --write-block-events Whether to write block events to the database (default true) + -B, --write-blocks Whether to write blocks to the database (default true) + --write-tx-events Whether to write transaction events to the database. This option could + significantly increase CPU and memory usage. (default true) -t, --write-txs Whether to write transactions to the database. This option could significantly increase CPU and memory usage. (default true) ``` From d3b11e35fe77b2dd00865718d6c0e82e7a4e8ba5 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Fri, 11 Aug 2023 14:57:57 -0400 Subject: [PATCH 25/32] update when the nodes file gets written --- cmd/p2p/sensor/sensor.go | 9 +++++---- p2p/log.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 647c0230..39e75ce6 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -214,13 +214,14 @@ var SensorCmd = &cobra.Command{ count := opts.Count.Load() opts.Count.Clear() log.Info().Interface("peers", server.PeerCount()).Interface("counts", count).Send() - - if err := p2p.WriteNodeSet(inputSensorParams.NodesFile, peers); err != nil { - log.Error().Err(err).Msg("Failed to write nodes to file") - } case peer := <-opts.Peers: + // Update the peer list and the nodes file. if _, ok := peers[peer.ID()]; !ok { peers[peer.ID()] = peer.URLv4() + + if err := p2p.WriteNodeSet(inputSensorParams.NodesFile, peers); err != nil { + log.Error().Err(err).Msg("Failed to write nodes to file") + } } case <-signals: // This gracefully stops the sensor so that the peers can be written to diff --git a/p2p/log.go b/p2p/log.go index c3956531..06d227eb 100644 --- a/p2p/log.go +++ b/p2p/log.go @@ -24,7 +24,7 @@ type MessageCount struct { } // Load takes a snapshot of all the counts in a thread-safe manner. Make sure -// you call this before reading any data from the message counter. +// you call this and read from the returned object. func (count *MessageCount) Load() MessageCount { return MessageCount{ BlockHeaders: atomic.LoadInt32(&count.BlockHeaders), From 01672da49885e8e003519ec2f9aeaca36225eaac Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Fri, 11 Aug 2023 15:00:17 -0400 Subject: [PATCH 26/32] update docs --- cmd/p2p/crawl/crawl.go | 10 +++++----- cmd/p2p/ping/ping.go | 4 ++-- doc/polycli_p2p_crawl.md | 10 +++++----- doc/polycli_p2p_ping.md | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/p2p/crawl/crawl.go b/cmd/p2p/crawl/crawl.go index 6b802fc2..327c9e46 100644 --- a/cmd/p2p/crawl/crawl.go +++ b/cmd/p2p/crawl/crawl.go @@ -101,9 +101,9 @@ required, so other nodes in the network can discover each other.`) if err := CrawlCmd.MarkPersistentFlagRequired("bootnodes"); err != nil { log.Error().Err(err).Msg("Failed to mark bootnodes as required persistent flag") } - CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.Timeout, "timeout", "t", "30m0s", "Time limit for the crawl.") - CrawlCmd.PersistentFlags().IntVarP(&inputCrawlParams.Threads, "parallel", "p", 16, "How many parallel discoveries to attempt.") - CrawlCmd.PersistentFlags().Uint64VarP(&inputCrawlParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network id.") - CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.Database, "database", "d", "", "Node database for updating and storing client information.") - CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.RevalidationInterval, "revalidation-interval", "r", "10m", "The amount of time it takes to retry connecting to a failed peer.") + CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.Timeout, "timeout", "t", "30m0s", "Time limit for the crawl") + CrawlCmd.PersistentFlags().IntVarP(&inputCrawlParams.Threads, "parallel", "p", 16, "How many parallel discoveries to attempt") + CrawlCmd.PersistentFlags().Uint64VarP(&inputCrawlParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network id") + CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.Database, "database", "d", "", "Node database for updating and storing client information") + CrawlCmd.PersistentFlags().StringVarP(&inputCrawlParams.RevalidationInterval, "revalidation-interval", "r", "10m", "Time before retrying to connect to a failed peer") } diff --git a/cmd/p2p/ping/ping.go b/cmd/p2p/ping/ping.go index e591d306..c34a2f6d 100644 --- a/cmd/p2p/ping/ping.go +++ b/cmd/p2p/ping/ping.go @@ -142,8 +142,8 @@ can see other messages the peer sends (e.g. blocks, transactions, etc.).`, } func init() { - PingCmd.PersistentFlags().StringVarP(&inputPingParams.OutputFile, "output", "o", "", "Write ping results to output file. (default stdout)") - PingCmd.PersistentFlags().IntVarP(&inputPingParams.Threads, "parallel", "p", 16, "How many parallel pings to attempt.") + PingCmd.PersistentFlags().StringVarP(&inputPingParams.OutputFile, "output", "o", "", "Write ping results to output file (default stdout)") + PingCmd.PersistentFlags().IntVarP(&inputPingParams.Threads, "parallel", "p", 16, "How many parallel pings to attempt") PingCmd.PersistentFlags().BoolVarP(&inputPingParams.Listen, "listen", "l", true, `Keep the connection open and listen to the peer. This only works if the first argument is an enode/enr, not a nodes file.`) diff --git a/doc/polycli_p2p_crawl.md b/doc/polycli_p2p_crawl.md index 94ce0b48..5090f98e 100644 --- a/doc/polycli_p2p_crawl.md +++ b/doc/polycli_p2p_crawl.md @@ -25,12 +25,12 @@ If no nodes.json file exists, it will be created. ```bash -b, --bootnodes string Comma separated nodes used for bootstrapping. At least one bootnode is required, so other nodes in the network can discover each other. - -d, --database string Node database for updating and storing client information. + -d, --database string Node database for updating and storing client information -h, --help help for crawl - -n, --network-id uint Filter discovered nodes by this network id. - -p, --parallel int How many parallel discoveries to attempt. (default 16) - -r, --revalidation-interval string The amount of time it takes to retry connecting to a failed peer. (default "10m") - -t, --timeout string Time limit for the crawl. (default "30m0s") + -n, --network-id uint Filter discovered nodes by this network id + -p, --parallel int How many parallel discoveries to attempt (default 16) + -r, --revalidation-interval string Time before retrying to connect to a failed peer (default "10m") + -t, --timeout string Time limit for the crawl (default "30m0s") ``` The command also inherits flags from parent commands. diff --git a/doc/polycli_p2p_ping.md b/doc/polycli_p2p_ping.md index d6070041..737da58b 100644 --- a/doc/polycli_p2p_ping.md +++ b/doc/polycli_p2p_ping.md @@ -31,8 +31,8 @@ can see other messages the peer sends (e.g. blocks, transactions, etc.). -h, --help help for ping -l, --listen Keep the connection open and listen to the peer. This only works if the first argument is an enode/enr, not a nodes file. (default true) - -o, --output string Write ping results to output file. (default stdout) - -p, --parallel int How many parallel pings to attempt. (default 16) + -o, --output string Write ping results to output file (default stdout) + -p, --parallel int How many parallel pings to attempt (default 16) ``` The command also inherits flags from parent commands. From 3a0e2b242a22d7d9aa21c9063ca3f497c5d55d26 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 14 Aug 2023 12:37:12 -0400 Subject: [PATCH 27/32] fix protocol msg codes --- p2p/protocol.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/p2p/protocol.go b/p2p/protocol.go index 361f31b6..aa9f20d2 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -184,7 +184,7 @@ func (c *conn) statusExchange(packet *eth.StatusPacket) error { } if status.NetworkID != packet.NetworkID { - return errors.New("network IDs mismatch") + return ethp2p.DiscUselessPeer } c.logger.Info().Interface("status", status).Msg("New peer") @@ -290,7 +290,7 @@ func (c *conn) handleGetBlockHeaders(msg ethp2p.Msg) error { return ethp2p.Send( c.rw, - eth.GetBlockHeadersMsg, + eth.BlockHeadersMsg, ð.BlockHeadersPacket66{RequestId: request.RequestId}, ) } @@ -325,7 +325,7 @@ func (c *conn) handleGetBlockBodies(msg ethp2p.Msg) error { return ethp2p.Send( c.rw, - eth.GetBlockHeadersMsg, + eth.BlockBodiesMsg, ð.BlockBodiesPacket66{RequestId: request.RequestId}, ) } @@ -402,9 +402,10 @@ func (c *conn) handleGetPooledTransactions(msg ethp2p.Msg) error { atomic.AddInt32(&c.count.TransactionRequests, int32(len(request.GetPooledTransactionsPacket))) - return ethp2p.Send(c.rw, eth.GetPooledTransactionsMsg, ð.PooledTransactionsPacket66{ - RequestId: request.RequestId, - }) + return ethp2p.Send( + c.rw, + eth.PooledTransactionsMsg, + ð.PooledTransactionsPacket66{RequestId: request.RequestId}) } func (c *conn) handleNewPooledTransactionHashes(ctx context.Context, msg ethp2p.Msg) error { @@ -446,7 +447,9 @@ func (c *conn) handleGetReceipts(msg ethp2p.Msg) error { if err := msg.Decode(&request); err != nil { return err } - return ethp2p.Send(c.rw, eth.GetBlockHeadersMsg, ð.ReceiptsPacket66{ - RequestId: request.RequestId, - }) + return ethp2p.Send( + c.rw, + eth.ReceiptsMsg, + ð.ReceiptsPacket66{RequestId: request.RequestId}, + ) } From 56f0b14c8a558c8528aecd15e2c45bebd6c1084e Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 14 Aug 2023 13:13:19 -0400 Subject: [PATCH 28/32] add quick start flag --- cmd/p2p/sensor/sensor.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index 39e75ce6..f9e32c3b 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -52,6 +52,7 @@ type ( GenesisHash string DialRatio int NAT string + QuickStart bool bootnodes []*enode.Node nodes []*enode.Node @@ -171,20 +172,23 @@ var SensorCmd = &cobra.Command{ Count: &p2p.MessageCount{}, } - server := ethp2p.Server{ - Config: ethp2p.Config{ - PrivateKey: inputSensorParams.privateKey, - BootstrapNodes: inputSensorParams.bootnodes, - StaticNodes: inputSensorParams.nodes, - MaxPeers: inputSensorParams.MaxPeers, - ListenAddr: fmt.Sprintf(":%d", inputSensorParams.Port), - DiscAddr: fmt.Sprintf(":%d", inputSensorParams.DiscoveryPort), - Protocols: []ethp2p.Protocol{p2p.NewEth66Protocol(opts)}, - DialRatio: inputSensorParams.DialRatio, - NAT: inputSensorParams.nat, - }, + config := ethp2p.Config{ + PrivateKey: inputSensorParams.privateKey, + BootstrapNodes: inputSensorParams.bootnodes, + MaxPeers: inputSensorParams.MaxPeers, + ListenAddr: fmt.Sprintf(":%d", inputSensorParams.Port), + DiscAddr: fmt.Sprintf(":%d", inputSensorParams.DiscoveryPort), + Protocols: []ethp2p.Protocol{p2p.NewEth66Protocol(opts)}, + DialRatio: inputSensorParams.DialRatio, + NAT: inputSensorParams.nat, } + if inputSensorParams.QuickStart { + config.StaticNodes = inputSensorParams.nodes + } + + server := ethp2p.Server{Config: config} + log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor") // Starting the server isn't actually a blocking call so the sensor needs to @@ -299,5 +303,9 @@ significantly increase CPU and memory usage.`) SensorCmd.PersistentFlags().IntVar(&inputSensorParams.DialRatio, "dial-ratio", 0, `Ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of connections to be dialed. Setting this to 0 defaults it to 3.`) - SensorCmd.PersistentFlags().StringVar(&inputSensorParams.NAT, "nat", "any", "The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)") + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.NAT, "nat", "any", "NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)") + SensorCmd.PersistentFlags().BoolVar(&inputSensorParams.QuickStart, "quick-start", false, + `Whether to load the nodes.json as static nodes to quickly start the network. +This produces faster development cycles but can prevent the sensor from being to +connect to new peers if the nodes.json file is large.`) } From b898915a460d4a7e53bca0255d0e392f71c27b41 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 14 Aug 2023 13:28:27 -0400 Subject: [PATCH 29/32] update docs --- doc/polycli_p2p_sensor.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index 0472bf7a..5f98f5a1 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -35,12 +35,15 @@ If no nodes.json file exists, it will be created. will result in less chance of missing data (i.e. broken pipes) but can significantly increase memory usage. (default 10000) -m, --max-peers int Maximum number of peers to connect to (default 200) - --nat string The NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:) (default "any") + --nat string NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:) (default "any") -n, --network-id uint Filter discovered nodes by this network ID --port int TCP network listening port (default 30303) --pprof Whether to run pprof --pprof-port uint Port pprof runs on (default 6060) -P, --project-id string GCP project ID + --quick-start Whether to load the nodes.json as static nodes to quickly start the network. + This produces faster development cycles but can prevent the sensor from being to + connect to new peers if the nodes.json file is large. --rpc string RPC endpoint used to fetch the latest block (default "https://polygon-rpc.com") -s, --sensor-id string Sensor ID when writing block/tx events --write-block-events Whether to write block events to the database (default true) From b627cc7f1889593c26caf90ba3e62840997d0464 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 14 Aug 2023 13:52:12 -0400 Subject: [PATCH 30/32] add trusted peers flag --- cmd/p2p/sensor/sensor.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/cmd/p2p/sensor/sensor.go b/cmd/p2p/sensor/sensor.go index f9e32c3b..db73f076 100644 --- a/cmd/p2p/sensor/sensor.go +++ b/cmd/p2p/sensor/sensor.go @@ -34,6 +34,7 @@ type ( Bootnodes string NetworkID uint64 NodesFile string + TrustedNodesFile string ProjectID string SensorID string MaxPeers int @@ -54,11 +55,12 @@ type ( NAT string QuickStart bool - bootnodes []*enode.Node - nodes []*enode.Node - privateKey *ecdsa.PrivateKey - genesis core.Genesis - nat nat.Interface + bootnodes []*enode.Node + nodes []*enode.Node + trustedNodes []*enode.Node + privateKey *ecdsa.PrivateKey + genesis core.Genesis + nat nat.Interface } ) @@ -80,6 +82,13 @@ var SensorCmd = &cobra.Command{ log.Warn().Err(err).Msgf("Creating nodes file %v because it does not exist", inputSensorParams.NodesFile) } + if len(inputSensorParams.TrustedNodesFile) > 0 { + inputSensorParams.trustedNodes, err = p2p.ReadNodeSet(inputSensorParams.TrustedNodesFile) + if err != nil { + log.Warn().Err(err).Msgf("Trusted nodes file %v not found", inputSensorParams.TrustedNodesFile) + } + } + if len(inputSensorParams.Bootnodes) > 0 { inputSensorParams.bootnodes, err = p2p.ParseBootnodes(inputSensorParams.Bootnodes) if err != nil { @@ -175,6 +184,7 @@ var SensorCmd = &cobra.Command{ config := ethp2p.Config{ PrivateKey: inputSensorParams.privateKey, BootstrapNodes: inputSensorParams.bootnodes, + TrustedNodes: inputSensorParams.trustedNodes, MaxPeers: inputSensorParams.MaxPeers, ListenAddr: fmt.Sprintf(":%d", inputSensorParams.Port), DiscAddr: fmt.Sprintf(":%d", inputSensorParams.DiscoveryPort), @@ -269,7 +279,7 @@ func getLatestBlock(url string) (*rpctypes.RawBlockResponse, error) { } func init() { - SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", `Comma separated nodes used for bootstrapping`) + SensorCmd.PersistentFlags().StringVarP(&inputSensorParams.Bootnodes, "bootnodes", "b", "", "Comma separated nodes used for bootstrapping") SensorCmd.PersistentFlags().Uint64VarP(&inputSensorParams.NetworkID, "network-id", "n", 0, "Filter discovered nodes by this network ID") if err := SensorCmd.MarkPersistentFlagRequired("network-id"); err != nil { log.Error().Err(err).Msg("Failed to mark network-id as required persistent flag") @@ -308,4 +318,5 @@ connections to be dialed. Setting this to 0 defaults it to 3.`) `Whether to load the nodes.json as static nodes to quickly start the network. This produces faster development cycles but can prevent the sensor from being to connect to new peers if the nodes.json file is large.`) + SensorCmd.PersistentFlags().StringVar(&inputSensorParams.TrustedNodesFile, "trusted-nodes", "", "Trusted nodes file") } From 6203cc723287274b598648a97cff4592212c4750 Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Mon, 14 Aug 2023 13:59:24 -0400 Subject: [PATCH 31/32] update docs --- doc/polycli_p2p_sensor.md | 59 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/doc/polycli_p2p_sensor.md b/doc/polycli_p2p_sensor.md index 5f98f5a1..91f54c76 100644 --- a/doc/polycli_p2p_sensor.md +++ b/doc/polycli_p2p_sensor.md @@ -23,35 +23,36 @@ If no nodes.json file exists, it will be created. ## Flags ```bash - -b, --bootnodes string Comma separated nodes used for bootstrapping - --dial-ratio int Ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of - connections to be dialed. Setting this to 0 defaults it to 3. - --discovery-port int UDP P2P discovery port (default 30303) - --genesis string Genesis file (default "genesis.json") - --genesis-hash string The genesis block hash (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") - -h, --help help for sensor - -k, --key-file string Private key file - -D, --max-db-writes int Maximum number of concurrent database writes to perform. Increasing this - will result in less chance of missing data (i.e. broken pipes) but can - significantly increase memory usage. (default 10000) - -m, --max-peers int Maximum number of peers to connect to (default 200) - --nat string NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:) (default "any") - -n, --network-id uint Filter discovered nodes by this network ID - --port int TCP network listening port (default 30303) - --pprof Whether to run pprof - --pprof-port uint Port pprof runs on (default 6060) - -P, --project-id string GCP project ID - --quick-start Whether to load the nodes.json as static nodes to quickly start the network. - This produces faster development cycles but can prevent the sensor from being to - connect to new peers if the nodes.json file is large. - --rpc string RPC endpoint used to fetch the latest block (default "https://polygon-rpc.com") - -s, --sensor-id string Sensor ID when writing block/tx events - --write-block-events Whether to write block events to the database (default true) - -B, --write-blocks Whether to write blocks to the database (default true) - --write-tx-events Whether to write transaction events to the database. This option could - significantly increase CPU and memory usage. (default true) - -t, --write-txs Whether to write transactions to the database. This option could significantly - increase CPU and memory usage. (default true) + -b, --bootnodes string Comma separated nodes used for bootstrapping + --dial-ratio int Ratio of inbound to dialed connections. A dial ratio of 2 allows 1/2 of + connections to be dialed. Setting this to 0 defaults it to 3. + --discovery-port int UDP P2P discovery port (default 30303) + --genesis string Genesis file (default "genesis.json") + --genesis-hash string The genesis block hash (default "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b") + -h, --help help for sensor + -k, --key-file string Private key file + -D, --max-db-writes int Maximum number of concurrent database writes to perform. Increasing this + will result in less chance of missing data (i.e. broken pipes) but can + significantly increase memory usage. (default 10000) + -m, --max-peers int Maximum number of peers to connect to (default 200) + --nat string NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:) (default "any") + -n, --network-id uint Filter discovered nodes by this network ID + --port int TCP network listening port (default 30303) + --pprof Whether to run pprof + --pprof-port uint Port pprof runs on (default 6060) + -P, --project-id string GCP project ID + --quick-start Whether to load the nodes.json as static nodes to quickly start the network. + This produces faster development cycles but can prevent the sensor from being to + connect to new peers if the nodes.json file is large. + --rpc string RPC endpoint used to fetch the latest block (default "https://polygon-rpc.com") + -s, --sensor-id string Sensor ID when writing block/tx events + --trusted-nodes string Trusted nodes file + --write-block-events Whether to write block events to the database (default true) + -B, --write-blocks Whether to write blocks to the database (default true) + --write-tx-events Whether to write transaction events to the database. This option could + significantly increase CPU and memory usage. (default true) + -t, --write-txs Whether to write transactions to the database. This option could significantly + increase CPU and memory usage. (default true) ``` The command also inherits flags from parent commands. From 7d0c0f99f71531876b4937be8494112d06adf35b Mon Sep 17 00:00:00 2001 From: minhd-vu Date: Tue, 15 Aug 2023 05:54:10 -0400 Subject: [PATCH 32/32] set head block differently --- p2p/protocol.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/p2p/protocol.go b/p2p/protocol.go index aa9f20d2..7bda78d1 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -378,9 +378,12 @@ func (c *conn) handleNewBlock(ctx context.Context, msg ethp2p.Msg) error { // Set the head block if newer. c.headMutex.Lock() if block.Block.Number().Uint64() > c.head.Number && block.TD.Cmp(c.head.TotalDifficulty) == 1 { - c.head.Hash = block.Block.Hash() - c.head.TotalDifficulty = block.TD - c.head.Number = block.Block.Number().Uint64() + *c.head = HeadBlock{ + Hash: block.Block.Hash(), + TotalDifficulty: block.TD, + Number: block.Block.Number().Uint64(), + } + c.logger.Info().Interface("head", c.head).Msg("Setting head block") } c.headMutex.Unlock()