From 23663afbfe54e15b3b552963ca8dd149f7b32d02 Mon Sep 17 00:00:00 2001 From: muXxer Date: Wed, 2 Aug 2023 16:06:10 +0200 Subject: [PATCH] Remove spammer plugin --- config.json | 12 -- pkg/config/spammer_config.go | 37 ---- pkg/spammer/bundle.go | 139 -------------- pkg/spammer/events.go | 35 ---- pkg/spammer/spammer.go | 176 ------------------ plugins/dashboard/plugin.go | 2 - plugins/dashboard/spammer.go | 31 ---- plugins/spammer/cpu_usage.go | 93 ---------- plugins/spammer/plugin.go | 346 ----------------------------------- plugins/webapi/plugin.go | 6 - plugins/webapi/spammer.go | 99 ---------- 11 files changed, 976 deletions(-) delete mode 100644 pkg/config/spammer_config.go delete mode 100644 pkg/spammer/bundle.go delete mode 100644 pkg/spammer/events.go delete mode 100644 pkg/spammer/spammer.go delete mode 100644 plugins/dashboard/spammer.go delete mode 100644 plugins/spammer/cpu_usage.go delete mode 100644 plugins/spammer/plugin.go delete mode 100644 plugins/webapi/spammer.go diff --git a/config.json b/config.json index fba7a5242..4485f1dfb 100644 --- a/config.json +++ b/config.json @@ -102,18 +102,6 @@ "stdout" ] }, - "spammer": { - "address": "HORNET99INTEGRATED99SPAMMER999999999999999999999999999999999999999999999999999999", - "message": "Spamming with HORNET tipselect", - "tag": "HORNET99INTEGRATED99SPAMMER", - "tagSemiLazy": "", - "cpuMaxUsage": 0.8, - "tpsRateLimit": 0.0, - "bundleSize": 1, - "valueSpam": false, - "workers": 0, - "autostart": false - }, "zmq": { "bindAddress": "localhost:5556" }, diff --git a/pkg/config/spammer_config.go b/pkg/config/spammer_config.go deleted file mode 100644 index 80fa9a391..000000000 --- a/pkg/config/spammer_config.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -const ( - // the target address of the spam - CfgSpammerAddress = "spammer.address" - // the message to embed within the spam transactions - CfgSpammerMessage = "spammer.message" - // the tag of the transaction - CfgSpammerTag = "spammer.tag" - // the tag of the transaction if the semi-lazy pool is used (uses "tag" if empty) - CfgSpammerTagSemiLazy = "spammer.tagSemiLazy" - // workers remains idle for a while when cpu usage gets over this limit (0 = disable) - CfgSpammerCPUMaxUsage = "spammer.cpuMaxUsage" - // the rate limit for the spammer (0 = no limit) - CfgSpammerTPSRateLimit = "spammer.tpsRateLimit" - // the size of the spam bundles - CfgSpammerBundleSize = "spammer.bundleSize" - // should be spammed with value bundles - CfgSpammerValueSpam = "spammer.valueSpam" - // the amount of parallel running spammers - CfgSpammerWorkers = "spammer.workers" - // CfgSpammerAutostart automatically starts the spammer on node startup - CfgSpammerAutostart = "spammer.autostart" -) - -func init() { - configFlagSet.String(CfgSpammerAddress, "HORNET99INTEGRATED99SPAMMER999999999999999999999999999999999999999999999999999999", "the target address of the spam") - configFlagSet.String(CfgSpammerMessage, "Spamming with HORNET tipselect", "the message to embed within the spam transactions") - configFlagSet.String(CfgSpammerTag, "HORNET99SPAMMER999999999999", "the tag of the transaction") - configFlagSet.String(CfgSpammerTagSemiLazy, "", "the tag of the transaction if the semi-lazy pool is used (uses \"tag\" if empty)") - configFlagSet.Float64(CfgSpammerCPUMaxUsage, 0.50, "workers remains idle for a while when cpu usage gets over this limit (0 = disable)") - configFlagSet.Float64(CfgSpammerTPSRateLimit, 0.10, "the rate limit for the spammer (0 = no limit)") - configFlagSet.Int(CfgSpammerBundleSize, 1, "the size of the spam bundles") - configFlagSet.Bool(CfgSpammerValueSpam, false, "should be spammed with value bundles") - configFlagSet.Int(CfgSpammerWorkers, 1, "the amount of parallel running spammers") - configFlagSet.Bool(CfgSpammerAutostart, false, "automatically start the spammer on node startup") -} diff --git a/pkg/spammer/bundle.go b/pkg/spammer/bundle.go deleted file mode 100644 index 239a9b493..000000000 --- a/pkg/spammer/bundle.go +++ /dev/null @@ -1,139 +0,0 @@ -package spammer - -import ( - "fmt" - "time" - - "github.com/iotaledger/iota.go/address" - "github.com/iotaledger/iota.go/bundle" - "github.com/iotaledger/iota.go/consts" - "github.com/iotaledger/iota.go/encoding/ascii" - "github.com/iotaledger/iota.go/kerl" - "github.com/iotaledger/iota.go/signing" - "github.com/iotaledger/iota.go/signing/key" - "github.com/iotaledger/iota.go/trinary" -) - -const ( - alphabet = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ" -) - -func integerToAscii(number int) string { - result := "" - for index := 0; index < 7; index++ { - pos := number % 27 - number /= 27 - result = string(alphabet[pos]) + result - } - return result -} - -func createBundle(seed trinary.Trytes, seedIndex uint64, txAddress trinary.Hash, msg string, tagSubstring string, bundleSize int, valueSpam bool, txCount int, additionalMesssage ...string) (bundle.Bundle, error) { - - tag, err := trinary.NewTrytes(tagSubstring + integerToAscii(txCount)) - if err != nil { - return nil, fmt.Errorf("NewTrytes: %v", err.Error()) - } - now := time.Now() - - messageString := msg + fmt.Sprintf("\nCount: %06d", txCount) - messageString += fmt.Sprintf("\nTimestamp: %s", now.Format(time.RFC3339)) - if len(additionalMesssage) > 0 { - messageString = fmt.Sprintf("%v\n%v", messageString, additionalMesssage[0]) - } - - message, err := ascii.EncodeToTrytes(messageString) - if err != nil { - return nil, fmt.Errorf("ASCIIToTrytes: %v", err.Error()) - } - - timestamp := uint64(now.UnixNano() / int64(time.Second)) - - var b bundle.Bundle - - if !valueSpam { - for i := 0; i < bundleSize; i++ { - outEntry := bundle.BundleEntry{ - Address: txAddress, - Value: 0, - Tag: tag, - Timestamp: timestamp, - Length: uint64(1), - SignatureMessageFragments: []trinary.Trytes{trinary.MustPad(message, consts.SignatureMessageFragmentSizeInTrytes)}, - } - b = bundle.AddEntry(b, outEntry) - } - } else { - addresses, err := address.GenerateAddresses(seed, seedIndex, 1, consts.SecurityLevelLow, true) - if err != nil { - return nil, fmt.Errorf("address.GenerateAddresses: %v", err.Error()) - } - - addr := addresses[0][:consts.HashTrytesSize] - - outEntry := bundle.BundleEntry{ - Address: addr, - Value: int64(bundleSize - 1), - Tag: tag, - Timestamp: timestamp, - Length: uint64(1), - SignatureMessageFragments: []trinary.Trytes{trinary.MustPad(message, consts.SignatureMessageFragmentSizeInTrytes)}, - } - b = bundle.AddEntry(b, outEntry) - - for i := 0; i < bundleSize-1; i++ { - inEntry := bundle.BundleEntry{ - Address: addr, - Value: int64(-1), - Tag: tag, - Timestamp: timestamp, - Length: uint64(consts.SecurityLevelLow), - } - b = bundle.AddEntry(b, inEntry) - } - } - - // finalize bundle by adding the bundle hash - b, err = bundle.FinalizeInsecure(b) - if err != nil { - return nil, fmt.Errorf("bundle.FinalizeInsecure: %v", err) - } - - if valueSpam { - // compute signatures for all input txs - normalizedBundleHash := signing.NormalizedBundleHash(b[0].Bundle) - - subseed, err := signing.Subseed(seed, seedIndex) - if err != nil { - return nil, fmt.Errorf("signing.Subseed: %v", err.Error()) - } - - h := kerl.NewKerl() - - prvKey, err := key.Sponge(subseed, consts.SecurityLevelLow, h) - if err != nil { - return nil, fmt.Errorf("signing.Key: %v", err.Error()) - } - - signedFrags := make([]trinary.Trytes, consts.SecurityLevelLow) - for i := 0; i < int(consts.SecurityLevelLow); i++ { - signedFragTrits, err := signing.SignatureFragment( - normalizedBundleHash[i*consts.HashTrytesSize/3:(i+1)*consts.HashTrytesSize/3], - prvKey[i*consts.KeyFragmentLength:(i+1)*consts.KeyFragmentLength], - ) - if err != nil { - return nil, fmt.Errorf("signing.SignatureFragment: %v", err.Error()) - } - signedFrags[i] = trinary.MustTritsToTrytes(signedFragTrits) - } - - // add signed fragments to txs - var indexFirstInputTx int = 1 - for i := 0; i < bundleSize-1; i++ { - b = bundle.AddTrytes(b, signedFrags, indexFirstInputTx) - indexFirstInputTx++ - } - } - - return b, nil -} diff --git a/pkg/spammer/events.go b/pkg/spammer/events.go deleted file mode 100644 index 460c9dc1f..000000000 --- a/pkg/spammer/events.go +++ /dev/null @@ -1,35 +0,0 @@ -package spammer - -import ( - "github.com/iotaledger/hive.go/events" -) - -// SpamStats are stats for a single spam transaction/bundle. -type SpamStats struct { - GTTA float32 `json:"gtta"` - POW float32 `json:"pow"` -} - -// AvgSpamMetrics are average metrics of the created spam. -type AvgSpamMetrics struct { - New uint32 `json:"new"` - AveragePerSecond float32 `json:"avg"` -} - -// SpammerEvents are the events issued by the spammer. -type SpammerEvents struct { - // Fired when a single spam transaction/bundle is issued. - SpamPerformed *events.Event - // Fired when average spam metrics were updated by the worker. - AvgSpamMetricsUpdated *events.Event -} - -// SpamStatsCaller is used to signal new SpamStats. -func SpamStatsCaller(handler interface{}, params ...interface{}) { - handler.(func(*SpamStats))(params[0].(*SpamStats)) -} - -// AvgSpamMetricsCaller is used to signal new AvgSpamMetrics. -func AvgSpamMetricsCaller(handler interface{}, params ...interface{}) { - handler.(func(*AvgSpamMetrics))(params[0].(*AvgSpamMetrics)) -} diff --git a/pkg/spammer/spammer.go b/pkg/spammer/spammer.go deleted file mode 100644 index e4351edd8..000000000 --- a/pkg/spammer/spammer.go +++ /dev/null @@ -1,176 +0,0 @@ -package spammer - -import ( - "fmt" - "time" - - "github.com/iotaledger/iota.go/bundle" - "github.com/iotaledger/iota.go/consts" - "github.com/iotaledger/iota.go/transaction" - "github.com/iotaledger/iota.go/trinary" - - "github.com/iotaledger/hornet/pkg/metrics" - "github.com/iotaledger/hornet/pkg/model/hornet" - "github.com/iotaledger/hornet/pkg/model/tangle" - "github.com/iotaledger/hornet/pkg/pow" - "github.com/iotaledger/hornet/pkg/utils" - "github.com/iotaledger/hornet/plugins/curl" - - "go.uber.org/atomic" -) - -// SendBundleFunc is a function which sends a bundle to the network. -type SendBundleFunc = func(b bundle.Bundle) error - -// SpammerTipselFunc selects tips for the spammer. -type SpammerTipselFunc = func() (isSemiLazy bool, tips hornet.Hashes, err error) - -// Spammer is used to issue transactions to the IOTA network to create load on the tangle. -type Spammer struct { - - // config options - txAddress string - message string - tagSubstring string - tagSemiLazySubstring string - tipselFunc SpammerTipselFunc - mwm int - powHandler *pow.Handler - sendBundleFunc SendBundleFunc - - seed trinary.Trytes - addrIndex *atomic.Uint64 -} - -// New creates a new spammer instance. -func New(txAddress string, message string, tag string, tagSemiLazy string, tipselFunc SpammerTipselFunc, mwm int, powHandler *pow.Handler, sendBundleFunc SendBundleFunc) *Spammer { - - tagSubstring := trinary.MustPad(tag, consts.TagTrinarySize/3)[:consts.TagTrinarySize/3] - tagSemiLazySubstring := tagSubstring - if tagSemiLazy != "" { - tagSemiLazySubstring = trinary.MustPad(tagSemiLazy, consts.TagTrinarySize/3)[:consts.TagTrinarySize/3] - } - - if len(tagSubstring) > 20 { - tagSubstring = string([]rune(tagSubstring)[:20]) - } - if len(tagSemiLazySubstring) > 20 { - tagSemiLazySubstring = string([]rune(tagSemiLazySubstring)[:20]) - } - - return &Spammer{ - txAddress: trinary.MustPad(txAddress, consts.AddressTrinarySize/3)[:consts.AddressTrinarySize/3], - message: message, - tagSubstring: tagSubstring, - tagSemiLazySubstring: tagSemiLazySubstring, - tipselFunc: tipselFunc, - mwm: mwm, - powHandler: powHandler, - sendBundleFunc: sendBundleFunc, - seed: utils.RandomTrytesInsecure(81), - addrIndex: atomic.NewUint64(0), - } -} - -func (s *Spammer) DoSpam(bundleSize int, valueSpam bool, shutdownSignal <-chan struct{}) (time.Duration, time.Duration, error) { - - tag := s.tagSubstring - - timeStart := time.Now() - isSemiLazy, tips, err := s.tipselFunc() - if err != nil { - return time.Duration(0), time.Duration(0), err - } - durationGTTA := time.Since(timeStart) - - if isSemiLazy { - tag = s.tagSemiLazySubstring - } - - infoMsg := fmt.Sprintf("gTTA took %v", durationGTTA.Truncate(time.Millisecond)) - - var seedIndex uint64 - if valueSpam { - seedIndex = s.addrIndex.Inc() - } - - txCountValue := int(metrics.SharedServerMetrics.SentSpamTransactions.Load()) + bundleSize - b, err := createBundle(s.seed, seedIndex, s.txAddress, s.message, tag, bundleSize, valueSpam, txCountValue, infoMsg) - if err != nil { - return time.Duration(0), time.Duration(0), err - } - - timeStart = time.Now() - err = s.doPow(b, tips[0].Trytes(), tips[1].Trytes(), s.mwm, shutdownSignal) - if err != nil { - return time.Duration(0), time.Duration(0), err - } - durationPOW := time.Since(timeStart) - - if err := s.sendBundleFunc(b); err != nil { - return time.Duration(0), time.Duration(0), err - } - - return durationGTTA, durationPOW, nil -} - -func (s *Spammer) doPow(b bundle.Bundle, trunk trinary.Hash, branch trinary.Hash, mwm int, shutdownSignal <-chan struct{}) error { - var prev trinary.Hash - - for i := len(b) - 1; i >= 0; i-- { - switch { - case i == len(b)-1: - // Last tx in the bundle - b[i].TrunkTransaction = trunk - b[i].BranchTransaction = branch - default: - b[i].TrunkTransaction = prev - b[i].BranchTransaction = trunk - } - - b[i].AttachmentTimestamp = time.Now().UnixNano() / int64(time.Millisecond) - b[i].AttachmentTimestampLowerBound = consts.LowerBoundAttachmentTimestamp - b[i].AttachmentTimestampUpperBound = consts.UpperBoundAttachmentTimestamp - - trytes, err := transaction.TransactionToTrytes(&b[i]) - if err != nil { - return err - } - - select { - case <-shutdownSignal: - return tangle.ErrOperationAborted - default: - } - - nonce, err := s.powHandler.DoPoW(trytes, mwm, 1) - if err != nil { - return err - } - - b[i].Nonce = nonce - - // set new transaction hash - hash, err := transactionHash(&b[i]) - if err != nil { - return err - } - - b[i].Hash = hash - prev = hash - } - return nil -} - -// transactionHash makes a transaction hash from the given transaction. -func transactionHash(t *transaction.Transaction) (trinary.Hash, error) { - trits, err := transaction.TransactionToTrits(t) - if err != nil { - return "", err - } - hashTrits, err := curl.Hasher().Hash(trits) - if err != nil { - return "", err - } - return trinary.MustTritsToTrytes(hashTrits), nil -} diff --git a/plugins/dashboard/plugin.go b/plugins/dashboard/plugin.go index dd272c914..da1960d6c 100644 --- a/plugins/dashboard/plugin.go +++ b/plugins/dashboard/plugin.go @@ -182,8 +182,6 @@ func run(_ *node.Plugin) { runTipSelMetricWorker() // run the database size collector runDatabaseSizeCollector() - // run the spammer feed - runSpammerMetricWorker() } func getMilestoneTailHash(index milestone.Index) hornet.Hash { diff --git a/plugins/dashboard/spammer.go b/plugins/dashboard/spammer.go deleted file mode 100644 index c504650e6..000000000 --- a/plugins/dashboard/spammer.go +++ /dev/null @@ -1,31 +0,0 @@ -package dashboard - -import ( - "github.com/iotaledger/hive.go/daemon" - "github.com/iotaledger/hive.go/events" - - "github.com/iotaledger/hornet/pkg/shutdown" - "github.com/iotaledger/hornet/pkg/spammer" - spammerplugin "github.com/iotaledger/hornet/plugins/spammer" -) - -func runSpammerMetricWorker() { - - onSpamPerformed := events.NewClosure(func(metrics *spammer.SpamStats) { - hub.BroadcastMsg(&Msg{Type: MsgTypeSpamMetrics, Data: metrics}) - }) - - onAvgSpamMetricsUpdated := events.NewClosure(func(metrics *spammer.AvgSpamMetrics) { - hub.BroadcastMsg(&Msg{Type: MsgTypeAvgSpamMetrics, Data: metrics}) - }) - - daemon.BackgroundWorker("Dashboard[SpammerMetricUpdater]", func(shutdownSignal <-chan struct{}) { - spammerplugin.Events.SpamPerformed.Attach(onSpamPerformed) - spammerplugin.Events.AvgSpamMetricsUpdated.Attach(onAvgSpamMetricsUpdated) - <-shutdownSignal - log.Info("Stopping Dashboard[SpammerMetricUpdater] ...") - spammerplugin.Events.SpamPerformed.Detach(onSpamPerformed) - spammerplugin.Events.AvgSpamMetricsUpdated.Detach(onAvgSpamMetricsUpdated) - log.Info("Stopping Dashboard[SpammerMetricUpdater] ... done") - }, shutdown.PriorityDashboard) -} diff --git a/plugins/spammer/cpu_usage.go b/plugins/spammer/cpu_usage.go deleted file mode 100644 index 9fc7ebeb8..000000000 --- a/plugins/spammer/cpu_usage.go +++ /dev/null @@ -1,93 +0,0 @@ -package spammer - -import ( - "errors" - "math/rand" - "runtime" - "time" - - "github.com/shirou/gopsutil/cpu" - - "github.com/iotaledger/hive.go/daemon" - - "github.com/iotaledger/hornet/pkg/model/tangle" -) - -const ( - cpuUsageSampleTime = 200 * time.Millisecond - cpuUsageSleepTime = int(200 * time.Millisecond) -) - -var ( - // ErrCPUPercentageUnknown is returned if the CPU usage couldn't be determined. - ErrCPUPercentageUnknown = errors.New("CPU percentage unknown") -) - -// cpuUsageUpdater starts a goroutine that computes cpu usage each cpuUsageSampleTime. -func cpuUsageUpdater() { - go func() { - for { - if daemon.IsStopped() { - return - } - - cpuUsagePSutil, err := cpu.Percent(cpuUsageSampleTime, false) - cpuUsageLock.Lock() - if err != nil { - cpuUsageError = ErrCPUPercentageUnknown - cpuUsageLock.Unlock() - return - } - cpuUsageError = nil - cpuUsageResult = cpuUsagePSutil[0] / 100.0 - cpuUsageLock.Unlock() - } - }() -} - -// cpuUsage returns latest cpu usage -func cpuUsage() (float64, error) { - cpuUsageLock.RLock() - defer cpuUsageLock.RUnlock() - - return cpuUsageResult, cpuUsageError -} - -// cpuUsageGuessWithAdditionalWorker returns guessed cpu usage with another core running at 100% load -func cpuUsageGuessWithAdditionalWorker() (float64, error) { - cpuUsage, err := cpuUsage() - if err != nil { - return 0.0, err - } - - return cpuUsage + (1.0 / float64(runtime.NumCPU())), nil -} - -// waitForLowerCPUUsage waits until the cpu usage drops below cpuMaxUsage. -func waitForLowerCPUUsage(cpuMaxUsage float64, shutdownSignal <-chan struct{}) error { - if cpuMaxUsage == 0.0 { - return nil - } - - for { - cpuUsage, err := cpuUsageGuessWithAdditionalWorker() - if err != nil { - return err - } - - if cpuUsage < cpuMaxUsage { - break - } - - select { - case <-shutdownSignal: - return tangle.ErrOperationAborted - default: - } - - // sleep a random time between cpuUsageSleepTime and 2*cpuUsageSleepTime - time.Sleep(time.Duration(cpuUsageSleepTime + rand.Intn(cpuUsageSleepTime))) - } - - return nil -} diff --git a/plugins/spammer/plugin.go b/plugins/spammer/plugin.go deleted file mode 100644 index 5a2873cfb..000000000 --- a/plugins/spammer/plugin.go +++ /dev/null @@ -1,346 +0,0 @@ -package spammer - -import ( - "errors" - "fmt" - "runtime" - "sync" - "time" - - "github.com/iotaledger/hive.go/daemon" - "github.com/iotaledger/hive.go/events" - "github.com/iotaledger/hive.go/logger" - "github.com/iotaledger/hive.go/node" - "github.com/iotaledger/hive.go/syncutils" - "github.com/iotaledger/hive.go/timeutil" - "github.com/iotaledger/iota.go/bundle" - "github.com/iotaledger/iota.go/transaction" - "go.uber.org/atomic" - - "github.com/iotaledger/hornet/pkg/config" - "github.com/iotaledger/hornet/pkg/metrics" - "github.com/iotaledger/hornet/pkg/model/tangle" - "github.com/iotaledger/hornet/pkg/shutdown" - "github.com/iotaledger/hornet/pkg/spammer" - "github.com/iotaledger/hornet/pkg/utils" - "github.com/iotaledger/hornet/plugins/coordinator" - "github.com/iotaledger/hornet/plugins/gossip" - "github.com/iotaledger/hornet/plugins/peering" - "github.com/iotaledger/hornet/plugins/pow" - "github.com/iotaledger/hornet/plugins/urts" -) - -var ( - PLUGIN = node.NewPlugin("Spammer", node.Disabled, configure, run) - log *logger.Logger - - spammerInstance *spammer.Spammer - spammerLock syncutils.RWMutex - - spammerStartTime time.Time - spammerAvgHeap *utils.TimeHeap - lastSentSpamTxsCnt uint32 - - cpuUsageLock syncutils.RWMutex - cpuUsageResult float64 - cpuUsageError error - - processID atomic.Uint32 - spammerWaitGroup sync.WaitGroup - - // events of the spammer - Events = &spammer.SpammerEvents{ - SpamPerformed: events.NewEvent(spammer.SpamStatsCaller), - AvgSpamMetricsUpdated: events.NewEvent(spammer.AvgSpamMetricsCaller), - } - - // ErrSpammerDisabled is returned if the spammer plugin is disabled. - ErrSpammerDisabled = errors.New("Spammer plugin disabled") -) - -func configure(plugin *node.Plugin) { - log = logger.NewLogger(plugin.Name) - - // do not enable the spammer if URTS is disabled - if node.IsSkipped(urts.PLUGIN) { - plugin.Status = node.Disabled - return - } - - spammerAvgHeap = utils.NewTimeHeap() - - // start the CPU usage updater - cpuUsageUpdater() - - // helper function to send the bundle to the network - sendBundle := func(b bundle.Bundle) error { - for _, t := range b { - tx := t // assign to new variable, otherwise it would be overwritten by the loop before processed - txTrits, _ := transaction.TransactionToTrits(&tx) - if err := gossip.Processor().CompressAndEmit(&tx, txTrits); err != nil { - return err - } - metrics.SharedServerMetrics.SentSpamTransactions.Inc() - } - return nil - } - - spammerInstance = spammer.New( - config.NodeConfig.GetString(config.CfgSpammerAddress), - config.NodeConfig.GetString(config.CfgSpammerMessage), - config.NodeConfig.GetString(config.CfgSpammerTag), - config.NodeConfig.GetString(config.CfgSpammerTagSemiLazy), - urts.TipSelector.SelectSpammerTips, - config.NodeConfig.GetInt(config.CfgCoordinatorMWM), - pow.Handler(), - sendBundle, - ) -} - -func run(_ *node.Plugin) { - - // do not enable the spammer if URTS is disabled - if node.IsSkipped(urts.PLUGIN) { - return - } - - // create a background worker that "measures" the spammer averages values every second - daemon.BackgroundWorker("Spammer Metrics Updater", func(shutdownSignal <-chan struct{}) { - timeutil.Ticker(measureSpammerMetrics, 1*time.Second, shutdownSignal) - }, shutdown.PrioritySpammer) - - // automatically start the spammer on node startup if the flag is set - if config.NodeConfig.GetBool(config.CfgSpammerAutostart) { - Start(nil, nil, nil, nil) - } -} - -// Start starts the spammer to spam with the given settings, otherwise it uses the settings from the config. -func Start(tpsRateLimit *float64, cpuMaxUsage *float64, bundleSize *int, valueSpam *bool) (float64, float64, int, bool, error) { - if spammerInstance == nil { - return 0.0, 0.0, 0, false, ErrSpammerDisabled - } - - spammerLock.Lock() - defer spammerLock.Unlock() - - stopWithoutLocking() - - tpsRateLimitCfg := config.NodeConfig.GetFloat64(config.CfgSpammerTPSRateLimit) - cpuMaxUsageCfg := config.NodeConfig.GetFloat64(config.CfgSpammerCPUMaxUsage) - bundleSizeCfg := config.NodeConfig.GetInt(config.CfgSpammerBundleSize) - valueSpamCfg := config.NodeConfig.GetBool(config.CfgSpammerValueSpam) - spammerWorkerCount := config.NodeConfig.GetInt(config.CfgSpammerWorkers) - checkPeersConnected := node.IsSkipped(coordinator.PLUGIN) - - if tpsRateLimit != nil { - tpsRateLimitCfg = *tpsRateLimit - } - - if cpuMaxUsage != nil { - cpuMaxUsageCfg = *cpuMaxUsage - } - - if cpuMaxUsageCfg > 0.0 && runtime.GOOS == "windows" { - log.Warn("spammer.cpuMaxUsage not supported on Windows. will be deactivated") - cpuMaxUsageCfg = 0.0 - } - - if cpuMaxUsageCfg > 0.0 && runtime.NumCPU() == 1 { - log.Warn("spammer.cpuMaxUsage not supported on single core machines. will be deactivated") - cpuMaxUsageCfg = 0.0 - } - - if bundleSize != nil { - bundleSizeCfg = *bundleSize - } - - if valueSpam != nil { - valueSpamCfg = *valueSpam - } - - if bundleSizeCfg < 1 { - bundleSizeCfg = 1 - } - - if valueSpamCfg && bundleSizeCfg < 2 { - // minimum size for a value tx with SecurityLevelLow - bundleSizeCfg = 2 - } - - if spammerWorkerCount >= runtime.NumCPU() || spammerWorkerCount == 0 { - spammerWorkerCount = runtime.NumCPU() - 1 - } - if spammerWorkerCount < 1 { - spammerWorkerCount = 1 - } - - startSpammerWorkers(tpsRateLimitCfg, cpuMaxUsageCfg, bundleSizeCfg, valueSpamCfg, spammerWorkerCount, checkPeersConnected) - - return tpsRateLimitCfg, cpuMaxUsageCfg, bundleSizeCfg, valueSpamCfg, nil -} - -func startSpammerWorkers(tpsRateLimit float64, cpuMaxUsage float64, bundleSize int, valueSpam bool, spammerWorkerCount int, checkPeersConnected bool) { - - var rateLimitChannel chan struct{} = nil - var rateLimitAbortSignal chan struct{} = nil - - if tpsRateLimit != 0.0 { - rateLimitChannelSize := int64(tpsRateLimit) * 2 - if rateLimitChannelSize < 2 { - rateLimitChannelSize = 2 - } - rateLimitChannel = make(chan struct{}, rateLimitChannelSize) - rateLimitAbortSignal = make(chan struct{}) - - // create a background worker that fills rateLimitChannel every second - daemon.BackgroundWorker("Spammer rate limit channel", func(shutdownSignal <-chan struct{}) { - spammerWaitGroup.Add(1) - done := make(chan struct{}) - currentProcessID := processID.Load() - - timeutil.Ticker(func() { - - if currentProcessID != processID.Load() { - close(rateLimitAbortSignal) - close(done) - return - } - - select { - case <-shutdownSignal: - close(rateLimitAbortSignal) - close(done) - case rateLimitChannel <- struct{}{}: - default: - // Channel full - } - }, time.Duration(int64(float64(time.Second)/tpsRateLimit)), done) - - spammerWaitGroup.Done() - }, shutdown.PrioritySpammer) - } - - spammerCnt := atomic.NewInt32(0) - for i := 0; i < spammerWorkerCount; i++ { - daemon.BackgroundWorker(fmt.Sprintf("Spammer_%d", i), func(shutdownSignal <-chan struct{}) { - spammerWaitGroup.Add(1) - spammerIndex := spammerCnt.Inc() - currentProcessID := processID.Load() - - log.Infof("Starting Spammer %d... done", spammerIndex) - - spammerLoop: - for { - select { - case <-shutdownSignal: - break spammerLoop - default: - if currentProcessID != processID.Load() { - break spammerLoop - } - - if tpsRateLimit != 0 { - // if rateLimit is activated, wait until this spammer thread gets a signal - select { - case <-rateLimitAbortSignal: - break spammerLoop - case <-shutdownSignal: - break spammerLoop - case <-rateLimitChannel: - } - } - - if !tangle.IsNodeSyncedWithThreshold() { - time.Sleep(time.Second) - continue - } - - if checkPeersConnected && peering.Manager().ConnectedPeerCount() == 0 { - time.Sleep(time.Second) - continue - } - - if err := waitForLowerCPUUsage(cpuMaxUsage, shutdownSignal); err != nil { - if err != tangle.ErrOperationAborted { - log.Warn(err.Error()) - } - continue - } - - if spammerStartTime.IsZero() { - // set the start time for the metrics - spammerStartTime = time.Now() - } - - durationGTTA, durationPOW, err := spammerInstance.DoSpam(bundleSize, valueSpam, shutdownSignal) - if err != nil { - continue - } - Events.SpamPerformed.Trigger(&spammer.SpamStats{GTTA: float32(durationGTTA.Seconds()), POW: float32(durationPOW.Seconds())}) - } - } - - log.Infof("Stopping Spammer %d...", spammerIndex) - log.Infof("Stopping Spammer %d... done", spammerIndex) - spammerWaitGroup.Done() - - }, shutdown.PrioritySpammer) - } -} - -// Stop stops the spammer. -func Stop() error { - if spammerInstance == nil { - return ErrSpammerDisabled - } - - spammerLock.Lock() - defer spammerLock.Unlock() - - stopWithoutLocking() - - return nil -} - -func stopWithoutLocking() { - // increase the process ID to stop all running workers - processID.Inc() - - // wait until all spammers are stopped - spammerWaitGroup.Wait() - - // reset the start time to stop the metrics - spammerStartTime = time.Time{} - - // clear the metrics heap - for spammerAvgHeap.Len() > 0 { - spammerAvgHeap.Pop() - } -} - -// measureSpammerMetrics measures the spammer metrics. -func measureSpammerMetrics() { - if spammerStartTime.IsZero() { - // Spammer not started yet - return - } - - sentSpamTxsCnt := metrics.SharedServerMetrics.SentSpamTransactions.Load() - new := utils.GetUint32Diff(sentSpamTxsCnt, lastSentSpamTxsCnt) - lastSentSpamTxsCnt = sentSpamTxsCnt - - spammerAvgHeap.Add(uint64(new)) - - timeDiff := time.Since(spammerStartTime) - if timeDiff > 60*time.Second { - // Only filter over one minute maximum - timeDiff = 60 * time.Second - } - - // trigger events for outside listeners - Events.AvgSpamMetricsUpdated.Trigger(&spammer.AvgSpamMetrics{ - New: new, - AveragePerSecond: spammerAvgHeap.GetAveragePerSecond(timeDiff), - }) -} diff --git a/plugins/webapi/plugin.go b/plugins/webapi/plugin.go index 4736e1e3b..f4faf40b1 100644 --- a/plugins/webapi/plugin.go +++ b/plugins/webapi/plugin.go @@ -19,7 +19,6 @@ import ( "github.com/iotaledger/hornet/pkg/config" "github.com/iotaledger/hornet/pkg/model/tangle" "github.com/iotaledger/hornet/pkg/shutdown" - "github.com/iotaledger/hornet/plugins/spammer" ) const ( @@ -163,11 +162,6 @@ func configure(plugin *node.Plugin) { if !config.NodeConfig.GetBool(config.CfgNetAutopeeringRunAsEntryNode) { webAPIRoute() - - // only handle spammer api calls if the spammer plugin is enabled - if !node.IsSkipped(spammer.PLUGIN) { - spammerRoute() - } } // return error, if route is not there diff --git a/plugins/webapi/spammer.go b/plugins/webapi/spammer.go deleted file mode 100644 index af6655001..000000000 --- a/plugins/webapi/spammer.go +++ /dev/null @@ -1,99 +0,0 @@ -package webapi - -import ( - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/gin-gonic/gin" - - "github.com/iotaledger/hornet/plugins/spammer" -) - -func spammerRoute() { - api.GET("/spammer", func(c *gin.Context) { - - if !networkWhitelisted(c) { - // network is not whitelisted, check if the route is permitted, otherwise deny it. - if _, permitted := permittedRESTroutes["spammer"]; !permitted { - c.JSON(http.StatusForbidden, ErrorReturn{Error: "route [spammer] is protected"}) - return - } - } - - switch strings.ToLower(c.Query("cmd")) { - case "start": - var err error - var tpsRateLimit *float64 = nil - var cpuMaxUsage *float64 = nil - var bundleSize *int = nil - var valueSpam *bool = nil - - tpsRateLimitQuery := c.Query("tpsRateLimit") - if tpsRateLimitQuery != "" { - tpsRateLimitParsed, err := strconv.ParseFloat(tpsRateLimitQuery, 64) - if err != nil || tpsRateLimitParsed < 0.0 { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("parsing tpsRateLimit failed: %w", err).Error()}) - return - } - tpsRateLimit = &tpsRateLimitParsed - } - - cpuMaxUsageQuery := c.Query("cpuMaxUsage") - if cpuMaxUsageQuery != "" { - cpuMaxUsageParsed, err := strconv.ParseFloat(cpuMaxUsageQuery, 64) - if err != nil || cpuMaxUsageParsed < 0.0 { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("parsing cpuMaxUsage failed: %w", err).Error()}) - return - } - cpuMaxUsage = &cpuMaxUsageParsed - } - - bundleSizeQuery := c.Query("bundleSize") - if bundleSizeQuery != "" { - bundleSizeParsed, err := strconv.Atoi(bundleSizeQuery) - if err != nil || bundleSizeParsed < 1 { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("parsing bundleSize failed: %w", err).Error()}) - return - } - bundleSize = &bundleSizeParsed - } - - valueSpamQuery := c.Query("valueSpam") - if valueSpamQuery != "" { - valueSpamParsed, err := strconv.ParseBool(valueSpamQuery) - if err != nil { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("parsing valueSpam failed: %w", err).Error()}) - return - } - valueSpam = &valueSpamParsed - } - - usedTpsRateLimit, usedCPUMaxUsage, usedBundleSize, usedValueSpam, err := spammer.Start(tpsRateLimit, cpuMaxUsage, bundleSize, valueSpam) - if err != nil { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("starting spammer failed: %w", err).Error()}) - return - } - - c.JSON(http.StatusOK, ResultReturn{Message: fmt.Sprintf("started spamming (TPS Limit: %0.2f, CPU Limit: %0.2f%%, BundleSize: %d, ValueSpam: %t)", usedTpsRateLimit, usedCPUMaxUsage*100.0, usedBundleSize, usedValueSpam)}) - return - - case "stop": - if err := spammer.Stop(); err != nil { - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Errorf("stopping spammer failed: %w", err).Error()}) - return - } - c.JSON(http.StatusOK, ResultReturn{Message: "stopped spamming"}) - return - - case "": - c.JSON(http.StatusBadRequest, ErrorReturn{Error: "no cmd given"}) - return - - default: - c.JSON(http.StatusBadRequest, ErrorReturn{Error: fmt.Sprintf("unknown cmd: %s", strings.ToLower(c.Query("cmd")))}) - return - } - }) -}