Help us shape the future of Go! Take the 2026 Go Developer Survey

sensor

package
v0.1.118 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: AGPL-3.0 Imports: 36 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var SensorCmd = &cobra.Command{
	Use:   "sensor [nodes file]",
	Short: "Start a devp2p sensor that discovers other peers and will receive blocks and transactions.",
	Long:  sensorUsage,
	Args:  cobra.MinimumNArgs(1),
	PreRunE: func(cmd *cobra.Command, args []string) (err error) {
		inputSensorParams.NodesFile = args[0]
		_, err = p2p.ReadNodeSet(inputSensorParams.NodesFile)
		if err != nil {
			log.Warn().Err(err).Msgf("Creating nodes file %v because it does not exist", inputSensorParams.NodesFile)
		}

		if len(inputSensorParams.StaticNodesFile) > 0 {
			inputSensorParams.staticNodes, err = p2p.ReadNodeSet(inputSensorParams.StaticNodesFile)
			if err != nil {
				log.Warn().Err(err).Msgf("Static nodes file %v not found", inputSensorParams.StaticNodesFile)
			}
		}

		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 {
				return fmt.Errorf("unable to parse bootnodes: %w", err)
			}
		}

		if inputSensorParams.NetworkID == 0 {
			return errors.New("network ID must be greater than zero")
		}

		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
			}
		}

		if len(inputSensorParams.PrivateKey) > 0 {
			inputSensorParams.privateKey, err = crypto.HexToECDSA(inputSensorParams.PrivateKey)
			if err != nil {
				log.Error().Err(err).Msg("Failed to parse PrivateKey")
				return err
			}
		}

		inputSensorParams.nat, err = nat.Parse(inputSensorParams.NAT)
		if err != nil {
			log.Error().Err(err).Msg("Failed to parse NAT")
			return err
		}

		if inputSensorParams.ValidateBlockSigner &&
			(inputSensorParams.ShouldBroadcastBlocks || inputSensorParams.ShouldBroadcastBlockHashes) {
			if inputSensorParams.HeimdallURL == "" {
				return errors.New("--heimdall-url is required when --validate-block-signer is enabled with block broadcasting")
			}
			if inputSensorParams.ValidatorSetRefresh <= 0 {
				return errors.New("--validator-set-refresh must be greater than zero when --validate-block-signer is enabled with block broadcasting")
			}
		}

		return nil
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		ctx, stop := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM)
		defer stop()

		db, err := newDatabase(ctx)
		if err != nil {
			return err
		}

		defer func() {
			if cerr := db.Close(); cerr != nil {
				log.Error().Err(cerr).Msg("Failed to close database")
			}
		}()

		rpcBlock, err := getLatestBlock(inputSensorParams.RPC)
		if err != nil {
			return err
		}

		head := p2p.NewBlockPacket{
			Block: rpcBlock.ToBlock(),
			TD:    rpcBlock.TotalDifficulty.ToBigInt(),
		}

		peersGauge := p2p.NewPeersGauge()
		metrics := p2p.NewBlockMetrics(head.Block)

		// When block rebroadcasting is enabled and signer validation is on, load
		// the validator set from Heimdall so we only rebroadcast blocks signed
		// by a known validator. The initial fetch is blocking and fatal on
		// failure so we never rebroadcast against an empty validator set.
		var validators *p2p.ValidatorSet
		if inputSensorParams.ValidateBlockSigner &&
			(inputSensorParams.ShouldBroadcastBlocks || inputSensorParams.ShouldBroadcastBlockHashes) {
			validators = p2p.NewValidatorSet(inputSensorParams.HeimdallURL, inputSensorParams.ValidatorSetRefresh)
			if err = validators.Start(ctx); err != nil {
				return fmt.Errorf("failed to load validator set from %s: %w", inputSensorParams.HeimdallURL, err)
			}
		}

		conns := p2p.NewConns(p2p.ConnsOptions{
			BlocksCache:                inputSensorParams.BlocksCache,
			TxsCache:                   inputSensorParams.TxsCache,
			KnownTxsBloom:              inputSensorParams.KnownTxsBloom,
			KnownBlocksMax:             inputSensorParams.KnownBlocksMax,
			Head:                       head,
			ShouldBroadcastTx:          inputSensorParams.ShouldBroadcastTx,
			ShouldBroadcastTxHashes:    inputSensorParams.ShouldBroadcastTxHashes,
			ShouldBroadcastBlocks:      inputSensorParams.ShouldBroadcastBlocks,
			ShouldBroadcastBlockHashes: inputSensorParams.ShouldBroadcastBlockHashes,
			BroadcastWorkers:           inputSensorParams.BroadcastWorkers,
			TxBatchTimeout:             inputSensorParams.TxBatchTimeout,
			TxBroadcastQueueSize:       inputSensorParams.TxBroadcastQueueSize,
			MaxTxPacketSize:            inputSensorParams.MaxTxPacketSize,
			MaxQueuedTxs:               inputSensorParams.MaxQueuedTxs,
			ValidatorSet:               validators,
			CacheOnlyValidatedBlocks:   inputSensorParams.CacheOnlyValidatedBlocks,
		})

		opts := p2p.EthProtocolOptions{
			Context:                    ctx,
			Database:                   db,
			GenesisHash:                common.HexToHash(inputSensorParams.GenesisHash),
			RPC:                        inputSensorParams.RPC,
			SensorID:                   inputSensorParams.SensorID,
			NetworkID:                  inputSensorParams.NetworkID,
			Conns:                      conns,
			ForkID:                     forkid.ID{Hash: [4]byte(inputSensorParams.ForkID)},
			RequestsCache:              inputSensorParams.RequestsCache,
			ParentsCache:               inputSensorParams.ParentsCache,
			ShouldBroadcastTx:          inputSensorParams.ShouldBroadcastTx,
			ShouldBroadcastTxHashes:    inputSensorParams.ShouldBroadcastTxHashes,
			ShouldBroadcastBlocks:      inputSensorParams.ShouldBroadcastBlocks,
			ShouldBroadcastBlockHashes: inputSensorParams.ShouldBroadcastBlockHashes,
		}

		config := ethp2p.Config{
			PrivateKey:     inputSensorParams.privateKey,
			BootstrapNodes: inputSensorParams.bootnodes,
			StaticNodes:    inputSensorParams.staticNodes,
			TrustedNodes:   inputSensorParams.trustedNodes,
			MaxPeers:       inputSensorParams.MaxPeers,
			ListenAddr:     fmt.Sprintf(":%d", inputSensorParams.Port),
			DiscAddr:       fmt.Sprintf(":%d", inputSensorParams.DiscoveryPort),
			DialRatio:      inputSensorParams.DialRatio,
			NAT:            inputSensorParams.nat,
			DiscoveryV4:    !inputSensorParams.NoDiscovery,
			DiscoveryV5:    !inputSensorParams.NoDiscovery,
			Protocols: []ethp2p.Protocol{
				p2p.NewEthProtocol(66, opts),
				p2p.NewEthProtocol(67, opts),
				p2p.NewEthProtocol(68, opts),
				p2p.NewEthProtocol(69, opts),
			},
		}

		server := ethp2p.Server{Config: config}

		log.Info().Str("enode", server.Self().URLv4()).Msg("Starting sensor")

		if err = server.Start(); err != nil {
			return err
		}
		defer stopServer(&server)
		defer conns.Close()

		events := make(chan *ethp2p.PeerEvent)
		sub := server.SubscribeEvents(events)
		defer sub.Unsubscribe()

		ticker := time.NewTicker(2 * time.Second)
		defer ticker.Stop()

		if inputSensorParams.ShouldRunPprof {
			go handlePprof()
		}

		if inputSensorParams.ShouldRunPrometheus {
			go handlePrometheus()
		}

		go handleAPI(&server, conns)
		go handleRPC(conns, inputSensorParams.NetworkID)
		go handleDNSDiscovery(&server)

		for {
			select {
			case <-ticker.C:
				peersGauge.Set(float64(server.PeerCount()))
				db.WritePeers(ctx, server.Peers(), time.Now())
				metrics.Update(conns.HeadBlock().Block, conns.OldestBlock())
				writePeers(server.Peers())
			case <-ctx.Done():
				log.Info().Msg("Stopping sensor")
				return nil
			case event := <-events:
				log.Debug().Any("event", event).Send()
			case err := <-sub.Err():
				log.Error().Err(err).Send()
			}
		}
	},
}

SensorCmd represents the sensor command. This is responsible for starting a sensor and transmitting blocks and transactions to a database.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL