Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loadtest script #658

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Add loadtest script #658

wants to merge 1 commit into from

Conversation

neekolas
Copy link
Contributor

@neekolas neekolas commented Mar 21, 2025

tl;dr

  • Adds a crude script to loadtest the chain

Notes

Having a single payer for the load test limits the pace that we can write messages because of blockchain limitations around nonces. We can only have 64 pending transactions in the mempool. The turnaround time to mark a message as "sent" is slow, which leaves a lot of time waiting for the semaphore.

I don't really see a way around this other than sending from multiple Payers.

Summary by CodeRabbit

  • New Features
    • Added a new CLI load testing command that enables users to simulate load scenarios with configurable options such as messages per second, duration, message size, and connection details.
    • Introduced a new package for load testing functionality on the blockchain, allowing users to conduct load tests with detailed metrics and logging.
  • Improvements
    • Increased the blockchain transaction receipt waiting period to better accommodate longer processing times and improve overall reliability.
  • Removals
    • Removed the custom nonce management implementation from tests, transitioning to a SQL-backed nonce manager.

Copy link
Contributor

coderabbitai bot commented Mar 21, 2025

Walkthrough

The changes introduce a new load test feature into the CLI, including the addition of a load testing command, configuration options, and implementation in a dedicated package. The CLI now registers a "loadtest" command that executes a new function invoking load test functionality. Additionally, the blockchain publisher’s waiting period for transaction receipts is increased, and updates to nonce management have been made, including refactoring tests and altering concurrency limit initialization. A new test nonce manager implementation is added in the test utilities package.

Changes

File(s) Change Summary
cmd/cli/main.go, pkg/config/cliOptions.go, pkg/loadtest/loadtest.go Added load test functionality: new CLI command (loadtest), new configuration type (LoadTestOptions), and load testing implementation including functions for execution and helper methods.
pkg/blockchain/blockchainPublisher.go Increased transaction receipt timeout from 2s to 10s.
pkg/blockchain/blockchainPublisher_test.go Updated nonce manager instantiation to use nonceManagerUtils.NewTestNonceManager(logger).
pkg/blockchain/nonceManager.go, pkg/blockchain/nonceManager_test.go Reorganized import statements; removed custom nonce manager testing types and methods.
pkg/testutils/noncemanager/nonceManager.go Introduced a new test nonce manager with an Int64Heap and TestNonceManager struct, implementing heap operations and nonce management methods.

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant Parser
    participant LoadTestCmd
    participant LoadTestPkg
    participant BlockchainPublisher
    participant Database
    participant Logger

    CLI->>Parser: Parse command-line options
    Parser->>CLI: Identify "loadtest" command with options
    CLI->>LoadTestCmd: Invoke loadTest() for "loadtest"
    LoadTestCmd->>LoadTestPkg: Call LoadTest(logger, options, contractsOptions)
    LoadTestPkg->>BlockchainPublisher: Initialize publisher & establish DB connection
    BlockchainPublisher->>Database: Open namespaced DB for nonce management
    BlockchainPublisher-->>LoadTestPkg: Return publisher instance
    LoadTestPkg->>LoadTestPkg: Setup ticker and wait group for sending messages
    LoadTestPkg->>BlockchainPublisher: Publish messages at configured rate
    BlockchainPublisher->>Logger: Log message publication status and errors
Loading

Possibly related PRs

  • Custom flag validation #303: The changes in the main PR introduce a new load testing command and related options in the CLI, while the retrieved PR modifies the CLI structure and command options, indicating a direct relationship in their modifications to the cmd/cli/main.go file.

Suggested reviewers

  • mkysel
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@neekolas neekolas force-pushed the 03-20-add_loadtest_script branch from 28b5027 to 2c1ef3a Compare March 21, 2025 17:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (8)
pkg/config/cliOptions.go (1)

83-89: New LoadTestOptions structure

A well-structured configuration object for the new load testing feature has been added with appropriate defaults:

  • Private key (required)
  • Message rate (25/sec default)
  • Test duration (60 sec default)
  • Message size (7168 bytes default)
  • DB connection string (from env variable)

These settings provide good flexibility for configuring various aspects of load testing.

Consider adding validation for these parameters, especially for minimum/maximum values. For example, you might want to enforce:

  • Minimum/maximum message rate
  • Minimum/maximum duration
  • Minimum/maximum message size

This would prevent users from accidentally configuring tests that are either too light to be meaningful or too heavy to be safe.

