diff --git a/.github/workflows/antithesis_daily.yml b/.github/workflows/antithesis_daily.yml new file mode 100644 index 0000000000..d343703280 --- /dev/null +++ b/.github/workflows/antithesis_daily.yml @@ -0,0 +1,77 @@ +name: Antithesis Daily Run +on: + workflow_dispatch: + inputs: + run_tests: + description: "Run Antithesis tests after pushing images" + type: boolean + default: true + pull_request: + types: [opened, synchronize, reopened] + paths: + - "test/antithesis/**" + - ".github/workflows/antithesis_daily.yml" + schedule: + - cron: "0 10 * * *" + +jobs: + trigger-daily-run: + name: Trigger daily run + runs-on: shipfox-4vcpu-ubuntu-2404 + env: + ANTITHESIS_PASSWORD: ${{ secrets.ANTITHESIS_PASSWORD }} + ANTITHESIS_SLACK_REPORT_RECIPIENT: ${{ secrets.ANTITHESIS_SLACK_REPORT_RECIPIENT }} + ANTITHESIS_REPOSITORY: ${{ secrets.ANTITHESIS_REPOSITORY }} + OPERATOR_TAG: "v2.10.1" + OPERATOR_UTILS_TAG: "v2.0.14" + GATEWAY_TAG: "v2.0.24" + LEDGER_PREVIOUS_TAG: "v2.2.47" + LEDGER_LATEST_TAG: "v2.3.0" + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Just + uses: extractions/setup-just@v3 + with: + just-version: "1.40.0" + + - name: Setup Environment + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: "NumaryBot" + password: ${{ secrets.NUMARY_GITHUB_TOKEN }} + + - name: Login to Antithesis Docker Registry + run: | + echo '${{ secrets.ANTITHESIS_JSON_KEY }}' | docker login -u _json_key https://us-central1-docker.pkg.dev --password-stdin + + - name: Build and Push Config Image + run: | + nix develop --impure --command just --justfile ./test/antithesis/Justfile requirements-push + + - name: Run Antithesis Tests + uses: antithesishq/antithesis-trigger-action@v0.8 + with: + notebook_name: formance-k8s + tenant: formance + username: ${{ secrets.ANTITHESIS_USERNAME }} + password: ${{ secrets.ANTITHESIS_PASSWORD }} + github_token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + images: "workload:latest;docker.io/library/postgres:15-alpine;ghcr.io/formancehq/operator:${{ env.OPERATOR_TAG }};ghcr.io/formancehq/operator-utils:${{ env.OPERATOR_UTILS_TAG }};ghcr.io/formancehq/gateway:${{ env.GATEWAY_TAG }};ghcr.io/formancehq/ledger-instrumented:${{ env.LEDGER_PREVIOUS_TAG }};ghcr.io/formancehq/ledger-instrumented:${{ env.LEDGER_LATEST_TAG }}" + config_image: "antithesis-config:daily_run" + email_recipients: ${{ secrets.ANTITHESIS_SLACK_REPORT_RECIPIENT }} diff --git a/.github/workflows/antithesis_release.yml b/.github/workflows/antithesis_release.yml new file mode 100644 index 0000000000..bcd5575cd8 --- /dev/null +++ b/.github/workflows/antithesis_release.yml @@ -0,0 +1,48 @@ +name: Antithesis Instrumented Release +on: + push: + tags: + - "*" + +jobs: + push-instrumented-ledger: + name: Push instrumented ledger + runs-on: shipfox-4vcpu-ubuntu-2404 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Just + uses: extractions/setup-just@v3 + with: + just-version: "1.40.0" + + - name: Setup Environment + uses: ./.github/actions/default + with: + token: ${{ secrets.NUMARY_GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: "NumaryBot" + password: ${{ secrets.NUMARY_GITHUB_TOKEN }} + + - name: Login to Antithesis Docker Registry + run: | + echo '${{ secrets.ANTITHESIS_JSON_KEY }}' | docker login -u _json_key https://us-central1-docker.pkg.dev --password-stdin + + - name: Build and Push Config Image + env: + LEDGER_TAG: ${{ env.GITHUB_REF_NAME }} + run: | + nix develop --impure --command just --justfile ./test/antithesis/image/Justfile push diff --git a/internal/storage/driver/driver.go b/internal/storage/driver/driver.go index 1210c1053a..fe20628be9 100644 --- a/internal/storage/driver/driver.go +++ b/internal/storage/driver/driver.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/formancehq/ledger/internal/storage/common" systemstore "github.com/formancehq/ledger/internal/storage/system" "github.com/formancehq/ledger/internal/tracing" @@ -36,9 +37,23 @@ type Driver struct { parallelBucketMigrations int } -func (d *Driver) CreateLedger(ctx context.Context, l *ledger.Ledger) (*ledgerstore.Store, error) { +/* +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. +*/ +func (d *Driver) CreateLedger(ctx context.Context, l *ledger.Ledger) (*ledgerstore.Store, error) { var ret *ledgerstore.Store + err := d.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { systemStore := d.systemStoreFactory.Create(tx) @@ -54,6 +69,7 @@ func (d *Driver) CreateLedger(ctx context.Context, l *ledger.Ledger) (*ledgersto if err != nil { return fmt.Errorf("checking if bucket is initialized: %w", err) } + if isInitialized { upToDate, err := b.IsUpToDate(ctx, tx) if err != nil { @@ -77,6 +93,7 @@ func (d *Driver) CreateLedger(ctx context.Context, l *ledger.Ledger) (*ledgersto return nil }) + if err != nil { return nil, postgres.ResolveError(err) } diff --git a/test/antithesis/Justfile b/test/antithesis/Justfile new file mode 100644 index 0000000000..a28536e1e4 --- /dev/null +++ b/test/antithesis/Justfile @@ -0,0 +1,27 @@ +run-6min: requirements-push + curl --fail \ + --user "formance:$ANTITHESIS_PASSWORD" \ + -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_PREVIOUS_TAG;ghcr.io/formancehq/ledger-instrumented:$LEDGER_LATEST_TAG"'" \ + } \ + }' + +run-1h: requirements-push + curl --fail \ + --user "formance:$ANTITHESIS_PASSWORD" \ + -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_PREVIOUS_TAG;ghcr.io/formancehq/ledger-instrumented:$LEDGER_LATEST_TAG"'" \ + } \ + }' + +requirements-push: + just config/push + just workload/push diff --git a/test/antithesis/config/.gitignore b/test/antithesis/config/.gitignore new file mode 100644 index 0000000000..a9a5aecf42 --- /dev/null +++ b/test/antithesis/config/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/test/antithesis/config/Dockerfile.config b/test/antithesis/config/Dockerfile.config new file mode 100644 index 0000000000..dabb4829c6 --- /dev/null +++ b/test/antithesis/config/Dockerfile.config @@ -0,0 +1,2 @@ +FROM scratch +COPY tmp/* /manifests/ diff --git a/test/antithesis/config/Justfile b/test/antithesis/config/Justfile new file mode 100644 index 0000000000..586bedb288 --- /dev/null +++ b/test/antithesis/config/Justfile @@ -0,0 +1,18 @@ +build-manifest: + 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 + +push: build-manifest + docker build -f Dockerfile.config -t $ANTITHESIS_REPOSITORY/antithesis-config:daily_run . + docker push $ANTITHESIS_REPOSITORY/antithesis-config:daily_run diff --git a/test/antithesis/config/manifests/ledger.yaml b/test/antithesis/config/manifests/ledger.yaml new file mode 100644 index 0000000000..cca83e3495 --- /dev/null +++ b/test/antithesis/config/manifests/ledger.yaml @@ -0,0 +1,7 @@ +apiVersion: formance.com/v1beta1 +kind: Ledger +metadata: + name: stack0-ledger +spec: + stack: stack0 + version: LEDGER_VERSION_PLACEHOLDER diff --git a/test/antithesis/config/manifests/namespace.yaml b/test/antithesis/config/manifests/namespace.yaml new file mode 100644 index 0000000000..b1395d2981 --- /dev/null +++ b/test/antithesis/config/manifests/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: formance-systems diff --git a/test/antithesis/config/manifests/postgres.yaml b/test/antithesis/config/manifests/postgres.yaml new file mode 100644 index 0000000000..7c048b38da --- /dev/null +++ b/test/antithesis/config/manifests/postgres.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: formance-systems +spec: + selector: + app: postgres + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: Pod +metadata: + name: postgres + namespace: formance-systems + labels: + app: postgres +spec: + containers: + - name: postgres + image: postgres:15-alpine + imagePullPolicy: IfNotPresent + args: ["-c", "max_connections=100"] + env: + - name: "POSTGRES_USER" + value: "ledger" + - name: "POSTGRES_PASSWORD" + value: "ledger" + - name: "POSTGRES_DB" + value: "ledger" + - name: "PGDATA" + value: /data/postgres + livenessProbe: + exec: + command: ["pg_isready", "-Uledger"] + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 diff --git a/test/antithesis/config/manifests/stack.yaml b/test/antithesis/config/manifests/stack.yaml new file mode 100644 index 0000000000..90e725bb96 --- /dev/null +++ b/test/antithesis/config/manifests/stack.yaml @@ -0,0 +1,33 @@ +apiVersion: formance.com/v1beta1 +kind: Settings +metadata: + name: formance-settings +spec: + key: postgres.*.uri + stacks: + - "stack0" + value: postgresql://ledger:ledger@postgres.formance-systems.svc.cluster.local:5432?disableSSLMode=true +--- +apiVersion: formance.com/v1beta1 +kind: Settings +metadata: + name: formance-setting-ledger-image +spec: + key: registries."ghcr.io".images."formancehq/ledger".rewrite + stacks: + - "stack0" + value: "formancehq/ledger-instrumented" +--- +apiVersion: formance.com/v1beta1 +kind: Stack +metadata: + name: stack0 +spec: + versionsFromFile: v2.0 +--- +apiVersion: formance.com/v1beta1 +kind: Gateway +metadata: + name: stack0-gateway +spec: + stack: stack0 diff --git a/test/antithesis/config/manifests/workload.yaml b/test/antithesis/config/manifests/workload.yaml new file mode 100644 index 0000000000..57d0612ad6 --- /dev/null +++ b/test/antithesis/config/manifests/workload.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Pod +metadata: + name: workload + namespace: formance-systems + labels: + app: workload +spec: + containers: + - name: workload + image: us-central1-docker.pkg.dev/molten-verve-216720/formance-repository/workload:latest + imagePullPolicy: IfNotPresent + readinessProbe: + exec: + command: + - sh + - -c + - "curl -sf http://gateway.stack0.svc.cluster.local:8080/api/ledger/v2" + initialDelaySeconds: 3 + periodSeconds: 3 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ledger-updater +rules: + - apiGroups: ["formance.com"] + resources: ["ledgers"] + verbs: ["get", "list", "watch", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ledger-updater-binding +subjects: + - kind: ServiceAccount + name: default + namespace: formance-systems +roleRef: + kind: ClusterRole + name: ledger-updater + apiGroup: rbac.authorization.k8s.io diff --git a/test/antithesis/image/Dockerfile b/test/antithesis/image/Dockerfile new file mode 100644 index 0000000000..cdf0c76b9b --- /dev/null +++ b/test/antithesis/image/Dockerfile @@ -0,0 +1,35 @@ +# Compiler +FROM golang:1.24-trixie AS compiler + +RUN apt-get update && apt-get install -y bash git gcc + +WORKDIR /src/pkg/client +COPY pkg/client/go.mod pkg/client/go.sum ./ +RUN go mod download + +WORKDIR /src +COPY go.mod go.sum main.go ./ +COPY internal internal +COPY pkg pkg +COPY cmd cmd + +RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest + +RUN mkdir /instrumented +RUN antithesis-go-instrumentor . /instrumented +WORKDIR /instrumented/customer +RUN go build -race -o ledger + +# Runner +FROM debian:trixie + +RUN apt-get update && apt-get install -y postgresql-client curl + +COPY --from=compiler /instrumented/customer/ledger /bin/ledger +COPY --from=compiler /instrumented/symbols/ /symbols +COPY test/antithesis/image/entrypoint.sh /bin/entrypoint.sh + +RUN chmod 777 /bin/entrypoint.sh + +ENTRYPOINT ["/bin/entrypoint.sh"] +EXPOSE 8080 diff --git a/test/antithesis/image/Justfile b/test/antithesis/image/Justfile new file mode 100644 index 0000000000..ecaea46489 --- /dev/null +++ b/test/antithesis/image/Justfile @@ -0,0 +1,3 @@ +push: + docker build -f Dockerfile -t "ghcr.io/formancehq/ledger-instrumented:$LEDGER_TAG" ../../../ + docker push "ghcr.io/formancehq/ledger-instrumented:$LEDGER_TAG" diff --git a/test/antithesis/image/entrypoint.sh b/test/antithesis/image/entrypoint.sh new file mode 100644 index 0000000000..9916e50581 --- /dev/null +++ b/test/antithesis/image/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# make sure pg is ready to accept connections +until pg_isready -d ledger -h postgres.formance-systems.svc.cluster.local -U ledger +do + echo "Waiting for postgres at: $POSTGRES_URI" + sleep 2; +done + +echo "Postgres is ready; serving ledger!" + +ledger serve diff --git a/test/antithesis/workload/Dockerfile b/test/antithesis/workload/Dockerfile new file mode 100644 index 0000000000..a24eef890f --- /dev/null +++ b/test/antithesis/workload/Dockerfile @@ -0,0 +1,33 @@ +# Compiler +FROM golang:1.24-alpine3.22 AS compiler + +RUN go install github.com/antithesishq/antithesis-sdk-go/tools/antithesis-go-instrumentor@latest + +WORKDIR /src/test/antithesis/workload + +COPY test/antithesis/workload/go.* . +COPY test/antithesis/workload/bin ./bin +COPY test/antithesis/workload/internal ./internal +COPY pkg/client /src/pkg/client + +RUN antithesis-go-instrumentor -assert_only . + +RUN go build -o /init ./bin/init +RUN mkdir -p /cmds +RUN for file in $(ls ./bin/cmds/); do go build -o /cmds/$file ./bin/cmds/$file; done + +RUN antithesis-go-instrumentor -assert_only . + +# Runner +FROM alpine:3.22 + +RUN apk update && apk add --no-cache curl + +ARG LEDGER_LATEST_TAG + +COPY --from=compiler /init /init +ENTRYPOINT ["/init"] + +COPY --from=compiler /cmds/* /opt/antithesis/test/v1/main/ + +RUN echo ${LEDGER_LATEST_TAG} > /ledger_latest_tag diff --git a/test/antithesis/workload/Justfile b/test/antithesis/workload/Justfile new file mode 100644 index 0000000000..a04a8b0085 --- /dev/null +++ b/test/antithesis/workload/Justfile @@ -0,0 +1,3 @@ +push: + docker build --build-arg LEDGER_LATEST_TAG="$LEDGER_LATEST_TAG" -f Dockerfile -t "$ANTITHESIS_REPOSITORY/workload:latest" ../../../ + docker push "$ANTITHESIS_REPOSITORY/workload:latest" diff --git a/test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go b/test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go new file mode 100644 index 0000000000..6a527b26d1 --- /dev/null +++ b/test/antithesis/workload/bin/cmds/anytime_version_upgrade/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/antithesishq/antithesis-sdk-go/assert" + "github.com/formancehq/ledger/test/antithesis/internal" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + + "k8s.io/client-go/tools/clientcmd" +) + +func main() { + // get latest version + latest_tag, err := os.ReadFile("/ledger_latest_tag") + if err != nil { + log.Fatal(err) + } + + // build dynamic client + config, err := clientcmd.BuildConfigFromFlags("", "") + if err != nil { + panic(err) + } + + dyn, err := dynamic.NewForConfig(config) + if err != nil { + panic(err) + } + + gvr := schema.GroupVersionResource { + Group: "formance.com", + Version: "v1beta1", + Resource: "ledgers", + } + + // 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{}) + + assert.Sometimes(err == nil, "stack0-ledger should successfully be updated", internal.Details{ + "ledger": res, + "error": err, + }) +} diff --git a/test/antithesis/workload/bin/cmds/eventually_correct/main.go b/test/antithesis/workload/bin/cmds/eventually_correct/main.go new file mode 100644 index 0000000000..48d571f55c --- /dev/null +++ b/test/antithesis/workload/bin/cmds/eventually_correct/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/big" + "sync" + + "github.com/antithesishq/antithesis-sdk-go/assert" + "github.com/formancehq/ledger/pkg/client" + "github.com/formancehq/ledger/pkg/client/models/operations" + "github.com/formancehq/ledger/test/antithesis/internal" +) + +func main() { + log.Println("composer: eventually_correct") + ctx := context.Background() + client := internal.NewClient() + + ledgers, err := client.Ledger.V2.ListLedgers(ctx, operations.V2ListLedgersRequest{}) + + assert.Sometimes(err == nil, "error listing ledgers", internal.Details { + "error": err, + }) + if err != nil { + return + } + + wg := sync.WaitGroup{} + for _, ledger := range ledgers.V2LedgerListResponse.Cursor.Data { + wg.Add(1) + go func(ledger string) { + defer wg.Done() + checkBalanced(ctx, client, ledger) + checkAccountBalances(ctx, client, ledger) + }(ledger.Name) + } + wg.Wait() +} + +func checkBalanced(ctx context.Context, client *client.Formance, ledger string) { + aggregated, err := client.Ledger.V2.GetBalancesAggregated(ctx, operations.V2GetBalancesAggregatedRequest{ + Ledger: ledger, + }) + assert.Sometimes( + err == nil, + "Client can aggregate balances", + internal.Details{ + "ledger": ledger, + "error": err, + }, + ) + if err != nil { + return + } + + 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{ + "asset": asset, + "volumes": volumes, + }) + } + + log.Printf("composer: balanced: done for ledger %s", ledger) +} + +func checkAccountBalances(ctx context.Context, client *client.Formance, ledger string) { + 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, + }) + assert.Sometimes(err == nil, "Client can aggregate account balances", internal.Details{ + "ledger": ledger, + "address": address, + "error": err, + }) + if err != nil { + continue + } + zero := big.NewInt(0) + for asset, volume := range account.V2AccountResponse.Data.Volumes { + balance := new(big.Int).Set(volume.Input) + balance.Sub(balance, volume.Output) + assert.Always(balance.Cmp(volume.Balance) == 0, "Reported balance and volumes should be consistent", internal.Details{ + "ledger": ledger, + "address": address, + "asset": asset, + "volume": volume, + }) + assert.Always(volume.Balance.Cmp(zero) != -1, "Balance should stay positive when no overdraft is allowed", internal.Details{ + "ledger": ledger, + "address": address, + "asset": asset, + "volume": volume, + }) + } + + } + + log.Printf("composer: account balances check: done for ledger %s", ledger) +} diff --git a/test/antithesis/workload/bin/cmds/first_default_ledger/main.go b/test/antithesis/workload/bin/cmds/first_default_ledger/main.go new file mode 100644 index 0000000000..b609ed331d --- /dev/null +++ b/test/antithesis/workload/bin/cmds/first_default_ledger/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "context" + "log" + + "github.com/formancehq/ledger/test/antithesis/internal" +) + +func main() { + log.Println("composer: first_default_ledger") + + ctx := context.Background() + client := internal.NewClient() + ledger := "default" + + _, err := internal.CreateLedger( + ctx, + client, + ledger, + ledger, + ) + + if err != nil { + log.Fatalf("error creating ledger %s: %s", ledger, err) + } + + log.Println("composer: first_default_ledger: done") +} diff --git a/test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go b/test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go new file mode 100644 index 0000000000..64c8c84466 --- /dev/null +++ b/test/antithesis/workload/bin/cmds/parallel_driver_ledger_create/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/antithesishq/antithesis-sdk-go/assert" + "github.com/antithesishq/antithesis-sdk-go/random" + "github.com/formancehq/ledger/test/antithesis/internal" +) + +func main() { + log.Println("composer: parallel_driver_ledger_create") + ctx := context.Background() + client := internal.NewClient() + id := random.GetRandom()%1e6 + ledger := fmt.Sprintf("ledger-%d", id) + + _, err := internal.CreateLedger( + ctx, + client, + ledger, + ledger, + ) + + assert.Sometimes(err == nil, "ledger should have been created properly", internal.Details{ + "error": err, + }) + + log.Println("composer: parallel_driver_ledger_create: done") +} diff --git a/test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go b/test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go new file mode 100644 index 0000000000..407dd3659c --- /dev/null +++ b/test/antithesis/workload/bin/cmds/parallel_driver_transactions/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "log" + + "github.com/alitto/pond" + "github.com/antithesishq/antithesis-sdk-go/assert" + "github.com/antithesishq/antithesis-sdk-go/random" + "github.com/formancehq/ledger/pkg/client" + "github.com/formancehq/ledger/pkg/client/models/components" + "github.com/formancehq/ledger/pkg/client/models/operations" + "github.com/formancehq/ledger/test/antithesis/internal" +) + +func main() { + log.Println("composer: parallel_driver_transactions") + + ctx := context.Background() + client := internal.NewClient() + + ledger, err := internal.GetRandomLedger(ctx, client) + assert.Sometimes(err == nil, "should be able to get a random ledger", internal.Details{ + "error": err, + }) + if err != nil { + return + } + + const count = 100 + + pool := pond.New(10, 10e3) + + for range count { + pool.Submit(func() { + CreateTransaction(ctx, client, ledger) + }) + } + + pool.StopAndWait() + + log.Println("composer: parallel_driver_transactions: done") +} + +type Postings []components.V2Posting + +func CreateTransaction( + ctx context.Context, + client *client.Formance, + ledger string, +) { + postings := RandomPostings() + _, err := client.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ + Ledger: ledger, + V2PostTransaction: components.V2PostTransaction{ + Postings: postings, + }, + }) + + assert.Sometimes(err == nil, "transaction was committed successfully", internal.Details{ + "ledger": ledger, + "postings": postings, + "error": err, + }) +} + +func RandomPostings() []components.V2Posting { + postings := []components.V2Posting{} + + for range random.GetRandom()%2+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, + }) + } + + return postings +} diff --git a/test/antithesis/workload/bin/init/main.go b/test/antithesis/workload/bin/init/main.go new file mode 100644 index 0000000000..592dadb565 --- /dev/null +++ b/test/antithesis/workload/bin/init/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + "fmt" + + "github.com/antithesishq/antithesis-sdk-go/lifecycle" + "github.com/formancehq/ledger/test/antithesis/internal" +) + +func main() { + ctx := context.Background() + client := internal.NewClient() + + for { + time.Sleep(time.Second) + + _, err := client.Ledger.GetInfo(ctx) + if err != nil { + fmt.Printf("Not ready: %s\n", err) + continue + } + break + } + + lifecycle.SetupComplete(map[string]any{}) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + <-sigs +} diff --git a/test/antithesis/workload/go.mod b/test/antithesis/workload/go.mod new file mode 100644 index 0000000000..69fab7afa6 --- /dev/null +++ b/test/antithesis/workload/go.mod @@ -0,0 +1,57 @@ +module github.com/formancehq/ledger/test/antithesis + +go 1.24.0 + +toolchain go1.24.5 + +replace github.com/formancehq/ledger/pkg/client => ../../../pkg/client + +require ( + github.com/alitto/pond v1.8.3 + github.com/antithesishq/antithesis-sdk-go v0.5.0 + github.com/formancehq/go-libs/v2 v2.0.1-0.20241114125605-4a3e447246a9 + github.com/formancehq/ledger/pkg/client v0.0.0-00010101000000-000000000000 + golang.org/x/sync v0.12.0 // indirect; indirect, for singleflight package +) + +require ( + k8s.io/apimachinery v0.34.0 + k8s.io/client-go v0.34.0 +) + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/invopop/jsonschema v0.12.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/test/antithesis/workload/go.sum b/test/antithesis/workload/go.sum new file mode 100644 index 0000000000..16bb7b7572 --- /dev/null +++ b/test/antithesis/workload/go.sum @@ -0,0 +1,162 @@ +github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= +github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= +github.com/antithesishq/antithesis-sdk-go v0.4.2 h1:cYLNRnojCYp6rKoLKdK6M9UKi9EahFXBtF6WR1vc6V0= +github.com/antithesishq/antithesis-sdk-go v0.4.2/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antithesishq/antithesis-sdk-go v0.5.0 h1:cudCFF83pDDANcXFzkQPUHHedfnnIbUO3JMr9fqwFJs= +github.com/antithesishq/antithesis-sdk-go v0.5.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo= +github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= +github.com/formancehq/go-libs/v2 v2.0.1-0.20241114125605-4a3e447246a9 h1:l6jieaR+sn4Ff+puBDMbTYmT2HTYC7Yt7GTxBAwC3eU= +github.com/formancehq/go-libs/v2 v2.0.1-0.20241114125605-4a3e447246a9/go.mod h1:m0uKkey9OC/AeyWMwjMfZqhLzoWrPFBk8vuYdSSYj4Y= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/test/antithesis/workload/internal/utils.go b/test/antithesis/workload/internal/utils.go new file mode 100644 index 0000000000..b4f0f9da81 --- /dev/null +++ b/test/antithesis/workload/internal/utils.go @@ -0,0 +1,106 @@ +package internal + +import ( + "context" + "fmt" + "math/big" + "net/http" + "os" + + "github.com/antithesishq/antithesis-sdk-go/assert" + "github.com/antithesishq/antithesis-sdk-go/random" + "github.com/formancehq/go-libs/v2/time" + "github.com/formancehq/ledger/pkg/client" + "github.com/formancehq/ledger/pkg/client/models/components" + "github.com/formancehq/ledger/pkg/client/models/operations" + "github.com/formancehq/ledger/pkg/client/retry" +) + +const USER_ACCOUNT_COUNT uint64 = 32; + +type Details map[string]any + +func RandomBigInt() *big.Int { + v := random.GetRandom() + ret := big.NewInt(0) + ret.SetString(fmt.Sprintf("%d", v), 10) + return ret +} + +func AssertAlways(condition bool, message string, details map[string]any) bool { + assert.Always(condition, message, details) + return condition +} + +func AssertAlwaysErrNil(err error, message string, details map[string]any) bool { + return AssertAlways(err == nil, message, Details{ + "error": fmt.Sprint(err), + "details": details, + }) +} + +func NewClient() *client.Formance { + gateway := os.Getenv("GATEWAY_URL") + if gateway == "" { + gateway = "http://gateway.stack0.svc.cluster.local:8080/api/ledger" + } + return client.New( + client.WithServerURL(gateway), + client.WithClient(&http.Client{ + Timeout: time.Minute, + }), + client.WithRetryConfig(retry.Config{ + Strategy: "backoff", + Backoff: &retry.BackoffStrategy{ + InitialInterval: 200, + Exponent: 1.5, + MaxElapsedTime: 10_000, + }, + RetryConnectionErrors: true, + }), + ) +} + +func CreateLedger(ctx context.Context, client *client.Formance, name string, bucket string) (*operations.V2CreateLedgerResponse, error) { + res, err := client.Ledger.V2.CreateLedger(ctx, operations.V2CreateLedgerRequest{ + Ledger: name, + V2CreateLedgerRequest: components.V2CreateLedgerRequest{ + Bucket: &bucket, + }, + }) + + return res, err +} + +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 GetRandomLedger(ctx context.Context, client *client.Formance) (string, error) { + ledgers, err := ListLedgers(ctx, client) + if err != nil { + return "", fmt.Errorf("error listing ledgers: %v", err) + } + + if len(ledgers) == 0 { + return "", fmt.Errorf("no ledgers found") + } + + randomIndex := random.GetRandom()%uint64(len(ledgers)) + + return ledgers[randomIndex], nil +} + +func GetRandomAddress() string { + return random.RandomChoice([]string{"world", fmt.Sprintf("users:%d", random.GetRandom()%USER_ACCOUNT_COUNT)}) +} diff --git a/tools/generator/cmd/root.go b/tools/generator/cmd/root.go index 30ae7b86df..7234194512 100644 --- a/tools/generator/cmd/root.go +++ b/tools/generator/cmd/root.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "fmt" + "github.com/formancehq/go-libs/v3/logging" "github.com/formancehq/go-libs/v3/service" ledgerclient "github.com/formancehq/ledger/pkg/client"