Skip to content

Conversation

@altitude
Copy link
Member

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Apr 23, 2025

Walkthrough

CreateLedger was moved to run entirely inside a database transaction with explicit constraint mapping, bucket retrieval, init/migration, up-to-date checks, and ledger-store creation. Numerous Antithesis test workloads, helper utilities, Dockerfiles, entrypoint script, and Justfile recipes were added for parallel workloads, ledger operations, health checks, and experiment orchestration.

Changes

Cohort / File(s) Summary
Core logic atomicity
internal/storage/driver/driver.go
CreateLedger now executes inside RunInTx, handling system ledger insertion (maps constraint violations to systemstore.ErrLedgerAlreadyExists), bucket retrieval, init vs. migrate flow, up-to-date assertions/ErrBucketOutdated, ledger-store creation in-transaction, and resolves/propagates DB errors. A doc comment describing steps was added.
Workload binaries — parallel & init
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go, test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go, test/antithesis/workload/bin/cmds/first_default_ledger/main.go, test/antithesis/workload/bin/init/main.go
Added binaries: parallel transaction submitter (worker pool + 100 CreateTransaction tasks), parallel ledger creator (random name), default ledger creator, and an init entrypoint that polls ledger readiness and blocks on termination signals.
Workload binaries — checks & upgrades
test/antithesis/workload/bin/cmds/eventually_correct/main.go, test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go
Added concurrent ledger health-check program (aggregated balances and per-account validations) and a dynamic Kubernetes client tool that reads /ledger_latest_tag and updates a Ledger CR's spec.version.
Workload utilities
test/antithesis/workload/internal/utils.go
New utilities: USER_ACCOUNT_COUNT, Details type, random helpers, assertion wrappers, NewClient, CreateLedger, ListLedgers, GetRandomLedger, and address generation utilities used by workload binaries.
Workload packaging & images
test/antithesis/workload/Dockerfile, test/antithesis/image/Dockerfile
Added multi-stage Dockerfiles to build instrumented workload and ledger binaries, run instrumentation, place binaries into runner images, and surface latest tag via /ledger_latest_tag.
Container entrypoint
test/antithesis/image/entrypoint.sh
New entrypoint script polling PostgreSQL readiness with pg_isready before executing ledger serve.
Build / experiment orchestration
test/antithesis/Justfile, test/antithesis/config/Justfile, test/antithesis/image/Justfile, test/antithesis/workload/Justfile
Added Justfile targets for launching experiments (run-6min, run-1h), building/pushing config and workload images, building manifests, and pushing instrumented images.
Minor formatting
tools/generator/cmd/root.go
Inserted a blank line in the import block; no functional change.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant Driver as Driver.CreateLedger
    participant DB as Database
    participant Bucket as BucketService
    participant Store as LedgerStoreFactory

    Caller->>Driver: CreateLedger(ctx, ledger)
    Driver->>DB: RunInTx(BEGIN)
    Note over Driver,DB: Insert system ledger
    DB-->>Driver: ok / constraint error
    alt constraint violation
        Driver->>DB: ROLLBACK
        Driver-->>Caller: ErrLedgerAlreadyExists
    else success
        Driver->>Bucket: GetBucket(ledger)
        Bucket-->>Driver: bucket, initialized?
        alt bucket initialized
            Driver->>Bucket: CheckUpToDate
            alt outdated
                Driver->>DB: ROLLBACK
                Driver-->>Caller: ErrBucketOutdated
            else up-to-date
                Driver->>Bucket: AddLedgerToBucket
            end
        else not initialized
            Driver->>Bucket: MigrateBucketSchema
        end
        Driver->>Store: NewStore(bucket, ledger)
        Store-->>Driver: store
        Driver->>DB: COMMIT
        Driver-->>Caller: store, nil
    end
Loading
sequenceDiagram
    participant Init as workload/bin/init
    participant LedgerAPI as Ledger service
    participant Workload as Workload binaries

    Init->>LedgerAPI: GET /ledgers (loop)
    alt not ready
        LedgerAPI-->>Init: error -> retry
    else ready
        LedgerAPI-->>Init: ok
        Init->>Workload: start workloads (parallel)
        par
            Workload->>LedgerAPI: CreateLedger / CreateTransaction / Balance checks
        and
            Workload->>LedgerAPI: Version upgrade / other ops
        end
        Workload-->>Init: done
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇
Hops through SQL, a transactional rhyme,
Buckets aligned in a careful time,
Workers scurry with postings to write,
Images baked and entrypoints light,
Tests leap forward — nimble and bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive No pull request description was provided by the author. While the pre-merge check for descriptions is lenient and should pass if the description is not off-topic, the absence of any description prevents evaluation of whether it relates to the changeset. An empty description does not provide any information about the motivation, approach, or context for the extensive changes being introduced in this pull request, which would typically be expected for substantial additions like this DST testing infrastructure. Consider adding a pull request description that explains the purpose of the DST setup, such as the motivation for adding Antithesis testing infrastructure, what workloads are being tested, and how the various components (workload binaries, Docker configurations, test utilities) work together. This would help reviewers understand the scope and rationale for the changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat: add DST setup" is directly related to the main changes in this pull request. The changeset primarily consists of setting up a comprehensive testing infrastructure for Antithesis (DST - Distributed System Testing), including new workload binaries, Docker configurations, shell scripts, and test utilities. The title accurately reflects this primary objective, using specific terminology ("DST setup") rather than generic terms, and would be understandable to team members familiar with the project's testing infrastructure.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/dst

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 12c399d and 3dc65d4.

📒 Files selected for processing (1)
  • internal/storage/driver/driver.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/storage/driver/driver.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests
  • GitHub Check: Dirty
  • GitHub Check: Trigger daily run

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Apr 23, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.72%. Comparing base (e486402) to head (3dc65d4).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #879      +/-   ##
==========================================
+ Coverage   80.67%   80.72%   +0.04%     
==========================================
  Files         186      186              
  Lines       10309    10309              
==========================================
+ Hits         8317     8322       +5     
+ Misses       1569     1565       -4     
+ Partials      423      422       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@altitude altitude marked this pull request as ready for review April 23, 2025 15:01
@altitude altitude requested a review from a team as a code owner April 23, 2025 15:01
Copy link

@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: 9

🧹 Nitpick comments (11)
test/antithesis/workload/bin/init/main.go (1)

28-34: Consider adding a log message for setup completion and shutdown.

Adding log messages would make it easier to understand the program flow, especially when running in containerized environments where logs are a primary debugging tool.

 	lifecycle.SetupComplete(map[string]any{})
+	fmt.Println("Setup complete, waiting for termination signal")
 
 	sigs := make(chan os.Signal, 1)
 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
 
 	<-sigs
+	fmt.Println("Received termination signal, shutting down")
 }
test/antithesis/image/entrypoint.sh (1)

11-11: Add proper signal handling for container graceful shutdown.

The script should properly handle termination signals to ensure clean shutdown of the ledger service.

 echo "Postgres is ready; serving ledger!"
 
+# Handle termination signals
+trap 'echo "Received termination signal. Shutting down..."; kill -TERM $child_pid 2>/dev/null; exit 0' SIGTERM SIGINT
+
 ledger serve
+child_pid=$!
+wait $child_pid
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (3)

69-71: Improve error handling consistency.

The error handling here doesn't provide specific error details in the assertion, unlike other error checks in the code.

-	if !internal.AssertAlwaysErrNil(err, "we should be able to query account 'world'", nil) {
+	if !internal.AssertAlwaysErrNil(err, "we should be able to query account 'world'", internal.Details{
+		"error": err,
+		"ledger": ledger,
+	}) {
 		return
 	}

19-31: Consider adding ledger cleanup after test completion.

The code creates a new ledger for each test run but doesn't clean it up afterward, which could lead to resource accumulation over time.

Add a defer statement after the ledger creation:

 	ledger := fmt.Sprintf("ledger-%d", internal.RandomBigInt().Int64())
 
+	// Ensure ledger cleanup after test
+	defer func() {
+		cleanupErr := internal.DeleteLedger(ctx, client, ledger)
+		if cleanupErr != nil {
+			fmt.Printf("Warning: Failed to clean up ledger %s: %v\n", ledger, cleanupErr)
+		}
+	}()
+
 	err := internal.CreateLedger(

Note: This assumes there's a DeleteLedger function in the internal package. If not, you would need to implement it.


58-61: Simplify conditional check.

The cond variable is only used once, making it unnecessary.

-	cond := !hasError.Load()
-	if assert.Always(cond, "all transactions should have been written", internal.Details{}); !cond {
+	if assert.Always(!hasError.Load(), "all transactions should have been written", internal.Details{}); hasError.Load() {
 		return
 	}
test/antithesis/workload/Earthfile (3)

43-45: Add filtering for Go files in the build loop.

The current loop builds all directories in bin/cmds without filtering. If there are non-Go files or directories without main packages, this could cause build failures.

-    FOR file IN $(ls bin/cmds)
-        RUN go build -o /out/cmds/$file ./bin/cmds/$file
+    FOR file IN $(find bin/cmds -type d -not -path "bin/cmds" | xargs -n1 basename)
+        # Check if the directory contains a main.go file before building
+        RUN if [ -f ./bin/cmds/$file/main.go ]; then \
+                go build -o /out/cmds/$file ./bin/cmds/$file; \
+            fi
     END

56-56: Consider providing documentation for the required ANTITHESIS_REPOSITORY argument.

The ANTITHESIS_REPOSITORY argument is required but has no documentation or validation on format.

-    ARG --required ANTITHESIS_REPOSITORY
+    # ANTITHESIS_REPOSITORY should be in the format of "registry.example.com/project"
+    ARG --required ANTITHESIS_REPOSITORY
+    # Validate repository format
+    RUN if ! echo "${ANTITHESIS_REPOSITORY}" | grep -q "^[a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z0-9][a-zA-Z0-9.-]*/[a-zA-Z0-9_/-]*$"; then \
+           echo "Invalid repository format: ${ANTITHESIS_REPOSITORY}"; \
+           echo "Expected format: registry.example.com/project"; \
+           exit 1; \
+        fi

15-26: Consider uncommenting the 'tidy' section or add a comment explaining why it's commented out.

The 'tidy' section is commented out but might be useful for maintaining clean go.mod files.

Either uncomment the section if it's meant to be used, or add a comment explaining why it's disabled:

-# tidy:
-#     FROM +sources
-#
-#     CACHE --id go-mod-cache /go/pkg/mod
-#     CACHE --id go-cache /root/.cache/go-build
-#
-#     WORKDIR /src/test/antithesis/workload
-#     RUN go mod tidy
-#
-#     SAVE ARTIFACT go.mod AS LOCAL go.mod
-#     SAVE ARTIFACT go.sum AS LOCAL go.sum
+# The 'tidy' target is currently disabled because module maintenance is handled in the main repository.
+# Uncomment and use this section when updates to go.mod/go.sum are needed:
+# tidy:
+#     FROM +sources
+#
+#     CACHE --id go-mod-cache /go/pkg/mod
+#     CACHE --id go-cache /root/.cache/go-build
+#
+#     WORKDIR /src/test/antithesis/workload
+#     RUN go mod tidy
+#
+#     SAVE ARTIFACT go.mod AS LOCAL go.mod
+#     SAVE ARTIFACT go.sum AS LOCAL go.sum
test/antithesis/Earthfile (1)

7-26: Review experiment parameters

The experiment duration is set to 0.1, which might be very short for meaningful testing. Consider whether this is intended as a minimum value for quick tests or if a longer duration would be more appropriate for thorough testing.

Also verify that the fixed image names (antithesis-config:latest, ledger:latest, workload:latest) match your intended tagging strategy.

test/antithesis/workload/internal/utils.go (2)

40-56: Hardcoded service endpoint

The client is configured with a hardcoded URL http://gateway:8080. Consider making this configurable via environment variables for better flexibility across different environments.

+func NewClientWithURL(serverURL string) *client.Formance {
+	if serverURL == "" {
+		serverURL = "http://gateway:8080" // Default URL
+	}
+	return client.New(
+		client.WithServerURL(serverURL),
+		client.WithClient(&http.Client{
+			Timeout: time.Minute,
+		}),
+		client.WithRetryConfig(retry.Config{
+			Strategy: "backoff",
+			Backoff: &retry.BackoffStrategy{
+				InitialInterval: 200,
+				Exponent:        1.5,
+				MaxElapsedTime:  4000,
+			},
+			RetryConnectionErrors: true,
+		}),
+	)
+}
+
 func NewClient() *client.Formance {
-	return client.New(
-		client.WithServerURL("http://gateway:8080"),
-		client.WithClient(&http.Client{
-			Timeout: time.Minute,
-		}),
-		client.WithRetryConfig(retry.Config{
-			Strategy: "backoff",
-			Backoff: &retry.BackoffStrategy{
-				InitialInterval: 200,
-				Exponent:        1.5,
-				MaxElapsedTime:  4000,
-			},
-			RetryConnectionErrors: true,
-		}),
-	)
+	return NewClientWithURL("")
 }

66-82: Transaction function with hardcoded values

The RunTx function uses hardcoded values for asset ("USD/2") and source account ("world"). Consider making these configurable parameters to support testing different asset types and account structures.

-func RunTx(ctx context.Context, client *client.Formance, amount *big.Int, ledger string) error {
+func RunTx(ctx context.Context, client *client.Formance, amount *big.Int, ledger string, asset string, source string) error {
+	if asset == "" {
+		asset = "USD/2" // Default asset
+	}
+	if source == "" {
+		source = "world" // Default source
+	}
 	orderID := fmt.Sprint(int64(math.Abs(float64(random.GetRandom()))))
 	dest := fmt.Sprintf("orders:%s", orderID)

 	_, err := client.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{
 		V2PostTransaction: components.V2PostTransaction{
 			Postings: []components.V2Posting{{
 				Amount:      amount,
-				Asset:       "USD/2",
+				Asset:       asset,
 				Destination: dest,
-				Source:      "world",
+				Source:      source,
 			}},
 		},
 		Ledger: ledger,
 	})
 	return err
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3b718a3 and 0919823.

⛔ Files ignored due to path filters (6)
  • .github/workflows/antithesis.yml is excluded by !**/*.yml
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • test/antithesis/config/docker-compose.yml is excluded by !**/*.yml
  • test/antithesis/workload/go.mod is excluded by !**/*.mod
  • test/antithesis/workload/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (13)
  • internal/controller/system/controller.go (1 hunks)
  • internal/storage/driver/driver.go (4 hunks)
  • pkg/client/Earthfile (1 hunks)
  • test/antithesis/Earthfile (1 hunks)
  • test/antithesis/config/Earthfile (1 hunks)
  • test/antithesis/config/gateway/Caddyfile (1 hunks)
  • test/antithesis/image/Earthfile (1 hunks)
  • test/antithesis/image/entrypoint.sh (1 hunks)
  • test/antithesis/workload/Earthfile (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_aggregated_volumes_should_be_zeroed/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • test/antithesis/workload/bin/init/main.go (1 hunks)
  • test/antithesis/workload/internal/utils.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
test/antithesis/workload/bin/init/main.go (2)
test/antithesis/workload/internal/utils.go (1)
  • NewClient (40-56)
internal/api/v1/controllers_config.go (1)
  • GetInfo (36-66)
test/antithesis/workload/internal/utils.go (5)
pkg/client/formance.go (3)
  • Formance (70-74)
  • WithClient (108-112)
  • WithRetryConfig (130-134)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-23)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (29-36)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
🪛 GitHub Check: codecov/patch
internal/storage/driver/driver.go

[warning] 84-95: internal/storage/driver/driver.go#L84-L95
Added lines #L84 - L95 were not covered by tests

🔇 Additional comments (15)
internal/controller/system/controller.go (1)

4-7: Import reordering looks good.

The standard library packages reflect and time have been moved to the top of the import block, following common Go style conventions where standard library imports are grouped together at the top.

pkg/client/Earthfile (1)

1-8: Clean Earthfile setup for the client component.

This is a good minimal Earthfile that creates a client artifact from the current directory. Using a scratch image keeps the build artifact lightweight. The golang version (1.22.2) is appropriately recent.

test/antithesis/config/gateway/Caddyfile (1)

1-12: Appropriate reverse proxy configuration for local development.

The Caddy configuration correctly sets up a reverse proxy to ledger services. The auto_https off and debug options are suitable for a development environment. The load balancing policy of "first" will try the first available backend before attempting the second one.

Just note that this configuration is explicitly for development and would need security enhancements (enabling HTTPS, etc.) for any production deployment.

test/antithesis/config/Earthfile (1)

1-11: Straightforward config build setup.

The Earthfile correctly configures a build process to create a config image containing the docker-compose file and gateway directory. The use of --no-manifest-list is appropriate when targeting a specific platform.

test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1)

35-43: 🛠️ Refactor suggestion

Fix potential race condition with totalAmount.

There's a potential race condition in how you're calculating the total amount. While the code is adding to totalAmount before submitting the tasks to the pool, this pattern is prone to bugs if the code is modified later.

 	hasError := atomic.NewBool(false)
-	totalAmount := big.NewInt(0)
+	// Track individual amounts for safer aggregation
+	amounts := make([]*big.Int, count)
 
 	pool := pond.New(10, 10e3)
 
 	for i := 0; i < count; i++ {
 		amount := internal.RandomBigInt()
-		totalAmount = totalAmount.Add(totalAmount, amount)
+		amounts[i] = amount
 		pool.Submit(func() {

Then after line 56 (after pool.StopAndWait()):

 	pool.StopAndWait()
 
+	// Calculate total after all tasks are submitted
+	totalAmount := big.NewInt(0)
+	for _, amount := range amounts {
+		totalAmount = totalAmount.Add(totalAmount, amount)
+	}
+
 	cond := !hasError.Load()

Likely an incorrect or invalid review comment.

internal/storage/driver/driver.go (3)

10-10: Great addition of the Antithesis SDK

The import of the Antithesis assert package is appropriate for the new assertion functionality added in this file.


40-53: Excellent documentation improvement

Adding this comprehensive doc comment significantly improves code clarity. The step-by-step explanation of the ledger creation process and transaction handling makes the complex functionality much easier to understand for developers.


57-59: Good inline comments

These explanatory comments help track the progress through the ledger creation process and align with the high-level documentation. This makes the code much more maintainable.

Also applies to: 62-63, 108-109, 114-115, 119-120

test/antithesis/Earthfile (3)

1-6: LGTM - Base configuration setup

The Earthfile version and imports look correct. Using a tagged version of the core Earthly configuration is a good practice for stability.


27-52: Good debugging setup

The debugging target properly handles session parameters and shows them for verification before submission. The approach of creating a temporary JSON file for the parameters is clean and maintainable.


53-58: LGTM - Build dependencies handled correctly

The requirements-build target properly passes arguments to the component build targets and enforces the required ANTITHESIS_REPOSITORY argument.

test/antithesis/image/Earthfile (1)

7-9: Good cache configuration

Using shared caching for Go modules and build artifacts is an excellent practice that will speed up builds.

test/antithesis/workload/internal/utils.go (3)

19-26: Good utility type and random function

The Details type and RandomBigInt function are well-implemented. The random function correctly converts the random value to a big integer for use in financial transactions.


28-38: Helpful assertion wrappers

These wrapper functions around the Antithesis assertion library provide a consistent interface for making assertions throughout the workload code. The return values allow for fluent usage in conditional blocks.


58-64: Simple, focused ledger creation function

This function provides a clean wrapper around the client API for creating ledgers, making the workload code more readable.

Comment on lines 14 to 27
func main() {
ctx := context.Background()
client := internal.NewClient()

for {
time.Sleep(time.Second)

_, err := client.Ledger.GetInfo(ctx)
if err != nil {
continue
}
break
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error logging and timeout to service readiness loop.

The polling mechanism to check for service readiness is a good approach, but it currently lacks error logging and a timeout mechanism. Without these, it could poll indefinitely if the service never becomes available, and debugging could be difficult if there are connection issues.

 func main() {
 	ctx := context.Background()
 	client := internal.NewClient()
+
+	maxAttempts := 60 // 1 minute timeout with 1-second intervals
+	attempt := 0
 
 	for {
+		attempt++
 		time.Sleep(time.Second)
 
 		_, err := client.Ledger.GetInfo(ctx)
 		if err != nil {
+			if attempt >= maxAttempts {
+				panic("Timed out waiting for ledger service to be ready")
+			}
+			fmt.Printf("Waiting for ledger service to be ready (attempt %d/%d): %v\n", attempt, maxAttempts, err)
 			continue
 		}
+		fmt.Println("Ledger service is ready")
 		break
 	}

Don't forget to add the fmt import:

 import (
 	"context"
+	"fmt"
 	"os"
 	"os/signal"
 	"syscall"
 	"time"
📝 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.

Suggested change
func main() {
ctx := context.Background()
client := internal.NewClient()
for {
time.Sleep(time.Second)
_, err := client.Ledger.GetInfo(ctx)
if err != nil {
continue
}
break
}
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
ctx := context.Background()
client := internal.NewClient()
maxAttempts := 60 // 1 minute timeout with 1-second intervals
attempt := 0
for {
attempt++
time.Sleep(time.Second)
_, err := client.Ledger.GetInfo(ctx)
if err != nil {
if attempt >= maxAttempts {
panic("Timed out waiting for ledger service to be ready")
}
fmt.Printf("Waiting for ledger service to be ready (attempt %d/%d): %v\n", attempt, maxAttempts, err)
continue
}
fmt.Println("Ledger service is ready")
break
}
// rest of your shutdown or signal handling logic…
}

Comment on lines 3 to 7
until pg_isready -d ledger -h 10.0.29.16 -U ledger
do
echo "Waiting for postgres at: $POSTGRES_URI"
sleep 2;
done
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make database connection parameters configurable via environment variables.

The script uses hardcoded database host, name, and user values. This reduces portability and makes configuration changes difficult.

-until pg_isready -d ledger -h 10.0.29.16 -U ledger
+# Extract parameters from POSTGRES_URI or use default values
+PG_HOST=${PG_HOST:-10.0.29.16}
+PG_DB=${PG_DB:-ledger}
+PG_USER=${PG_USER:-ledger}
+
+until pg_isready -d "$PG_DB" -h "$PG_HOST" -U "$PG_USER"
📝 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.

Suggested change
until pg_isready -d ledger -h 10.0.29.16 -U ledger
do
echo "Waiting for postgres at: $POSTGRES_URI"
sleep 2;
done
# Extract parameters from environment or fall back to existing defaults
PG_HOST=${PG_HOST:-10.0.29.16}
PG_DB=${PG_DB:-ledger}
PG_USER=${PG_USER:-ledger}
until pg_isready -d "$PG_DB" -h "$PG_HOST" -U "$PG_USER"
do
echo "Waiting for postgres at: $POSTGRES_URI"
sleep 2;
done

🛠️ Refactor suggestion

Add a timeout to prevent infinite waiting.

The current loop will wait indefinitely if PostgreSQL never becomes available.

+MAX_RETRIES=30
+retry_count=0
+
 until pg_isready -d ledger -h 10.0.29.16 -U ledger
 do
+  retry_count=$((retry_count+1))
+  if [ $retry_count -ge $MAX_RETRIES ]; then
+    echo "ERROR: PostgreSQL not available after $MAX_RETRIES attempts. Exiting."
+    exit 1
+  fi
   echo "Waiting for postgres at: $POSTGRES_URI"
   sleep 2;
 done
📝 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.

Suggested change
until pg_isready -d ledger -h 10.0.29.16 -U ledger
do
echo "Waiting for postgres at: $POSTGRES_URI"
sleep 2;
done
MAX_RETRIES=30
retry_count=0
until pg_isready -d ledger -h 10.0.29.16 -U ledger
do
retry_count=$((retry_count+1))
if [ $retry_count -ge $MAX_RETRIES ]; then
echo "ERROR: PostgreSQL not available after $MAX_RETRIES attempts. Exiting."
exit 1
fi
echo "Waiting for postgres at: $POSTGRES_URI"
sleep 2;
done

Comment on lines 26 to 30
for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
"error": err,
})
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix incorrect error reference in assertion details.

In the assertion for each asset's volume, you're referencing the err variable that has already been checked to be nil earlier in the code. This is misleading and could cause confusion.

 for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
-	assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
-		"error": err,
-	})
+	assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
+		"asset": asset,
+		"value": volumes.String(),
+	})
 }
📝 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.

Suggested change
for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
"error": err,
})
}
for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
"asset": asset,
"value": volumes.String(),
})
}

Comment on lines 12 to 24
func main() {
ctx := context.Background()
client := internal.NewClient()

aggregated, err := client.Ledger.V2.GetBalancesAggregated(ctx, operations.V2GetBalancesAggregatedRequest{
Ledger: "default",
})
if err != nil {
assert.Always(err == nil, "error getting aggregated balances", internal.Details{
"error": err,
})
return
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement retry mechanism for eventual consistency check.

The filename includes "eventually_" which suggests this check should be retried until it succeeds or times out. Currently, it only checks once without any retry logic.

 func main() {
 	ctx := context.Background()
 	client := internal.NewClient()
+
+	// Add retry with timeout for eventual consistency
+	const maxRetries = 30
+	const retryDelayMs = 500
+	
+	var lastErr error
+	for i := 0; i < maxRetries; i++ {
+		success := tryCheck(ctx, client)
+		if success {
+			return // Success case exits cleanly
+		}
+		
+		// Wait before retry
+		time.Sleep(time.Duration(retryDelayMs) * time.Millisecond)
+	}
+	
+	// If we get here, we've exceeded our retry attempts
+	assert.Always(false, "aggregated volumes not zeroed after maximum retries", internal.Details{})
+}
+
+func tryCheck(ctx context.Context, client *internal.Client) bool {

Further down, you would need to modify the end of the function to return a boolean success value instead of just returning:

 	if err != nil {
 		assert.Always(err == nil, "error getting aggregated balances", internal.Details{
 			"error": err,
 		})
-		return
+		return false
 	}
 
 	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
-		assert.Always(volumes.Cmp(new(big.Int)) == 0, "aggregated volumes for asset "+asset+" should be 0", internal.Details{
-			"error": err,
-		})
+		if volumes.Cmp(new(big.Int)) != 0 {
+			return false
+		}
 	}
+	return true
 }

Committable suggestion skipped: line range outside the PR's diff.

CACHE --id go-mod-cache /go/pkg/mod
CACHE --id go-cache /root/.cache/go-build

RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Pin the version of antithesis-go-instrumentor tool.

Using @latest for tools can lead to inconsistent builds and unexpected breaking changes. Consider pinning to a specific version.

-    RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+    RUN go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]  # Use appropriate version
📝 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.

Suggested change
RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@v0.5.2 # Use appropriate version

Comment on lines 84 to 95
assert.AlwaysOrUnreachable(
// @todo: replace this with a proper flag detailing wether we're
// operating a new version of the binary or not.
// if we are, we are definitely expecting this to happen.
// if we're not, this should be unreachable.
false,
"Bucket is outdated",
map[string]any{
"bucket": l.Bucket,
},
)

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Todo indicates temporary assertion pattern

The assertion is using false as the condition, which means it will always trigger as "unreachable". The TODO comments indicate this is a temporary solution, but this code path isn't covered by tests.

Consider:

  1. Adding a proper flag parameter as mentioned in the TODO
  2. Adding tests to cover this code path

🏁 Script executed:

#!/bin/bash
# Check if there are any tests covering this assertion in the driver code
rg -A 5 -B 5 "test.*Bucket.*Outdated" --type go

Length of output: 49


To be certain this assertion path is untested, let’s list any driver test files and search them for AlwaysOrUnreachable:


🏁 Script executed:

#!/bin/bash
# List Go test files in internal/storage/driver
fd -e go --glob '*_test.go' internal/storage/driver

# Search those test files for AlwaysOrUnreachable
rg "AlwaysOrUnreachable" -n --glob "internal/storage/driver/*_test.go"

Length of output: 348


Ensure proper flag control and add tests for the unreachable assertion

The assert.AlwaysOrUnreachable(false, …) at internal/storage/driver/driver.go:84–95 is built to always fire, and our search shows no tests reference AlwaysOrUnreachable to cover this path. To address:

• Introduce the boolean flag referenced in the TODO (e.g. isNewBinaryVersion) instead of hard‑coding false.
• Add unit tests in internal/storage/driver/driver_test.go (or a new test) that cover both cases:

  • Flag = true (assertion bypassed)
  • Flag = false (assertion triggers)

This will satisfy the TODO and ensure the code path is exercised.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 84-95: internal/storage/driver/driver.go#L84-L95
Added lines #L84 - L95 were not covered by tests

Comment on lines 29 to 44
build:
FROM --platform=linux/amd64 ubuntu:latest
RUN apt-get update -y && apt-get install -y postgresql-client curl

COPY (+compile/ledger) /bin/ledger
COPY (+compile/symbols) /symbols
COPY entrypoint.sh /bin/entrypoint.sh

RUN chmod 777 /bin/entrypoint.sh

ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 8080

ARG --required ANTITHESIS_REPOSITORY

SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:latest" No newline at end of file
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider version tagging strategy

The image is currently saved with the tag ledger:latest. While this works for simple workflows, consider adding a versioning strategy (like using git tags, commits, or timestamps) for better traceability in production environments.

Also verify that the entrypoint script has appropriate error handling for graceful failures.


🏁 Script executed:

#!/bin/bash
# Check entrypoint script for error handling patterns
cat test/antithesis/image/entrypoint.sh | grep -E 'set -e|exit [1-9]||| exit [1-9]'

Length of output: 316


Implement image version tagging and enhance entrypoint error handling

To improve traceability and robustness, please make the following updates:

• In test/antithesis/image/Earthfile
– Replace the hard‑coded ledger:latest tag with a parameterized/versioned tag (e.g. from a Git tag or commit SHA).
– Example diff:

 ARG --required ANTITHESIS_REPOSITORY
-  SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:latest"
+  ARG IMAGE_TAG?=$(git describe --tags --always)
+  SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:${IMAGE_TAG}"

• In test/antithesis/image/entrypoint.sh
– Add strict error handling so failures inside the script halt the container startup.
– Example diff at top of the file:

#!/bin/sh
+ set -euo pipefail
+ trap 'echo "⛔ Entrypoint failed on line $LINENO"; exit 1' ERR
# make sure pg is ready to accept connections
until pg_isready -d ledger -h 10.0.29.16 -U ledger; do
  echo "Waiting for postgres at: $POSTGRES_URI"

– Ensure any commands (including ledger serve) propagate non‑zero exit codes.

These changes will give you deterministic image tags and guarantee the container fails fast on errors.

📝 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.

Suggested change
build:
FROM --platform=linux/amd64 ubuntu:latest
RUN apt-get update -y && apt-get install -y postgresql-client curl
COPY (+compile/ledger) /bin/ledger
COPY (+compile/symbols) /symbols
COPY entrypoint.sh /bin/entrypoint.sh
RUN chmod 777 /bin/entrypoint.sh
ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 8080
ARG --required ANTITHESIS_REPOSITORY
SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:latest"
# test/antithesis/image/Earthfile
build:
FROM --platform=linux/amd64 ubuntu:latest
RUN apt-get update -y && apt-get install -y postgresql-client curl
COPY (+compile/ledger) /bin/ledger
COPY (+compile/symbols) /symbols
COPY entrypoint.sh /bin/entrypoint.sh
RUN chmod 777 /bin/entrypoint.sh
ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 8080
ARG --required ANTITHESIS_REPOSITORY
ARG IMAGE_TAG?=$(git describe --tags --always)
SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:${IMAGE_TAG}"
Suggested change
build:
FROM --platform=linux/amd64 ubuntu:latest
RUN apt-get update -y && apt-get install -y postgresql-client curl
COPY (+compile/ledger) /bin/ledger
COPY (+compile/symbols) /symbols
COPY entrypoint.sh /bin/entrypoint.sh
RUN chmod 777 /bin/entrypoint.sh
ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 8080
ARG --required ANTITHESIS_REPOSITORY
SAVE IMAGE --push --no-manifest-list "${ANTITHESIS_REPOSITORY}/ledger:latest"
# test/antithesis/image/entrypoint.sh
#!/bin/sh
set -euo pipefail
trap 'echo "⛔ Entrypoint failed on line $LINENO"; exit 1' ERR
# make sure pg is ready to accept connections
until pg_isready -d ledger -h 10.0.29.16 -U ledger; do
echo "Waiting for postgres at: $POSTGRES_URI"
done
# ... rest of your startup logic (e.g. ledger serve)


// Step 1: Create the ledger record in the system store
if err := systemStore.CreateLedger(ctx, l); err != nil {
// Handle the case where the ledger already exists
Copy link
Contributor

Choose a reason for hiding this comment

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

I know comments are useful, but this kind of comment just rephrase the code ^^

@@ -0,0 +1,8 @@
VERSION 0.7
Copy link
Contributor

@gfyrag gfyrag Apr 24, 2025

Choose a reason for hiding this comment

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

Stop using Earthfile, there is no Earthfile anymore in the repsoitory ;)

Copy link

@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

🧹 Nitpick comments (7)
test/antithesis/workload/bin/cmds/parallel_drive_ledger_create/main.go (2)

12-28: The ledger creation appears correct, but consider adding confirmation logging.

The code properly creates a ledger with a randomized name, but it would be helpful to add a success log message to confirm when the ledger has been created successfully. This would make troubleshooting easier when reviewing test logs.

	if err != nil {
		assert.Always(err == nil, "ledger should have been created properly", internal.Details{
			"error": err,
		})
		return
	}
+	// Print confirmation that ledger was created successfully
+	fmt.Printf("Ledger '%s' created successfully\n", ledger)
}

15-16: Consider checking for overflow when converting to int64.

Using the absolute value of a randomly generated big integer and directly converting to int64 could potentially lead to overflow issues if the value exceeds the maximum int64 value.

-	id := big.NewInt(0).Abs(internal.RandomBigInt()).Int64()
+	// Ensure the ID fits within int64 bounds
+	bigInt := big.NewInt(0).Abs(internal.RandomBigInt())
+	// Use Mod to ensure it's within int64 range
+	maxInt64 := big.NewInt(math.MaxInt64)
+	id := big.NewInt(0).Mod(bigInt, maxInt64).Int64()
	ledger := fmt.Sprintf("ledger-%d", id)
test/antithesis/workload/bin/cmds/first_default_ledger/main.go (1)

10-25: Consider adding confirmation logging.

The code properly creates the default ledger, but lacks any success message that would be helpful for logging and debugging purposes.

	if err != nil {
		assert.Always(err == nil, "ledger should have been created properly", internal.Details{
			"error": err,
		})
		return
	}
+	fmt.Println("Default ledger created successfully")
}

Don't forget to add the fmt import if you implement this change.

test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1)

14-24: Add error handling for the edge case of no ledgers.

The program should handle the case where no ledgers are returned, which is possible especially during initial setup.

	ledgers, err := client.Ledger.V2.ListLedgers(ctx, operations.V2ListLedgersRequest{})
	if err != nil {
		assert.Always(err == nil, "error listing ledgers", internal.Details{
			"error": err,
		})
		return
	}

+	// Handle the case of no ledgers found
+	if len(ledgers.V2LedgerListResponse.Cursor.Data) == 0 {
+		fmt.Println("No ledgers found to check volumes")
+		return
+	}
test/antithesis/workload/internal/utils.go (3)

22-27: Add more context and validation to RandomBigInt function.

The function relies on an external random number generator without validation or documentation about what it returns. Consider adding more context to make the function more robust.

func RandomBigInt() *big.Int {
+	// random.GetRandom() returns a 64-bit random integer
	v := random.GetRandom()
	ret := big.NewInt(0)
	ret.SetString(fmt.Sprintf("%d", v), 10)
+	// Ensure the returned value is non-negative as a safeguard
+	if ret.Sign() < 0 {
+		ret.Neg(ret)
+	}
	return ret
}

81-96: Seed the random generator for better randomization.

The rand.Intn() function is used without seeding the random generator, which can lead to predictable sequences if called in quick succession. Consider seeding the random generator for better randomization.

func GetRandomLedger(ctx context.Context, client *client.Formance) (string, error) {
	ledgers, err := ListLedgers(ctx, client)
	if err != nil {
		return "", err
	}

	if len(ledgers) == 0 {
		return "", fmt.Errorf("no ledgers found")
	}

	// Get a random index within the ledgers slice
+	// Seed the random generator with current time to ensure randomness
+	rand.Seed(time.Now().UnixNano())
	randomIndex := rand.Intn(len(ledgers))

	// Return the ledger at the random index
	return ledgers[randomIndex], nil
}

Note: If you're using Go 1.20+, you should use the newer rand.New(rand.NewSource(time.Now().UnixNano())) approach instead, as rand.Seed is deprecated.


98-114: Hardcoded asset type in RunTx function.

The function hardcodes "USD/2" as the asset, which may not be flexible enough for all test scenarios. Consider making the asset type configurable or adding documentation explaining why this specific asset is used.

-func RunTx(ctx context.Context, client *client.Formance, amount *big.Int, ledger string) error {
+// RunTx creates a transaction on the specified ledger with the given amount of the default asset (USD/2)
+// If you need to use a different asset, use the RunTxWithAsset function instead
+func RunTx(ctx context.Context, client *client.Formance, amount *big.Int, ledger string) error {
+	return RunTxWithAsset(ctx, client, amount, "USD/2", ledger)
+}
+
+// RunTxWithAsset creates a transaction on the specified ledger with the given amount and asset
+func RunTxWithAsset(ctx context.Context, client *client.Formance, amount *big.Int, asset string, ledger string) error {
	orderID := fmt.Sprint(int64(math.Abs(float64(random.GetRandom()))))
	dest := fmt.Sprintf("orders:%s", orderID)

	_, err := client.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{
		V2PostTransaction: components.V2PostTransaction{
			Postings: []components.V2Posting{{
				Amount:      amount,
-				Asset:       "USD/2",
+				Asset:       asset,
				Destination: dest,
				Source:      "world",
			}},
		},
		Ledger: ledger,
	})
	return err
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0919823 and a2351d6.

⛔ Files ignored due to path filters (3)
  • .github/workflows/antithesis.yml is excluded by !**/*.yml
  • test/antithesis/workload/go.mod is excluded by !**/*.mod
  • test/antithesis/workload/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (6)
  • test/antithesis/workload/bin/cmds/first_default_ledger/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_drive_ledger_create/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1 hunks)
  • test/antithesis/workload/internal/utils.go (1 hunks)
  • tools/generator/cmd/root.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tools/generator/cmd/root.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
🧰 Additional context used
🧬 Code Graph Analysis (3)
test/antithesis/workload/bin/cmds/first_default_ledger/main.go (1)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (41-57)
  • CreateLedger (59-65)
  • Details (20-20)
test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (4)
test/antithesis/workload/internal/utils.go (2)
  • NewClient (41-57)
  • ListLedgers (67-79)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-20)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
test/antithesis/workload/bin/cmds/parallel_drive_ledger_create/main.go (1)
test/antithesis/workload/internal/utils.go (4)
  • NewClient (41-57)
  • RandomBigInt (22-27)
  • CreateLedger (59-65)
  • Details (20-20)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Tests
  • GitHub Check: Dirty
  • GitHub Check: Push Images to Antithesis

Comment on lines 42 to 50
for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
assert.Always(
volumes.Cmp(new(big.Int)) == 0,
fmt.Sprintf("aggregated volumes for asset %s should be 0",
asset,
), internal.Details{
"error": err,
})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect error variable in assertion details.

The err variable in the assertion details is from the GetBalancesAggregated call, which would be nil at this point. The details should contain relevant information about the volumes instead.

	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
		assert.Always(
			volumes.Cmp(new(big.Int)) == 0,
			fmt.Sprintf("aggregated volumes for asset %s should be 0",
				asset,
			), internal.Details{
-				"error": err,
+				"asset": asset,
+				"volume": volumes.String(),
			})
	}
🤖 Prompt for AI Agents (early access)
In test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go around lines 42 to 50, the assertion details incorrectly include the variable 'err', which is nil here. Replace 'err' with relevant information about the 'volumes' variable to provide meaningful context in the assertion failure details.

Copy link

@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 (2)
test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (2)

20-30: Error handling could be more descriptive.

The error handling is functional but could benefit from a more specific error message that includes the ledger name.

 	if err != nil {
-		assert.Always(err == nil, "ledger should have been created properly", internal.Details{
+		assert.Always(err == nil, fmt.Sprintf("Failed to create ledger %s", ledger), internal.Details{
 			"error": err,
+			"ledger": ledger,
 		})
 		return
 	}

32-33: Consider adding more detailed logging.

The completion message could include the ledger name to make logs more traceable in a parallel environment.

-	log.Println("composer: parallel_driver_ledger_create: done")
+	log.Printf("composer: parallel_driver_ledger_create: done (created ledger: %s)", ledger)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a2351d6 and 58524d7.

📒 Files selected for processing (6)
  • test/antithesis/Earthfile (1 hunks)
  • test/antithesis/workload/Earthfile (1 hunks)
  • test/antithesis/workload/bin/cmds/first_default_ledger/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
  • test/antithesis/workload/bin/cmds/first_default_ledger/main.go
  • test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go
  • test/antithesis/Earthfile
  • test/antithesis/workload/Earthfile
🧰 Additional context used
🧬 Code Graph Analysis (1)
test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (1)
test/antithesis/workload/internal/utils.go (4)
  • NewClient (41-57)
  • RandomBigInt (22-27)
  • CreateLedger (59-65)
  • Details (20-20)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Dirty
  • GitHub Check: Tests
  • GitHub Check: Push Images to Antithesis
🔇 Additional comments (3)
test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (3)

1-12: Good package structure and imports.

The package structure and imports are well-organized. The code correctly imports the necessary libraries for context, formatting, logging, and big integer operations, as well as the Antithesis assertion library and internal utilities.


13-19: Solid initialization with unique ledger naming.

The initialization logic is sound:

  • Properly sets up a background context
  • Initializes a client using the internal utility
  • Generates a unique ledger name using a random positive ID

Good use of the Abs() method to ensure the randomly generated ID is positive before formatting it into a ledger name.


1-34:

❓ Verification inconclusive

Add cleanup or documentation for created resources.

The program creates a ledger but doesn't have a mechanism to clean it up. While this might be handled elsewhere, it would be beneficial to either:

  1. Add a cleanup step, or
  2. Add documentation explaining why cleanup isn't needed or how it's handled

This helps prevent resource leakage in testing environments.


🏁 Script executed:

#!/bin/bash
# Check if there are any existing cleanup mechanisms for ledgers in the codebase
echo "Searching for ledger cleanup mechanisms..."
rg -i "delete\s+ledger|cleanup\s+ledger|remove\s+ledger" --type go

Length of output: 313


Add cleanup or document resource handling

I searched the repo for ledger‐cleanup routines and found only:

  • internal/storage/system/migrations.go (“Remove ledger state column”)
  • pkg/client/v2.go (DeleteLedgerMetadata) – only deletes metadata, not the ledger itself

There’s currently no API call in this test CLI to delete the created ledger. To avoid resource leakage in your test environment, please either:

  1. Invoke or implement a proper DeleteLedger (or similar) helper after creation, or
  2. Add documentation explaining why ledger cleanup isn’t needed here (e.g. handled by a global teardown or another process)

Copy link

@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 (1)
test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1)

53-61: Fix incorrect error variable in assertion details.

The err variable in the assertion details is from the GetBalancesAggregated call, which would be nil at this point. The details should contain relevant information about the volumes instead.

	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
		assert.Always(
			volumes.Cmp(new(big.Int)) == 0,
			fmt.Sprintf("aggregated volumes for asset %s should be 0",
				asset,
			), internal.Details{
-				"error": err,
+				"asset": asset,
+				"volume": volumes.String(),
			})
	}
🧹 Nitpick comments (2)
test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (2)

39-51: Logging improvement suggestion.

Consider adding more detailed logging before making the API call to help with debugging and monitoring the workflow.

func checkVolumes(ctx context.Context, client *client.Formance, ledger string) {
+	log.Printf("composer: parallel_driver_volumes: checking volumes for ledger %s", ledger)
	aggregated, err := client.Ledger.V2.GetBalancesAggregated(ctx, operations.V2GetBalancesAggregatedRequest{
		Ledger: ledger,
	})

17-22: Consider adding a timeout to the context.

For better resource management, consider using a context with timeout to prevent indefinite hanging if an operation gets stuck.

func main() {
	log.Println("composer: parallel_driver_volumes")
-	ctx := context.Background()
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	defer cancel()
	client := internal.NewClient()

Don't forget to add the time import:

import (
	"context"
	"fmt"
	"log"
	"math/big"
	"os"
	"sync"
+	"time"
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 58524d7 and 4ed1eb5.

📒 Files selected for processing (1)
  • test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (4)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (41-57)
  • ListLedgers (67-79)
  • Details (20-20)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-20)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
🔇 Additional comments (1)
test/antithesis/workload/bin/cmds/parallel_driver_volumes/main.go (1)

17-37: LGTM: Good concurrency implementation with WaitGroup.

The main function correctly implements the WaitGroup pattern for concurrency, ensuring all goroutines complete before the program exits. This addresses the synchronization issue that was mentioned in past reviews.

Signed-off-by: Clément Salaün <[email protected]>
Copy link

@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 (4)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (4)

81-83: Clarify Sometimes-assert message

The predicate asserts that errors may occur; adjust message to match intent.

-		assert.Sometimes(err != nil, "Client can aggregate account balances", internal.Details{
+		assert.Sometimes(err != nil, "GetAccount may transiently fail", internal.Details{
 			"error": err,
 		})

132-134: Remove panic; rely on assertion for failure reporting

Panicking in a goroutine kills the whole workload; the assert below already captures the invariant.

-			if tx.ID.Cmp(expectedTxId) != 0 {
-				panic(fmt.Sprintf("oop: expected %v and got %v", expectedTxId, tx.ID))
-			}

121-121: Use log.Printf and include newline

Minor logging consistency.

-			fmt.Printf("error listing transactions: %v", err)
+			log.Printf("error listing transactions: %v", err)

13-13: Drop unused import introduced by the duplicate IsServerError

Once the helper is removed, this import is unused.

-	"github.com/formancehq/ledger/pkg/client/models/components"
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a5e3d61 and f51e01f.

⛔ Files ignored due to path filters (2)
  • .github/workflows/antithesis_daily.yml is excluded by !**/*.yml
  • .github/workflows/antithesis_release.yml is excluded by !**/*.yml
📒 Files selected for processing (3)
  • test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
  • test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go
🧰 Additional context used
🧬 Code graph analysis (1)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (6)
test/antithesis/workload/internal/utils.go (4)
  • NewClient (43-63)
  • ListLedgers (80-92)
  • Details (22-22)
  • USER_ACCOUNT_COUNT (20-20)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-24)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
pkg/client/models/operations/v2listtransactions.go (1)
  • V2ListTransactionsRequest (37-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Dirty
  • GitHub Check: Tests
  • GitHub Check: Push Images to Antithesis
🔇 Additional comments (5)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (5)

61-69: Fix assertion details: err is nil here; include actual volume

Use the actual value to make the failure actionable.

 	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
 		assert.Always(
 			volumes.Cmp(new(big.Int)) == 0,
 			fmt.Sprintf("aggregated volumes for asset %s should be 0",
 				asset,
 			), internal.Details{
-				"error": err,
+				"asset": asset,
+				"actual_volume": volumes.String(),
 			})
 	}

112-115: LGTM on pagination

Passing the cursor and advancing it removes the infinite-loop risk. Good fix.

Also applies to: 140-144


148-150: Remove duplicate IsServerError; use internal.IsServerError everywhere

This duplicates internal.IsServerError and risks divergence.

-func IsServerError(httpMeta components.HTTPMetadata) bool {
-	return httpMeta.Response.StatusCode >= 400 && httpMeta.Response.StatusCode < 600
-}

Note: also remove the now-unused import of components (see next comment).


47-59: Avoid nil deref, don’t Fatalf in goroutines, and return after handling error

  • aggregated may be nil; guard before GetHTTPMeta().
  • log.Fatalf inside these goroutines kills the whole program; prefer non-fatal log + return.
  • Ensure early-return on error to avoid using aggregated later.
-	if err != nil {
-		if internal.IsServerError(aggregated.GetHTTPMeta()) {
+	if err != nil {
+		if aggregated != nil && internal.IsServerError(aggregated.GetHTTPMeta()) {
 			assert.Always(
 				false,
 				fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
 				internal.Details{
 					"error": err,
 				},
 			)
 		} else {
-			log.Fatalf("error getting aggregated balances for ledger %s: %s", ledger, err)
+			log.Printf("error getting aggregated balances for ledger %s: %s", ledger, err)
 		}
+		return
 	}

75-76: Ensure Go toolchain ≥ 1.22 or replace range-over-integer

Go 1.22 introduced "for i := range N". This repo has no go.mod or CI Go-version and the code at test/antithesis/workload/bin/cmds/eventually_correct/main.go:75–76 uses for i := range internal.USER_ACCOUNT_COUNT; either ensure builds use Go ≥1.22 or change to a classic loop (USER_ACCOUNT_COUNT is uint64):

for i := uint64(0); i < internal.USER_ACCOUNT_COUNT; i++ {

Copy link

@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: 1

🧹 Nitpick comments (3)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (3)

81-83: Clarify Sometimes assertion semantics

Message says “Client can aggregate…”, but the condition asserts an error. Likely should assert success.

Apply this diff (if intent is to assert occasional success):

-		assert.Sometimes(err != nil, "Client can aggregate account balances", internal.Details{
+		assert.Sometimes(err == nil, "Client can aggregate account balances", internal.Details{
 			"error": err,
 		})

Please confirm intended semantics of Sometimes in this suite.


126-129: Avoid mutating the transaction’s ID via shared big.Int pointer

Copy the value before arithmetic; Add/Sub mutate the receiver.

Apply this diff:

-		if expectedTxId == nil {
-			expectedTxId = transactions.V2TransactionsCursorResponse.Cursor.Data[0].ID
-			expectedTxId.Add(expectedTxId, big.NewInt(1))
-		}
+		if expectedTxId == nil {
+			// copy value to avoid mutating the transaction ID
+			expectedTxId = new(big.Int).Set(transactions.V2TransactionsCursorResponse.Cursor.Data[0].ID)
+			expectedTxId.Add(expectedTxId, big.NewInt(1))
+		}

120-121: Use consistent logging

Prefer log.Printf for consistency with the rest of the file.

Apply this diff:

-			fmt.Printf("error listing transactions: %v", err)
+			log.Printf("error listing transactions: %v", err)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f51e01f and 47401f6.

📒 Files selected for processing (2)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-05-20T13:08:10.034Z
Learnt from: gfyrag
PR: formancehq/ledger#935
File: test/e2e/api_ledgers_import_test.go:338-352
Timestamp: 2025-05-20T13:08:10.034Z
Learning: The transactions list API for the ledger service has a stable ordering by ID that will never change, making it safe to rely on transactionsFromOriginalLedger.Data[0].ID in tests.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
🧬 Code graph analysis (1)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (6)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (43-63)
  • Details (22-22)
  • USER_ACCOUNT_COUNT (20-20)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
pkg/client/models/operations/v2listtransactions.go (1)
  • V2ListTransactionsRequest (37-61)
pkg/client/models/components/v2transactionscursorresponse.go (1)
  • V2TransactionsCursorResponse (48-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests
  • GitHub Check: Dirty
  • GitHub Check: Push Images to Antithesis
🔇 Additional comments (4)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (4)

61-69: Assertion details should include actual values, not a possibly nil error

Provide actionable context by reporting the asset and the actual aggregated volume.

Apply this diff:

-		assert.Always(
-			volumes.Cmp(new(big.Int)) == 0,
-			fmt.Sprintf("aggregated volumes for asset %s should be 0",
-				asset,
-			), internal.Details{
-				"error": err,
-			})
+		assert.Always(
+			volumes.Cmp(new(big.Int)) == 0,
+			fmt.Sprintf("aggregated volumes for asset %s should be 0", asset),
+			internal.Details{
+				"asset":         asset,
+				"actual_volume": volumes.String(),
+			},
+		)

108-141: Pagination logic looks good

Using Cursor/HasMore prevents the infinite-loop hazard and iterates pages correctly.


145-147: Remove duplicate IsServerError; use internal.IsServerError

This duplicates existing logic used above.

Apply this diff:

-func IsServerError(httpMeta components.HTTPMetadata) bool {
-	return httpMeta.Response.StatusCode >= 400 && httpMeta.Response.StatusCode < 600
-}

47-59: Avoid nil deref and don’t terminate the program from a goroutine; return after handling errors

Guard aggregated before GetHTTPMeta(), avoid log.Fatalf inside goroutines, and make sure to return to prevent panics on nil aggregated.

Apply this diff:

-	if err != nil {
-		if internal.IsServerError(aggregated.GetHTTPMeta()) {
-			assert.Always(
-				false,
-				fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
-				internal.Details{
-					"error": err,
-				},
-			)
-		} else {
-			log.Fatalf("error getting aggregated balances for ledger %s: %s", ledger, err)
-		}
-	}
+	if err != nil {
+		// aggregated may be nil on error
+		var httpMeta components.HTTPMetadata
+		if aggregated != nil {
+			httpMeta = aggregated.GetHTTPMeta()
+		}
+		if internal.IsServerError(httpMeta) {
+			assert.Always(false,
+				fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
+				internal.Details{"error": err, "ledger": ledger},
+			)
+			return
+		}
+		log.Printf("error getting aggregated balances for ledger %s: %s", ledger, err)
+		return
+	}

Comment on lines +75 to +80
for i := range internal.USER_ACCOUNT_COUNT {
address := fmt.Sprintf("users:%d", i)
account, err := client.Ledger.V2.GetAccount(ctx, operations.V2GetAccountRequest{
Ledger: ledger,
Address: address,
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Go does not support ranging over an integer

This does not compile. Use a classic indexed loop.

Apply this diff:

-	for i := range internal.USER_ACCOUNT_COUNT {
-		address := fmt.Sprintf("users:%d", i)
+	for i := 0; i < int(internal.USER_ACCOUNT_COUNT); i++ {
+		address := fmt.Sprintf("users:%d", i)
📝 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.

Suggested change
for i := range internal.USER_ACCOUNT_COUNT {
address := fmt.Sprintf("users:%d", i)
account, err := client.Ledger.V2.GetAccount(ctx, operations.V2GetAccountRequest{
Ledger: ledger,
Address: address,
})
for i := 0; i < int(internal.USER_ACCOUNT_COUNT); i++ {
address := fmt.Sprintf("users:%d", i)
account, err := client.Ledger.V2.GetAccount(ctx, operations.V2GetAccountRequest{
Ledger: ledger,
Address: address,
})
🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/eventually_correct/main.go around lines 75
to 80, the code incorrectly uses "for i := range internal.USER_ACCOUNT_COUNT"
which fails because Go cannot range over an integer; replace it with a classic
indexed loop such as "for i := 0; i < internal.USER_ACCOUNT_COUNT; i++"
(ensuring USER_ACCOUNT_COUNT is an int or cast as needed) and keep the body
unchanged so address := fmt.Sprintf("users:%d", i) and the subsequent GetAccount
call continue to use the loop index.

Copy link

@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: 6

🧹 Nitpick comments (2)
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1)

48-52: Optional: Avoid name shadowing (client param vs imported package).

Rename the parameter for readability.

Apply this diff:

-func CreateTransaction(
-	ctx context.Context,
-	client *client.Formance,
-	ledger string,
-) {
+func CreateTransaction(
+	ctx context.Context,
+	cli *client.Formance,
+	ledger string,
+) {
@@
-	res, err := client.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{
+	res, err := cli.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{
test/antithesis/workload/bin/cmds/eventually_correct/main.go (1)

123-123: Use log.Printf for consistency

Align logging with the rest of the file and include timestamps.

-			fmt.Printf("error listing transactions: %v", err)
+			log.Printf("error listing transactions: %v", err)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 47401f6 and c40afec.

📒 Files selected for processing (3)
  • test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-20T13:08:10.034Z
Learnt from: gfyrag
PR: formancehq/ledger#935
File: test/e2e/api_ledgers_import_test.go:338-352
Timestamp: 2025-05-20T13:08:10.034Z
Learning: The transactions list API for the ledger service has a stable ordering by ID that will never change, making it safe to rely on transactionsFromOriginalLedger.Data[0].ID in tests.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
📚 Learning: 2025-02-24T11:31:17.135Z
Learnt from: gfyrag
PR: formancehq/ledger#703
File: internal/storage/driver/driver_test.go:31-34
Timestamp: 2025-02-24T11:31:17.135Z
Learning: In Go, using range with an integer constant (e.g., `for i := range countLedgers`) is valid syntax and compiles correctly.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
🧬 Code graph analysis (3)
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (5)
test/antithesis/workload/internal/utils.go (5)
  • NewClient (43-63)
  • GetRandomLedger (94-107)
  • Details (22-22)
  • GetRandomAddress (109-111)
  • RandomBigInt (24-29)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-25)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
pkg/client/models/components/v2errorsenum.go (1)
  • V2ErrorsEnumInsufficientFund (14-14)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (6)
test/antithesis/workload/internal/utils.go (5)
  • NewClient (43-63)
  • ListLedgers (80-92)
  • IsServerError (65-67)
  • Details (22-22)
  • USER_ACCOUNT_COUNT (20-20)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
pkg/client/models/operations/v2listtransactions.go (1)
  • V2ListTransactionsRequest (37-61)
pkg/client/models/components/v2transactionscursorresponse.go (1)
  • V2TransactionsCursorResponse (48-50)
test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go (1)
test/antithesis/workload/internal/utils.go (1)
  • Details (22-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Dirty
  • GitHub Check: Tests
  • GitHub Check: Push Images to Antithesis
🔇 Additional comments (9)
test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go (3)

3-17: Add missing imports for trimming and timeouts (and in-cluster config if adopted).

Required for the fixes above and the timeout/config changes below.

Apply this diff:

 import (
 	"context"
 	"log"
 	"os"
+	"strings"
+	"time"
@@
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/dynamic"
@@
+	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 )

26-35: Prefer in-cluster config with local kubeconfig fallback.

Works in pods and locally without changing flags.

Apply this diff:

-	// build dynamic client
-	config, err := clientcmd.BuildConfigFromFlags("", "")
-	if err != nil {
-		panic(err)
-	}
+	// build dynamic client (prefer in-cluster, fallback to local)
+	config, err := rest.InClusterConfig()
+	if err != nil {
+		config, err = clientcmd.BuildConfigFromFlags("", "")
+		if err != nil {
+			panic(err)
+		}
+	}

43-57: Optional: Use MergePatch to update spec.version directly.

Reduces read/modify/write races and payload size; avoids field drift.

Apply this diff if you prefer Patch over Update:

-	// fetch the previous Ledger resource
-	res, err := dyn.Resource(gvr).Get(context.Background(), "stack0-ledger", metav1.GetOptions{})
-	if err != nil {
-		panic(err)
-	}
-
-	// set the version to the latest tag
-	unstructured.SetNestedField(res.Object, string(latest_tag), "spec", "version")
-
-	res, err = dyn.Resource(gvr).Update(context.Background(), res, metav1.UpdateOptions{})
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	payload, _ := json.Marshal(map[string]any{
+		"spec": map[string]any{"version": tag},
+	})
+	res, err := dyn.Resource(gvr).
+		Namespace("formance-systems").
+		Patch(ctx, "stack0-ledger", types.MergePatchType, payload, metav1.PatchOptions{})
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1)

33-39: Optional: Capture outer variables for clarity in goroutines.

Not strictly necessary here, but avoids surprises if the variables become mutable later.

Apply this diff:

-	for i := 0; i < count; i++ {
-		pool.Submit(func() { CreateTransaction(ctx, client, ledger) })
-	}
+	for i := 0; i < count; i++ {
+		lctx, lclient, lledger := ctx, client, ledger
+		pool.Submit(func() { CreateTransaction(lctx, lclient, lledger) })
+	}
test/antithesis/workload/bin/cmds/eventually_correct/main.go (5)

61-69: Assertion details: include actual volume; avoid nil err in details

err will be nil here; include the measured volume (and keep asset) for useful diagnostics.

 	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
 		assert.Always(
 			volumes.Cmp(new(big.Int)) == 0,
 			fmt.Sprintf("aggregated volumes for asset %s should be 0",
 				asset,
-			), internal.Details{
-				"error": err,
-			})
+			), internal.Details{
+				"asset":         asset,
+				"actual_volume": volumes.String(),
+			})
 	}

75-80: LGTM: integer range loop is valid

Using for i := range internal.USER_ACCOUNT_COUNT is valid with recent Go versions and reads clearly.


111-144: LGTM: pagination and sequential ID checks

Good use of cursor-based pagination and stable ordering to verify sequential IDs across pages.


148-150: Remove duplicate IsServerError; use internal.IsServerError

This duplicates internal.IsServerError. Remove to avoid drift.

-func IsServerError(httpMeta components.HTTPMetadata) bool {
-	return httpMeta.Response.StatusCode >= 400 && httpMeta.Response.StatusCode < 600
-}

Note: If nothing else uses components after this change, drop that import too; otherwise the import stays needed if you adopt the earlier httpMeta fix.


47-59: Fix nil-deref and avoid Fatalf in goroutine; return after handling error

aggregated can be nil when err != nil leading to a panic on GetHTTPMeta(). Also, log.Fatalf inside a goroutine terminates the whole program. Build httpMeta safely, use internal.IsServerError, log non-server errors, and return to avoid subsequent nil use.

 	if err != nil {
-		if internal.IsServerError(aggregated.GetHTTPMeta()) {
-			assert.Always(
-				false,
-				fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
-				internal.Details{
-					"error": err,
-				},
-			)
-		} else {
-			log.Fatalf("error getting aggregated balances for ledger %s: %s", ledger, err)
-		}
+		httpMeta := components.HTTPMetadata{}
+		if aggregated != nil {
+			httpMeta = aggregated.GetHTTPMeta()
+		}
+		if internal.IsServerError(httpMeta) {
+			assert.Always(
+				false,
+				fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
+				internal.Details{
+					"error": err,
+				},
+			)
+		} else {
+			log.Printf("error getting aggregated balances for ledger %s: %s", ledger, err)
+		}
+		return
 	}

Comment on lines +21 to +24
latest_tag, err := os.ReadFile("/ledger_latest_tag")
if err != nil {
log.Fatal(err)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Trim newline from /ledger_latest_tag and validate non-empty; use the trimmed value when setting spec.version.

Using the raw file contents can include a trailing newline and empty values.

Apply this diff:

-	latest_tag, err := os.ReadFile("/ledger_latest_tag")
+	b, err := os.ReadFile("/ledger_latest_tag")
 	if err != nil {
 		log.Fatal(err)
 	}
+	tag := strings.TrimSpace(string(b))
+	if tag == "" {
+		log.Fatal("empty LEDGER_LATEST_TAG")
+	}
@@
-	// set the version to the latest tag
-	unstructured.SetNestedField(res.Object, string(latest_tag), "spec", "version")
+	// set the version to the latest tag
+	if err := unstructured.SetNestedField(res.Object, tag, "spec", "version"); err != nil {
+		log.Fatal(err)
+	}

Also applies to: 49-51

🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go around
lines 21-24 (and also apply the same change at lines 49-51), replace the raw
os.ReadFile usage with reading the file, trimming whitespace/newlines (e.g.,
strings.TrimSpace) and validating the result is non-empty; if trimmed value is
empty return or log.Fatal with a clear message, and use the trimmed value when
assigning spec.version so you don't propagate trailing newlines or empty values.

Comment on lines +43 to +47
// fetch the previous Ledger resource
res, err := dyn.Resource(gvr).Get(context.Background(), "stack0-ledger", metav1.GetOptions{})
if err != nil {
panic(err)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add namespace to requests and use a bounded timeout context.

CR is namespaced; missing Namespace(...) will fail. Also avoid unbounded calls.

Apply this diff:

-	// fetch the previous Ledger resource
-	res, err := dyn.Resource(gvr).Get(context.Background(), "stack0-ledger", metav1.GetOptions{})
+	// fetch the previous Ledger resource
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	res, err := dyn.Resource(gvr).
+		Namespace("formance-systems").
+		Get(ctx, "stack0-ledger", metav1.GetOptions{})
@@
-	res, err = dyn.Resource(gvr).Update(context.Background(), res, metav1.UpdateOptions{})
+	res, err = dyn.Resource(gvr).
+		Namespace("formance-systems").
+		Update(ctx, res, metav1.UpdateOptions{})

Also applies to: 52-56

🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go around
lines 43-47 (and similarly 52-56), the dynamic client Get calls are missing a
Namespace(...) and use an unbounded context; update both calls to use a bounded
context created via context.WithTimeout (e.g., 5–10s) with defer cancel(), and
chain Namespace("your-namespace") before Resource(gvr).Get(...); ensure you pass
the timeout context into Get and replace any context.Background() used there
with the new ctx.

Comment on lines 24 to 29
ledger, err := internal.GetRandomLedger(ctx, client)
if err != nil {
ledger = "default"
log.Printf("error getting random ledger: %s", err)
return
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t return after fallback to default ledger.

Currently exits before submitting any work.

Apply this diff:

 	ledger, err := internal.GetRandomLedger(ctx, client)
 	if err != nil {
 		ledger = "default"
 		log.Printf("error getting random ledger: %s", err)
-		return
 	}
📝 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.

Suggested change
ledger, err := internal.GetRandomLedger(ctx, client)
if err != nil {
ledger = "default"
log.Printf("error getting random ledger: %s", err)
return
}
ledger, err := internal.GetRandomLedger(ctx, client)
if err != nil {
ledger = "default"
log.Printf("error getting random ledger: %s", err)
}
🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go around
lines 24-29, the code currently returns immediately after falling back to the
default ledger which prevents any work from being submitted; remove the
premature return so that after logging the error and setting ledger = "default"
execution continues and the program proceeds to submit transactions using the
default ledger (ensure no other control flow relies on the returned error).

Comment on lines +31 to +39
const count = 100

pool := pond.New(10, 10e3)

for range count {
pool.Submit(func() {
CreateTransaction(ctx, client, ledger)
})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix invalid loop: for range count won’t compile.

Use a standard counted loop.

Apply this diff:

-	const count = 100
+	const count = 100
@@
-	for range count {
-		pool.Submit(func() {
-			CreateTransaction(ctx, client, ledger)
-		})
-	}
+	for i := 0; i < count; i++ {
+		pool.Submit(func() { CreateTransaction(ctx, client, ledger) })
+	}
📝 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.

Suggested change
const count = 100
pool := pond.New(10, 10e3)
for range count {
pool.Submit(func() {
CreateTransaction(ctx, client, ledger)
})
}
const count = 100
pool := pond.New(10, 10e3)
for i := 0; i < count; i++ {
pool.Submit(func() { CreateTransaction(ctx, client, ledger) })
}
🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go around
lines 31 to 39, the loop uses the invalid construct "for range count" which
won't compile; replace it with a standard counted loop such as "for i := 0; i <
count; i++ { pool.Submit(func() { CreateTransaction(ctx, client, ledger) }) }"
so the loop iterates count times and submits a task for each iteration.

Comment on lines 77 to 93
func RandomPostings() []components.V2Posting {
postings := []components.V2Posting{}

for range random.GetRandom()%20+1 {
source := internal.GetRandomAddress()
destination := internal.GetRandomAddress()
amount := internal.RandomBigInt()
asset := random.RandomChoice([]string{"USD/2", "EUR/2", "COIN"})

postings = append(postings, components.V2Posting{
Amount: amount,
Asset: asset,
Destination: destination,
Source: source,
})
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix invalid loop in RandomPostings and preallocate slice.

for range <int> is invalid Go; also preallocate to reduce allocations.

Apply this diff:

-func RandomPostings() []components.V2Posting {
-	postings := []components.V2Posting{}
-
-	for range random.GetRandom()%20+1 {
+func RandomPostings() []components.V2Posting {
+	n := int(random.GetRandom()%20 + 1)
+	postings := make([]components.V2Posting, 0, n)
+	for i := 0; i < n; i++ {
 		source := internal.GetRandomAddress()
 		destination := internal.GetRandomAddress()
 		amount := internal.RandomBigInt()
 		asset := random.RandomChoice([]string{"USD/2", "EUR/2", "COIN"})
 
 		postings = append(postings, components.V2Posting{
 			Amount:      amount,
 			Asset:       asset,
 			Destination: destination,
 			Source:      source,
 		})
 	}
 
 	return postings
 }
📝 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.

Suggested change
func RandomPostings() []components.V2Posting {
postings := []components.V2Posting{}
for range random.GetRandom()%20+1 {
source := internal.GetRandomAddress()
destination := internal.GetRandomAddress()
amount := internal.RandomBigInt()
asset := random.RandomChoice([]string{"USD/2", "EUR/2", "COIN"})
postings = append(postings, components.V2Posting{
Amount: amount,
Asset: asset,
Destination: destination,
Source: source,
})
}
func RandomPostings() []components.V2Posting {
n := int(random.GetRandom()%20 + 1)
postings := make([]components.V2Posting, 0, n)
for i := 0; i < n; i++ {
source := internal.GetRandomAddress()
destination := internal.GetRandomAddress()
amount := internal.RandomBigInt()
asset := random.RandomChoice([]string{"USD/2", "EUR/2", "COIN"})
postings = append(postings, components.V2Posting{
Amount: amount,
Asset: asset,
Destination: destination,
Source: source,
})
}
return postings
}
🤖 Prompt for AI Agents
In test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go around
lines 77 to 93, the loop using "for range random.GetRandom()%20+1" is invalid in
Go and the postings slice isn't preallocated; replace the invalid loop with a
conventional integer for-loop (e.g., compute n := int(random.GetRandom()%20) + 1
and use "for i := 0; i < n; i++") and preallocate the slice with capacity n
(make([]components.V2Posting, 0, n)) before appending to reduce allocations.

Copy link

@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

🧹 Nitpick comments (7)
test/antithesis/Justfile (2)

25-27: Add preflight env checks for build inputs.

Fail fast if required variables are missing.

 requirements-build:
-    just config/push
-    earthly --push ./workload+build --ANTITHESIS_REPOSITORY="$ANTITHESIS_REPOSITORY" --LEDGER_LATEST_TAG="$LEDGER_LATEST_TAG"
+    : "${ANTITHESIS_REPOSITORY:?ANTITHESIS_REPOSITORY is required}"
+    : "${LEDGER_LATEST_TAG:?LEDGER_LATEST_TAG is required}"
+    just config/push
+    earthly --push ./workload+build --ANTITHESIS_REPOSITORY="$ANTITHESIS_REPOSITORY" --LEDGER_LATEST_TAG="$LEDGER_LATEST_TAG"

1-23: Optional: de-duplicate with a parameterized recipe.

Reduce drift by having one run target with a duration parameter, e.g., run DURATION: requirements-build; then call just run 0.1 and just run 1.

-run-6min: requirements-build
-    # ...
-    # "custom.duration": "0.1",
-    # ...
-
-run-1h: requirements-build
-    # ...
-    # "custom.duration": "1",
-    # ...
+run duration: requirements-build
+    # ... same curl as above ...
+    # Replace the duration field with:
+    #   "custom.duration": "'"{{duration}}"'", \
+
+# Convenience aliases
+run-6min: run "0.1"
+run-1h: run "1"
test/antithesis/workload/bin/cmds/eventually_correct/main.go (1)

130-133: Don’t mutate tx ID from the response; copy big.Int before arithmetic
Mutating the pointer returned in Data[0].ID changes the underlying struct. Copy it first.

-    if expectedTxId == nil {
-      expectedTxId = transactions.V2TransactionsCursorResponse.Cursor.Data[0].ID
-      expectedTxId.Add(expectedTxId, big.NewInt(1))
-    }
+    if expectedTxId == nil {
+      expectedTxId = new(big.Int).Set(transactions.V2TransactionsCursorResponse.Cursor.Data[0].ID)
+      expectedTxId.Add(expectedTxId, big.NewInt(1))
+    }

Based on learnings.

test/antithesis/workload/internal/utils.go (3)

23-28: Simplify RandomBigInt; avoid fmt conversions
Use SetUint64 for clarity and speed.

-func RandomBigInt() *big.Int {
-  v := random.GetRandom()
-  ret := big.NewInt(0)
-  ret.SetString(fmt.Sprintf("%d", v), 10)
-  return ret
-}
+func RandomBigInt() *big.Int {
+  return new(big.Int).SetUint64(random.GetRandom())
+}

79-91: List all ledgers: add pagination
Current code returns only the first page. Iterate until HasMore is false.

-func ListLedgers(ctx context.Context, client *client.Formance) ([]string, error) {
-  res, err := client.Ledger.V2.ListLedgers(ctx, operations.V2ListLedgersRequest{})
-  if err != nil {
-    return nil, err
-  }
-
-  ledgers := []string{}
-  for _, ledger := range res.V2LedgerListResponse.Cursor.Data {
-    ledgers = append(ledgers, ledger.Name)
-  }
-
-  return ledgers, nil
-}
+func ListLedgers(ctx context.Context, client *client.Formance) ([]string, error) {
+  ledgers := []string{}
+  var cursor *string
+  for {
+    res, err := client.Ledger.V2.ListLedgers(ctx, operations.V2ListLedgersRequest{Cursor: cursor})
+    if err != nil {
+      return nil, err
+    }
+    for _, l := range res.V2LedgerListResponse.Cursor.Data {
+      ledgers = append(ledgers, l.Name)
+    }
+    if !res.V2LedgerListResponse.Cursor.HasMore {
+      break
+    }
+    cursor = res.V2LedgerListResponse.Cursor.Next
+  }
+  return ledgers, nil
+}

19-19: Prefer int for USER_ACCOUNT_COUNT to avoid repeated casts
Keeps loops simpler; adjust GetRandomAddress cast accordingly.

-const USER_ACCOUNT_COUNT uint64 = 1000;
+const USER_ACCOUNT_COUNT int = 1000

Outside this hunk, update GetRandomAddress:

-func GetRandomAddress() string {
-  return random.RandomChoice([]string{"world", fmt.Sprintf("users:%d", random.GetRandom()%USER_ACCOUNT_COUNT)})
-}
+func GetRandomAddress() string {
+  n := int(random.GetRandom()%uint64(USER_ACCOUNT_COUNT))
+  return random.RandomChoice([]string{"world", fmt.Sprintf("users:%d", n)})
+}

Also applies to: 108-110

test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1)

33-39: Optional: capture variables inside the closure
Avoid accidental races if outer variables change in future edits.

-  for i := 0; i < count; i++ {
-    pool.Submit(func() { CreateTransaction(ctx, client, ledger) })
-  }
+  for i := 0; i < count; i++ {
+    localCtx, localClient, localLedger := ctx, client, ledger
+    pool.Submit(func() { CreateTransaction(localCtx, localClient, localLedger) })
+  }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c40afec and a59cf2f.

📒 Files selected for processing (5)
  • test/antithesis/Justfile (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • test/antithesis/workload/internal/utils.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-20T13:08:10.034Z
Learnt from: gfyrag
PR: formancehq/ledger#935
File: test/e2e/api_ledgers_import_test.go:338-352
Timestamp: 2025-05-20T13:08:10.034Z
Learning: The transactions list API for the ledger service has a stable ordering by ID that will never change, making it safe to rely on transactionsFromOriginalLedger.Data[0].ID in tests.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
📚 Learning: 2025-02-24T11:31:17.135Z
Learnt from: gfyrag
PR: formancehq/ledger#703
File: internal/storage/driver/driver_test.go:31-34
Timestamp: 2025-02-24T11:31:17.135Z
Learning: In Go, using range with an integer constant (e.g., `for i := range countLedgers`) is valid syntax and compiles correctly.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
🧬 Code graph analysis (3)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (7)
test/antithesis/workload/internal/utils.go (4)
  • NewClient (42-62)
  • ListLedgers (79-91)
  • Details (21-21)
  • USER_ACCOUNT_COUNT (19-19)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-24)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
pkg/client/models/operations/v2listtransactions.go (1)
  • V2ListTransactionsRequest (37-61)
pkg/client/models/components/v2transactionscursorresponse.go (1)
  • V2TransactionsCursorResponse (48-50)
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (5)
test/antithesis/workload/internal/utils.go (5)
  • NewClient (42-62)
  • GetRandomLedger (93-106)
  • Details (21-21)
  • GetRandomAddress (108-110)
  • RandomBigInt (23-28)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-25)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
pkg/client/models/components/v2errorsenum.go (1)
  • V2ErrorsEnumInsufficientFund (14-14)
test/antithesis/workload/internal/utils.go (4)
pkg/client/formance.go (3)
  • Formance (50-56)
  • WithClient (90-94)
  • WithRetryConfig (112-116)
pkg/client/models/operations/v2createledger.go (2)
  • V2CreateLedgerResponse (29-31)
  • V2CreateLedgerRequest (9-13)
pkg/client/v2.go (1)
  • V2 (20-24)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-24)
🔇 Additional comments (13)
test/antithesis/Justfile (3)

6-6: Confirm duration units with the API.

"0.1" likely means 0.1 hours (6 minutes) while "1" is 1 hour. Please confirm the API expects hours-as-string vs minutes/seconds.

Also applies to: 18-18


2-11: Stop leaking credentials; use netrc, set JSON header, and harden curl.

Current command exposes secrets via --user, lacks Content-Type, and has no retries/timeouts. Use a temp netrc and standard resiliency flags.

-    curl --fail \
-        --user "formance:$ANTITHESIS_PASSWORD" \
-        -X POST https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s -d '{ \
+    # Preconditions & secure auth (avoid exposing secrets in process list)
+    : "${ANTITHESIS_PASSWORD:?ANTITHESIS_PASSWORD is required}"
+    : "${ANTITHESIS_SLACK_REPORT_RECIPIENT:?ANTITHESIS_SLACK_REPORT_RECIPIENT is required}"
+    tmp_netrc="$(mktemp)"; trap 'rm -f "$tmp_netrc"' EXIT
+    printf 'machine formance.antithesis.com login formance password %s\n' "$ANTITHESIS_PASSWORD" > "$tmp_netrc"
+    chmod 600 "$tmp_netrc"
+    curl --fail --silent --show-error --location --retry 3 --retry-all-errors --connect-timeout 10 --max-time 120 \
+        --netrc-file "$tmp_netrc" \
+        -H 'Content-Type: application/json' \
+        -X POST 'https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s' -d '{ \

14-23: Mirror security and resiliency fixes in run-1h.

Apply the same netrc, JSON header, and curl hardening here.

-    curl --fail \
-        --user "formance:$ANTITHESIS_PASSWORD" \
-        -X POST https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s -d '{ \
+    : "${ANTITHESIS_PASSWORD:?ANTITHESIS_PASSWORD is required}"
+    : "${ANTITHESIS_SLACK_REPORT_RECIPIENT:?ANTITHESIS_SLACK_REPORT_RECIPIENT is required}"
+    tmp_netrc="$(mktemp)"; trap 'rm -f "$tmp_netrc"' EXIT
+    printf 'machine formance.antithesis.com login formance password %s\n' "$ANTITHESIS_PASSWORD" > "$tmp_netrc"
+    chmod 600 "$tmp_netrc"
+    curl --fail --silent --show-error --location --retry 3 --retry-all-errors --connect-timeout 10 --max-time 120 \
+        --netrc-file "$tmp_netrc" \
+        -H 'Content-Type: application/json' \
+        -X POST 'https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s' -d '{ \
test/antithesis/workload/bin/cmds/eventually_correct/main.go (6)

112-119: Pagination logic for transactions looks good
Passing and advancing the cursor avoids the infinite loop on HasMore.

Also applies to: 141-145


149-151: Remove duplicate IsServerError (use internal.IsServerError instead)
Local copy risks divergence and it’s unused here.

-func IsServerError(httpMeta components.HTTPMetadata) bool {
-  return httpMeta.Response.StatusCode >= 400 && httpMeta.Response.StatusCode < 600
-}

44-61: Fix nil-deref and avoid killing the process from goroutines; early-return on error

Guard aggregated before GetHTTPMeta(), replace log.Fatalf with non-fatal logging, and return to avoid using aggregated after errors.

 func checkBalanced(ctx context.Context, client *client.Formance, ledger string) {
   aggregated, err := client.Ledger.V2.GetBalancesAggregated(ctx, operations.V2GetBalancesAggregatedRequest{
     Ledger: ledger,
   })
   if err != nil {
-    if internal.IsServerError(aggregated.GetHTTPMeta()) {
-      assert.Always(
-        false,
-        fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
-        internal.Details{
-          "error": err,
-        },
-      )
-    } else {
-      log.Fatalf("error getting aggregated balances for ledger %s: %s", ledger, err)
-    }
+    httpMeta := components.HTTPMetadata{}
+    if aggregated != nil {
+      httpMeta = aggregated.GetHTTPMeta()
+    }
+    if internal.IsServerError(httpMeta) {
+      assert.Always(false,
+        fmt.Sprintf("error getting aggregated balances for ledger %s: %s", ledger, err),
+        internal.Details{"error": err})
+      return
+    }
+    log.Printf("error getting aggregated balances for ledger %s: %s", ledger, err)
+    return
   }

62-70: Assert details should include actual volume, not err (err is nil here)

Report the measured value and context.

   for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
     assert.Always(
       volumes.Cmp(new(big.Int)) == 0,
       fmt.Sprintf("aggregated volumes for asset %s should be 0",
         asset,
-      ), internal.Details{
-        "error": err,
-      })
+      ), internal.Details{
+        "ledger":        ledger,
+        "asset":         asset,
+        "actual_volume": volumes.String(),
+      })
   }

75-81: Invalid loop: cannot range over an integer constant
Use a counted loop.

-func checkAccountBalances(ctx context.Context, client *client.Formance, ledger string) {
-  for i := range internal.USER_ACCOUNT_COUNT {
+func checkAccountBalances(ctx context.Context, client *client.Formance, ledger string) {
+  for i := 0; i < int(internal.USER_ACCOUNT_COUNT); i++ {
     address := fmt.Sprintf("users:%d", i)

82-86: Invert Sometimes condition and enrich details
Assert success (err == nil) and include the address for context.

-    assert.Sometimes(err != nil, "Client can aggregate account balances", internal.Details{
-      "ledger": ledger,
-      "error": err,
-    })
+    assert.Sometimes(err == nil, "Client can get account", internal.Details{
+      "ledger":  ledger,
+      "address": address,
+      "error":   err,
+    })
test/antithesis/workload/internal/utils.go (1)

64-66: Confirm intent: 4xx treated as “server error”?
If you only want 5xx, restrict to 500–599; otherwise consider renaming to IsHTTPError for clarity.

test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (3)

24-29: Don’t return after falling back to the default ledger
Continue and submit work against "default".

   ledger, err := internal.GetRandomLedger(ctx, client)
   if err != nil {
     ledger = "default"
     log.Printf("error getting random ledger: %s", err)
-    return
   }

35-39: Fix invalid loop: for range count won’t compile
Use a counted loop.

-  for range count {
-    pool.Submit(func() {
-      CreateTransaction(ctx, client, ledger)
-    })
-  }
+  for i := 0; i < count; i++ {
+    pool.Submit(func() { CreateTransaction(ctx, client, ledger) })
+  }

77-95: Fix invalid loop and preallocate slice in RandomPostings
The current loop won’t compile; also preallocate to reduce allocations.

 func RandomPostings() []components.V2Posting {
-  postings := []components.V2Posting{}
-
-  for range random.GetRandom()%2+1 {
+  n := int(random.GetRandom()%2 + 1)
+  postings := make([]components.V2Posting, 0, n)
+  for i := 0; i < n; i++ {
     source := internal.GetRandomAddress()
     destination := internal.GetRandomAddress()
     amount := internal.RandomBigInt()
     asset := random.RandomChoice([]string{"USD/2", "EUR/2", "COIN"})
 
     postings = append(postings, components.V2Posting{
       Amount:      amount,
       Asset:       asset,
       Destination: destination,
       Source:      source,
     })
   }
 
   return postings
 }

Copy link

@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: 1

♻️ Duplicate comments (3)
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (3)

66-82: Replace the invalid postings loop with a counted loop.

Line 69 uses for range random.GetRandom()%2+1, which is not valid Go syntax and prevents the binary from compiling. Compute the count and loop with an index.

-func RandomPostings() []components.V2Posting {
-	postings := []components.V2Posting{}
-
-	for range random.GetRandom()%2+1 {
+func RandomPostings() []components.V2Posting {
+	n := int(random.GetRandom()%2 + 1)
+	postings := make([]components.V2Posting, 0, n)
+
+	for i := 0; i < n; i++ {

29-36: Fix invalid loop construct.

for range count on Line 33 does not compile; range expects an iterable, not a bare integer. Use a counted loop instead.

-	for range count {
+	for i := 0; i < count; i++ {
 		pool.Submit(func() {
 			CreateTransaction(ctx, client, ledger)
 		})
 	}

23-27: Keep running after falling back to the default ledger.

Line 26 returns immediately after assigning ledger = "default", so no workload runs when we hit the fallback path. Drop the return so the tasks still execute.

 	if err != nil {
 		ledger = "default"
 		log.Printf("error getting random ledger: %s", err)
-		return
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a59cf2f and 015ca6a.

⛔ Files ignored due to path filters (5)
  • .github/workflows/antithesis_daily.yml is excluded by !**/*.yml
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • test/antithesis/workload/go.mod is excluded by !**/*.mod
  • test/antithesis/workload/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (11)
  • Earthfile (1 hunks)
  • internal/storage/driver/driver.go (4 hunks)
  • test/antithesis/Justfile (1 hunks)
  • test/antithesis/config/Justfile (1 hunks)
  • test/antithesis/image/Dockerfile.ledger (1 hunks)
  • test/antithesis/image/Justfile (1 hunks)
  • test/antithesis/workload/Dockerfile.workload (1 hunks)
  • test/antithesis/workload/Justfile (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • test/antithesis/workload/internal/utils.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Earthfile
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/antithesis/config/Justfile
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
  • test/antithesis/Justfile
🧰 Additional context used
🧬 Code graph analysis (3)
internal/storage/driver/driver.go (4)
internal/ledger.go (1)
  • Ledger (18-26)
internal/storage/ledger/store.go (1)
  • Store (22-44)
internal/storage/system/store.go (1)
  • Store (20-31)
internal/storage/bucket/bucket.go (1)
  • Bucket (13-21)
test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (4)
test/antithesis/workload/internal/utils.go (5)
  • NewClient (42-62)
  • GetRandomLedger (93-106)
  • Details (21-21)
  • GetRandomAddress (108-110)
  • RandomBigInt (23-28)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-25)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
test/antithesis/workload/internal/utils.go (4)
pkg/client/formance.go (3)
  • Formance (50-56)
  • WithClient (90-94)
  • WithRetryConfig (112-116)
pkg/client/models/operations/v2createledger.go (2)
  • V2CreateLedgerResponse (29-31)
  • V2CreateLedgerRequest (9-13)
pkg/client/v2.go (1)
  • V2 (20-24)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-24)

Comment on lines 19 to 23
mkdir -p out
go build -o ./out/init ./bin/init
for file in $(ls ./bin/cmds/); do \
go build -o ./out/cmds/$file ./bin/cmds/$file; \
done
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Create the output directory for command binaries.

Line 21 writes go build -o ./out/cmds/$file, but ./out/cmds is never created, so the first build fails with “no such file or directory”. Please create the directory before the loop.

-	mkdir -p out
+	mkdir -p out
+	mkdir -p out/cmds
📝 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.

Suggested change
mkdir -p out
go build -o ./out/init ./bin/init
for file in $(ls ./bin/cmds/); do \
go build -o ./out/cmds/$file ./bin/cmds/$file; \
done
mkdir -p out
mkdir -p out/cmds
go build -o ./out/init ./bin/init
for file in $(ls ./bin/cmds/); do \
go build -o ./out/cmds/$file ./bin/cmds/$file; \
done
🤖 Prompt for AI Agents
In test/antithesis/workload/Justfile around lines 19 to 23, the recipe builds
command binaries into ./out/cmds but never creates that directory, causing the
first go build to fail; add a step before the loop to create the directory
(e.g., mkdir -p ./out/cmds) so the output path exists prior to running the
for-loop builds.

Copy link

@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 (3)
test/antithesis/config/Justfile (1)

1-14: Add strict error handling and validate required environment variables.

The build-manifest recipe lacks error handling and does not verify that LEDGER_PREVIOUS_TAG is set before using it in the yq command. This can lead to silent failures or incorrect manifest generation.

Apply this diff to add strict shell flags and environment variable validation:

 build-manifest:
+	set -euo pipefail
+	: "$${LEDGER_PREVIOUS_TAG:?LEDGER_PREVIOUS_TAG is required}"
 	rm -f -- tmp/resources.yaml
 	mkdir -p tmp
 	cat manifests/namespace.yaml >> tmp/resources.yaml
 	echo "---" >> tmp/resources.yaml
 	cat manifests/postgres.yaml >> tmp/resources.yaml
 	echo "---" >> tmp/resources.yaml
 	helm template regions oci://ghcr.io/formancehq/helm/regions --version 2.15.2 --namespace formance-systems >> tmp/resources.yaml
 	echo "---" >> tmp/resources.yaml
 	cat manifests/stack.yaml >> tmp/resources.yaml
 	echo "---" >> tmp/resources.yaml
 	yq '.spec.version = strenv(LEDGER_PREVIOUS_TAG)' manifests/ledger.yaml >> tmp/resources.yaml
 	echo "---" >> tmp/resources.yaml
 	cat manifests/workload.yaml >> tmp/resources.yaml

Note: In Just recipes, you may need to prefix each line with the shell invocation or use a here-doc if Just doesn't propagate set flags across lines.

test/antithesis/Justfile (2)

1-11: Switch to netrc for credentials, add JSON header, and harden curl.

The run-6min recipe exposes $ANTITHESIS_PASSWORD in process listings via --user, lacks a JSON content-type header, and has no retry or timeout flags for resilience.

Apply this diff to address security and resilience concerns:

 run-6min: requirements-push
+    : "$${ANTITHESIS_PASSWORD:?ANTITHESIS_PASSWORD is required}"
+    : "$${ANTITHESIS_SLACK_REPORT_RECIPIENT:?ANTITHESIS_SLACK_REPORT_RECIPIENT is required}"
+    tmp_netrc="$$(mktemp)"; trap 'rm -f "$$tmp_netrc"' EXIT
+    printf 'machine formance.antithesis.com login formance password %s\n' "$$ANTITHESIS_PASSWORD" > "$$tmp_netrc"
+    chmod 600 "$$tmp_netrc"
-    curl --fail \
-        --user "formance:$ANTITHESIS_PASSWORD" \
-        -X POST https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s -d '{ \
+    curl --fail --silent --show-error --retry 3 --retry-all-errors \
+        --netrc-file "$$tmp_netrc" \
+        -H 'Content-Type: application/json' \
+        -X POST 'https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s' -d '{ \
           "params": { \
             "custom.duration": "0.1", \
             "antithesis.report.recipients": "'"$ANTITHESIS_SLACK_REPORT_RECIPIENT"'", \
             "antithesis.config_image": "antithesis-config:daily_run", \
             "antithesis.images": "workload:latest;docker.io/library/postgres:15-alpine;ghcr.io/formancehq/operator:v2.10.1;ghcr.io/formancehq/operator-utils:v2.0.14;ghcr.io/formancehq/gateway:v2.0.24;ghcr.io/formancehq/ledger-instrumented:'"$LEDGER_LATEST_TAG"'" \
           } \
         }'

Note: Adjust the variable escaping ($$) according to Just's syntax if needed.


13-23: Mirror the security and resiliency fixes in run-1h.

The run-1h recipe has the same credential exposure and resilience issues as run-6min.

Apply this diff:

 run-1h: requirements-push
+    : "$${ANTITHESIS_PASSWORD:?ANTITHESIS_PASSWORD is required}"
+    : "$${ANTITHESIS_SLACK_REPORT_RECIPIENT:?ANTITHESIS_SLACK_REPORT_RECIPIENT is required}"
+    tmp_netrc="$$(mktemp)"; trap 'rm -f "$$tmp_netrc"' EXIT
+    printf 'machine formance.antithesis.com login formance password %s\n' "$$ANTITHESIS_PASSWORD" > "$$tmp_netrc"
+    chmod 600 "$$tmp_netrc"
-    curl --fail \
-        --user "formance:$ANTITHESIS_PASSWORD" \
-        -X POST https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s -d '{ \
+    curl --fail --silent --show-error --retry 3 --retry-all-errors \
+        --netrc-file "$$tmp_netrc" \
+        -H 'Content-Type: application/json' \
+        -X POST 'https://formance.antithesis.com/api/v1/launch_experiment/formance-k8s' -d '{ \
           "params": { \
             "custom.duration": "1", \
             "antithesis.report.recipients": "'"$ANTITHESIS_SLACK_REPORT_RECIPIENT"'", \
             "antithesis.config_image": "antithesis-config:daily_run", \
             "antithesis.images": "workload:latest;docker.io/library/postgres:15-alpine;ghcr.io/formancehq/operator:v2.10.1;ghcr.io/formancehq/operator-utils:v2.0.14;ghcr.io/formancehq/gateway:v2.0.24;ghcr.io/formancehq/ledger-instrumented:"'"$LEDGER_LATEST_TAG"'" \
           } \
         }'
🧹 Nitpick comments (2)
test/antithesis/image/Justfile (2)

6-6: Consider pinning the instrumentor version.

Using @latest can lead to non-reproducible builds and unexpected breakage if the tool is updated with breaking changes.

Apply this diff to pin to a specific version:

-	go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+	go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]

Note: Replace v0.3.11 with the desired version.


19-20: Validate prerequisites and environment variables.

The script relies on $LEDGER_TAG being set and Docker being installed and authenticated, but doesn't validate these prerequisites. While set -u will catch undefined LEDGER_TAG, explicit validation would provide clearer error messages and fail faster.

Consider adding validation at the beginning of the script:

 	set -euxo pipefail
 
+	# Validate prerequisites
+	if [ -z "${LEDGER_TAG:-}" ]; then
+		echo "Error: LEDGER_TAG environment variable must be set"
+		exit 1
+	fi
+	
+	if ! command -v docker &> /dev/null; then
+		echo "Error: docker is not installed"
+		exit 1
+	fi
+
 	go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e854a9 and 1e0ff72.

⛔ Files ignored due to path filters (2)
  • .github/workflows/antithesis_daily.yml is excluded by !**/*.yml
  • .github/workflows/antithesis_release.yml is excluded by !**/*.yml
📒 Files selected for processing (4)
  • test/antithesis/Justfile (1 hunks)
  • test/antithesis/config/Justfile (1 hunks)
  • test/antithesis/image/Justfile (1 hunks)
  • test/antithesis/workload/Justfile (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/workload/Justfile
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests
  • GitHub Check: Dirty
  • GitHub Check: Push Images to Antithesis
🔇 Additional comments (4)
test/antithesis/image/Justfile (3)

1-2: LGTM!

Recipe definition and shebang are appropriate for this orchestration task.


4-4: LGTM!

Strict error handling with set -euxo pipefail is appropriate for a build and push workflow, ensuring failures are caught early.


10-10: scriptdir=$(pwd) is valid
Just automatically executes recipes in the Justfile’s directory, so pwd reliably returns the script location.

Likely an incorrect or invalid review comment.

test/antithesis/Justfile (1)

25-27: LGTM!

The requirements-push recipe correctly orchestrates the push of config and workload artifacts.

Comment on lines +16 to +18
push: build-manifest
docker build -f Dockerfile.config -t $ANTITHESIS_REPOSITORY/antithesis-config:daily_run .
docker push $ANTITHESIS_REPOSITORY/antithesis-config:daily_run
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Validate ANTITHESIS_REPOSITORY before use.

The push recipe uses $ANTITHESIS_REPOSITORY without verifying it is set. If unset, the Docker commands will fail with unclear error messages.

Apply this diff to add validation:

 push: build-manifest
+	: "$${ANTITHESIS_REPOSITORY:?ANTITHESIS_REPOSITORY is required}"
 	docker build -f Dockerfile.config -t $ANTITHESIS_REPOSITORY/antithesis-config:daily_run .
 	docker push $ANTITHESIS_REPOSITORY/antithesis-config:daily_run
📝 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.

Suggested change
push: build-manifest
docker build -f Dockerfile.config -t $ANTITHESIS_REPOSITORY/antithesis-config:daily_run .
docker push $ANTITHESIS_REPOSITORY/antithesis-config:daily_run
push: build-manifest
: "$${ANTITHESIS_REPOSITORY:?ANTITHESIS_REPOSITORY is required}"
docker build -f Dockerfile.config -t $ANTITHESIS_REPOSITORY/antithesis-config:daily_run .
docker push $ANTITHESIS_REPOSITORY/antithesis-config:daily_run
🤖 Prompt for AI Agents
In test/antithesis/config/Justfile around lines 16 to 18, the push recipe uses
$ANTITHESIS_REPOSITORY without validating it; add a guard at the start of the
push recipe that checks the variable is set and non-empty and fails with a clear
error if not. Specifically, prepend a test like: if [ -z
"$ANTITHESIS_REPOSITORY" ]; then echo "ANTITHESIS_REPOSITORY must be set"; exit
1; fi — then proceed with the docker build and push commands so the recipe fails
fast with a helpful message instead of producing unclear Docker errors.

Copy link

@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: 1

♻️ Duplicate comments (1)
test/antithesis/workload/Justfile (1)

20-24: Create the output directory for command binaries.

Line 23 writes to ./out/cmds/$file, but ./out/cmds is never created. The first build will fail with "no such file or directory".

Apply this diff:

 	mkdir -p out
+	mkdir -p out/cmds
 	go build -o ./out/init ./bin/init
🧹 Nitpick comments (2)
test/antithesis/workload/Justfile (2)

17-17: Pin the instrumentor version for reproducible builds.

Using @latest makes builds non-reproducible. Consider pinning to a specific version or commit hash.

Example:

-	GOBIN=$(pwd) go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+	GOBIN=$(pwd) go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]

26-29: Validate required environment variables.

The script relies on LEDGER_LATEST_TAG and ANTITHESIS_REPOSITORY but doesn't validate they are set. If empty, this could lead to silent failures or confusing error messages.

Add validation at the beginning of the script (after line 7):

	scriptdir=$(pwd)
	
	: "${LEDGER_LATEST_TAG:?LEDGER_LATEST_TAG must be set}"
	: "${ANTITHESIS_REPOSITORY:?ANTITHESIS_REPOSITORY must be set}"
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1e0ff72 and 14785db.

⛔ Files ignored due to path filters (2)
  • .github/workflows/antithesis_daily.yml is excluded by !**/*.yml
  • .github/workflows/antithesis_release.yml is excluded by !**/*.yml
📒 Files selected for processing (2)
  • test/antithesis/image/Justfile (1 hunks)
  • test/antithesis/workload/Justfile (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/image/Justfile


set -euxo pipefail
tmpdir=$(mktemp -d)
trap 'rm -rf $tmpdir' EXIT
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quote the variable in the trap command.

The trap uses $tmpdir without quotes. If the temporary directory path contains spaces, the cleanup will fail.

Apply this diff:

-	trap 'rm -rf $tmpdir' EXIT
+	trap 'rm -rf "$tmpdir"' EXIT
📝 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.

Suggested change
trap 'rm -rf $tmpdir' EXIT
trap 'rm -rf "$tmpdir"' EXIT
🤖 Prompt for AI Agents
In test/antithesis/workload/Justfile around line 6, the trap currently uses
$tmpdir unquoted which will break if the temporary path contains spaces; update
the trap command so the variable is quoted (i.e., ensure rm -rf is invoked with
"$tmpdir" quoted inside the trap command) so cleanup works for paths with
spaces.

Copy link

@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

🧹 Nitpick comments (4)
test/antithesis/workload/Dockerfile.workload (4)

4-4: Harden and slim the apt install.

Use no‑recommends, install CA certs, and clean apt lists to reduce image size.

Apply this diff:

-RUN apt-get update && apt-get install -y curl
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ca-certificates curl \
+ && rm -rf /var/lib/apt/lists/*

6-7: Ensure /init is executable; consider non‑root runtime.

Make the binary executable; optionally drop root for better container security.

Apply this diff:

 COPY init /init
+RUN chmod +x /init
 ENTRYPOINT ["/init"]

Optional (non-root user) — add after package install:

# Create non-root user and switch
RUN useradd -r -u 10001 -g nogroup appuser
USER 10001

9-9: Prefer directory copy over wildcard.

COPY cmds/* can miss dotfiles and fail on empty matches. Copy the directory recursively; add --chown if switching to non-root.

Apply this diff:

-COPY cmds/* /opt/antithesis/test/v1/main/
+COPY cmds/ /opt/antithesis/test/v1/main/

If you adopt a non-root user:

-COPY cmds/ /opt/antithesis/test/v1/main/
+COPY --chown=appuser:nogroup cmds/ /opt/antithesis/test/v1/main/

11-11: Confirm intent for ledger_latest_tag.

If it’s only metadata, consider passing it as ARG LEDGER_LATEST_TAG + ENV or LABEL instead of copying a file.

Example:

ARG LEDGER_LATEST_TAG
ENV LEDGER_LATEST_TAG=${LEDGER_LATEST_TAG}
# or
LABEL org.opencontainers.image.version=${LEDGER_LATEST_TAG}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 14785db and 16c0cde.

📒 Files selected for processing (2)
  • test/antithesis/image/Justfile (1 hunks)
  • test/antithesis/workload/Dockerfile.workload (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/antithesis/image/Justfile
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dirty
  • GitHub Check: Trigger daily run

@@ -0,0 +1,11 @@
FROM ubuntu:latest
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pin the base image (avoid latest).

Floating tags hurt reproducibility and security posture. Pin to a specific version (and ideally a digest).

Apply this diff:

-FROM ubuntu:latest
+FROM ubuntu:22.04
📝 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.

Suggested change
FROM ubuntu:latest
FROM ubuntu:22.04
🤖 Prompt for AI Agents
In test/antithesis/workload/Dockerfile.workload around line 1, the base image is
using the floating tag "ubuntu:latest"; replace it with a specific, immutable
tag or digest (e.g., ubuntu:22.04 or ubuntu@sha256:<digest>) to ensure
reproducibility and improve security — update the FROM line to reference the
chosen stable version or digest and commit the change.

COPY init /init
ENTRYPOINT ["/init"]

COPY cmds/* /opt/antithesis/test/v1/main/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify build artifact paths (cmds/ vs bin/cmds/) and tag file presence.

The workload build may output binaries under bin/cmds/, not cmds/. Also ensure ledger_latest_tag exists at build time.

Run this script to confirm paths:

Also applies to: 11-11


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check for workload command outputs:"
fd -t d -a 'cmds' | sed 's/^/DIR: /'
fd -t f -a 'cmds' -E '**/vendor/**' | sed 's/^/FILE: /' | head -n 20

echo
echo "Check for bin/cmds:"
fd -t d -a 'bin/cmds' | sed 's/^/DIR: /'
fd -t f -a 'bin/cmds' -E '**/vendor/**' | sed 's/^/FILE: /' | head -n 20

echo
echo "Check ledger_latest_tag presence near Dockerfile:"
fd -a 'ledger_latest_tag' -g 'test/antithesis/**' | sed 's/^/FOUND: /'

Length of output: 272


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check for ledger_latest_tag in test/antithesis/workload:"
fd -t f -a 'ledger_latest_tag' test/antithesis/workload

Length of output: 56


Correct workload COPY paths and include ledger_latest_tag
In test/antithesis/workload/Dockerfile.workload (lines 9 & 11) replace

COPY cmds/* /opt/antithesis/test/v1/main/

with

COPY bin/cmds/* /opt/antithesis/test/v1/main/

and add a step to copy your ledger_latest_tag into the image (e.g.

COPY ledger_latest_tag /opt/antithesis/test/v1/main/ledger_latest_tag
```) so the tag file is present at build time.

<details>
<summary>🤖 Prompt for AI Agents</summary>

In test/antithesis/workload/Dockerfile.workload around lines 9 and 11, the
Dockerfile copies from cmds/* which is the wrong source path and it does not
include the ledger_latest_tag file; change the COPY source to bin/cmds/* so
binaries are copied from bin/cmds into /opt/antithesis/test/v1/main/, and add an
additional COPY instruction to copy ledger_latest_tag into
/opt/antithesis/test/v1/main/ledger_latest_tag so the tag file is present at
build time.


</details>

<!-- fingerprinting:phantom:triton:chinchilla -->

<!-- This is an auto-generated comment by CodeRabbit -->

Copy link

@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: 4

♻️ Duplicate comments (1)
internal/storage/driver/driver.go (1)

81-91: Unconditional assertion still trips every outdated-bucket path.

We’re still calling assert.AlwaysOrUnreachable(false, …) with a literal false, so the assertion fires every time we hit the outdated-bucket path. The earlier review already flagged this; the TODO still needs to be resolved by gating the assertion behind the actual “new binary version” flag (or removing it). As-is, the DST agent treats this branch as unreachable in all scenarios and reports a failure even for expected rollouts.

Please replace the hard-coded false with the real flag (or another proper condition) and cover both cases with tests.

🧹 Nitpick comments (6)
test/antithesis/workload/Justfile (1)

1-3: Validate required environment variables.

The recipe depends on $LEDGER_LATEST_TAG and $ANTITHESIS_REPOSITORY being set but does not validate their presence. If either is unset, Docker will silently use empty strings, producing broken tags or pushing to invalid repositories.

Consider adding validation at the start of the recipe:

 push:
+	@test -n "$$LEDGER_LATEST_TAG" || (echo "LEDGER_LATEST_TAG is required" && exit 1)
+	@test -n "$$ANTITHESIS_REPOSITORY" || (echo "ANTITHESIS_REPOSITORY is required" && exit 1)
 	docker build --build-arg LEDGER_LATEST_TAG="$LEDGER_LATEST_TAG" -f Dockerfile -t "$ANTITHESIS_REPOSITORY/workload:latest" ../../../
 	docker push "$ANTITHESIS_REPOSITORY/workload:latest"
test/antithesis/image/Dockerfile (3)

4-4: Clean up apt cache to reduce image size.

The apt-get update installs packages but leaves the apt cache, unnecessarily inflating the layer size.

Apply this diff:

-RUN apt-get update && apt-get install -y bash git gcc
+RUN apt-get update && apt-get install -y bash git gcc && rm -rf /var/lib/apt/lists/*

16-16: Pin the instrumentor version for reproducibility.

Using @latest for the Antithesis instrumentor means builds are not reproducible and may break unexpectedly when the tool is updated.

Pin to a specific version:

-RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+RUN go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]

Replace v0.x.x with the current stable version.


26-26: Clean up apt cache in runner stage.

Similar to the compiler stage, the apt cache should be cleaned to reduce the final image size.

Apply this diff:

-RUN apt-get update && apt-get install -y postgresql-client curl
+RUN apt-get update && apt-get install -y postgresql-client curl && rm -rf /var/lib/apt/lists/*
test/antithesis/image/Justfile (1)

1-3: Validate required environment variable.

The recipe depends on $LEDGER_TAG being set but does not validate its presence. If unset, Docker will use an empty string, producing a malformed tag like ghcr.io/formancehq/ledger-instrumented:.

Add validation at the start of the recipe:

 push:
+	@test -n "$$LEDGER_TAG" || (echo "LEDGER_TAG is required" && exit 1)
 	docker build -f Dockerfile -t "ghcr.io/formancehq/ledger-instrumented:$LEDGER_TAG" ../../../
 	docker push "ghcr.io/formancehq/ledger-instrumented:$LEDGER_TAG"
test/antithesis/workload/Dockerfile (1)

4-4: Pin the instrumentor version for reproducibility.

Using @latest means builds are not reproducible and may break when the tool is updated.

Pin to a specific version:

-RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+RUN go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]

Replace v0.x.x with the current stable version.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 16c0cde and 160b453.

📒 Files selected for processing (5)
  • internal/storage/driver/driver.go (4 hunks)
  • test/antithesis/image/Dockerfile (1 hunks)
  • test/antithesis/image/Justfile (1 hunks)
  • test/antithesis/workload/Dockerfile (1 hunks)
  • test/antithesis/workload/Justfile (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/storage/driver/driver.go (4)
internal/controller/system/store.go (2)
  • Driver (20-24)
  • Store (13-18)
internal/ledger.go (1)
  • Ledger (18-26)
internal/storage/system/store.go (1)
  • Store (20-31)
internal/storage/bucket/bucket.go (1)
  • Bucket (13-21)
🔇 Additional comments (1)
test/antithesis/workload/Dockerfile (1)

13-18: Clarify why instrumentor runs twice.

The instrumentor is invoked with -assert_only before building (Line 13) and again after building (Line 18). If both invocations are necessary, consider adding comments explaining the purpose of each run. If the second invocation is redundant, it should be removed.

Please confirm whether both instrumentor runs are intentional and document their purpose if so.

COPY --from=compiler /instrumented/symbols/ /symbols
COPY test/antithesis/image/entrypoint.sh /bin/entrypoint.sh

RUN chmod 777 /bin/entrypoint.sh
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reduce permissions on the entrypoint script.

chmod 777 grants write and execute permissions to all users, which is a security risk. The script only needs to be executable by the container user.

Apply this diff:

-RUN chmod 777 /bin/entrypoint.sh
+RUN chmod 755 /bin/entrypoint.sh
🤖 Prompt for AI Agents
In test/antithesis/image/Dockerfile around line 32, replace the unsafe chmod 777
on /bin/entrypoint.sh with a more restrictive permission (e.g., chmod 755 or
750) so the script is executable by the container user but not writable by
everyone; if needed also ensure the file ownership is appropriate (chown
root:root or to the intended runtime user) before setting permissions.

Copy link

@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)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (2)

58-66: Incorrect error variable in assert details.

The err variable is nil at this point since this code executes after the error check. Include the actual volume value instead to provide more meaningful assertion details.

Apply this diff to fix:

 	for asset, volumes := range aggregated.V2AggregateBalancesResponse.Data {
 		assert.Always(
 			volumes.Cmp(new(big.Int)) == 0,
 			fmt.Sprintf("aggregated volumes for asset %s should be 0",
 				asset,
 			), internal.Details{
-				"error": err,
+				"actual_volume": volumes.String(),
+				"asset": asset,
+				"ledger": ledger,
 			})
 	}

78-81: Invert Sometimes condition.

The assertion message says "Client can aggregate account balances" but checks err != nil, which fires when there's an error. This should assert success (err == nil).

Apply this diff:

-		assert.Sometimes(err != nil, "Client can aggregate account balances", internal.Details{
+		assert.Sometimes(err == nil, "Client can get account", internal.Details{
 			"ledger": ledger,
+			"address": address,
 			"error": err,
 		})
🧹 Nitpick comments (8)
test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (3)

17-19: Avoid high collision rate in ledger IDs

Using %1e6 makes collisions likely under parallel runs. Use the full 64-bit space.

-	id := random.GetRandom()%1e6
-	ledger := fmt.Sprintf("ledger-%d", id)
+	id := random.GetRandom()
+	ledger := fmt.Sprintf("ledger-%d", id)

27-29: Add more context to assertion and clarify intent

Include the ledger name to aid debugging and make the intent explicit that collisions are expected.

-	assert.Sometimes(err == nil, "ledger should have been created properly", internal.Details{
-		"error": err,
-	})
+	assert.Sometimes(err == nil, "ledger creation should sometimes succeed (collisions expected)", internal.Details{
+		"error":  err,
+		"ledger": ledger,
+	})

15-25: Consider bounding the operation with a timeout

For CLI workloads, a short timeout helps bound hangs if the gateway is unreachable.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

Requires importing the standard time package in this file.

test/antithesis/workload/internal/utils.go (5)

23-28: Simplify RandomBigInt: avoid fmt and string parsing

Use SetUint64 directly; cheaper and clearer.

-func RandomBigInt() *big.Int {
-	v := random.GetRandom()
-	ret := big.NewInt(0)
-	ret.SetString(fmt.Sprintf("%d", v), 10)
-	return ret
-}
+func RandomBigInt() *big.Int {
+	return new(big.Int).SetUint64(random.GetRandom())
+}

35-39: Keep error typed in details

Passing err directly avoids unnecessary stringification; nil stays nil.

-	return AssertAlways(err == nil, message, Details{
-		"error":   fmt.Sprint(err),
-		"details": details,
-	})
+	return AssertAlways(err == nil, message, Details{
+		"error":   err,
+		"details": details,
+	})

12-12: Optional: alias go-libs time import to avoid confusion with stdlib

Helps readability since the identifier is time.Minute on an http.Client.

-	"github.com/formancehq/go-libs/v2/time"
+	ftime "github.com/formancehq/go-libs/v2/time"
-			Timeout: time.Minute,
+			Timeout: ftime.Minute,

Also applies to: 49-51


75-87: ListLedgers ignores pagination; only first page is used

Default page size is limited (15 per docs). Sampling from the first page biases selection and may miss most ledgers.

  • Iterate pages using the cursor until exhaustion, aggregating names, or
  • Randomly select a page first, then pick within that page.

Based on relevant code snippet indicating page size limits.


42-61: Retry config vs non-idempotent create

CreateLedger is not idempotent; retries on connection errors can yield 409 Already Exists if the first attempt succeeded server-side. This is fine for the workload but ensure this is intentional.

If unintended, disable retries for POSTs or gate retries on idempotent methods only.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e559f0c and e7f7110.

📒 Files selected for processing (3)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (1 hunks)
  • test/antithesis/workload/internal/utils.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-24T11:31:17.135Z
Learnt from: gfyrag
PR: formancehq/ledger#703
File: internal/storage/driver/driver_test.go:31-34
Timestamp: 2025-02-24T11:31:17.135Z
Learning: In Go, using range with an integer constant (e.g., `for i := range countLedgers`) is valid syntax and compiles correctly.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
🧬 Code graph analysis (3)
test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go (1)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (42-62)
  • CreateLedger (64-73)
  • Details (21-21)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (6)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (42-62)
  • Details (21-21)
  • USER_ACCOUNT_COUNT (19-19)
pkg/client/v2.go (1)
  • V2 (20-24)
pkg/client/formance.go (1)
  • Formance (50-56)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
test/antithesis/workload/internal/utils.go (4)
pkg/client/formance.go (3)
  • Formance (50-56)
  • WithClient (90-94)
  • WithRetryConfig (112-116)
pkg/client/models/operations/v2createledger.go (2)
  • V2CreateLedgerResponse (29-31)
  • V2CreateLedgerRequest (9-13)
pkg/client/v2.go (1)
  • V2 (20-24)
pkg/client/models/operations/v2listledgers.go (1)
  • V2ListLedgersRequest (9-24)
🔇 Additional comments (2)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (2)

16-40: LGTM!

The main function correctly lists ledgers, handles errors with appropriate assertions, and spawns concurrent health checks with proper WaitGroup synchronization. The goroutine properly captures ledger.Name in the function parameter to avoid closure issues.


85-101: LGTM!

The balance consistency checks correctly validate that:

  1. Reported balance equals input minus output
  2. Balances remain non-negative when overdraft is not allowed

The assertions include comprehensive details for debugging.

Comment on lines +99 to +101
randomIndex := random.GetRandom()%uint64(len(ledgers))

return ledgers[randomIndex], nil
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Compile error: slice index type mismatch (uint64 vs int)

randomIndex is uint64; slice indices must be int.

-	randomIndex := random.GetRandom()%uint64(len(ledgers))
-
-	return ledgers[randomIndex], nil
+	idx := int(random.GetRandom() % uint64(len(ledgers)))
+	return ledgers[idx], nil
📝 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.

Suggested change
randomIndex := random.GetRandom()%uint64(len(ledgers))
return ledgers[randomIndex], nil
idx := int(random.GetRandom() % uint64(len(ledgers)))
return ledgers[idx], nil
🤖 Prompt for AI Agents
In test/antithesis/workload/internal/utils.go around lines 99 to 101, the code
uses a uint64 randomIndex to index a slice (ledgers) which requires an int;
change the index to an int by converting the result before using it (for example
compute the modulus then cast to int, or generate an int index directly) and use
that int to access ledgers[randomIndex]. Ensure the conversion happens on the
value used for indexing to satisfy the slice index type.

Copy link

@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 (1)
internal/storage/driver/driver.go (1)

81-92: Previously flagged: Assertion needs proper flag control.

This assertion logic has already been reviewed. The hardcoded false and missing test coverage were flagged in previous comments.

🧹 Nitpick comments (5)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (4)

23-25: Assertion message should describe the success case.

The condition checks err == nil (success), but the message says "error listing ledgers". For Sometimes assertions, the message should describe what succeeds when the condition is true.

Apply this diff:

-	assert.Sometimes(err == nil, "error listing ledgers", internal.Details {
+	assert.Sometimes(err == nil, "Client can list ledgers", internal.Details{
 		"error": err,
 	})

79-83: Fix assertion message to match the operation.

The message says "aggregate account balances" but the operation is GetAccount. The message should accurately describe what the API call does.

Apply this diff:

-		assert.Sometimes(err == nil, "Client can aggregate account balances", internal.Details{
+		assert.Sometimes(err == nil, "Client can get account", internal.Details{
 			"ledger": ledger,
 			"address": address,
 			"error": err,

97-102: Assertion message should say "non-negative" instead of "positive".

The condition checks for >= 0, which includes zero. In mathematics, zero is not positive—it's non-negative. The message should accurately reflect the condition.

Apply this diff:

-			assert.Always(volume.Balance.Cmp(zero) != -1, "Balance should stay positive when no overdraft is allowed", internal.Details{
+			assert.Always(volume.Balance.Cmp(zero) != -1, "Balance should be non-negative when no overdraft is allowed", internal.Details{
 				"ledger": ledger,
 				"address": address,
 				"asset": asset,

104-104: Remove trailing empty line inside the loop.

Minor formatting cleanup.

Apply this diff:

 		}
-		
 	}
internal/storage/driver/driver.go (1)

41-54: Consider condensing the doc comment.

The detailed step-by-step breakdown largely mirrors the code flow. Based on past feedback, consider keeping only the essential information: the function's purpose and the atomicity guarantee.

Apply this diff to condense the comment:

-/*
-CreateLedger creates a new ledger in the system and sets up all necessary database objects.
-
-The function follows these steps:
- 1. Create a ledger record in the system store (_system.ledgers table)
- 2. Get the bucket (database schema) for this ledger
- 3. Check if the bucket is already initialized:
-    a. If initialized: Verify it's up to date and add ledger-specific objects to it
-    b. If not initialized: Create the bucket schema with all necessary tables
- 4. Return a ledger store that provides an interface to interact with the ledger
-
-Note: This entire process is wrapped in a database transaction, ensuring atomicity.
-If any step fails, the entire transaction is rolled back, preventing partial state.
-*/
+// CreateLedger atomically creates a new ledger, initializing or updating its bucket schema as needed.
+// The entire operation runs in a transaction; any failure rolls back all changes.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e7f7110 and ea4e906.

⛔ Files ignored due to path filters (6)
  • .github/workflows/antithesis_daily.yml is excluded by !**/*.yml
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • tools/generator/go.mod is excluded by !**/*.mod
  • tools/generator/go.sum is excluded by !**/*.sum, !**/*.sum
  • tools/provisioner/go.mod is excluded by !**/*.mod
  • tools/provisioner/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (6)
  • internal/storage/driver/driver.go (4 hunks)
  • test/antithesis/Justfile (1 hunks)
  • test/antithesis/workload/Dockerfile (1 hunks)
  • test/antithesis/workload/bin/cmds/eventually_correct/main.go (1 hunks)
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go (1 hunks)
  • tools/generator/cmd/root.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tools/generator/cmd/root.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/antithesis/Justfile
  • test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-24T11:31:17.135Z
Learnt from: gfyrag
PR: formancehq/ledger#703
File: internal/storage/driver/driver_test.go:31-34
Timestamp: 2025-02-24T11:31:17.135Z
Learning: In Go, using range with an integer constant (e.g., `for i := range countLedgers`) is valid syntax and compiles correctly.

Applied to files:

  • test/antithesis/workload/bin/cmds/eventually_correct/main.go
🧬 Code graph analysis (2)
internal/storage/driver/driver.go (3)
internal/ledger.go (1)
  • Ledger (18-26)
internal/storage/system/store.go (1)
  • Store (20-31)
internal/storage/bucket/bucket.go (1)
  • Bucket (13-21)
test/antithesis/workload/bin/cmds/eventually_correct/main.go (4)
test/antithesis/workload/internal/utils.go (3)
  • NewClient (42-62)
  • Details (21-21)
  • USER_ACCOUNT_COUNT (19-19)
pkg/client/models/operations/v2getbalancesaggregated.go (1)
  • V2GetBalancesAggregatedRequest (11-18)
pkg/client/models/components/v2aggregatebalancesresponse.go (1)
  • V2AggregateBalancesResponse (10-12)
pkg/client/models/operations/v2getaccount.go (1)
  • V2GetAccountRequest (11-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dirty
  • GitHub Check: Tests
🔇 Additional comments (7)
test/antithesis/workload/Dockerfile (4)

16-17: Directory creation and build loop are correct.

The /cmds directory is now properly created before the build loop, and the binaries are correctly compiled to /cmds/$file. This addresses the prior critical issue.


28-31: COPY paths and entrypoint configuration are correct.

The absolute path /cmds/* is now correctly used in the COPY command, and the /init binary is properly set as the entrypoint. These changes address prior critical issues.


33-33: Echo command syntax is correct.

The redirect operator > is now present, correctly writing LEDGER_LATEST_TAG to /ledger_latest_tag. This addresses the prior syntax error.


4-4: Pin antithesis-go-instrumentor to a specific version instead of @latest, and add -instrumentor_version flag to instrumentor invocations.

Using @latest creates non-deterministic builds. However, the suggested version (v0.5.0) could not be confirmed as the current release; web search shows v0.4.4 (July 2025) as the latest. Since your codebase's go.mod already specifies SDK v0.5.0, verify whether v0.5.0 is available and correct, or whether you should align to the latest stable (v0.4.4). Also add the -instrumentor_version flag to both instrumentor invocations (lines 13 and 19) to ensure alignment with the SDK version.

Apply to line 4:

-RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest
+RUN go install github.com/antithesishq/antithesis-sdk-go/tools/[email protected]

Apply to lines 13 and 19:

-RUN antithesis-go-instrumentor -assert_only .
+RUN antithesis-go-instrumentor -assert_only -instrumentor_version v0.5.0 .
internal/storage/driver/driver.go (3)

8-8: LGTM: Antithesis assertion support added.

The import is correctly placed to support the assertion at lines 81-92. Based on learnings, ensure your go.mod pins antithesis-sdk-go to v0.5.0 or later and that the Go instrumentor runs before build to catalog assertions.


58-58: Transaction wrapper ensures atomicity.

Using nil for the isolation level applies the default (READ COMMITTED), which is appropriate here. The constraint check on line 62 correctly handles concurrent ledger creation attempts.


61-66: Verify whether double ResolveError call is necessary.

The postgres.ResolveError function is from an external dependency (github.com/formancehq/go-libs/v3 v3.0.1), and I cannot access its implementation. However, the code pattern at lines 63 and 65 calls postgres.ResolveError(err) twice on the same error object:

  1. Line 63: postgres.ResolveError(err) to check if it's a constraint failure
  2. Line 65: postgres.ResolveError(err) again if the constraint check fails

Consider storing the resolved error from line 63 and reusing it at line 65 to avoid potential redundant transformation, unless double resolution is intentional or the function performs necessary side effects on each call.

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.

5 participants