cmd/cli/main.go (1)

139-141: Registering the loadtest command
Good addition for parsing load test flags. Make sure usage instructions for this new command are clear so users know how to invoke it.

pkg/testutils/noncemanager/nonceManager.go (2)

49-78: GetNonce method

  1. Locking the mutex is correct to protect shared state.
  2. Pushing abandoned nonces back onto the heap is a useful mechanism for reuse.
  3. Consider adding a context timeout check if needed for stress testing scenarios, but that might be optional.

88-90: Replenish method
Currently a no-op. If the test harness eventually requires replenishing the nonce manager from an external source, consider implementing it or removing the stub.

pkg/loadtest/loadtest.go (1)

65-127: doLoadTest function

  1. Uses a ticker to schedule messages at a precise rate.
  2. wg.Wait() ensures all goroutines finish.
  3. Consider adding context-based cancellation or early stop conditions if needed for more dynamic load test control.
pkg/blockchain/nonceManager_test.go (3)

35-54: TestGetNonce_RevertMany: Validating Nonce Reversion Behavior
This test verifies that when a nonce is cancelled (via nonce.Cancel()), subsequent calls return the same nonce value (0 in this case). This is important to ensure that cancellation reverts the nonce usage properly. However, adding a brief inline comment to explain the expected behavior—i.e., that cancellation should “revert” the nonce consumption so that the nonce remains at its initial value—could enhance maintainability and clarity for future developers.


56-76: TestGetNonce_ConsumeMany: Sequential Consumption Check
The test iterates over 10 nonce consumptions and verifies that each returned nonce’s value matches the expected sequence. This effectively tests the incrementing logic upon consumption. A short comment explaining the rationale behind expecting the nonce to equal the loop index would be beneficial to maintain readability and clarity for future maintainers.


78-106: TestGetNonce_ConsumeManyConcurrent: Assessing Concurrency Robustness
The concurrent test uses goroutines with a sync.WaitGroup to simulate multiple clients consuming nonces simultaneously. The error checks ensure that the nonce manager handles concurrent access appropriately. One suggestion would be to consider asserting the uniqueness of nonce values in a concurrent environment if the design contracts require that each nonce, once consumed, be unique. This additional check could improve confidence in the thread-safety and correctness of the nonce incrementation logic under heavy load.

🛑 Comments failed to post (1)
pkg/loadtest/loadtest.go (1)

15-63: 🛠️ Refactor suggestion

LoadTest function

  1. Properly initializes blockchain client, signer, DB, and publisher before calling doLoadTest.
  2. Adequate error handling with immediate returns.
  3. Consider returning an error instead of using logger.Fatal if partial failures must be handled upstream.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
cmd/cli/main.go (2)

42-42: Add a short doc comment for clarity.
Although optional, adding a brief comment for the LoadTest field helps maintain consistency with other fields.


803-807: Ensure consistent error handling strategy.
This function terminates execution on error via logger.Fatal. You may consider returning an error from loadTest instead, if you prefer graceful handling, but this approach is acceptable if a load test failure should halt execution.

pkg/testutils/noncemanager/nonceManager.go (3)

38-43: TestNonceManager struct is well-defined.
Consider adding a short doc comment explaining usage, especially around the abandoned heap.


45-47: Initialization may need a heap.Init call.
Currently, abandoned is used with heap.Pop, but we never call heap.Init. Although it may work with an empty slice, consider explicitly initializing the heap for safety.

 func NewTestNonceManager(logger *zap.Logger) *TestNonceManager {
+    tm := &TestNonceManager{logger: logger}
+    heap.Init(&tm.abandoned)
+    return tm
-    return &TestNonceManager{logger: logger}
 }

88-90: Method is a no-op.
If this is intentionally a placeholder, consider briefly documenting the reasoning or removing the method if unneeded.

pkg/loadtest/loadtest.go (1)

15-63: Ensure consistent error handling in LoadTest.
The function returns an error for most failures, but uses logger.Fatal when BlockchainPublisher creation fails. Consider returning errors uniformly if partial failures need distinct handling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28b5027 and 2c1ef3a.

📒 Files selected for processing (8)
  • cmd/cli/main.go (7 hunks)
  • pkg/blockchain/blockchainPublisher.go (2 hunks)
  • pkg/blockchain/blockchainPublisher_test.go (2 hunks)
  • pkg/blockchain/nonceManager.go (2 hunks)
  • pkg/blockchain/nonceManager_test.go (1 hunks)
  • pkg/config/cliOptions.go (1 hunks)
  • pkg/loadtest/loadtest.go (1 hunks)
  • pkg/testutils/noncemanager/nonceManager.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/blockchain/blockchainPublisher_test.go
  • pkg/blockchain/blockchainPublisher.go
  • pkg/blockchain/nonceManager.go
  • pkg/config/cliOptions.go
🧰 Additional context used
🧬 Code Definitions (2)
pkg/testutils/noncemanager/nonceManager.go (1)
pkg/blockchain/nonceManager.go (1) (1)
  • NonceContext (14-18)
cmd/cli/main.go (2)
pkg/loadtest/loadtest.go (1) (1)
  • LoadTest (15-63)
pkg/config/cliOptions.go (1) (1)
  • LoadTestOptions (83-89)
🔇 Additional comments (19)
cmd/cli/main.go (5)

15-15: No issues with the new import.


70-70: Variable declaration is straightforward.
Looks good; no issues found.


139-141: Load test command registered successfully.
Command name, help text, and error handling align with existing patterns.


170-170: Populating CLI struct with loadTestOptions.
No concerns; this maintains consistency across the CLI’s aggregated options.


763-765: New switch case for 'loadtest'
This case logically parallels other commands. No issues with calling loadTest.

pkg/testutils/noncemanager/nonceManager.go (5)

1-3: Package declaration is clear.
No issues found with naming or structure.


4-11: Imports are appropriate.
The chosen packages (heap, context, sync, etc.) are correct for this use case.


13-36: Custom heap implementation deviates from container/heap defaults.
Returning the first element in Pop instead of the last is non-standard for a min-heap. This matches a previous review concern.


80-86: Validate nonce progression upon fast-forward.
Currently, this method unconditionally sets tm.nonce. If it’s set to a lower value than the current nonce, subsequent usage might be confusing or lead to collisions.


49-78:

❓ Verification inconclusive

Concurrency and context checks appear correct.
Locking around push/pop from abandoned is good. However, double-check that heap.Init is called before the first push/pop to avoid unexpected behavior.


🏁 Script executed:

#!/bin/bash
# Verify if heap.Init is invoked anywhere in the codebase
rg "heap.Init"

Length of output: 14


Attention: Potential Missing heap.Init Call in NonceManager Initialization

Concurrency and context handling in GetNonce are correct, and the locking around push/pop operations on abandoned is properly managed. However, our search for occurrences of heap.Init didn’t return any evidence that the abandoned heap is being initialized. Please verify that heap.Init(&tm.abandoned) is invoked during the initialization (e.g., in the constructor of the TestNonceManager or an init function) before any push/pop operations occur to ensure heap invariants are maintained.

pkg/loadtest/loadtest.go (3)

1-13: Package and imports are appropriate.
No immediate issues with dependencies.


65-127: Improper integer iteration in doLoadTest.
Using for i := range totalMessages on an integer is invalid. This was previously flagged. Also, be cautious with the goroutine capturing loop variables.

-for i := range totalMessages {
+for i := 0; i < totalMessages; i++ {

129-140: Error from cryptoRand.Read is discarded.
As previously noted, ignoring potential errors can compromise reliability or security.

- _, _ = cryptoRand.Read(b)
+ if _, err := cryptoRand.Read(b); err != nil {
+    panic(err) // or handle appropriately
+ }
pkg/blockchain/nonceManager_test.go (6)

3-13: Import statements look well-organized and appropriate.

The imports needed for the nonce manager tests are well-structured and include all necessary packages for the SQL-backed nonce manager implementation.


15-33: Simple nonce test looks good.

This test verifies the basic functionality of getting a nonce and ensures it starts at the expected value (0). The test properly uses the SQL-backed nonce manager, initializes it, and cleans up resources.


35-54: Multiple nonce reservation/cancellation test is well-structured.

This test verifies that when nonces are repeatedly requested and canceled, the same nonce (0) is correctly reused each time. The test demonstrates proper resource management with appropriate setup and teardown.


56-76: Sequential nonce consumption test is robust.

This test ensures that when consuming nonces sequentially, each nonce increments properly (0 through 9). The test correctly verifies the behavior of the SQL-backed nonce manager when nonces are consumed rather than canceled.


78-106: Concurrent nonce consumption test is valuable.

This test verifies the thread-safety of the nonce manager implementation by requesting and consuming nonces concurrently from multiple goroutines. The use of WaitGroup ensures proper synchronization.


1-106: Refactoring improves code organization.

The removal of the Int64Heap type and TestNonceManager struct from this file and their relocation to pkg/testutils/noncemanager/nonceManager.go (as noted in the summary) is a good architectural decision. This refactoring improves code organization by:

  1. Separating test utilities from the code being tested
  2. Making the test nonce manager implementation reusable across multiple test files
  3. Focusing this test file on testing the SQL-backed implementation

This change aligns well with the PR objective of supporting load testing with multiple payers.

@mkysel
Copy link
Collaborator

mkysel commented Mar 21, 2025

do you already have results from localhost? I wonder how different anvil is from say staging

Copy link
Contributor Author

I've only tested against testnet staging.

I don't think it's hitting staging at its full capacity though. When I look in the block explorer I don't see fees increasing, which would be a clear indicator that the blocks are full.

@neekolas neekolas force-pushed the 03-20-add_loadtest_script branch from 2c1ef3a to 8bbedfb Compare March 21, 2025 20:05
Copy link

macroscopeapp bot commented Mar 21, 2025

Add blockchain load testing functionality

Added new loadtest CLI command and package for executing configurable load tests against blockchain contracts. Includes concurrent message publishing with rate limiting, configurable message sizes and durations. Moved TestNonceManager to dedicated test utils package and increased blockchain transaction timeout to 10 seconds.

📍Where to Start

The main CLI entry point in main.go followed by the core load testing implementation in loadtest.go


Macroscope summarized e31a9ea.

@neekolas neekolas force-pushed the 03-20-add_loadtest_script branch 2 times, most recently from 30fc01c to 9562a95 Compare March 21, 2025 20:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

♻️ Duplicate comments (3)
pkg/testutils/noncemanager/nonceManager.go (2)

69-73: ⚠️ Potential issue

Use heap.Push instead of direct access to abandoned heap.

The heap.Push function should be used instead of directly calling Push on the abandoned heap to maintain the heap invariant.

Cancel: func() {
	tm.mu.Lock()
	defer tm.mu.Unlock()
-	tm.abandoned.Push(nonce)
+	heap.Push(&tm.abandoned, nonce)
},

23-29: 💡 Verification agent

🧩 Analysis chain

Fix the heap Pop implementation to follow standard heap behavior.

The current Pop implementation removes the first element (smallest) rather than the last element, which is how heap.Pop typically works in Go's container/heap package. This deviation could lead to confusion or subtle bugs.

func (h *Int64Heap) Pop() interface{} {
	old := *h
	n := len(old)
-	x := old[0] // Get the smallest element
-	*h = old[1:n]
+	x := old[n-1] // Get the last element
+	*h = old[:n-1]
	return x
}

🏁 Script executed:

#!/bin/bash
# Check Go's container/heap implementation for comparison
cat $(fd -t f -p "heap/.+\.go$" /usr/local/go/src/container/ | head -1)

Length of output: 1262


Action Required: Correct the Pop method to match Go's container/heap behavior by removing the last element.

  • The current implementation incorrectly removes the first element (index 0), while the standard behavior (as seen in Go's container/heap example) removes the last element.
  • Update your implementation accordingly:
func (h *Int64Heap) Pop() interface{} {
	old := *h
	n := len(old)
-	x := old[0] // Get the smallest element
-	*h = old[1:n]
+	x := old[n-1] // Get the last element
+	*h = old[:n-1]
	return x
}
pkg/loadtest/loadtest.go (1)

94-94: ⚠️ Potential issue

Fix the loop iteration over totalMessages.

Using range with an integer is incorrect syntax in Go. It should be a traditional for loop.

-	for i := range totalMessages {
+	for i := 0; i < totalMessages; i++ {
🧹 Nitpick comments (4)
cmd/cli/main.go (1)

139-141: Maintain consistent capitalization in error messages.

The error message here starts with "Could" (capitalized), while other similar error messages in the file use "could" (lowercase).

-		return nil, fmt.Errorf("Could not add loadtest command: %s", err)
+		return nil, fmt.Errorf("could not add loadtest command: %s", err)
pkg/testutils/noncemanager/nonceManager.go (1)

73-73: Remove misleading comment.

The "No-op" comment is misleading since the Cancel function actually has an implementation.

-		}, // No-op
+		},
pkg/loadtest/loadtest.go (2)

71-71: Use testutils.RandomGroupID instead of reimplementing.

You're importing testutils but then defining a separate randomBytes function. Use the existing testutils implementation for consistency.

-	groupID := testutils.RandomGroupID()
+	groupID := testutils.RandomGroupID()

117-119: Consider implementing a timeout for waitgroup.

Using wg.Wait() without a timeout can cause the function to hang indefinitely if a goroutine doesn't complete, which could happen in load testing scenarios.

-	wg.Wait()
+	// Set up a timeout channel
+	done := make(chan struct{})
+	go func() {
+		wg.Wait()
+		close(done)
+	}()
+
+	// Wait with timeout
+	select {
+	case <-done:
+		// All goroutines completed successfully
+	case <-time.After(time.Duration(options.Duration+30) * time.Second):
+		logger.Warn("Timed out waiting for all messages to complete")
+	}
🛑 Comments failed to post (3)
pkg/testutils/noncemanager/nonceManager.go (1)

45-47: 🛠️ Refactor suggestion

Initialize heap in constructor.

The abandoned heap is not properly initialized. You should initialize it and call heap.Init in the constructor to ensure it maintains the heap property.

func NewTestNonceManager(logger *zap.Logger) *TestNonceManager {
-	return &TestNonceManager{logger: logger}
+	abandoned := Int64Heap{}
+	heap.Init(&abandoned)
+	return &TestNonceManager{
+		logger:    logger,
+		abandoned: abandoned,
+	}
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func NewTestNonceManager(logger *zap.Logger) *TestNonceManager {
	abandoned := Int64Heap{}
	heap.Init(&abandoned)
	return &TestNonceManager{
		logger:    logger,
		abandoned: abandoned,
	}
}
pkg/loadtest/loadtest.go (2)

47-59: 🛠️ Refactor suggestion

Propagate error on publisher creation.

The function returns an early error when creating the client, signer, or DB connection, but calls Fatal when the publisher creation fails, which doesn't match the error handling pattern in the rest of the function.

publisher, err := blockchain.NewBlockchainPublisher(
	ctx,
	logger,
	chainClient,
	signer,
	*contractsOptions,
	nonceManager,
)
if err != nil {
-	logger.Fatal("could not create blockchain publisher", zap.Error(err))
+	return err
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

	nonceManager := blockchain.NewSQLBackedNonceManager(dbConn, logger)
	publisher, err := blockchain.NewBlockchainPublisher(
		ctx,
		logger,
		chainClient,
		signer,
		*contractsOptions,
		nonceManager,
	)
	if err != nil {
		return err
	}

60-62: 🛠️ Refactor suggestion

Return errors from doLoadTest.

The doLoadTest function doesn't currently return any errors that might occur during execution. Consider modifying it to return an error and propagate it back to the LoadTest caller.

-func doLoadTest(
+func doLoadTest(
	ctx context.Context,
	logger *zap.Logger,
	publisher *blockchain.BlockchainPublisher,
	options *config.LoadTestOptions,
-) {
+) error {
	// Function body...
+	return nil
}

// And in LoadTest:
-	doLoadTest(ctx, logger, publisher, options)
-	return nil
+	return doLoadTest(ctx, logger, publisher, options)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
pkg/testutils/noncemanager/nonceManager.go (2)

13-36: ⚠️ Potential issue

Incorrect heap implementation may cause ordering issues

The Int64Heap heap implementation has an issue in the Pop() method. It removes the first element instead of the last element from the slice, which doesn't follow standard heap operations:

func (h *Int64Heap) Pop() interface{} {
	old := *h
	n := len(old)
-	x := old[0] // Get the smallest element
-	*h = old[1:n]
+	x := old[n-1] // Get the last element
+	*h = old[:n-1]
	return x
}

The container/heap package expects Pop() to remove and return the last element of the slice, while the heap management is handled by the package itself.


67-78: ⚠️ Potential issue

Direct heap manipulation breaks heap invariants

The Cancel function directly calls tm.abandoned.Push(nonce) instead of using heap.Push(&tm.abandoned, nonce), which bypasses the heap package's functionality to maintain heap invariants:

Cancel: func() {
	tm.mu.Lock()
	defer tm.mu.Unlock()
-	tm.abandoned.Push(nonce)
+	heap.Push(&tm.abandoned, nonce)
},

This could lead to incorrect ordering when later using heap.Pop.

🧹 Nitpick comments (4)
pkg/testutils/noncemanager/nonceManager.go (2)

73-73: Remove misleading comment

The // No-op comment after the tm.abandoned.Push(nonce) statement is misleading since the function is actually performing an operation. This comment should be removed.


88-90: Consider documenting why Replenish is a no-op

The Replenish method is implemented as a no-op (returns nil) but lacks documentation explaining why this implementation is sufficient for testing purposes.

func (tm *TestNonceManager) Replenish(ctx context.Context, nonce big.Int) error {
+	// No-op for test implementation as we don't need to synchronize with external nonce state
	return nil
}
cmd/cli/main.go (2)

139-141: Command description might need more detail.
It could help to include usage examples or information about parameters in the help text to guide users better.


802-807: Consider avoiding fatal exit in case of load test failure.
Using logger.Fatal will terminate the entire CLI run on any error, which may be overly restrictive if partial failures are acceptable in a testing context. Returning an error or using a non-fatal log method would allow the CLI to exit with a controlled status code that can be handled by automation scripts.

 func loadTest(logger *zap.Logger, options *CLI) {
     if err := loadtest.LoadTest(logger, &options.LoadTest, &options.Contracts); err != nil {
-        logger.Fatal("could not run load test", zap.Error(err))
+        logger.Error("could not run load test", zap.Error(err))
+        os.Exit(1) // or return instead
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30fc01c and 9562a95.

📒 Files selected for processing (8)
  • cmd/cli/main.go (7 hunks)
  • pkg/blockchain/blockchainPublisher.go (2 hunks)
  • pkg/blockchain/blockchainPublisher_test.go (2 hunks)
  • pkg/blockchain/nonceManager.go (1 hunks)
  • pkg/blockchain/nonceManager_test.go (1 hunks)
  • pkg/config/cliOptions.go (1 hunks)
  • pkg/loadtest/loadtest.go (1 hunks)
  • pkg/testutils/noncemanager/nonceManager.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/blockchain/nonceManager.go
  • pkg/blockchain/blockchainPublisher.go
  • pkg/config/cliOptions.go
  • pkg/blockchain/blockchainPublisher_test.go
🧰 Additional context used
🧬 Code Definitions (1)
pkg/testutils/noncemanager/nonceManager.go (1)
pkg/blockchain/nonceManager.go (1) (1)
  • NonceContext (14-18)
🔇 Additional comments (15)
pkg/testutils/noncemanager/nonceManager.go (4)

38-43: LGTM! TestNonceManager structure

The TestNonceManager struct with mutex for synchronization, nonce tracking, logger, and abandoned heap looks well-designed for test purposes.


45-47: LGTM! Factory function

Simple and straightforward factory function.


49-66: LGTM! GetNonce method implementation

The GetNonce method correctly handles:

  1. Context cancellation
  2. Thread safety with mutex
  3. Prioritizing abandoned nonces when available
  4. Incrementing the nonce counter when needed
  5. Proper logging

80-86: LGTM! FastForwardNonce implementation

The method correctly handles thread safety with mutex locking and properly updates the nonce value.

pkg/blockchain/nonceManager_test.go (5)

5-7: LGTM! Import adjustments

The import of math/big and sync packages is appropriate for the tests.


15-33: LGTM! TestGetNonce_Simple test

The test properly initializes a SQL-backed nonce manager and verifies that:

  1. The first nonce is 0
  2. Error handling works correctly
  3. Resources are properly cleaned up

35-54: LGTM! TestGetNonce_RevertMany test

Good test for verifying that canceling nonces allows them to be reused. The test properly checks that the same nonce (0) is returned after each cancellation.


56-76: LGTM! TestGetNonce_ConsumeMany test

Good test for verifying sequential nonce consumption. The test properly checks that incrementing nonces (0-9) are returned and can be consumed without errors.


78-106: LGTM! TestGetNonce_ConsumeManyConcurrent test

Great concurrent test that creates 20 goroutines to simultaneously get and consume nonces. This effectively tests the thread safety of the nonce manager implementation.

cmd/cli/main.go (5)

15-15: No concerns about this new import.


42-42: Field name looks consistent.
Storing the load test configuration in LoadTest aligns well with the existing naming pattern for other CLI commands.


70-70: Variable definition is clear.
No issues with introducing this variable; it's immediately used for the new command.


170-170: No concerns.
New field is properly integrated into the CLI struct.


763-765: No issues with registering the new command.
This follows the established pattern for other CLI commands.

pkg/loadtest/loadtest.go (1)

1-13: Imports and package definition look appropriate.
No issues identified; all imported packages appear to be used.

@neekolas neekolas marked this pull request as ready for review March 21, 2025 22:09
@neekolas neekolas requested a review from a team as a code owner March 21, 2025 22:09
@neekolas neekolas force-pushed the 03-20-add_loadtest_script branch 2 times, most recently from 9c5c645 to 0f40bbb Compare March 21, 2025 22:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/blockchain/nonceManager_test.go (1)

79-106: Consider verifying uniqueness in concurrency test.

The concurrent test correctly checks for errors using a wait group. For stronger validation, you could confirm that each consumed nonce is unique across goroutines. This additional check can help catch subtle concurrency or nonce allocation issues.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9562a95 and 0f40bbb.

📒 Files selected for processing (8)
  • cmd/cli/main.go (7 hunks)
  • pkg/blockchain/blockchainPublisher.go (2 hunks)
  • pkg/blockchain/blockchainPublisher_test.go (2 hunks)
  • pkg/blockchain/nonceManager.go (1 hunks)
  • pkg/blockchain/nonceManager_test.go (1 hunks)
  • pkg/config/cliOptions.go (1 hunks)
  • pkg/loadtest/loadtest.go (1 hunks)
  • pkg/testutils/noncemanager/nonceManager.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/blockchain/nonceManager.go
  • pkg/blockchain/blockchainPublisher_test.go
  • pkg/blockchain/blockchainPublisher.go
  • pkg/config/cliOptions.go
🧰 Additional context used
🧬 Code Definitions (1)
pkg/testutils/noncemanager/nonceManager.go (1)
pkg/blockchain/nonceManager.go (1) (1)
  • NonceContext (14-18)
🔇 Additional comments (7)
pkg/testutils/noncemanager/nonceManager.go (2)

23-29: Preserve heap invariants or document custom 'Pop' behavior.

The Pop method is returning the first element in the slice instead of removing the last element as the standard heap.Interface expects. This deviates from the default min-heap usage in container/heap. Consider returning the last element as required by the standard heap contract or update documentation to clarify your custom approach if intentional.


70-73: Use heap.Push to maintain correctness.

Calling tm.abandoned.Push(nonce) directly bypasses the invariants maintained by heap.Push(&tm.abandoned, nonce). Consider switching to heap.Push(&tm.abandoned, nonce) so that the heap properties remain intact.

cmd/cli/main.go (3)

70-71: Load test command registration looks good.

The addition of loadTestOptions and registration of "loadtest" command in the parser is consistent with the other commands. Usage details appear correctly configured.

Also applies to: 139-141


763-766: New 'loadtest' CLI case is well-integrated.

Invoking the loadTest function from the switch statement aligns with the existing pattern of subcommand handling in this CLI. Implementation remains consistent with other commands.


802-806: Load test handler aligns with CLI conventions.

The loadTest function calls loadtest.LoadTest and uses logger.Fatal for errors, matching the CLI’s overall error-handling style. This is clear, straightforward, and maintains consistency.

pkg/loadtest/loadtest.go (2)

93-93: Invalid range clause over integer.

for i := range totalMessages won't compile because totalMessages is an integer, not a slice, channel, or map. Use a traditional loop, for example:

-for i := range totalMessages {
+for i := 0; i < totalMessages; i++ {

90-106: Concurrent assignment to shared err variable can cause data races.

Multiple goroutines write to err without synchronization, risking race conditions and lost error details. Use a mutex or another thread-safe mechanism to track errors in parallel computations.

@neekolas neekolas requested a review from mkysel March 26, 2025 21:25
@neekolas neekolas force-pushed the 03-20-add_loadtest_script branch from 0f40bbb to e31a9ea Compare March 27, 2025 20:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
pkg/testutils/noncemanager/nonceManager.go (1)

72-73: ⚠️ Potential issue

Bypassing standard heap invariants in Cancel.

Rather than calling tm.abandoned.Push(nonce) directly, using heap.Push(&tm.abandoned, nonce) is generally recommended to maintain correct heap ordering. Currently, you rely on the custom Pop approach; if the program expands or changes how the heap is used, this might cause subtle bugs.

-			tm.abandoned.Push(nonce)
+			heap.Push(&tm.abandoned, nonce)
pkg/loadtest/loadtest.go (1)

93-107: ⚠️ Potential issue

Logic errors in the send loop: invalid range and unsynchronized error assignment.

  1. for i := range totalMessages { will not compile if totalMessages is an integer.
  2. Assigning to err from multiple goroutines without synchronization causes a data race.

Below is a possible fix:

-	for i := range totalMessages {
+	var mu sync.Mutex
+	for i := 0; i < totalMessages; i++ {
 		<-ticker.C
 		wg.Add(1)
 		go func(messageNumber int) {
 			defer wg.Done()
 			if _, publishError := publisher.PublishGroupMessage(ctx, groupID, testutils.RandomBytes(options.MessageSizeBytes)); publishError != nil {
-				err = publishError
+				mu.Lock()
+				if err == nil {
+					err = publishError
+				}
+				mu.Unlock()
 				logger.Error(
 					"Failed to publish message",
-					zap.Error(err),
+					zap.Error(publishError),
 					zap.Int("messageNumber", messageNumber+1),
 				)
 			}
 		}(i)
 	}
🧹 Nitpick comments (1)
pkg/testutils/noncemanager/nonceManager.go (1)

23-29: Pop implementation deviates from the default heap interface behavior.

The standard library example for heap.Interface typically swaps and pops from the end of the slice. Here, the smallest element is removed from the front, which can still work if your Less definition is consistent. However, it's unconventional and may surprise maintainers. Consider documenting the rationale or aligning with the standard approach for clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f40bbb and e31a9ea.

📒 Files selected for processing (8)
  • cmd/cli/main.go (7 hunks)
  • pkg/blockchain/blockchainPublisher.go (2 hunks)
  • pkg/blockchain/blockchainPublisher_test.go (2 hunks)
  • pkg/blockchain/nonceManager.go (1 hunks)
  • pkg/blockchain/nonceManager_test.go (1 hunks)
  • pkg/config/cliOptions.go (1 hunks)
  • pkg/loadtest/loadtest.go (1 hunks)
  • pkg/testutils/noncemanager/nonceManager.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/blockchain/nonceManager.go
  • pkg/config/cliOptions.go
  • pkg/blockchain/blockchainPublisher_test.go
  • pkg/blockchain/blockchainPublisher.go
🧰 Additional context used
🧬 Code Definitions (3)
pkg/testutils/noncemanager/nonceManager.go (1)
pkg/blockchain/nonceManager.go (1)
  • NonceContext (14-18)
cmd/cli/main.go (2)
pkg/loadtest/loadtest.go (1)
  • LoadTest (15-61)
pkg/config/cliOptions.go (1)
  • LoadTestOptions (83-89)
pkg/loadtest/loadtest.go (3)
pkg/config/cliOptions.go (1)
  • LoadTestOptions (83-89)
pkg/blockchain/nonceManager.go (1)
  • NewSQLBackedNonceManager (51-57)
pkg/blockchain/blockchainPublisher.go (2)
  • NewBlockchainPublisher (40-117)
  • BlockchainPublisher (29-38)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test (Node)
  • GitHub Check: Lint-Go
🔇 Additional comments (8)
cmd/cli/main.go (6)

15-15: New import for load testing looks correct.

This addition appears to properly import the new loadtest package. No issues identified.


42-42: New LoadTest field in CLI struct is appropriate.

Exposing LoadTest options on the main CLI struct aligns well with the new command. No concerns noted.


70-70: Definition of loadTestOptions variable is consistent with other commands.

This variable is necessary to parse the load test parameters. Looks good.


139-141: Registration of the "loadtest" command is consistent.

The added command follows the same command registration pattern as other CLI commands. No issues found.


763-765: Case branch for "loadtest" is coherent.

Utilizing the loadTest helper function upon selecting the "loadtest" command is in line with the CLI architecture.


803-807: Load test function correctly delegates to the loadtest package.

Invoking loadtest.LoadTest here is consistent with other CLI commands that delegate logic to underlying packages. The use of logger.Fatal matches other error handling patterns in this file.

pkg/testutils/noncemanager/nonceManager.go (1)

49-78: Potential concurrency considerations in GetNonce.

Although you're locking around the abandoned-heap access and nonce increment, ensure all usage patterns are thoroughly tested under concurrent conditions. The logic appears correct, but concurrency with repeated Cancel() calls might require additional validation.

pkg/blockchain/nonceManager_test.go (1)

5-7: New imports for working with big integers, concurrency, and testing.

These changes are aligned with the test logic that requires math/big, sync, and testing. No concerns found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants