├── .github ├── demo.gif ├── workflows │ ├── main.yaml │ ├── lint.yaml │ ├── test.yaml │ ├── dagger.yaml │ └── release-binary.yml ├── dependabot.yaml ├── goreleaser.yaml └── golangci.yaml ├── tools ├── tools.go └── go.mod ├── ci └── dagger │ └── run-supernova │ ├── .gitignore │ ├── dagger.json │ ├── .gitattributes │ ├── go.mod │ ├── main.go │ └── go.sum ├── .gitignore ├── internal ├── common │ ├── types.go │ └── common.go ├── batcher │ ├── types.go │ ├── mock_test.go │ ├── batcher_test.go │ └── batcher.go ├── client │ ├── batch.go │ └── client.go ├── runtime │ ├── type.go │ ├── source.go │ ├── type_test.go │ ├── runtime.go │ ├── helper_test.go │ ├── realm_deployment.go │ ├── package_deployment.go │ ├── realm_call.go │ ├── helper.go │ └── runtime_test.go ├── collector │ ├── types.go │ ├── mock_test.go │ ├── collector_test.go │ └── collector.go ├── testing │ └── testing.go ├── output.go ├── distributor │ ├── mock_test.go │ ├── distributor_test.go │ └── distributor.go ├── signer │ └── signer.go ├── config.go └── pipeline.go ├── Dockerfile.release ├── Makefile ├── cmd └── root.go ├── README.md ├── go.mod ├── LICENSE └── go.sum /.github/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnolang/supernova/HEAD/.github/demo.gif -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 5 | ) 6 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/.gitignore: -------------------------------------------------------------------------------- 1 | /dagger.gen.go 2 | /internal/dagger 3 | /internal/querybuilder 4 | /internal/telemetry 5 | /.env 6 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/dagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supernova", 3 | "engineVersion": "v0.18.14", 4 | "sdk": { 5 | "source": "go" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS Leftovers 2 | .DS_Store 3 | 4 | # Editor Leftovers 5 | .vscode 6 | .idea 7 | 8 | # Build Leftovers 9 | build/* 10 | 11 | # Results 12 | *.json 13 | !ci/**/*.json 14 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/.gitattributes: -------------------------------------------------------------------------------- 1 | /dagger.gen.go linguist-generated 2 | /internal/dagger/** linguist-generated 3 | /internal/querybuilder/** linguist-generated 4 | /internal/telemetry/** linguist-generated 5 | -------------------------------------------------------------------------------- /internal/common/types.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // Batch is a common transaction batch 4 | type Batch interface { 5 | // AddTxBroadcast adds the transaction broadcast to the batch 6 | AddTxBroadcast(tx []byte) error 7 | 8 | // Execute executes the batch send 9 | Execute() ([]interface{}, error) 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile.release: -------------------------------------------------------------------------------- 1 | # NOTE: Gathering CA certicates from alpine BASE image 2 | FROM alpine:3 as alpine-ca 3 | RUN apk add --no-cache ca-certificates 4 | 5 | FROM scratch 6 | COPY --from=alpine-ca \ 7 | /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 8 | 9 | COPY supernova /usr/bin/supernova 10 | ENTRYPOINT [ "/usr/bin/supernova" ] 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Main Branch - Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths: 9 | - '**/*.go' 10 | - 'go.mod' 11 | - 'go.sum' 12 | 13 | jobs: 14 | lint: 15 | name: Go Linter 16 | uses: ./.github/workflows/lint.yaml 17 | 18 | test: 19 | name: Go Test 20 | uses: ./.github/workflows/test.yaml 21 | -------------------------------------------------------------------------------- /internal/batcher/types.go: -------------------------------------------------------------------------------- 1 | package batcher 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gnolang/supernova/internal/common" 7 | ) 8 | 9 | type Client interface { 10 | CreateBatch() common.Batch 11 | GetLatestBlockHeight(ctx context.Context) (int64, error) 12 | } 13 | 14 | // TxBatchResult contains batching results 15 | type TxBatchResult struct { 16 | TxHashes [][]byte // the tx hashes 17 | StartBlock int64 // the initial block for querying 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint Go Code 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v6 13 | with: 14 | go-version: 1.23 15 | 16 | - name: Checkout code 17 | uses: actions/checkout@v5 18 | 19 | - name: Lint 20 | uses: golangci/golangci-lint-action@v8 21 | with: 22 | version: v2.1 23 | args: --timeout=5m 24 | -------------------------------------------------------------------------------- /internal/client/batch.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" 8 | ) 9 | 10 | type Batch struct { 11 | batch *client.RPCBatch 12 | } 13 | 14 | func (b *Batch) AddTxBroadcast(tx []byte) error { 15 | if err := b.batch.BroadcastTxSync(tx); err != nil { 16 | return fmt.Errorf("unable to prepare transaction, %w", err) 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func (b *Batch) Execute() ([]interface{}, error) { 23 | return b.batch.Send(context.Background()) 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | golangci_lint := go run -modfile=./tools/go.mod github.com/golangci/golangci-lint/cmd/golangci-lint 2 | 3 | all: build 4 | 5 | .PHONY: build 6 | build: 7 | @echo "Building supernova binary" 8 | go build -o build/supernova ./cmd 9 | 10 | test: 11 | go test -v ./... 12 | 13 | .PHONY: lint 14 | lint: 15 | $(golangci_lint) run --config .github/golangci.yaml 16 | 17 | .PHONY: gofumpt 18 | gofumpt: 19 | go install mvdan.cc/gofumpt@latest 20 | gofumpt -l -w . 21 | 22 | .PHONY: fixalign 23 | fixalign: 24 | go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest 25 | fieldalignment -fix $(filter-out $@,$(MAKECMDGOALS)) # the full package name (not path!) -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | labels: 10 | - "github_actions" 11 | 12 | # Maintain dependencies for top level Go modules 13 | - package-ecosystem: gomod 14 | directory: / 15 | target-branch: "main" 16 | schedule: 17 | interval: weekly 18 | labels: 19 | - "dependencies" 20 | groups: 21 | golang-x: 22 | patterns: 23 | - "golang.org/x/*" 24 | everything-else: 25 | patterns: 26 | - "*" 27 | open-pull-requests-limit: 10 28 | pull-request-branch-name: 29 | separator: "-" 30 | reviewers: 31 | - "zivkovicmilos" 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Go Tests 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v6 13 | with: 14 | go-version: 1.23 15 | 16 | - name: Checkout code 17 | uses: actions/checkout@v5 18 | 19 | - name: Go test 20 | run: go test -shuffle=on -coverprofile coverage.out -timeout 5m ./... 21 | 22 | test-with-race: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Install Go 26 | uses: actions/setup-go@v6 27 | with: 28 | go-version: 1.23 29 | 30 | - name: Checkout code 31 | uses: actions/checkout@v5 32 | 33 | - name: Go race test 34 | run: go test -race -shuffle=on -timeout 5m ./... 35 | -------------------------------------------------------------------------------- /internal/runtime/type.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | type Type string 4 | 5 | const ( 6 | RealmDeployment Type = "REALM_DEPLOYMENT" 7 | PackageDeployment Type = "PACKAGE_DEPLOYMENT" 8 | RealmCall Type = "REALM_CALL" 9 | unknown Type = "UNKNOWN" 10 | ) 11 | 12 | // IsRuntime checks if the passed in runtime 13 | // is a supported runtime type 14 | func IsRuntime(runtime Type) bool { 15 | return runtime == RealmCall || 16 | runtime == RealmDeployment || 17 | runtime == PackageDeployment 18 | } 19 | 20 | // String returns a string representation 21 | // of the runtime type 22 | func (r Type) String() string { 23 | switch r { 24 | case RealmDeployment: 25 | return string(RealmDeployment) 26 | case PackageDeployment: 27 | return string(PackageDeployment) 28 | case RealmCall: 29 | return string(RealmCall) 30 | default: 31 | return string(unknown) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/gnolang/gno/tm2/pkg/std" 4 | 5 | const Denomination = "ugnot" 6 | 7 | // DefaultGasPrice represents the gno.land chain's 8 | // default minimum gas price ratio, which is 0.001ugnot/gas 9 | var DefaultGasPrice = std.GasPrice{ 10 | Gas: 1000, 11 | Price: std.Coin{ 12 | Denom: Denomination, 13 | Amount: 1, 14 | }, 15 | } 16 | 17 | // CalculateFeeInRatio calculates the minimum gas fee that should be specified 18 | // in a transaction, given the gas wanted (of the tx) and the reference gas ratio 19 | func CalculateFeeInRatio(gasWanted int64, reference std.GasPrice) std.Fee { 20 | // required amount = ceil((gas wanted * reference.Price.Amount) / reference.Gas) 21 | requiredAmount := (gasWanted*reference.Price.Amount + reference.Gas - 1) / reference.Gas 22 | 23 | return std.Fee{ 24 | GasWanted: gasWanted, 25 | GasFee: std.Coin{ 26 | Denom: reference.Price.Denom, 27 | Amount: requiredAmount, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/collector/types.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 8 | ) 9 | 10 | type Client interface { 11 | GetBlock(ctx context.Context, height *int64) (*core_types.ResultBlock, error) 12 | GetBlockGasUsed(ctx context.Context, height int64) (int64, error) 13 | GetBlockGasLimit(ctx context.Context, height int64) (int64, error) 14 | GetLatestBlockHeight(ctx context.Context) (int64, error) 15 | } 16 | 17 | // RunResult is the complete test-run result 18 | type RunResult struct { 19 | Blocks []*BlockResult `json:"blocks"` 20 | AverageTPS float64 `json:"averageTPS"` 21 | } 22 | 23 | // BlockResult is the single-block test run result 24 | type BlockResult struct { 25 | Time time.Time `json:"created"` 26 | Number int64 `json:"blockNumber"` 27 | Transactions int64 `json:"numTransactions"` 28 | GasUsed int64 `json:"gasUsed"` 29 | GasLimit int64 `json:"gasLimit"` 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/dagger.yaml: -------------------------------------------------------------------------------- 1 | name: Dagger Supernova 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 1 * * *' 10 | 11 | jobs: 12 | supernova: 13 | name: supernova-ci 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | env: [PACKAGE_DEPLOYMENT, REALM_DEPLOYMENT, REALM_CALL] 18 | max-parallel: 1 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v5 23 | 24 | - name: Get Dagger version 25 | id: dagger_version 26 | uses: sagikazarmark/dagger-version-action@v0.0.2 27 | with: 28 | path: ci/dagger/run-supernova/ 29 | 30 | - name: Supernova Run 31 | uses: dagger/dagger-for-github@v7 32 | env: 33 | RPC_URL: https://rpc.gno.land 34 | CHAIN_ID: staging 35 | with: 36 | version: ${{ steps.dagger_version.outputs.version }} 37 | verb: call 38 | module: ci/dagger/run-supernova/ 39 | args: run-stress-test --src-dir . --chain-id ${CHAIN_ID} --rpc-endpoint ${RPC_URL} --mode ${{ matrix.env }} --sub-accounts 2 40 | -------------------------------------------------------------------------------- /internal/testing/testing.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gnolang/gno/tm2/pkg/crypto" 7 | "github.com/gnolang/gno/tm2/pkg/crypto/bip39" 8 | "github.com/gnolang/supernova/internal/signer" 9 | ) 10 | 11 | // GenerateMnemonic generates a new BIP39 mnemonic 12 | func GenerateMnemonic(t *testing.T) string { 13 | t.Helper() 14 | 15 | // Generate the entropy seed 16 | entropySeed, err := bip39.NewEntropy(256) 17 | if err != nil { 18 | t.Fatalf("unable to generate entropy seed, %v", err) 19 | } 20 | 21 | // Generate the actual mnemonic 22 | mnemonic, err := bip39.NewMnemonic(entropySeed) 23 | if err != nil { 24 | t.Fatalf("unable to generate mnemonic, %v", err) 25 | } 26 | 27 | return mnemonic 28 | } 29 | 30 | // GenerateAccounts generates mock keybase accounts 31 | func GenerateAccounts(t *testing.T, count int) []crypto.PrivKey { 32 | t.Helper() 33 | 34 | var ( 35 | accounts = make([]crypto.PrivKey, count) 36 | mnemonic = GenerateMnemonic(t) 37 | seed = bip39.NewSeed(mnemonic, "") 38 | ) 39 | 40 | for i := 0; i < count; i++ { 41 | accounts[i] = signer.GenerateKeyFromSeed(seed, uint32(i)) 42 | } 43 | 44 | return accounts 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: Release Binaries 2 | 3 | permissions: 4 | contents: write # needed to write releases 5 | id-token: write # needed for keyless signing 6 | packages: write # needed for ghcr access 7 | 8 | on: 9 | push: 10 | tags: 11 | - "v*" 12 | 13 | jobs: 14 | binary: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Set up Go 18 | uses: actions/setup-go@v6 19 | with: 20 | go-version: 1.23 21 | 22 | - uses: sigstore/cosign-installer@v3.10.0 23 | - uses: anchore/sbom-action/download-syft@v0.20.6 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v5 27 | with: 28 | fetch-depth: 0 29 | 30 | - uses: docker/login-action@v3 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.repository_owner }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Set up QEMU 37 | uses: docker/setup-qemu-action@v3 38 | 39 | - name: Run GoReleaser 40 | uses: goreleaser/goreleaser-action@v6 41 | with: 42 | version: latest 43 | args: release --clean --config ./.github/goreleaser.yaml 44 | env: 45 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 46 | -------------------------------------------------------------------------------- /internal/runtime/source.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | const ( 4 | realmBody = `package runtime 5 | 6 | var greeting string 7 | 8 | func init() { 9 | greeting = "Hello" 10 | } 11 | 12 | // SayHello says hello to the specified name, using 13 | // the saved greeting 14 | func SayHello(cur realm, name string) string { 15 | return greeting + " " + name + "!" 16 | } 17 | ` 18 | packageBody = `package runtime 19 | 20 | type Language string 21 | 22 | const ( 23 | French Language = "french" 24 | Italian Language = "italian" 25 | Spanish Language = "spanish" 26 | Hindi Language = "hindi" 27 | Bulgarian Language = "bulgarian" 28 | Serbian Language = "serbian" 29 | ) 30 | 31 | // GetGreeting generates a greeting in 32 | // the specified language 33 | func GetGreeting(language Language) string { 34 | switch language { 35 | case French: 36 | return "Bonjour" 37 | case Italian: 38 | return "Ciao" 39 | case Spanish: 40 | return "Hola" 41 | case Hindi: 42 | return "नमस्ते" 43 | case Bulgarian: 44 | return "Здравейте" 45 | case Serbian: 46 | return "Здраво" 47 | default: 48 | return "Hello" 49 | } 50 | } 51 | ` 52 | gnomodBody = ` 53 | module = "gno.land/r/demo/runtime" 54 | gno = "0.9" 55 | ` 56 | ) 57 | 58 | const ( 59 | packageName = "runtime" 60 | realmFileName = "realm.gno" 61 | packageFileName = "package.gno" 62 | gnomodFileName = "gnomod.toml" 63 | ) 64 | -------------------------------------------------------------------------------- /internal/batcher/mock_test.go: -------------------------------------------------------------------------------- 1 | package batcher 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gnolang/supernova/internal/common" 7 | ) 8 | 9 | type ( 10 | createBatchDelegate func() common.Batch 11 | getLatestBlockHeightDelegate func(context.Context) (int64, error) 12 | ) 13 | 14 | type mockClient struct { 15 | createBatchFn createBatchDelegate 16 | getLatestBlockHeightFn getLatestBlockHeightDelegate 17 | } 18 | 19 | func (m *mockClient) CreateBatch() common.Batch { 20 | if m.createBatchFn != nil { 21 | return m.createBatchFn() 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (m *mockClient) GetLatestBlockHeight(ctx context.Context) (int64, error) { 28 | if m.getLatestBlockHeightFn != nil { 29 | return m.getLatestBlockHeightFn(ctx) 30 | } 31 | 32 | return 0, nil 33 | } 34 | 35 | type ( 36 | addTxBroadcastDelegate func(tx []byte) error 37 | executeDelegate func() ([]interface{}, error) 38 | ) 39 | 40 | type mockBatch struct { 41 | addTxBroadcastFn addTxBroadcastDelegate 42 | executeFn executeDelegate 43 | } 44 | 45 | func (m *mockBatch) AddTxBroadcast(tx []byte) error { 46 | if m.addTxBroadcastFn != nil { 47 | return m.addTxBroadcastFn(tx) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (m *mockBatch) Execute() ([]interface{}, error) { 54 | if m.executeFn != nil { 55 | return m.executeFn() 56 | } 57 | 58 | return nil, nil 59 | } 60 | -------------------------------------------------------------------------------- /internal/output.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "text/tabwriter" 8 | 9 | "github.com/gnolang/supernova/internal/collector" 10 | ) 11 | 12 | // displayResults displays the runtime result in the terminal 13 | func displayResults(result *collector.RunResult) { 14 | w := tabwriter.NewWriter(os.Stdout, 10, 20, 2, ' ', 0) 15 | 16 | // TPS // 17 | _, _ = fmt.Fprintf(w, "\nTPS: %.2f\n", result.AverageTPS) 18 | 19 | // Block info // 20 | _, _ = fmt.Fprintln(w, "\nBlock #\tGas Used\tGas Limit\tTransactions\tUtilization") 21 | for _, block := range result.Blocks { 22 | _, _ = fmt.Fprintf( 23 | w, 24 | "Block #%d\t%d\t%d\t%d\t%.2f%%\n", 25 | block.Number, 26 | block.GasUsed, 27 | block.GasLimit, 28 | block.Transactions, 29 | (float64(block.GasUsed)/float64(block.GasLimit))*100, 30 | ) 31 | } 32 | 33 | _, _ = fmt.Fprintln(w, "") 34 | 35 | _ = w.Flush() 36 | } 37 | 38 | // saveResults saves the runtime results to a file 39 | func saveResults(result *collector.RunResult, path string) error { 40 | // Marshal the results 41 | resultJSON, err := json.Marshal(result) 42 | if err != nil { 43 | return fmt.Errorf("unable to marshal result, %w", err) 44 | } 45 | 46 | // Create the file 47 | f, err := os.Create(path) 48 | if err != nil { 49 | return fmt.Errorf("unable to create file, %w", err) 50 | } 51 | 52 | defer func() { 53 | _ = f.Close() 54 | }() 55 | 56 | // Write to file 57 | _, err = f.Write(resultJSON) 58 | if err != nil { 59 | return fmt.Errorf("unable to write to file, %w", err) 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/distributor/mock_test.go: -------------------------------------------------------------------------------- 1 | package distributor 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gnolang/gno/gno.land/pkg/gnoland" 7 | "github.com/gnolang/gno/tm2/pkg/std" 8 | ) 9 | 10 | type ( 11 | broadcastTransactionDelegate func(context.Context, *std.Tx) error 12 | getAccountDelegate func(context.Context, string) (*gnoland.GnoAccount, error) 13 | estimateGasDelegate func(context.Context, *std.Tx) (int64, error) 14 | fetchGasPriceDelegate func(context.Context) (std.GasPrice, error) 15 | ) 16 | 17 | type mockClient struct { 18 | broadcastTransactionFn broadcastTransactionDelegate 19 | getAccountFn getAccountDelegate 20 | estimateGasFn estimateGasDelegate 21 | fetchGasPriceFn fetchGasPriceDelegate 22 | } 23 | 24 | func (m *mockClient) BroadcastTransaction(ctx context.Context, tx *std.Tx) error { 25 | if m.broadcastTransactionFn != nil { 26 | return m.broadcastTransactionFn(ctx, tx) 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (m *mockClient) GetAccount(ctx context.Context, address string) (*gnoland.GnoAccount, error) { 33 | if m.getAccountFn != nil { 34 | return m.getAccountFn(ctx, address) 35 | } 36 | 37 | return nil, nil 38 | } 39 | 40 | func (m *mockClient) EstimateGas(ctx context.Context, tx *std.Tx) (int64, error) { 41 | if m.estimateGasFn != nil { 42 | return m.estimateGasFn(ctx, tx) 43 | } 44 | 45 | return 0, nil 46 | } 47 | 48 | func (m *mockClient) FetchGasPrice(ctx context.Context) (std.GasPrice, error) { 49 | if m.fetchGasPriceFn != nil { 50 | return m.fetchGasPriceFn(ctx) 51 | } 52 | 53 | return std.GasPrice{}, nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/runtime/type_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestType_IsRuntime(t *testing.T) { 10 | t.Parallel() 11 | 12 | testTable := []struct { 13 | name string 14 | mode Type 15 | isValid bool 16 | }{ 17 | { 18 | "Realm Deployment", 19 | RealmDeployment, 20 | true, 21 | }, 22 | { 23 | "Package Deployment", 24 | PackageDeployment, 25 | true, 26 | }, 27 | { 28 | "Realm Call", 29 | RealmCall, 30 | true, 31 | }, 32 | { 33 | "Dummy mode", 34 | Type("Dummy mode"), 35 | false, 36 | }, 37 | } 38 | 39 | for _, testCase := range testTable { 40 | t.Run(testCase.name, func(t *testing.T) { 41 | t.Parallel() 42 | 43 | assert.Equal(t, testCase.isValid, IsRuntime(testCase.mode)) 44 | }) 45 | } 46 | } 47 | 48 | func TestType_String(t *testing.T) { 49 | t.Parallel() 50 | 51 | testTable := []struct { 52 | name string 53 | mode Type 54 | expectedStr string 55 | }{ 56 | { 57 | "Realm Deployment", 58 | RealmDeployment, 59 | string(RealmDeployment), 60 | }, 61 | { 62 | "Package Deployment", 63 | PackageDeployment, 64 | string(PackageDeployment), 65 | }, 66 | { 67 | "Realm Call", 68 | RealmCall, 69 | string(RealmCall), 70 | }, 71 | { 72 | "Dummy mode", 73 | Type("Dummy mode"), 74 | string(unknown), 75 | }, 76 | } 77 | 78 | for _, testCase := range testTable { 79 | t.Run(testCase.name, func(t *testing.T) { 80 | t.Parallel() 81 | 82 | assert.Equal(t, testCase.expectedStr, testCase.mode.String()) 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /internal/collector/mock_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | 6 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 7 | ) 8 | 9 | type ( 10 | getBlockDelegate func(ctx context.Context, height *int64) (*core_types.ResultBlock, error) 11 | getBlockGasUsedDelegate func(ctx context.Context, height int64) (int64, error) 12 | getBlockGasLimitDelegate func(ctx context.Context, height int64) (int64, error) 13 | getLatestBlockHeightDelegate func(ctx context.Context) (int64, error) 14 | ) 15 | 16 | type mockClient struct { 17 | getBlockFn getBlockDelegate 18 | getBlockGasUsedFn getBlockGasUsedDelegate 19 | getBlockGasLimitFn getBlockGasLimitDelegate 20 | getLatestBlockHeightFn getLatestBlockHeightDelegate 21 | } 22 | 23 | func (m *mockClient) GetBlock(ctx context.Context, height *int64) (*core_types.ResultBlock, error) { 24 | if m.getBlockFn != nil { 25 | return m.getBlockFn(ctx, height) 26 | } 27 | 28 | return nil, nil 29 | } 30 | 31 | func (m *mockClient) GetBlockGasUsed(ctx context.Context, height int64) (int64, error) { 32 | if m.getBlockGasUsedFn != nil { 33 | return m.getBlockGasUsedFn(ctx, height) 34 | } 35 | 36 | return 0, nil 37 | } 38 | 39 | func (m *mockClient) GetBlockGasLimit(ctx context.Context, height int64) (int64, error) { 40 | if m.getBlockGasLimitFn != nil { 41 | return m.getBlockGasLimitFn(ctx, height) 42 | } 43 | 44 | return 0, nil 45 | } 46 | 47 | func (m *mockClient) GetLatestBlockHeight(ctx context.Context) (int64, error) { 48 | if m.getLatestBlockHeightFn != nil { 49 | return m.getLatestBlockHeightFn(ctx) 50 | } 51 | 52 | return 0, nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/signer/signer.go: -------------------------------------------------------------------------------- 1 | package signer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gnolang/gno/tm2/pkg/crypto" 7 | "github.com/gnolang/gno/tm2/pkg/crypto/hd" 8 | "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" 9 | "github.com/gnolang/gno/tm2/pkg/std" 10 | ) 11 | 12 | // SignCfg specifies the sign configuration 13 | type SignCfg struct { 14 | ChainID string // the ID of the chain 15 | AccountNumber uint64 // the account number of the signer 16 | Sequence uint64 // the Sequence of the signer 17 | } 18 | 19 | // SignTx signs the specified transaction using 20 | // the provided key and config 21 | func SignTx(tx *std.Tx, key crypto.PrivKey, cfg SignCfg) error { 22 | // Get the sign bytes 23 | signBytes, err := tx.GetSignBytes( 24 | cfg.ChainID, 25 | cfg.AccountNumber, 26 | cfg.Sequence, 27 | ) 28 | if err != nil { 29 | return fmt.Errorf("unable to get tx signature payload, %w", err) 30 | } 31 | 32 | // Sign the transaction 33 | signature, err := key.Sign(signBytes) 34 | if err != nil { 35 | return fmt.Errorf("unable to sign transaction, %w", err) 36 | } 37 | 38 | // Save the signature 39 | tx.Signatures = append(tx.Signatures, std.Signature{ 40 | PubKey: key.PubKey(), 41 | Signature: signature, 42 | }) 43 | 44 | return nil 45 | } 46 | 47 | // GenerateKeyFromSeed generates a private key from 48 | // the provided seed and index 49 | func GenerateKeyFromSeed(seed []byte, index uint32) crypto.PrivKey { 50 | pathParams := hd.NewFundraiserParams(0, crypto.CoinType, index) 51 | 52 | masterPriv, ch := hd.ComputeMastersFromSeed(seed) 53 | 54 | //nolint:errcheck // This derivation can never error out, since the path params 55 | // are always going to be valid 56 | derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, pathParams.String()) 57 | 58 | return secp256k1.PrivKeySecp256k1(derivedPriv) 59 | } 60 | -------------------------------------------------------------------------------- /internal/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gnolang/gno/tm2/pkg/crypto" 7 | "github.com/gnolang/gno/tm2/pkg/std" 8 | ) 9 | 10 | const ( 11 | realmPathPrefix = "gno.land/r" 12 | packagePathPrefix = "gno.land/p" 13 | ) 14 | 15 | // EstimateGasFn is the gas estimation callback 16 | type EstimateGasFn func(ctx context.Context, tx *std.Tx) (int64, error) 17 | 18 | // SignFn is the tx signing callback 19 | type SignFn func(tx *std.Tx) error 20 | 21 | // Runtime is the base interface for all runtime 22 | // implementations. 23 | // 24 | // The runtime's job is to prepare the transactions for the stress test (generate + sign), 25 | // and to predeploy (initialize) any infrastructure (package) 26 | type Runtime interface { 27 | // Initialize prepares any infrastructure transactions that are required 28 | // to be executed before the stress test runs, if any 29 | Initialize( 30 | account std.Account, 31 | signFn SignFn, 32 | estimateFn EstimateGasFn, 33 | currentMaxGas int64, 34 | gasPrice std.GasPrice, 35 | ) ([]*std.Tx, error) 36 | // CalculateRuntimeCosts calculates the amount of funds 37 | // each account needs to have in order to participate in the 38 | // stress test run 39 | CalculateRuntimeCosts( 40 | account std.Account, 41 | estimateFn EstimateGasFn, 42 | signFn SignFn, 43 | currentMaxGas int64, 44 | gasPrice std.GasPrice, 45 | transactions uint64, 46 | ) (std.Coin, error) 47 | 48 | // ConstructTransactions generates and signs the required transactions 49 | // that will be used in the stress test 50 | ConstructTransactions( 51 | keys []crypto.PrivKey, 52 | accounts []std.Account, 53 | transactions uint64, 54 | maxGas int64, 55 | gasPrice std.GasPrice, 56 | chainID string, 57 | estimateFn EstimateGasFn, 58 | ) ([]*std.Tx, error) 59 | } 60 | 61 | // GetRuntime fetches the specified runtime, if any 62 | func GetRuntime(ctx context.Context, runtimeType Type) Runtime { 63 | switch runtimeType { 64 | case RealmCall: 65 | return newRealmCall(ctx) 66 | case RealmDeployment: 67 | return newRealmDeployment(ctx) 68 | case PackageDeployment: 69 | return newPackageDeployment(ctx) 70 | default: 71 | return nil 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/runtime/helper_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gnolang/gno/gno.land/pkg/gnoland" 8 | "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 9 | "github.com/gnolang/gno/tm2/pkg/std" 10 | "github.com/gnolang/supernova/internal/common" 11 | testutils "github.com/gnolang/supernova/internal/testing" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | // generateAccounts generates mock gno accounts 17 | func generateAccounts(count int) []std.Account { 18 | accounts := make([]std.Account, count) 19 | 20 | for i := 0; i < count; i++ { 21 | accounts[i] = &gnoland.GnoAccount{ 22 | BaseAccount: std.BaseAccount{ 23 | AccountNumber: uint64(i), 24 | }, 25 | } 26 | } 27 | 28 | return accounts 29 | } 30 | 31 | func TestHelper_ConstructTransactions(t *testing.T) { 32 | t.Parallel() 33 | 34 | var ( 35 | accounts = generateAccounts(10) 36 | accountKeys = testutils.GenerateAccounts(t, 10) 37 | nonceMap = make(map[uint64]uint64, len(accounts)) 38 | ) 39 | 40 | // Initialize the nonce map 41 | for _, account := range accounts { 42 | nonceMap[account.GetAccountNumber()] = 0 43 | } 44 | 45 | var ( 46 | transactions = uint64(100) 47 | msg = vm.MsgAddPackage{} 48 | 49 | getMsgFn = func(_ std.Account, _ int) std.Msg { 50 | return msg 51 | } 52 | ) 53 | 54 | txs, err := constructTransactions( 55 | context.Background(), 56 | accountKeys, 57 | accounts, 58 | transactions, 59 | 1_000_000, 60 | common.DefaultGasPrice, 61 | "dummy", 62 | getMsgFn, 63 | func(_ context.Context, _ *std.Tx) (int64, error) { 64 | return 1_000_000, nil 65 | }, 66 | ) 67 | require.NoError(t, err) 68 | 69 | assert.Len(t, txs, int(transactions)) 70 | 71 | // Make sure the constructed transactions are valid 72 | for _, tx := range txs { 73 | // Make sure the fee is valid 74 | assert.Equal( 75 | t, 76 | common.CalculateFeeInRatio(1_000_000+gasBuffer, common.DefaultGasPrice), 77 | tx.Fee, 78 | ) 79 | 80 | // Make sure the message is valid 81 | if len(tx.Msgs) != 1 { 82 | t.Fatalf("invalid number of transaction messages, %d", len(tx.Msgs)) 83 | } 84 | 85 | assert.Equal(t, msg, tx.Msgs[0]) 86 | assert.NotEmpty(t, tx.Msgs[0].GetSigners()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /internal/runtime/realm_deployment.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 9 | "github.com/gnolang/gno/tm2/pkg/crypto" 10 | "github.com/gnolang/gno/tm2/pkg/std" 11 | ) 12 | 13 | type realmDeployment struct { 14 | ctx context.Context 15 | } 16 | 17 | func newRealmDeployment(ctx context.Context) *realmDeployment { 18 | return &realmDeployment{ 19 | ctx: ctx, 20 | } 21 | } 22 | 23 | func (c *realmDeployment) Initialize( 24 | _ std.Account, 25 | _ SignFn, 26 | _ EstimateGasFn, 27 | _ int64, 28 | _ std.GasPrice, 29 | ) ([]*std.Tx, error) { 30 | // No extra setup needed for this runtime type 31 | return nil, nil 32 | } 33 | 34 | func (c *realmDeployment) CalculateRuntimeCosts( 35 | account std.Account, 36 | estimateFn EstimateGasFn, 37 | signFn SignFn, 38 | currentMaxGas int64, 39 | gasPrice std.GasPrice, 40 | transactions uint64, 41 | ) (std.Coin, error) { 42 | return calculateRuntimeCosts( 43 | c.ctx, 44 | account, 45 | transactions, 46 | currentMaxGas, 47 | gasPrice, 48 | c.getMsgFn, 49 | signFn, 50 | estimateFn, 51 | ) 52 | } 53 | 54 | func (c *realmDeployment) getMsgFn(creator std.Account, index int) std.Msg { 55 | timestamp := time.Now().Unix() 56 | memPkg := &std.MemPackage{ 57 | Name: packageName, 58 | Path: fmt.Sprintf( 59 | "%s/%s/stress_%d_%d", 60 | realmPathPrefix, 61 | creator.GetAddress().String(), 62 | timestamp, 63 | index, 64 | ), 65 | Files: []*std.MemFile{ 66 | { 67 | Name: gnomodFileName, 68 | Body: gnomodBody, 69 | }, 70 | { 71 | Name: realmFileName, 72 | Body: realmBody, 73 | }, 74 | }, 75 | } 76 | 77 | return vm.MsgAddPackage{ 78 | Creator: creator.GetAddress(), 79 | Package: memPkg, 80 | } 81 | } 82 | 83 | func (c *realmDeployment) ConstructTransactions( 84 | keys []crypto.PrivKey, 85 | accounts []std.Account, 86 | transactions uint64, 87 | maxGas int64, 88 | gasPrice std.GasPrice, 89 | chainID string, 90 | estimateFn EstimateGasFn, 91 | ) ([]*std.Tx, error) { 92 | return constructTransactions( 93 | c.ctx, 94 | keys, 95 | accounts, 96 | transactions, 97 | maxGas, 98 | gasPrice, 99 | chainID, 100 | c.getMsgFn, 101 | estimateFn, 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /internal/runtime/package_deployment.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 9 | "github.com/gnolang/gno/tm2/pkg/crypto" 10 | "github.com/gnolang/gno/tm2/pkg/std" 11 | ) 12 | 13 | type packageDeployment struct { 14 | ctx context.Context 15 | } 16 | 17 | func newPackageDeployment(ctx context.Context) *packageDeployment { 18 | return &packageDeployment{ 19 | ctx: ctx, 20 | } 21 | } 22 | 23 | func (c *packageDeployment) Initialize( 24 | _ std.Account, 25 | _ SignFn, 26 | _ EstimateGasFn, 27 | _ int64, 28 | _ std.GasPrice, 29 | ) ([]*std.Tx, error) { 30 | // No extra setup needed for this runtime type 31 | return nil, nil 32 | } 33 | 34 | func (c *packageDeployment) CalculateRuntimeCosts( 35 | account std.Account, 36 | estimateFn EstimateGasFn, 37 | signFn SignFn, 38 | currentMaxGas int64, 39 | gasPrice std.GasPrice, 40 | transactions uint64, 41 | ) (std.Coin, error) { 42 | return calculateRuntimeCosts( 43 | c.ctx, 44 | account, 45 | transactions, 46 | currentMaxGas, 47 | gasPrice, 48 | c.getMsgFn, 49 | signFn, 50 | estimateFn, 51 | ) 52 | } 53 | 54 | func (c *packageDeployment) ConstructTransactions( 55 | keys []crypto.PrivKey, 56 | accounts []std.Account, 57 | transactions uint64, 58 | maxGas int64, 59 | gasPrice std.GasPrice, 60 | chainID string, 61 | estimateFn EstimateGasFn, 62 | ) ([]*std.Tx, error) { 63 | return constructTransactions( 64 | c.ctx, 65 | keys, 66 | accounts, 67 | transactions, 68 | maxGas, 69 | gasPrice, 70 | chainID, 71 | c.getMsgFn, 72 | estimateFn, 73 | ) 74 | } 75 | 76 | func (c *packageDeployment) getMsgFn(creator std.Account, index int) std.Msg { 77 | timestamp := time.Now().Unix() 78 | 79 | memPkg := &std.MemPackage{ 80 | Name: packageName, 81 | Path: fmt.Sprintf( 82 | "%s/%s/stress_%d_%d", 83 | packagePathPrefix, 84 | creator.GetAddress().String(), 85 | timestamp, 86 | index, 87 | ), 88 | Files: []*std.MemFile{ 89 | { 90 | Name: gnomodFileName, 91 | Body: gnomodBody, 92 | }, 93 | { 94 | Name: packageFileName, 95 | Body: packageBody, 96 | }, 97 | }, 98 | } 99 | 100 | return vm.MsgAddPackage{ 101 | Creator: creator.GetAddress(), 102 | Package: memPkg, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /internal/config.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | 7 | "github.com/gnolang/gno/tm2/pkg/crypto/bip39" 8 | "github.com/gnolang/supernova/internal/runtime" 9 | ) 10 | 11 | var ( 12 | errInvalidURL = errors.New("invalid node URL specified") 13 | errInvalidMnemonic = errors.New("invalid Mnemonic specified") 14 | errInvalidMode = errors.New("invalid mode specified") 15 | errInvalidSubaccounts = errors.New("invalid number of subaccounts specified") 16 | errInvalidTransactions = errors.New("invalid number of transactions specified") 17 | errInvalidBatchSize = errors.New("invalid batch size specified") 18 | ) 19 | 20 | var ( 21 | // httpRegex is used for verifying the cluster's JSON-RPC HTTP endpoint 22 | httpRegex = regexp.MustCompile(`(https?://.*)(:(\d*)/?(.*))?`) 23 | 24 | // wsRegex is used for verifying the cluster's JSON-RPC WS endpoint 25 | wsRegex = regexp.MustCompile(`(wss?://.*)(:(\d*)/?(.*))?`) 26 | ) 27 | 28 | // Config is the central pipeline configuration 29 | type Config struct { 30 | URL string // the URL of the cluster 31 | ChainID string // the chain ID of the cluster 32 | Mnemonic string // the mnemonic for the keyring 33 | Mode string // the stress test mode 34 | Output string // output path for results JSON, if any 35 | 36 | SubAccounts uint64 // the number of sub-accounts in the run 37 | Transactions uint64 // the total number of transactions 38 | BatchSize uint64 // the maximum size of the batch 39 | } 40 | 41 | // Validate validates the stress-test configuration 42 | func (cfg *Config) Validate() error { 43 | // Make sure the URL is valid 44 | if !httpRegex.MatchString(cfg.URL) && 45 | !wsRegex.MatchString(cfg.URL) { 46 | return errInvalidURL 47 | } 48 | 49 | // Make sure the mnemonic is valid 50 | if !bip39.IsMnemonicValid(cfg.Mnemonic) { 51 | return errInvalidMnemonic 52 | } 53 | 54 | // Make sure the mode is valid 55 | if !runtime.IsRuntime(runtime.Type(cfg.Mode)) { 56 | return errInvalidMode 57 | } 58 | 59 | // Make sure the number of subaccounts is valid 60 | if cfg.SubAccounts < 1 { 61 | return errInvalidSubaccounts 62 | } 63 | 64 | // Make sure the number of transactions is valid 65 | if cfg.Transactions < 1 { 66 | return errInvalidTransactions 67 | } 68 | 69 | // Make sure the batch size is valid 70 | if cfg.BatchSize < 1 { 71 | return errInvalidBatchSize 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/go.mod: -------------------------------------------------------------------------------- 1 | module dagger/run-supernova 2 | 3 | go 1.24.4 4 | 5 | require ( 6 | github.com/99designs/gqlgen v0.17.75 7 | github.com/Khan/genqlient v0.8.1 8 | github.com/vektah/gqlparser/v2 v2.5.28 9 | go.opentelemetry.io/otel v1.36.0 10 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 11 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 12 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 13 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 14 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 15 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 16 | go.opentelemetry.io/otel/log v0.12.2 17 | go.opentelemetry.io/otel/metric v1.36.0 18 | go.opentelemetry.io/otel/sdk v1.36.0 19 | go.opentelemetry.io/otel/sdk/log v0.12.2 20 | go.opentelemetry.io/otel/sdk/metric v1.36.0 21 | go.opentelemetry.io/otel/trace v1.36.0 22 | go.opentelemetry.io/proto/otlp v1.6.0 23 | golang.org/x/sync v0.15.0 24 | google.golang.org/grpc v1.73.0 25 | ) 26 | 27 | require ( 28 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 29 | github.com/cenkalti/backoff/v5 v5.0.2 // indirect 30 | github.com/go-logr/logr v1.4.2 // indirect 31 | github.com/go-logr/stdr v1.2.2 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 34 | github.com/sosodev/duration v1.3.1 // indirect 35 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 36 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect 37 | golang.org/x/net v0.41.0 // indirect 38 | golang.org/x/sys v0.33.0 // indirect 39 | golang.org/x/text v0.26.0 // indirect 40 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect 41 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 42 | google.golang.org/protobuf v1.36.6 // indirect 43 | ) 44 | 45 | replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 46 | 47 | replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 48 | 49 | replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.12.2 50 | 51 | replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.12.2 52 | -------------------------------------------------------------------------------- /internal/batcher/batcher_test.go: -------------------------------------------------------------------------------- 1 | package batcher 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/rand" 7 | "fmt" 8 | "testing" 9 | 10 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 11 | "github.com/gnolang/gno/tm2/pkg/std" 12 | "github.com/gnolang/supernova/internal/common" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | // generateRandomData generates random 32B chunks 17 | func generateRandomData(t *testing.T, count int) [][]byte { 18 | t.Helper() 19 | 20 | data := make([][]byte, count) 21 | 22 | for i := 0; i < count; i++ { 23 | buf := make([]byte, 32) 24 | 25 | _, err := rand.Read(buf) 26 | if err != nil { 27 | t.Fatalf("unable to generate random data, %v", err) 28 | } 29 | 30 | data[i] = buf 31 | } 32 | 33 | return data 34 | } 35 | 36 | // generateTestTransactions generates test transactions 37 | func generateTestTransactions(count int) []*std.Tx { 38 | data := make([]*std.Tx, count) 39 | 40 | for i := 0; i < count; i++ { 41 | data[i] = &std.Tx{ 42 | Memo: fmt.Sprintf("tx-%d", i), 43 | } 44 | } 45 | 46 | return data 47 | } 48 | 49 | func TestBatcher_BatchTransactions(t *testing.T) { 50 | t.Parallel() 51 | 52 | var ( 53 | numTxs = 100 54 | batchSize = 20 55 | txs = generateTestTransactions(numTxs) 56 | txHashes = generateRandomData(t, numTxs) 57 | 58 | broadcastTxs = make([][]byte, 0) 59 | currIndex = 0 60 | 61 | mockBatch = &mockBatch{ 62 | addTxBroadcastFn: func(tx []byte) error { 63 | broadcastTxs = append(broadcastTxs, tx) 64 | 65 | return nil 66 | }, 67 | executeFn: func() ([]interface{}, error) { 68 | res := make([]any, batchSize) 69 | 70 | for i := 0; i < batchSize; i++ { 71 | res[i] = &core_types.ResultBroadcastTx{ 72 | Hash: txHashes[currIndex], 73 | } 74 | 75 | currIndex++ 76 | } 77 | 78 | return res, nil 79 | }, 80 | } 81 | mockClient = &mockClient{ 82 | createBatchFn: func() common.Batch { 83 | return mockBatch 84 | }, 85 | } 86 | ) 87 | 88 | // Create the batcher 89 | b := NewBatcher(context.Background(), mockClient) 90 | 91 | // Batch the transactions 92 | res, err := b.BatchTransactions(txs, batchSize) 93 | if err != nil { 94 | t.Fatalf("unable to batch transactions, %v", err) 95 | } 96 | 97 | assert.NotNil(t, res) 98 | 99 | if len(res.TxHashes) != numTxs { 100 | t.Fatalf("invalid tx hashes returned, %d", len(res.TxHashes)) 101 | } 102 | 103 | for index, txHash := range txHashes { 104 | assert.True(t, bytes.Equal(txHash, txHashes[index])) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/gnolang/supernova/internal" 10 | "github.com/gnolang/supernova/internal/runtime" 11 | "github.com/peterbourgon/ff/v3/ffcli" 12 | ) 13 | 14 | func main() { 15 | var ( 16 | cfg = &internal.Config{} 17 | fs = flag.NewFlagSet("pipeline", flag.ExitOnError) 18 | ) 19 | 20 | // Register the flags 21 | registerFlags(fs, cfg) 22 | 23 | cmd := &ffcli.Command{ 24 | ShortUsage: "[flags] [...]", 25 | LongHelp: "Starts the stress testing suite against a Gno TM2 cluster", 26 | FlagSet: fs, 27 | Exec: func(_ context.Context, _ []string) error { 28 | return execMain(cfg) 29 | }, 30 | } 31 | 32 | if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { 33 | _, _ = fmt.Fprintf(os.Stderr, "%+v", err) 34 | 35 | os.Exit(1) 36 | } 37 | } 38 | 39 | // registerFlags registers the main configuration flags 40 | func registerFlags(fs *flag.FlagSet, c *internal.Config) { 41 | fs.StringVar( 42 | &c.URL, 43 | "url", 44 | "", 45 | "the JSON-RPC URL of the cluster", 46 | ) 47 | 48 | fs.StringVar( 49 | &c.ChainID, 50 | "chain-id", 51 | "dev", 52 | "the chain ID of the Gno blockchain", 53 | ) 54 | 55 | fs.StringVar( 56 | &c.Mnemonic, 57 | "mnemonic", 58 | "", 59 | "the mnemonic used to generate sub-accounts", 60 | ) 61 | 62 | fs.StringVar( 63 | &c.Mode, 64 | "mode", 65 | runtime.RealmDeployment.String(), 66 | fmt.Sprintf( 67 | "the mode for the stress test. Possible modes: [%s, %s, %s]", 68 | runtime.RealmDeployment.String(), runtime.PackageDeployment.String(), runtime.RealmCall.String(), 69 | ), 70 | ) 71 | 72 | fs.StringVar( 73 | &c.Output, 74 | "output", 75 | "", 76 | "the output path for the results JSON", 77 | ) 78 | 79 | fs.Uint64Var( 80 | &c.SubAccounts, 81 | "sub-accounts", 82 | 10, 83 | "the number of sub-accounts that will send out transactions", 84 | ) 85 | 86 | fs.Uint64Var( 87 | &c.Transactions, 88 | "transactions", 89 | 100, 90 | "the total number of transactions to be emitted", 91 | ) 92 | 93 | fs.Uint64Var( 94 | &c.BatchSize, 95 | "batch", 96 | 100, 97 | "the batch size of JSON-RPC transactions", 98 | ) 99 | } 100 | 101 | // execMain starts the stress test workflow (runs the pipeline) 102 | func execMain(cfg *internal.Config) error { 103 | // Validate the configuration 104 | if err := cfg.Validate(); err != nil { 105 | return fmt.Errorf("invalid configuration, %w", err) 106 | } 107 | 108 | // Create and run the pipeline 109 | pipeline, err := internal.NewPipeline(cfg) 110 | if err != nil { 111 | return fmt.Errorf("unable to create pipeline, %w", err) 112 | } 113 | 114 | return pipeline.Execute(context.Background()) 115 | } 116 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "dagger/run-supernova/internal/dagger" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | DEFAULT_CHAINID = "dev" 11 | DEFAULT_SUBACCOUNTS = 1 12 | DEFAULT_TRANSACTIONS = 10 13 | MNEMONIC = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" 14 | ) 15 | 16 | type supernovaMode string 17 | 18 | const ( 19 | PACKAGE_DEPLOYMENT supernovaMode = "PACKAGE_DEPLOYMENT" 20 | REALM_DEPLOYMENT supernovaMode = "REALM_DEPLOYMENT" 21 | REALM_CALL supernovaMode = "REALM_CALL" 22 | ) 23 | 24 | type Supernova struct{} 25 | 26 | // Builds Supernova image from code passed into a *dagger.Directory item 27 | func (s *Supernova) BuildImage(directory *dagger.Directory) *dagger.Container { 28 | baseBuilder := dag.Container(). 29 | From("golang:1.23-alpine"). 30 | WithDirectory("/src", directory). 31 | WithWorkdir("/src"). 32 | WithEnvVariable("CGO_ENABLED", "0"). 33 | WithExec([]string{"go", "build", "-o", "supernova", "./cmd"}). 34 | WithExec([]string{"apk", "add", "--no-cache", "ca-certificates"}) 35 | 36 | return dag.Container(). 37 | From("busybox"). 38 | WithFile("/bin/supernova", baseBuilder.File("/src/supernova")). 39 | WithFile("/etc/ssl/certs/ca-certificates.crt", baseBuilder.File("/etc/ssl/certs/ca-certificates.crt")). 40 | WithEntrypoint([]string{"/bin/supernova"}) 41 | } 42 | 43 | // Build image from code or use latest prebuild Docker image 44 | func (s *Supernova) buildOrPull(srcDir *dagger.Directory) *dagger.Container { 45 | if srcDir == nil { 46 | return dag.Container(). 47 | From("ghcr.io/gnolang/supernova:latest") 48 | } 49 | return s.BuildImage(srcDir) 50 | } 51 | 52 | // Runs a simple Supernova task generating transactions 53 | func (s *Supernova) RunStressTest( 54 | ctx context.Context, 55 | rpcEndpoint string, 56 | // +optional 57 | chainId string, 58 | // +optional 59 | subAccounts int, 60 | // +optional 61 | transactions int, 62 | // +default="REALM_DEPLOYMENT" 63 | mode string, 64 | // +optional 65 | srcDir *dagger.Directory, 66 | ) (int, error) { 67 | 68 | if chainId == "" { 69 | chainId = DEFAULT_CHAINID 70 | } 71 | if subAccounts == 0 { 72 | subAccounts = DEFAULT_SUBACCOUNTS 73 | } 74 | if transactions == 0 { 75 | transactions = DEFAULT_TRANSACTIONS 76 | } 77 | 78 | runningMode, err := toSupernovaMode(mode) 79 | if err != nil { 80 | return -1, err 81 | } 82 | 83 | return s.buildOrPull(srcDir). 84 | WithExec([]string{ 85 | "-sub-accounts", fmt.Sprintf("%d", subAccounts), 86 | "-transactions", fmt.Sprintf("%d", transactions), 87 | "-mode", string(runningMode), 88 | "-chain-id", chainId, 89 | "-url", rpcEndpoint, 90 | "-mnemonic", MNEMONIC}, 91 | dagger.ContainerWithExecOpts{ 92 | UseEntrypoint: true, 93 | }). 94 | ExitCode(ctx) 95 | } 96 | 97 | func toSupernovaMode(s string) (supernovaMode, error) { 98 | switch s { 99 | case string(PACKAGE_DEPLOYMENT): 100 | return PACKAGE_DEPLOYMENT, nil 101 | case string(REALM_DEPLOYMENT): 102 | return REALM_DEPLOYMENT, nil 103 | case string(REALM_CALL): 104 | return REALM_CALL, nil 105 | default: 106 | return "", fmt.Errorf("invalid supernova oode: %s", s) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /internal/collector/collector_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "testing" 7 | "time" 8 | 9 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 10 | "github.com/gnolang/gno/tm2/pkg/bft/types" 11 | "github.com/gnolang/gno/tm2/pkg/crypto/tmhash" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | // generateRandomData generates random 32B chunks 16 | func generateRandomData(t *testing.T, count int) [][]byte { 17 | t.Helper() 18 | 19 | data := make([][]byte, count) 20 | 21 | for i := 0; i < count; i++ { 22 | buf := make([]byte, 32) 23 | 24 | _, err := rand.Read(buf) 25 | if err != nil { 26 | t.Fatalf("unable to generate random data, %v", err) 27 | } 28 | 29 | data[i] = buf 30 | } 31 | 32 | return data 33 | } 34 | 35 | func TestCollector_GetRunResults(t *testing.T) { 36 | t.Parallel() 37 | 38 | numTxs := 100 39 | startTime := time.Now() 40 | blockTimes := make([]time.Time, numTxs) 41 | 42 | for i := 0; i < numTxs; i++ { 43 | if i == 0 { 44 | blockTimes[i] = startTime 45 | } 46 | 47 | blockTimes[i] = startTime.Add(time.Duration(i) * time.Second) 48 | } 49 | 50 | txs := generateRandomData(t, numTxs) 51 | txHashes := make([][]byte, numTxs) 52 | 53 | for i := 0; i < numTxs; i++ { 54 | txHashes[i] = tmhash.Sum(txs[i]) 55 | } 56 | 57 | var ( 58 | gasLimit = int64(1000) 59 | gasUsed = int64(100) 60 | 61 | mockClient = &mockClient{ 62 | getBlockFn: func(ctx context.Context, height *int64) (*core_types.ResultBlock, error) { 63 | if *height > int64(numTxs) { 64 | t.Fatalf("invalid height requested") 65 | } 66 | 67 | return &core_types.ResultBlock{ 68 | BlockMeta: &types.BlockMeta{ 69 | Header: types.Header{ 70 | Height: *height, 71 | Time: blockTimes[*height-1], 72 | NumTxs: 1, 73 | }, 74 | }, 75 | Block: &types.Block{ 76 | Data: types.Data{ 77 | Txs: []types.Tx{ 78 | txs[*height-1], 79 | }, 80 | }, 81 | }, 82 | }, nil 83 | }, 84 | getLatestBlockHeightFn: func(ctx context.Context) (int64, error) { 85 | return int64(numTxs), nil 86 | }, 87 | getBlockGasLimitFn: func(ctx context.Context, height int64) (int64, error) { 88 | if height > int64(numTxs) { 89 | t.Fatalf("invalid height requested") 90 | } 91 | 92 | return gasLimit, nil 93 | }, 94 | getBlockGasUsedFn: func(ctx context.Context, height int64) (int64, error) { 95 | if height > int64(numTxs) { 96 | t.Fatalf("invalid height requested") 97 | } 98 | 99 | return gasUsed, nil 100 | }, 101 | } 102 | ) 103 | 104 | // Create the collector 105 | c := NewCollector(context.Background(), mockClient) 106 | c.requestTimeout = time.Second * 0 107 | 108 | // Collect the results 109 | result, err := c.GetRunResult(txHashes, 1, startTime) 110 | if err != nil { 111 | t.Fatalf("unable to get run results, %v", err) 112 | } 113 | 114 | if result == nil { 115 | t.Fatal("result should not be nil") 116 | } 117 | 118 | assert.NotZero(t, result.AverageTPS) 119 | assert.Len(t, result.Blocks, numTxs) 120 | 121 | for index, block := range result.Blocks { 122 | assert.Equal(t, int64(index+1), block.Number) 123 | assert.Equal(t, gasUsed, block.GasUsed) 124 | assert.Equal(t, gasLimit, block.GasLimit) 125 | assert.Equal(t, int64(1), block.Transactions) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | `supernova` is a command-line interface (CLI) tool for stress-testing Gno Tendermint 2 networks. It is used to monitor 4 | and report on node performance by executing transactions and measuring response-time. 5 | 6 | ## Key Features 7 | 8 | - 🚀 Batch transactions to make stress testing easier to orchestrate 9 | - 🛠 Multiple stress testing modes: REALM_DEPLOYMENT, PACKAGE_DEPLOYMENT, and REALM_CALL 10 | - 💰 Distributed transaction stress testing through subaccounts 11 | - 💸 Automatic subaccount fund top-up 12 | - 📊 Detailed statistics calculation 13 | - 📈 Output cycle run results to a file 14 | 15 | ## Results 16 | 17 | To view the results of the stress tests, visit the [benchmarks reports for supernova](https://github.com/gnolang/benchmarks/tree/main/reports/supernova). 18 | 19 | ## Usage Example 20 | 21 | To run a stress test with `supernova`, you will need to have `go 1.19` or greater. 22 | 23 | 1. Build out the binary 24 | 25 | To build out the binary, run the following command: 26 | 27 | ```bash 28 | make build 29 | ``` 30 | 31 | 2. Run the stress test by specifying options 32 | 33 | ```bash 34 | ./build/supernova -sub-accounts 5 -transactions 100 -url http://localhost:26657 -mnemonic "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" -output result.json 35 | ``` 36 | 37 | This will run a stress test against a Gno TM2 node running at `http://localhost:26657`. The test will use `5` 38 | sub-accounts, and send out `100` transactions. The sub-accounts are derived from the specified mnemonic. Finally, 39 | results are saved 40 | to a file `result.json`. 41 | 42 | For any stress test run, there need to be funds on a specific address. 43 | The address that is in charge of funds distribution to subaccounts is the **first address** with index 0 in the 44 | specified mnemonic. Make sure this address has an appropriate amount of funds before running the stress test. 45 | 46 | ![Banner](.github/demo.gif) 47 | 48 | `supernova` supports the following options: 49 | 50 | ```bash 51 | USAGE 52 | [flags] [...] 53 | 54 | Starts the stress testing suite against a Gno TM2 cluster 55 | 56 | FLAGS 57 | -batch 100 the batch size of JSON-RPC transactions 58 | -chain-id dev the chain ID of the Gno blockchain 59 | -mnemonic string the mnemonic used to generate sub-accounts 60 | -mode REALM_DEPLOYMENT the mode for the stress test. Possible modes: [REALM_DEPLOYMENT, PACKAGE_DEPLOYMENT, REALM_CALL] 61 | -output string the output path for the results JSON 62 | -sub-accounts 10 the number of sub-accounts that will send out transactions 63 | -transactions 100 the total number of transactions to be emitted 64 | -url string the JSON-RPC URL of the cluster 65 | ``` 66 | 67 | ## Modes 68 | 69 | ### REALM_DEPLOYMENT 70 | 71 | The `REALM_DEPLOYMENT` mode is pretty straightforward - it is a simple `Realm` deployment mode from accounts. 72 | This mode sends out transactions that are deploy transactions for a realm holding state. 73 | 74 | ### PACKAGE_DEPLOYMENT 75 | 76 | The `PACKAGE_DEPLOYMENT` is similar to `REALM_DEPLOYMENT`. This mode also sends out transactions, but these transactions 77 | deploy a package. 78 | 79 | ### REALM_CALL 80 | 81 | The `REALM_CALL` mode deploys a `Realm` to the Gno blockchain network being tested before starting the cycle run. 82 | When the cycle run begins, the transactions that are sent out are method calls. 83 | -------------------------------------------------------------------------------- /internal/runtime/realm_call.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 9 | "github.com/gnolang/gno/tm2/pkg/crypto" 10 | "github.com/gnolang/gno/tm2/pkg/std" 11 | "github.com/gnolang/supernova/internal/common" 12 | ) 13 | 14 | const methodName = "SayHello" 15 | 16 | type realmCall struct { 17 | realmPath string 18 | ctx context.Context 19 | } 20 | 21 | func newRealmCall(ctx context.Context) *realmCall { 22 | return &realmCall{ 23 | ctx: ctx, 24 | } 25 | } 26 | 27 | func (r *realmCall) Initialize( 28 | account std.Account, 29 | signFn SignFn, 30 | estimateFn EstimateGasFn, 31 | currentMaxGas int64, 32 | gasPrice std.GasPrice, 33 | ) ([]*std.Tx, error) { 34 | // The Realm needs to be deployed before 35 | // it can be interacted with 36 | r.realmPath = fmt.Sprintf( 37 | "%s/%s/stress_%d", 38 | realmPathPrefix, 39 | account.GetAddress().String(), 40 | time.Now().Unix(), 41 | ) 42 | 43 | // Construct the transaction 44 | msg := vm.MsgAddPackage{ 45 | Creator: account.GetAddress(), 46 | Package: &std.MemPackage{ 47 | Name: packageName, 48 | Path: r.realmPath, 49 | Files: []*std.MemFile{ 50 | { 51 | Name: gnomodFileName, 52 | Body: gnomodBody, 53 | }, 54 | { 55 | Name: realmFileName, 56 | Body: realmBody, 57 | }, 58 | }, 59 | }, 60 | } 61 | 62 | tx := &std.Tx{ 63 | Msgs: []std.Msg{msg}, 64 | // passing in the maximum block gas, this is just a simulation 65 | Fee: common.CalculateFeeInRatio(currentMaxGas, gasPrice), 66 | } 67 | 68 | err := signFn(tx) 69 | if err != nil { 70 | return nil, fmt.Errorf("unable to sign initialize transaction, %w", err) 71 | } 72 | 73 | // Estimate the gas for the initial tx 74 | gasWanted, err := estimateFn(r.ctx, tx) 75 | if err != nil { 76 | return nil, fmt.Errorf("unable to estimate gas: %w", err) 77 | } 78 | 79 | // Wipe the signatures, because we will change the fee, 80 | // and cause the previous ones to be invalid 81 | tx.Signatures = make([]std.Signature, 0) 82 | tx.Fee = common.CalculateFeeInRatio(gasWanted+gasBuffer, gasPrice) // buffer with 10k gas 83 | 84 | err = signFn(tx) 85 | if err != nil { 86 | return nil, fmt.Errorf("unable to sign initialize transaction, %w", err) 87 | } 88 | 89 | return []*std.Tx{tx}, nil 90 | } 91 | 92 | func (r *realmCall) CalculateRuntimeCosts( 93 | account std.Account, 94 | estimateFn EstimateGasFn, 95 | signFn SignFn, 96 | currentMaxGas int64, 97 | gasPrice std.GasPrice, 98 | transactions uint64, 99 | ) (std.Coin, error) { 100 | return calculateRuntimeCosts( 101 | r.ctx, 102 | account, 103 | transactions, 104 | currentMaxGas, 105 | gasPrice, 106 | r.getMsgFn, 107 | signFn, 108 | estimateFn, 109 | ) 110 | } 111 | 112 | func (r *realmCall) ConstructTransactions( 113 | keys []crypto.PrivKey, 114 | accounts []std.Account, 115 | transactions uint64, 116 | maxGas int64, 117 | gasPrice std.GasPrice, 118 | chainID string, 119 | estimateFn EstimateGasFn, 120 | ) ([]*std.Tx, error) { 121 | return constructTransactions( 122 | r.ctx, 123 | keys, 124 | accounts, 125 | transactions, 126 | maxGas, 127 | gasPrice, 128 | chainID, 129 | r.getMsgFn, 130 | estimateFn, 131 | ) 132 | } 133 | 134 | func (r *realmCall) getMsgFn(creator std.Account, index int) std.Msg { 135 | return vm.MsgCall{ 136 | Caller: creator.GetAddress(), 137 | PkgPath: r.realmPath, 138 | Func: methodName, 139 | Args: []string{fmt.Sprintf("Account-%d", index)}, 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /.github/goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | project_name: supernova 3 | version: 2 4 | 5 | before: 6 | hooks: 7 | - go mod tidy 8 | 9 | builds: 10 | - main: ./cmd 11 | binary: supernova 12 | env: 13 | - CGO_ENABLED=0 14 | goos: 15 | - linux 16 | - darwin 17 | goarch: 18 | - amd64 19 | - arm64 20 | 21 | gomod: 22 | proxy: true 23 | 24 | archives: 25 | # https://goreleaser.com/customization/archive/ 26 | - files: 27 | # Standard Release Files 28 | - LICENSE 29 | - README.md 30 | 31 | signs: 32 | - cmd: cosign 33 | env: 34 | - COSIGN_EXPERIMENTAL=1 35 | certificate: '${artifact}.pem' 36 | args: 37 | - sign-blob 38 | - '--output-certificate=${certificate}' 39 | - '--output-signature=${signature}' 40 | - '${artifact}' 41 | - "--yes" # needed on cosign 2.0.0+ 42 | artifacts: checksum 43 | output: true 44 | 45 | dockers: 46 | # https://goreleaser.com/customization/docker/ 47 | - use: buildx 48 | dockerfile: Dockerfile.release 49 | goos: linux 50 | goarch: amd64 51 | image_templates: 52 | - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64" 53 | - "ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64" 54 | build_flag_templates: 55 | - "--platform=linux/amd64" 56 | - "--label=org.opencontainers.image.created={{.Date}}" 57 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 58 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 59 | - "--label=org.opencontainers.image.version={{.Version}}" 60 | - use: buildx 61 | dockerfile: Dockerfile.release 62 | goos: linux 63 | goarch: arm64 64 | image_templates: 65 | - "ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8" 66 | - "ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8" 67 | build_flag_templates: 68 | - "--platform=linux/arm64/v8" 69 | - "--label=org.opencontainers.image.created={{.Date}}" 70 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 71 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 72 | - "--label=org.opencontainers.image.version={{.Version}}" 73 | 74 | docker_manifests: 75 | # https://goreleaser.com/customization/docker_manifest/ 76 | - name_template: ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }} 77 | image_templates: 78 | - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-amd64 79 | - ghcr.io/gnolang/{{ .ProjectName }}:{{ .Version }}-arm64v8 80 | - name_template: ghcr.io/gnolang/{{ .ProjectName }}:latest 81 | image_templates: 82 | - ghcr.io/gnolang/{{ .ProjectName }}:latest-amd64 83 | - ghcr.io/gnolang/{{ .ProjectName }}:latest-arm64v8 84 | 85 | docker_signs: 86 | - cmd: cosign 87 | env: 88 | - COSIGN_EXPERIMENTAL=1 89 | artifacts: images 90 | output: true 91 | args: 92 | - 'sign' 93 | - '${artifact}' 94 | - "--yes" # needed on cosign 2.0.0+ 95 | 96 | checksum: 97 | name_template: 'checksums.txt' 98 | 99 | changelog: 100 | sort: asc 101 | 102 | source: 103 | enabled: true 104 | 105 | sboms: 106 | - artifacts: archive 107 | - id: source # Two different sbom configurations need two different IDs 108 | artifacts: source 109 | 110 | release: 111 | draft: true 112 | replace_existing_draft: true 113 | prerelease: auto 114 | footer: | 115 | ### Container Images 116 | 117 | https://ghcr.io/gnolang/{{ .ProjectName }}:{{ .Tag }} 118 | 119 | For example: 120 | ``` 121 | docker pull ghcr.io/gnolang/{{ .ProjectName }}:{{ .Tag }} 122 | ``` 123 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gnolang/supernova 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/gnolang/gno v0.0.0-20250926084639-6974fdb8ae0e 9 | github.com/peterbourgon/ff/v3 v3.4.0 10 | github.com/schollz/progressbar/v3 v3.18.0 11 | github.com/stretchr/testify v1.11.1 12 | ) 13 | 14 | require ( 15 | dario.cat/mergo v1.0.1 // indirect 16 | github.com/DataDog/zstd v1.4.5 // indirect 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect 19 | github.com/btcsuite/btcd/btcutil v1.1.6 // indirect 20 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/cockroachdb/apd/v3 v3.2.1 // indirect 23 | github.com/cockroachdb/errors v1.11.3 // indirect 24 | github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect 25 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect 26 | github.com/cockroachdb/pebble v1.1.5 // indirect 27 | github.com/cockroachdb/redact v1.1.5 // indirect 28 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect 29 | github.com/davecgh/go-spew v1.1.1 // indirect 30 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect 31 | github.com/fsnotify/fsnotify v1.5.4 // indirect 32 | github.com/getsentry/sentry-go v0.27.0 // indirect 33 | github.com/go-logr/logr v1.4.2 // indirect 34 | github.com/go-logr/stdr v1.2.2 // indirect 35 | github.com/gofrs/flock v0.12.1 // indirect 36 | github.com/gogo/protobuf v1.3.2 // indirect 37 | github.com/golang/protobuf v1.5.4 // indirect 38 | github.com/golang/snappy v0.0.4 // indirect 39 | github.com/google/uuid v1.6.0 // indirect 40 | github.com/gorilla/websocket v1.5.3 // indirect 41 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 42 | github.com/klauspost/compress v1.16.0 // indirect 43 | github.com/kr/pretty v0.3.1 // indirect 44 | github.com/kr/text v0.2.0 // indirect 45 | github.com/lib/pq v1.10.9 // indirect 46 | github.com/libp2p/go-buffer-pool v0.1.0 // indirect 47 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 48 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 49 | github.com/onsi/gomega v1.31.1 // indirect 50 | github.com/pelletier/go-toml v1.9.5 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/pmezard/go-difflib v1.0.0 // indirect 53 | github.com/prometheus/client_golang v1.15.0 // indirect 54 | github.com/prometheus/client_model v0.3.0 // indirect 55 | github.com/prometheus/common v0.42.0 // indirect 56 | github.com/prometheus/procfs v0.9.0 // indirect 57 | github.com/rivo/uniseg v0.4.7 // indirect 58 | github.com/rogpeppe/go-internal v1.13.1 // indirect 59 | github.com/rs/cors v1.11.1 // indirect 60 | github.com/rs/xid v1.6.0 // indirect 61 | github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect 62 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect 63 | github.com/valyala/bytebufferpool v1.0.0 // indirect 64 | go.etcd.io/bbolt v1.3.11 // indirect 65 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 66 | go.opentelemetry.io/otel v1.34.0 // indirect 67 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect 68 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect 69 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 70 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 71 | go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect 72 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 73 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 74 | go.uber.org/multierr v1.11.0 // indirect 75 | golang.org/x/crypto v0.40.0 // indirect 76 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect 77 | golang.org/x/mod v0.26.0 // indirect 78 | golang.org/x/net v0.42.0 // indirect 79 | golang.org/x/sync v0.16.0 // indirect 80 | golang.org/x/sys v0.34.0 // indirect 81 | golang.org/x/term v0.33.0 // indirect 82 | golang.org/x/text v0.28.0 // indirect 83 | golang.org/x/tools v0.35.0 // indirect 84 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect 85 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect 86 | google.golang.org/grpc v1.69.4 // indirect 87 | google.golang.org/protobuf v1.36.3 // indirect 88 | gopkg.in/yaml.v3 v3.0.1 // indirect 89 | ) 90 | -------------------------------------------------------------------------------- /internal/collector/collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/gnolang/gno/tm2/pkg/bft/types" 10 | "github.com/schollz/progressbar/v3" 11 | ) 12 | 13 | var errTimeout = errors.New("collector timed out") 14 | 15 | // Collector is the transaction / block stat 16 | // collector. 17 | // This implementation will heavily change when 18 | // transaction indexing is introduced 19 | type Collector struct { 20 | cli Client 21 | ctx context.Context 22 | 23 | requestTimeout time.Duration 24 | } 25 | 26 | // NewCollector creates a new instance of the collector 27 | func NewCollector(ctx context.Context, cli Client) *Collector { 28 | return &Collector{ 29 | cli: cli, 30 | requestTimeout: time.Second * 2, 31 | ctx: ctx, 32 | } 33 | } 34 | 35 | // GetRunResult generates the run result for the passed in transaction hashes and start range 36 | func (c *Collector) GetRunResult( 37 | txHashes [][]byte, 38 | startBlock int64, 39 | startTime time.Time, 40 | ) (*RunResult, error) { 41 | var ( 42 | blockResults = make([]*BlockResult, 0) 43 | timeout = time.After(5 * time.Minute) 44 | start = startBlock 45 | txMap = newTxLookup(txHashes) 46 | processed = 0 47 | ) 48 | 49 | fmt.Printf("\n📊 Collecting Results 📊\n\n") 50 | 51 | bar := progressbar.Default(int64(len(txHashes)), "txs collected") 52 | 53 | for { 54 | // Check if all original transactions 55 | // were processed 56 | //nolint:staticcheck 57 | if processed >= len(txHashes) { 58 | break 59 | } 60 | 61 | select { 62 | case <-timeout: 63 | return nil, errTimeout 64 | case <-time.After(c.requestTimeout): 65 | latest, err := c.cli.GetLatestBlockHeight(c.ctx) 66 | if err != nil { 67 | return nil, fmt.Errorf("unable to fetch latest block height, %w", err) 68 | } 69 | 70 | if latest < start { 71 | // No need to parse older blocks 72 | continue 73 | } 74 | 75 | // Iterate over each block and find relevant transactions 76 | for blockNum := start; blockNum <= latest; blockNum++ { 77 | // Fetch the block 78 | block, err := c.cli.GetBlock(c.ctx, &blockNum) 79 | if err != nil { 80 | return nil, fmt.Errorf("unable to fetch block, %w", err) 81 | } 82 | 83 | // Check if any of the block transactions are the ones 84 | // sent out in the stress test 85 | belong := txMap.anyBelong(block.Block.Txs) 86 | if belong == 0 { 87 | continue 88 | } 89 | 90 | processed += belong 91 | _ = bar.Add(belong) //nolint:errcheck // No need to check 92 | 93 | // Fetch the total gas used by transactions 94 | blockGasUsed, err := c.cli.GetBlockGasUsed(c.ctx, blockNum) 95 | if err != nil { 96 | return nil, fmt.Errorf("unable to fetch block gas used, %w", err) 97 | } 98 | 99 | // Fetch the block gas limit 100 | blockGasLimit, err := c.cli.GetBlockGasLimit(c.ctx, blockNum) 101 | if err != nil { 102 | return nil, fmt.Errorf("unable to fetch block gas limit, %w", err) 103 | } 104 | 105 | blockResults = append(blockResults, &BlockResult{ 106 | Number: blockNum, 107 | Time: block.BlockMeta.Header.Time, 108 | Transactions: block.BlockMeta.Header.NumTxs, 109 | GasUsed: blockGasUsed, 110 | GasLimit: blockGasLimit, 111 | }) 112 | } 113 | 114 | // Update the iteration range 115 | start = latest + 1 116 | } 117 | } 118 | 119 | return &RunResult{ 120 | AverageTPS: calculateTPS( 121 | startTime, 122 | len(txHashes), 123 | ), 124 | Blocks: blockResults, 125 | }, nil 126 | } 127 | 128 | // txLookup is a simple lookup map for transaction hashes 129 | type txLookup struct { 130 | lookup map[string]struct{} 131 | } 132 | 133 | // newTxLookup creates a new instance of the tx lookup map 134 | func newTxLookup(txs [][]byte) *txLookup { 135 | lookup := make(map[string]struct{}) 136 | 137 | for _, tx := range txs { 138 | lookup[string(tx)] = struct{}{} 139 | } 140 | 141 | return &txLookup{ 142 | lookup: lookup, 143 | } 144 | } 145 | 146 | // anyBelong returns the number of transactions 147 | // that have been found in the lookup map 148 | func (t *txLookup) anyBelong(txs types.Txs) int { 149 | belong := 0 150 | 151 | for _, tx := range txs { 152 | txHash := tx.Hash() 153 | 154 | if _, ok := t.lookup[string(txHash)]; ok { 155 | belong++ 156 | } 157 | } 158 | 159 | return belong 160 | } 161 | 162 | // calculateTPS calculates the TPS for the sequence 163 | func calculateTPS(startTime time.Time, totalTx int) float64 { 164 | diff := time.Since(startTime).Seconds() 165 | 166 | return float64(totalTx) / diff 167 | } 168 | -------------------------------------------------------------------------------- /.github/golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | concurrency: 8 3 | timeout: 10m 4 | issue-exit-code: 1 5 | tests: true 6 | modules-download-mode: readonly 7 | allow-parallel-runners: false 8 | go: "" 9 | 10 | output: 11 | path-prefix: "" 12 | sort-results: true 13 | 14 | issues: 15 | max-issues-per-linter: 0 16 | max-same-issues: 0 17 | new: false 18 | fix: false 19 | exclude-rules: 20 | - path: (.+)_test.go 21 | linters: 22 | - nilnil 23 | - gosec 24 | exclude-dirs-use-default: true 25 | uniq-by-line: false 26 | 27 | linters: 28 | fast: false 29 | disable-all: true 30 | enable: 31 | - asasalint # Check for pass []any as any in variadic func(...any) 32 | - asciicheck # Detects funky ASCII characters 33 | - bidichk # Checks for dangerous unicode character sequences 34 | - durationcheck # Check for two durations multiplied together 35 | - errcheck # Forces to not skip error check 36 | - copyloopvar # Checks for pointers to enclosing loop variables 37 | - gocritic # Bundles different linting checks 38 | - godot # Checks for periods at the end of comments 39 | - gomoddirectives # Allow or ban replace directives in go.mod 40 | - gosimple # Code simplification 41 | - govet # Official Go tool 42 | - ineffassign # Detects when assignments to existing variables are not used 43 | - nakedret # Finds naked/bare returns and requires change them 44 | - nilerr # Requires explicit returns 45 | - nilnil # Requires explicit returns 46 | - promlinter # Lints Prometheus metrics names 47 | - reassign # Checks that package variables are not reassigned 48 | - revive # Drop-in replacement for golint 49 | - tenv # Detects using os.Setenv instead of t.Setenv 50 | - testableexamples # Checks if examples are testable (have expected output) 51 | - unparam # Finds unused params 52 | - usestdlibvars # Detects the possibility to use variables/constants from stdlib 53 | - wastedassign # Finds wasted assignment statements 54 | - loggercheck # Checks the odd number of key and value pairs for common logger libraries 55 | - nestif # Finds deeply nested if statements 56 | - nonamedreturns # Reports all named returns 57 | - decorder # Check declaration order of types, consts, vars and funcs 58 | - gocheckcompilerdirectives # Checks that compiler directive comments (//go:) are valid 59 | - gochecknoinits # Checks for init methods 60 | - whitespace # Tool for detection of leading and trailing whitespace 61 | - wsl # Forces you to use empty lines 62 | - unconvert # Unnecessary type conversions 63 | - tparallel # Detects inappropriate usage of t.Parallel() method in your Go test codes 64 | - thelper # Detects golang test helpers without t.Helper() call and checks the consistency of test helpers 65 | - stylecheck # Stylecheck is a replacement for golint 66 | - prealloc # Finds slice declarations that could potentially be pre-allocated 67 | - predeclared # Finds code that shadows one of Go's predeclared identifiers 68 | - nolintlint # Ill-formed or insufficient nolint directives 69 | - nlreturn # Checks for a new line before return and branch statements to increase code clarity 70 | - misspell # Misspelled English words in comments 71 | - makezero # Finds slice declarations with non-zero initial length 72 | - lll # Long lines 73 | - importas # Enforces consistent import aliases 74 | - gosec # Security problems 75 | - gofmt # Whether the code was gofmt-ed 76 | - gofumpt # Stricter gofmt 77 | - goimports # Unused imports 78 | - goconst # Repeated strings that could be replaced by a constant 79 | - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) 80 | - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error 81 | - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 82 | - unused # Checks Go code for unused constants, variables, functions and types 83 | 84 | linters-settings: 85 | gocritic: 86 | enabled-tags: 87 | - diagnostic 88 | - experimental 89 | - opinionated 90 | - performance 91 | - style 92 | disabled-checks: 93 | - hugeParam 94 | - rangeExprCopy 95 | - rangeValCopy 96 | - importShadow 97 | - unnamedResult 98 | errcheck: 99 | check-type-assertions: false 100 | check-blank: true 101 | exclude-functions: 102 | - io/ioutil.ReadFile 103 | - io.Copy(*bytes.Buffer) 104 | - io.Copy(os.Stdout) 105 | nakedret: 106 | max-func-lines: 1 107 | govet: 108 | enable-all: true 109 | gofmt: 110 | simplify: true 111 | goconst: 112 | min-len: 3 113 | min-occurrences: 3 114 | godot: 115 | scope: all 116 | period: false 117 | gosec: 118 | excludes: 119 | - G115 # We tolerate casting shenanigans -------------------------------------------------------------------------------- /internal/runtime/helper.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gnolang/gno/tm2/pkg/crypto" 8 | "github.com/gnolang/gno/tm2/pkg/std" 9 | "github.com/gnolang/supernova/internal/common" 10 | "github.com/gnolang/supernova/internal/signer" 11 | "github.com/schollz/progressbar/v3" 12 | ) 13 | 14 | const gasBuffer = 10_000 // 10k gas 15 | 16 | // msgFn defines the transaction message constructor 17 | type msgFn func(creator std.Account, index int) std.Msg 18 | 19 | // constructTransactions constructs and signs the transactions 20 | // using the passed in message generator and signer 21 | func constructTransactions( 22 | ctx context.Context, 23 | keys []crypto.PrivKey, 24 | accounts []std.Account, 25 | transactions uint64, 26 | maxGas int64, 27 | gasPrice std.GasPrice, 28 | chainID string, 29 | getMsg msgFn, 30 | estimateFn EstimateGasFn, 31 | ) ([]*std.Tx, error) { 32 | var ( 33 | txs = make([]*std.Tx, transactions) 34 | 35 | // A local nonce map is updated to avoid unnecessary calls 36 | // for fetching the fresh info from the chain every time 37 | // an account is used 38 | nonceMap = make(map[uint64]uint64) // accountNumber -> nonce 39 | ) 40 | 41 | fmt.Printf("\n⏳ Estimating Gas ⏳\n") 42 | 43 | // Estimate the fee for the transaction batch 44 | // passing in the maximum block gas, this is just a simulation 45 | txFee := common.CalculateFeeInRatio( 46 | maxGas, 47 | gasPrice, 48 | ) 49 | 50 | // Construct the first tx 51 | var ( 52 | creator = accounts[0] 53 | creatorKey = keys[0] 54 | ) 55 | 56 | tx := &std.Tx{ 57 | Msgs: []std.Msg{getMsg(creator, 0)}, 58 | Fee: txFee, 59 | } 60 | 61 | // Sign the transaction 62 | cfg := signer.SignCfg{ 63 | ChainID: chainID, 64 | AccountNumber: creator.GetAccountNumber(), 65 | Sequence: creator.GetSequence(), 66 | } 67 | 68 | if err := signer.SignTx(tx, creatorKey, cfg); err != nil { 69 | return nil, fmt.Errorf("unable to sign transaction, %w", err) 70 | } 71 | 72 | gasWanted, err := estimateFn(ctx, tx) 73 | if err != nil { 74 | return nil, fmt.Errorf("unable to estimate gas, %w", err) 75 | } 76 | 77 | // Clear the old signatures, because they need 78 | // to be regenerated 79 | clear(tx.Signatures) 80 | 81 | // Use the estimated gas limit 82 | txFee = common.CalculateFeeInRatio(gasWanted+gasBuffer, gasPrice) // 10k gas buffer 83 | 84 | if err = signer.SignTx(tx, creatorKey, cfg); err != nil { 85 | return nil, fmt.Errorf("unable to sign transaction, %w", err) 86 | } 87 | 88 | fmt.Printf("\nEstimated Gas for 1 run tx: %d \n", tx.Fee.GasWanted) 89 | fmt.Printf("\n🔨 Constructing Transactions 🔨\n\n") 90 | 91 | bar := progressbar.Default(int64(transactions), "constructing txs") 92 | 93 | for i := 0; i < int(transactions); i++ { 94 | // Generate the transaction 95 | var ( 96 | creator = accounts[i%len(accounts)] 97 | creatorKey = keys[i%len(accounts)] 98 | accountNumber = creator.GetAccountNumber() 99 | ) 100 | 101 | tx := &std.Tx{ 102 | Msgs: []std.Msg{getMsg(creator, i)}, 103 | Fee: txFee, 104 | } 105 | 106 | // Fetch the next account nonce 107 | nonce, found := nonceMap[creator.GetAccountNumber()] 108 | if !found { 109 | nonce = creator.GetSequence() 110 | nonceMap[creator.GetAccountNumber()] = nonce 111 | } 112 | 113 | // Sign the transaction 114 | cfg := signer.SignCfg{ 115 | ChainID: chainID, 116 | AccountNumber: accountNumber, 117 | Sequence: nonce, 118 | } 119 | 120 | if err := signer.SignTx(tx, creatorKey, cfg); err != nil { 121 | return nil, fmt.Errorf("unable to sign transaction, %w", err) 122 | } 123 | 124 | // Increase the creator nonce locally 125 | nonceMap[accountNumber] = nonce + 1 126 | 127 | // Mark the transaction as ready 128 | txs[i] = tx 129 | _ = bar.Add(1) //nolint:errcheck // No need to check 130 | } 131 | 132 | fmt.Printf("✅ Successfully constructed %d transactions\n", transactions) 133 | 134 | return txs, nil 135 | } 136 | 137 | func calculateRuntimeCosts( 138 | ctx context.Context, 139 | account std.Account, 140 | transactions uint64, 141 | maxBlockMaxGas int64, 142 | gasPrice std.GasPrice, 143 | getMsg msgFn, 144 | signFn SignFn, 145 | estimateFn EstimateGasFn, 146 | ) (std.Coin, error) { 147 | fmt.Printf("\n⏳ Estimating Gas ⏳\n") 148 | 149 | // Estimate the fee for the transaction batch 150 | // passing in the maximum block gas, this is just a simulation 151 | txFee := common.CalculateFeeInRatio( 152 | maxBlockMaxGas, 153 | gasPrice, 154 | ) 155 | 156 | tx := &std.Tx{ 157 | Msgs: []std.Msg{getMsg(account, 0)}, 158 | Fee: txFee, 159 | } 160 | 161 | err := signFn(tx) 162 | if err != nil { 163 | return std.Coin{}, fmt.Errorf("unable to sign transaction, %w", err) 164 | } 165 | 166 | estimatedGas, err := estimateFn(ctx, tx) 167 | if err != nil { 168 | return std.Coin{}, fmt.Errorf("unable to estimate gas, %w", err) 169 | } 170 | 171 | return std.Coin{ 172 | Denom: common.Denomination, 173 | Amount: int64(transactions) * estimatedGas, 174 | }, nil 175 | } 176 | 177 | func SignTransactionsCb(chainID string, account std.Account, key crypto.PrivKey) SignFn { 178 | // Sign the transaction 179 | cfg := signer.SignCfg{ 180 | ChainID: chainID, 181 | AccountNumber: account.GetAccountNumber(), 182 | Sequence: account.GetSequence(), 183 | } 184 | 185 | return func(tx *std.Tx) error { 186 | return signer.SignTx(tx, key, cfg) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /internal/runtime/runtime_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 8 | "github.com/gnolang/gno/tm2/pkg/std" 9 | "github.com/gnolang/supernova/internal/common" 10 | testutils "github.com/gnolang/supernova/internal/testing" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | // verifyDeployTxCommon does common transaction verification 15 | func verifyDeployTxCommon(t *testing.T, tx *std.Tx, expectedPrefix string) { 16 | t.Helper() 17 | 18 | if len(tx.Msgs) != 1 { 19 | t.Fatalf("invalid number of tx messages, %d", len(tx.Msgs)) 20 | } 21 | 22 | msg := tx.Msgs[0] 23 | 24 | vmMsg, ok := msg.(vm.MsgAddPackage) 25 | if !ok { 26 | t.Fatal("invalid tx message type") 27 | } 28 | 29 | // Make sure the deploy params are valid 30 | assert.Contains(t, vmMsg.Package.Path, expectedPrefix) 31 | assert.Len(t, vmMsg.Package.Files, 2) 32 | assert.NotNil(t, vmMsg.Creator) 33 | assert.Nil(t, vmMsg.Send) 34 | 35 | // Make sure the fee is valid 36 | assert.Equal( 37 | t, 38 | common.CalculateFeeInRatio(1_000_000+gasBuffer, common.DefaultGasPrice), 39 | tx.Fee, 40 | ) 41 | } 42 | 43 | func TestRuntime_CommonDeployment(t *testing.T) { 44 | t.Parallel() 45 | 46 | testTable := []struct { 47 | name string 48 | mode Type 49 | expectedPrefix string 50 | }{ 51 | { 52 | "Realm Deployment", 53 | RealmDeployment, 54 | realmPathPrefix, 55 | }, 56 | { 57 | "Package Deployment", 58 | PackageDeployment, 59 | packagePathPrefix, 60 | }, 61 | } 62 | 63 | for _, testCase := range testTable { 64 | t.Run(testCase.name, func(t *testing.T) { 65 | t.Parallel() 66 | 67 | var ( 68 | transactions = uint64(100) 69 | accounts = generateAccounts(10) 70 | accountKeys = testutils.GenerateAccounts(t, 10) 71 | ) 72 | 73 | // Get the runtime 74 | r := GetRuntime(context.Background(), testCase.mode) 75 | 76 | // Make sure there is no initialization logic 77 | initialTxs, err := r.Initialize( 78 | accounts[0], 79 | func(_ *std.Tx) error { 80 | return nil 81 | }, 82 | func(_ context.Context, _ *std.Tx) (int64, error) { 83 | return 1_000_000, nil 84 | }, 85 | 1_000_000, 86 | common.DefaultGasPrice, 87 | ) 88 | 89 | assert.Nil(t, initialTxs) 90 | assert.Nil(t, err) 91 | 92 | // Construct the transactions 93 | txs, err := r.ConstructTransactions( 94 | accountKeys, 95 | accounts, 96 | transactions, 97 | 1_000_000, 98 | common.DefaultGasPrice, 99 | "dummy", 100 | func(_ context.Context, _ *std.Tx) (int64, error) { 101 | return 1_000_000, nil 102 | }, 103 | ) 104 | if err != nil { 105 | t.Fatalf("unable to construct transactions, %v", err) 106 | } 107 | 108 | // Make sure they were constructed properly 109 | if len(txs) != int(transactions) { 110 | t.Fatalf("invalid number of transactions constructed, %d", len(txs)) 111 | } 112 | 113 | for _, tx := range txs { 114 | verifyDeployTxCommon(t, tx, testCase.expectedPrefix) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | func TestRuntime_RealmCall(t *testing.T) { 121 | t.Parallel() 122 | 123 | var ( 124 | transactions = uint64(100) 125 | accounts = generateAccounts(11) 126 | accountKeys = testutils.GenerateAccounts(t, 11) 127 | ) 128 | 129 | // Get the runtime 130 | r := GetRuntime(context.Background(), RealmCall) 131 | 132 | // Make sure the initialization logic is present 133 | initialTxs, err := r.Initialize( 134 | accounts[0], 135 | func(_ *std.Tx) error { 136 | return nil 137 | }, 138 | func(_ context.Context, _ *std.Tx) (int64, error) { 139 | return 1_000_000, nil 140 | }, 141 | 1_000_000, 142 | common.DefaultGasPrice, 143 | ) 144 | if err != nil { 145 | t.Fatalf("unable to generate init transactions, %v", err) 146 | } 147 | 148 | if len(initialTxs) != 1 { 149 | t.Fatalf("invalid number of initial transactions, %d", len(initialTxs)) 150 | } 151 | 152 | for _, tx := range initialTxs { 153 | verifyDeployTxCommon(t, tx, realmPathPrefix) 154 | } 155 | 156 | // Construct the transactions 157 | txs, err := r.ConstructTransactions( 158 | accountKeys[1:], 159 | accounts[1:], 160 | transactions, 161 | 1_000_000, 162 | common.DefaultGasPrice, 163 | "dummy", 164 | func(_ context.Context, _ *std.Tx) (int64, error) { 165 | return 1_000_000, nil 166 | }, 167 | ) 168 | if err != nil { 169 | t.Fatalf("unable to construct transactions, %v", err) 170 | } 171 | 172 | // Make sure they were constructed properly 173 | if len(txs) != int(transactions) { 174 | t.Fatalf("invalid number of transactions constructed, %d", len(txs)) 175 | } 176 | 177 | for _, tx := range txs { 178 | if len(tx.Msgs) != 1 { 179 | t.Fatalf("invalid number of tx messages, %d", len(tx.Msgs)) 180 | } 181 | 182 | msg := tx.Msgs[0] 183 | 184 | vmMsg, ok := msg.(vm.MsgCall) 185 | if !ok { 186 | t.Fatal("invalid tx message type") 187 | } 188 | 189 | // Make sure the call params are valid 190 | assert.Equal(t, vmMsg.Func, methodName) 191 | assert.NotNil(t, vmMsg.Caller) 192 | assert.Nil(t, vmMsg.Send) 193 | 194 | if len(vmMsg.Args) != 1 { 195 | t.Fatalf("invalid number of arguments provided for call") 196 | } 197 | 198 | assert.Contains(t, vmMsg.Args[0], "Account") 199 | 200 | // Make sure the fee is valid 201 | assert.Equal( 202 | t, 203 | common.CalculateFeeInRatio(1_000_000+gasBuffer, common.DefaultGasPrice), 204 | tx.Fee, 205 | ) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /internal/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/gnolang/gno/gno.land/pkg/gnoland" 8 | "github.com/gnolang/gno/tm2/pkg/amino" 9 | abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" 10 | "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" 11 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 12 | "github.com/gnolang/gno/tm2/pkg/std" 13 | "github.com/gnolang/supernova/internal/common" 14 | ) 15 | 16 | const ( 17 | simulatePath = ".app/simulate" 18 | gaspricePath = "auth/gasprice" 19 | ) 20 | 21 | type Client struct { 22 | conn *client.RPCClient 23 | } 24 | 25 | // NewWSClient creates a new instance of the WS client 26 | func NewWSClient(url string) (*Client, error) { 27 | cli, err := client.NewWSClient(url) 28 | if err != nil { 29 | return nil, fmt.Errorf("unable to create ws client, %w", err) 30 | } 31 | 32 | return &Client{ 33 | conn: cli, 34 | }, nil 35 | } 36 | 37 | // NewHTTPClient creates a new instance of the HTTP client 38 | func NewHTTPClient(url string) (*Client, error) { 39 | cli, err := client.NewHTTPClient(url) 40 | if err != nil { 41 | return nil, fmt.Errorf("unable to create http client, %w", err) 42 | } 43 | 44 | return &Client{ 45 | conn: cli, 46 | }, nil 47 | } 48 | 49 | func (h *Client) CreateBatch() common.Batch { 50 | return &Batch{batch: h.conn.NewBatch()} 51 | } 52 | 53 | func (h *Client) ExecuteABCIQuery(ctx context.Context, path string, data []byte) (*core_types.ResultABCIQuery, error) { 54 | return h.conn.ABCIQuery(ctx, path, data) 55 | } 56 | 57 | func (h *Client) GetLatestBlockHeight(ctx context.Context) (int64, error) { 58 | status, err := h.conn.Status(ctx, nil) 59 | if err != nil { 60 | return 0, fmt.Errorf("unable to fetch status, %w", err) 61 | } 62 | 63 | return status.SyncInfo.LatestBlockHeight, nil 64 | } 65 | 66 | func (h *Client) GetBlock(ctx context.Context, height *int64) (*core_types.ResultBlock, error) { 67 | return h.conn.Block(ctx, height) 68 | } 69 | 70 | func (h *Client) GetBlockResults(ctx context.Context, height *int64) (*core_types.ResultBlockResults, error) { 71 | return h.conn.BlockResults(ctx, height) 72 | } 73 | 74 | func (h *Client) GetConsensusParams(ctx context.Context, height *int64) (*core_types.ResultConsensusParams, error) { 75 | return h.conn.ConsensusParams(ctx, height) 76 | } 77 | 78 | func (h *Client) BroadcastTransaction(ctx context.Context, tx *std.Tx) error { 79 | marshalledTx, err := amino.Marshal(tx) 80 | if err != nil { 81 | return fmt.Errorf("unable to marshal transaction, %w", err) 82 | } 83 | 84 | res, err := h.conn.BroadcastTxCommit(ctx, marshalledTx) 85 | if err != nil { 86 | return fmt.Errorf("unable to broadcast transaction, %w", err) 87 | } 88 | 89 | if res.CheckTx.IsErr() { 90 | return fmt.Errorf("broadcast transaction check failed, %w", res.CheckTx.Error) 91 | } 92 | 93 | if res.DeliverTx.IsErr() { 94 | return fmt.Errorf("broadcast transaction delivery failed, %w", res.DeliverTx.Error) 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func (h *Client) GetAccount(ctx context.Context, address string) (*gnoland.GnoAccount, error) { 101 | queryResult, err := h.conn.ABCIQuery( 102 | ctx, 103 | fmt.Sprintf("auth/accounts/%s", address), 104 | []byte{}, 105 | ) 106 | if err != nil { 107 | return nil, fmt.Errorf("unable to fetch account %s, %w", address, err) 108 | } 109 | 110 | if queryResult.Response.IsErr() { 111 | return nil, fmt.Errorf("invalid account query result, %w", queryResult.Response.Error) 112 | } 113 | 114 | var acc gnoland.GnoAccount 115 | if err := amino.UnmarshalJSON(queryResult.Response.Data, &acc); err != nil { 116 | return nil, fmt.Errorf("unable to unmarshal query response, %w", err) 117 | } 118 | 119 | return &acc, nil 120 | } 121 | 122 | func (h *Client) GetBlockGasUsed(ctx context.Context, height int64) (int64, error) { 123 | blockRes, err := h.conn.BlockResults(ctx, &height) 124 | if err != nil { 125 | return 0, fmt.Errorf("unable to fetch block results, %w", err) 126 | } 127 | 128 | gasUsed := int64(0) 129 | for _, tx := range blockRes.Results.DeliverTxs { 130 | gasUsed += tx.GasUsed 131 | } 132 | 133 | return gasUsed, nil 134 | } 135 | 136 | func (h *Client) GetBlockGasLimit(ctx context.Context, height int64) (int64, error) { 137 | consensusParams, err := h.conn.ConsensusParams(ctx, &height) 138 | if err != nil { 139 | return 0, fmt.Errorf("unable to fetch block info, %w", err) 140 | } 141 | 142 | return consensusParams.ConsensusParams.Block.MaxGas, nil 143 | } 144 | 145 | func (h *Client) EstimateGas(ctx context.Context, tx *std.Tx) (int64, error) { 146 | // Prepare the transaction. 147 | // The transaction needs to be amino-binary encoded 148 | // in order to be estimated 149 | encodedTx, err := amino.Marshal(tx) 150 | if err != nil { 151 | return 0, fmt.Errorf("unable to marshal tx: %w", err) 152 | } 153 | 154 | // Perform the simulation query 155 | resp, err := h.conn.ABCIQuery(ctx, simulatePath, encodedTx) 156 | if err != nil { 157 | return 0, fmt.Errorf("unable to perform ABCI query: %w", err) 158 | } 159 | 160 | // Extract the query response 161 | if err = resp.Response.Error; err != nil { 162 | return 0, fmt.Errorf("error encountered during ABCI query: %w", err) 163 | } 164 | 165 | var deliverTx abci.ResponseDeliverTx 166 | if err = amino.Unmarshal(resp.Response.Value, &deliverTx); err != nil { 167 | return 0, fmt.Errorf("unable to unmarshal gas estimation response: %w", err) 168 | } 169 | 170 | if err = deliverTx.Error; err != nil { 171 | return 0, fmt.Errorf("error encountered during gas estimation: %w", err) 172 | } 173 | 174 | // Return the actual value returned by the node 175 | // for executing the transaction 176 | return deliverTx.GasUsed, nil 177 | } 178 | 179 | func (h *Client) FetchGasPrice(ctx context.Context) (std.GasPrice, error) { 180 | // Perform auth/gasprice 181 | gp := std.GasPrice{} 182 | 183 | qres, err := h.conn.ABCIQuery(ctx, gaspricePath, []byte{}) 184 | if err != nil { 185 | return gp, err 186 | } 187 | 188 | err = amino.UnmarshalJSON(qres.Response.Data, &gp) 189 | if err != nil { 190 | return gp, err 191 | } 192 | 193 | return gp, nil 194 | } 195 | -------------------------------------------------------------------------------- /internal/batcher/batcher.go: -------------------------------------------------------------------------------- 1 | package batcher 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math" 8 | 9 | "github.com/gnolang/gno/tm2/pkg/amino" 10 | core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 11 | "github.com/gnolang/gno/tm2/pkg/std" 12 | "github.com/gnolang/supernova/internal/common" 13 | "github.com/schollz/progressbar/v3" 14 | ) 15 | 16 | // Batcher batches signed transactions 17 | // to the Gno Tendermint node 18 | type Batcher struct { 19 | cli Client 20 | ctx context.Context 21 | } 22 | 23 | // NewBatcher creates a new Batcher instance 24 | func NewBatcher(ctx context.Context, cli Client) *Batcher { 25 | return &Batcher{ 26 | cli: cli, 27 | ctx: ctx, 28 | } 29 | } 30 | 31 | // BatchTransactions batches provided transactions using the 32 | // specified batch size 33 | func (b *Batcher) BatchTransactions(txs []*std.Tx, batchSize int) (*TxBatchResult, error) { 34 | fmt.Printf("\n📦 Batching Transactions 📦\n\n") 35 | 36 | // Note the current latest block 37 | latest, err := b.cli.GetLatestBlockHeight(b.ctx) 38 | if err != nil { 39 | return nil, fmt.Errorf("unable to fetch latest block %w", err) 40 | } 41 | 42 | fmt.Printf("Latest block number: %d\n", latest) 43 | 44 | // Marshal the transactions 45 | fmt.Printf("\nPreparing transactions...\n") 46 | 47 | preparedTxs, err := prepareTransactions(txs) 48 | if err != nil { 49 | return nil, fmt.Errorf("unable to batch transactions, %w", err) 50 | } 51 | 52 | // Generate the batches 53 | readyBatches, err := b.generateBatches(preparedTxs, batchSize) 54 | if err != nil { 55 | return nil, fmt.Errorf("unable to generate batches, %w", err) 56 | } 57 | 58 | // Execute the batch requests. 59 | // Batch requests need to be sent out sequentially 60 | // to preserve account sequence order 61 | batchResults, err := sendBatches(readyBatches) 62 | if err != nil { 63 | return nil, fmt.Errorf("unable to send batches, %w", err) 64 | } 65 | 66 | // Parse the results 67 | txHashes, err := parseBatchResults(batchResults, len(txs)) 68 | if err != nil { 69 | return nil, fmt.Errorf("unable to parse batch results, %w", err) 70 | } 71 | 72 | fmt.Printf("✅ Successfully sent %d txs in %d batches\n", len(txs), len(readyBatches)) 73 | 74 | return &TxBatchResult{ 75 | TxHashes: txHashes, 76 | StartBlock: latest, 77 | }, nil 78 | } 79 | 80 | // prepareTransactions marshals the transactions into amino binary 81 | func prepareTransactions(txs []*std.Tx) ([][]byte, error) { 82 | marshalledTxs := make([][]byte, len(txs)) 83 | bar := progressbar.Default(int64(len(txs)), "txs prepared") 84 | 85 | for index, tx := range txs { 86 | txBin, err := amino.Marshal(tx) 87 | if err != nil { 88 | return nil, fmt.Errorf("unable to marshal tx, %w", err) 89 | } 90 | 91 | marshalledTxs[index] = txBin 92 | 93 | _ = bar.Add(1) //nolint:errcheck // No need to check 94 | } 95 | 96 | return marshalledTxs, nil 97 | } 98 | 99 | // generateBatches generates batches of transactions 100 | func (b *Batcher) generateBatches(txs [][]byte, batchSize int) ([]common.Batch, error) { 101 | var ( 102 | batches = generateBatches(txs, batchSize) 103 | numBatches = len(batches) 104 | readyBatches = make([]common.Batch, numBatches) 105 | ) 106 | 107 | fmt.Printf("\nGenerating batches...\n") 108 | 109 | bar := progressbar.Default(int64(numBatches), "batches generated") 110 | 111 | for index, batch := range batches { 112 | cliBatch := b.cli.CreateBatch() 113 | 114 | for _, tx := range batch { 115 | // Append the transaction 116 | if err := cliBatch.AddTxBroadcast(tx); err != nil { 117 | return nil, fmt.Errorf("unable to prepare transaction, %w", err) 118 | } 119 | } 120 | 121 | readyBatches[index] = cliBatch 122 | 123 | _ = bar.Add(1) //nolint:errcheck // No need to check 124 | } 125 | 126 | return readyBatches, nil 127 | } 128 | 129 | // sendBatches sends the prepared batch requests 130 | func sendBatches(readyBatches []common.Batch) ([][]any, error) { 131 | var ( 132 | numBatches = len(readyBatches) 133 | batchResults = make([][]any, numBatches) 134 | ) 135 | 136 | fmt.Printf("\nSending batches...\n") 137 | 138 | bar := progressbar.Default(int64(numBatches), "batches sent") 139 | 140 | for index, readyBatch := range readyBatches { 141 | batchResult, err := readyBatch.Execute() 142 | if err != nil { 143 | return nil, fmt.Errorf("unable to batch request, %w", err) 144 | } 145 | 146 | batchResults[index] = batchResult 147 | 148 | _ = bar.Add(1) //nolint:errcheck // No need to check 149 | } 150 | 151 | fmt.Printf("✅ Successfully sent %d batches\n", numBatches) 152 | 153 | return batchResults, nil 154 | } 155 | 156 | // parseBatchResults extracts transaction hashes 157 | // from batch results 158 | func parseBatchResults(batchResults [][]any, numTx int) ([][]byte, error) { 159 | var ( 160 | txHashes = make([][]byte, numTx) 161 | index = 0 162 | ) 163 | 164 | fmt.Printf("\nParsing batch results...\n") 165 | 166 | bar := progressbar.Default(int64(numTx), "results parsed") 167 | 168 | // Parsing is done in a separate loop to not hinder 169 | // the batch send speed (as txs need to be parsed sequentially) 170 | for _, batchResult := range batchResults { 171 | // For each batch, extract the transaction hashes 172 | for _, txResultRaw := range batchResult { 173 | txResult, ok := txResultRaw.(*core_types.ResultBroadcastTx) 174 | if !ok { 175 | return nil, errors.New("invalid result type returned") 176 | } 177 | 178 | // Check the errors 179 | if txResult.Error != nil { 180 | return nil, fmt.Errorf( 181 | "error when parsing transaction %s, %w", 182 | txResult.Hash, 183 | txResult.Error, 184 | ) 185 | } 186 | 187 | txHashes[index] = txResult.Hash 188 | index++ 189 | 190 | _ = bar.Add(1) //nolint:errcheck // No need to check 191 | } 192 | } 193 | 194 | fmt.Printf("✅ Successfully parsed %d batch results\n", len(batchResults)) 195 | 196 | return txHashes, nil 197 | } 198 | 199 | // generateBatches generates data batches based on passed in params 200 | func generateBatches(items [][]byte, batchSize int) [][][]byte { 201 | numBatches := int(math.Ceil(float64(len(items)) / float64(batchSize))) 202 | if numBatches == 0 { 203 | numBatches = 1 204 | } 205 | 206 | batches := make([][][]byte, numBatches) 207 | for i := 0; i < numBatches; i++ { 208 | batches[i] = make([][]byte, 0) 209 | } 210 | 211 | currentBatch := 0 212 | for _, item := range items { 213 | batches[currentBatch] = append(batches[currentBatch], item) 214 | 215 | if len(batches[currentBatch])%batchSize == 0 { 216 | currentBatch++ 217 | } 218 | } 219 | 220 | return batches 221 | } 222 | -------------------------------------------------------------------------------- /internal/distributor/distributor_test.go: -------------------------------------------------------------------------------- 1 | package distributor 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gnolang/gno/gno.land/pkg/gnoland" 8 | "github.com/gnolang/gno/tm2/pkg/crypto" 9 | "github.com/gnolang/gno/tm2/pkg/sdk/bank" 10 | "github.com/gnolang/gno/tm2/pkg/std" 11 | "github.com/gnolang/supernova/internal/common" 12 | testutils "github.com/gnolang/supernova/internal/testing" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestDistributor_Distribute(t *testing.T) { 17 | t.Parallel() 18 | 19 | var ( 20 | numTx = uint64(1000) 21 | singleCost = std.Coin{ 22 | Denom: common.Denomination, 23 | Amount: int64(numTx) * 100_00, 24 | } 25 | ) 26 | 27 | getAccount := func(address string, accounts []crypto.PrivKey) crypto.PrivKey { 28 | for _, account := range accounts { 29 | if address == account.PubKey().Address().String() { 30 | return account 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | 37 | t.Run("all accounts funded", func(t *testing.T) { 38 | t.Parallel() 39 | 40 | var ( 41 | accounts = testutils.GenerateAccounts(t, 10) 42 | 43 | mockClient = &mockClient{ 44 | getAccountFn: func(ctx context.Context, address string) (*gnoland.GnoAccount, error) { 45 | acc := getAccount(address, accounts) 46 | if acc == nil { 47 | t.Fatal("invalid account requested") 48 | } 49 | 50 | return &gnoland.GnoAccount{ 51 | BaseAccount: *std.NewBaseAccount( 52 | acc.PubKey().Address(), 53 | std.NewCoins(singleCost), 54 | nil, 55 | 0, 56 | 0, 57 | ), 58 | }, nil 59 | }, 60 | } 61 | ) 62 | 63 | d := NewDistributor( 64 | context.Background(), 65 | mockClient, 66 | ) 67 | 68 | // Extract the addresses 69 | addresses := make([]crypto.Address, 0, len(accounts[1:])) 70 | for _, account := range accounts[1:] { 71 | addresses = append(addresses, account.PubKey().Address()) 72 | } 73 | 74 | readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost) 75 | if err != nil { 76 | t.Fatalf("unable to distribute funds, %v", err) 77 | } 78 | 79 | // Make sure all accounts are funded 80 | // (the distributor does not participate in the run, hence -1) 81 | assert.Len(t, readyAccounts, len(accounts)-1) 82 | 83 | // Make sure the accounts match 84 | for index, account := range accounts[1:] { 85 | assert.Equal(t, account.PubKey().Address().String(), readyAccounts[index].GetAddress().String()) 86 | } 87 | }) 88 | 89 | t.Run("insufficient distributor funds", func(t *testing.T) { 90 | t.Parallel() 91 | 92 | emptyBalance := std.Coin{ 93 | Denom: common.Denomination, 94 | Amount: 0, 95 | } 96 | 97 | var ( 98 | accounts = testutils.GenerateAccounts(t, 10) 99 | 100 | mockClient = &mockClient{ 101 | getAccountFn: func(ctx context.Context, address string) (*gnoland.GnoAccount, error) { 102 | acc := getAccount(address, accounts) 103 | if acc == nil { 104 | t.Fatal("invalid account requested") 105 | } 106 | 107 | return &gnoland.GnoAccount{ 108 | BaseAccount: *std.NewBaseAccount( 109 | acc.PubKey().Address(), 110 | std.NewCoins(emptyBalance), 111 | nil, 112 | 0, 113 | 0, 114 | ), 115 | }, nil 116 | }, 117 | } 118 | ) 119 | 120 | d := NewDistributor( 121 | context.Background(), 122 | mockClient, 123 | ) 124 | 125 | // Extract the addresses 126 | addresses := make([]crypto.Address, 0, len(accounts[1:])) 127 | for _, account := range accounts[1:] { 128 | addresses = append(addresses, account.PubKey().Address()) 129 | } 130 | 131 | readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost) 132 | 133 | assert.Nil(t, readyAccounts) 134 | assert.ErrorIs(t, err, errInsufficientFunds) 135 | }) 136 | 137 | t.Run("fund all short accounts", func(t *testing.T) { 138 | t.Parallel() 139 | 140 | emptyBalance := std.Coin{ 141 | Denom: common.Denomination, 142 | Amount: 0, 143 | } 144 | 145 | var ( 146 | accounts = testutils.GenerateAccounts(t, 10) 147 | capturedBroadcasts = make([]*std.Tx, 0) 148 | 149 | mockClient = &mockClient{ 150 | getAccountFn: func(ctx context.Context, address string) (*gnoland.GnoAccount, error) { 151 | acc := getAccount(address, accounts) 152 | if acc == nil { 153 | t.Fatal("invalid account requested") 154 | } 155 | 156 | if acc.Equals(accounts[0]) { 157 | sendCost := common.CalculateFeeInRatio(100_000, common.DefaultGasPrice) 158 | 159 | return &gnoland.GnoAccount{ 160 | BaseAccount: *std.NewBaseAccount( 161 | acc.PubKey().Address(), 162 | std.NewCoins(std.Coin{ 163 | Denom: common.Denomination, 164 | Amount: int64(numTx) * sendCost.GasFee.Add(singleCost).Amount, 165 | }), 166 | nil, 167 | 0, 168 | 0, 169 | ), 170 | }, nil 171 | } 172 | 173 | return &gnoland.GnoAccount{ 174 | BaseAccount: *std.NewBaseAccount( 175 | acc.PubKey().Address(), 176 | std.NewCoins(emptyBalance), 177 | nil, 178 | 0, 179 | 0, 180 | ), 181 | }, nil 182 | }, 183 | broadcastTransactionFn: func(ctx context.Context, tx *std.Tx) error { 184 | capturedBroadcasts = append(capturedBroadcasts, tx) 185 | 186 | return nil 187 | }, 188 | } 189 | ) 190 | 191 | d := NewDistributor( 192 | context.Background(), 193 | mockClient, 194 | ) 195 | 196 | // Extract the addresses 197 | addresses := make([]crypto.Address, 0, len(accounts[1:])) 198 | for _, account := range accounts[1:] { 199 | addresses = append(addresses, account.PubKey().Address()) 200 | } 201 | 202 | readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost) 203 | if err != nil { 204 | t.Fatalf("unable to distribute funds, %v", err) 205 | } 206 | 207 | // Make sure all accounts are funded 208 | // (the distributor does not participate in the run, hence -1) 209 | assert.Len(t, readyAccounts, len(accounts)-1) 210 | 211 | // Make sure the accounts match 212 | for index, account := range accounts[1:] { 213 | assert.Equal(t, account.PubKey().Address(), readyAccounts[index].GetAddress()) 214 | } 215 | 216 | // Check the broadcast transactions 217 | assert.Len(t, capturedBroadcasts, len(accounts)-1) 218 | 219 | sendType := bank.MsgSend{}.Type() 220 | 221 | for _, tx := range capturedBroadcasts { 222 | if len(tx.Msgs) != 1 { 223 | t.Fatal("invalid number of messages") 224 | } 225 | 226 | assert.Equal(t, sendType, tx.Msgs[0].Type()) 227 | } 228 | }) 229 | } 230 | -------------------------------------------------------------------------------- /internal/distributor/distributor.go: -------------------------------------------------------------------------------- 1 | package distributor 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "sort" 8 | 9 | "github.com/gnolang/gno/gno.land/pkg/gnoland" 10 | "github.com/gnolang/gno/tm2/pkg/crypto" 11 | "github.com/gnolang/gno/tm2/pkg/sdk/bank" 12 | "github.com/gnolang/gno/tm2/pkg/std" 13 | "github.com/gnolang/supernova/internal/common" 14 | "github.com/gnolang/supernova/internal/signer" 15 | "github.com/schollz/progressbar/v3" 16 | ) 17 | 18 | var errInsufficientFunds = errors.New("insufficient distributor funds") 19 | 20 | type Client interface { 21 | GetAccount(ctx context.Context, address string) (*gnoland.GnoAccount, error) 22 | BroadcastTransaction(ctx context.Context, tx *std.Tx) error 23 | EstimateGas(ctx context.Context, tx *std.Tx) (int64, error) 24 | FetchGasPrice(ctx context.Context) (std.GasPrice, error) 25 | } 26 | 27 | // Distributor is the process 28 | // that manages sub-account distributions 29 | type Distributor struct { 30 | cli Client 31 | ctx context.Context 32 | } 33 | 34 | // NewDistributor creates a new instance of the distributor 35 | func NewDistributor( 36 | ctx context.Context, 37 | cli Client, 38 | ) *Distributor { 39 | return &Distributor{ 40 | cli: cli, 41 | ctx: ctx, 42 | } 43 | } 44 | 45 | // Distribute distributes the funds from the base account 46 | // (account 0 in the mnemonic) to other subaccounts 47 | func (d *Distributor) Distribute( 48 | distributor crypto.PrivKey, 49 | accounts []crypto.Address, 50 | chainID string, 51 | gasPrice std.GasPrice, 52 | calculatedRuntimeCost std.Coin, 53 | ) ([]std.Account, error) { 54 | fmt.Printf("\n💸 Starting Fund Distribution 💸\n\n") 55 | 56 | fmt.Printf( 57 | "Calculated sub-account cost as %d %s\n", 58 | calculatedRuntimeCost.Amount, 59 | calculatedRuntimeCost.Denom, 60 | ) 61 | 62 | // Fund the accounts 63 | return d.fundAccounts(distributor, accounts, calculatedRuntimeCost, chainID, gasPrice) 64 | } 65 | 66 | // fundAccounts attempts to fund accounts that have missing funds, 67 | // and returns the accounts that can participate in the stress test 68 | func (d *Distributor) fundAccounts( 69 | distributorKey crypto.PrivKey, 70 | accounts []crypto.Address, 71 | singleRunCost std.Coin, 72 | chainID string, 73 | gasPrice std.GasPrice, 74 | ) ([]std.Account, error) { 75 | type shortAccount struct { 76 | missingFunds std.Coin 77 | address crypto.Address 78 | } 79 | 80 | var ( 81 | // Accounts that are ready (funded) for the run 82 | readyAccounts = make([]std.Account, 0, len(accounts)) 83 | 84 | // Accounts that need funding 85 | shortAccounts = make([]shortAccount, 0, len(accounts)) 86 | ) 87 | 88 | // Check if there are any accounts that need to be funded 89 | // before the stress test starts 90 | for _, account := range accounts { 91 | // Fetch the account balance 92 | subAccount, err := d.cli.GetAccount(d.ctx, account.String()) 93 | if err != nil { 94 | return nil, fmt.Errorf("unable to fetch sub-account, %w", err) 95 | } 96 | 97 | // Check if it has enough funds for the run 98 | if subAccount.Coins.AmountOf(common.Denomination) < singleRunCost.Amount { 99 | // Mark the account as needing a top-up 100 | shortAccounts = append(shortAccounts, shortAccount{ 101 | address: account, 102 | missingFunds: std.Coin{ 103 | Denom: common.Denomination, 104 | Amount: singleRunCost.Amount - subAccount.Coins.AmountOf(common.Denomination), 105 | }, 106 | }) 107 | 108 | continue 109 | } 110 | 111 | // The account is cleared for the stress test 112 | readyAccounts = append(readyAccounts, subAccount) 113 | } 114 | 115 | // Check if funding is even necessary 116 | if len(shortAccounts) == 0 { 117 | // All accounts are already funded 118 | fmt.Printf("✅ All %d accounts are already funded\n", len(readyAccounts)) 119 | 120 | return readyAccounts, nil 121 | } 122 | 123 | // Sort the short accounts so the ones with 124 | // the lowest missing funds are funded first 125 | sort.Slice(shortAccounts, func(i, j int) bool { 126 | return shortAccounts[i].missingFunds.IsLT(shortAccounts[j].missingFunds) 127 | }) 128 | 129 | // Figure out how many accounts can actually be funded 130 | distributor, err := d.cli.GetAccount(d.ctx, distributorKey.PubKey().Address().String()) 131 | if err != nil { 132 | return nil, fmt.Errorf("unable to fetch distributor account, %w", err) 133 | } 134 | 135 | var ( 136 | distributorBalance = distributor.Coins 137 | fundableIndex = 0 138 | defaultFee = common.CalculateFeeInRatio(100_000, gasPrice) 139 | ) 140 | 141 | for _, account := range shortAccounts { 142 | // The transfer cost is the single run cost (missing balance) + approximate transfer cost 143 | transferCost := std.NewCoins(defaultFee.GasFee.Add(account.missingFunds)) 144 | 145 | if distributorBalance.IsAllLT(transferCost) { 146 | // Distributor does not have any more funds 147 | // to cover the run cost 148 | break 149 | } 150 | 151 | fundableIndex++ 152 | 153 | distributorBalance.Sub(transferCost) 154 | } 155 | 156 | if fundableIndex == 0 { 157 | // The distributor does not have funds to fund 158 | // any account for the stress test 159 | fmt.Printf( 160 | "❌ Distributor cannot fund any account, balance is %d %s\n", 161 | distributorBalance.AmountOf(common.Denomination), 162 | common.Denomination, 163 | ) 164 | 165 | return nil, errInsufficientFunds 166 | } 167 | 168 | // Locally keep track of the nonce, so 169 | // there is no need to re-fetch the account again 170 | // before signing a future tx 171 | nonce := distributor.Sequence 172 | 173 | fmt.Printf("Funding %d accounts...\n", len(shortAccounts)) 174 | bar := progressbar.Default(int64(len(shortAccounts)), "funding short accounts") 175 | 176 | for _, account := range shortAccounts { 177 | // Generate the transaction 178 | tx := &std.Tx{ 179 | Msgs: []std.Msg{ 180 | bank.MsgSend{ 181 | FromAddress: distributor.GetAddress(), 182 | ToAddress: account.address, 183 | Amount: std.NewCoins(account.missingFunds), 184 | }, 185 | }, 186 | Fee: defaultFee, 187 | } 188 | 189 | cfg := signer.SignCfg{ 190 | ChainID: chainID, 191 | AccountNumber: distributor.AccountNumber, 192 | Sequence: nonce, 193 | } 194 | 195 | // Sign the transaction 196 | if err := signer.SignTx(tx, distributorKey, cfg); err != nil { 197 | return nil, fmt.Errorf("unable to sign transaction, %w", err) 198 | } 199 | 200 | // Update the local nonce 201 | nonce++ 202 | 203 | // Broadcast the tx and wait for it to be committed 204 | if err := d.cli.BroadcastTransaction(d.ctx, tx); err != nil { 205 | return nil, fmt.Errorf("unable to broadcast tx with commit, %w", err) 206 | } 207 | 208 | // Since accounts can be uninitialized on the node, after the 209 | // transfer they will have acquired a storage slot, and need 210 | // to be re-fetched for their data (Sequence + Account Number) 211 | nodeAccount, err := d.cli.GetAccount(d.ctx, account.address.String()) 212 | if err != nil { 213 | return nil, fmt.Errorf("unable to fetch account, %w", err) 214 | } 215 | 216 | // Mark the account as funded 217 | readyAccounts = append(readyAccounts, nodeAccount) 218 | 219 | _ = bar.Add(1) //nolint:errcheck // No need to check 220 | } 221 | 222 | fmt.Printf("✅ Successfully funded %d accounts\n", len(shortAccounts)) 223 | 224 | return readyAccounts, nil 225 | } 226 | -------------------------------------------------------------------------------- /internal/pipeline.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gnolang/gno/tm2/pkg/crypto" 9 | "github.com/gnolang/gno/tm2/pkg/crypto/bip39" 10 | "github.com/gnolang/gno/tm2/pkg/std" 11 | "github.com/gnolang/supernova/internal/batcher" 12 | "github.com/gnolang/supernova/internal/client" 13 | "github.com/gnolang/supernova/internal/collector" 14 | "github.com/gnolang/supernova/internal/distributor" 15 | "github.com/gnolang/supernova/internal/runtime" 16 | "github.com/gnolang/supernova/internal/signer" 17 | "github.com/schollz/progressbar/v3" 18 | ) 19 | 20 | type pipelineClient interface { 21 | distributor.Client 22 | batcher.Client 23 | collector.Client 24 | } 25 | 26 | // Pipeline is the central run point 27 | // for the stress test 28 | type Pipeline struct { 29 | cfg *Config // the run configuration 30 | cli pipelineClient // HTTP client connection 31 | } 32 | 33 | // NewPipeline creates a new pipeline instance 34 | func NewPipeline(cfg *Config) (*Pipeline, error) { 35 | var ( 36 | cli *client.Client 37 | err error 38 | ) 39 | 40 | // Check which kind of client to create 41 | if httpRegex.MatchString(cfg.URL) { 42 | cli, err = client.NewHTTPClient(cfg.URL) 43 | } else { 44 | cli, err = client.NewWSClient(cfg.URL) 45 | } 46 | 47 | if err != nil { 48 | return nil, fmt.Errorf("unable to create RPC client, %w", err) 49 | } 50 | 51 | return &Pipeline{ 52 | cfg: cfg, 53 | cli: cli, 54 | }, nil 55 | } 56 | 57 | // Execute runs the entire pipeline process 58 | func (p *Pipeline) Execute(ctx context.Context) error { 59 | var ( 60 | mode = runtime.Type(p.cfg.Mode) 61 | 62 | txBatcher = batcher.NewBatcher(ctx, p.cli) 63 | txCollector = collector.NewCollector(ctx, p.cli) 64 | txRuntime = runtime.GetRuntime(ctx, mode) 65 | ) 66 | 67 | // Initialize the accounts for the runtime 68 | accounts := p.initializeAccounts() 69 | 70 | gasPrice, err := p.cli.FetchGasPrice(ctx) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | lastBlock, err := p.cli.GetLatestBlockHeight(ctx) 76 | if err != nil { 77 | return fmt.Errorf("unable to get last block, %w", err) 78 | } 79 | 80 | maxGas, err := p.cli.GetBlockGasLimit(ctx, lastBlock) 81 | if err != nil { 82 | return fmt.Errorf("unable to get block gas limit, %w", err) 83 | } 84 | // Predeploy any pending transactions 85 | estimatedGas, err := prepareRuntime( 86 | ctx, 87 | mode, 88 | accounts[0], 89 | p.cfg.ChainID, 90 | p.cli, 91 | txRuntime, 92 | maxGas, 93 | gasPrice, 94 | p.cfg.Transactions, 95 | ) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | // Extract the addresses 101 | addresses := make([]crypto.Address, 0, len(accounts[1:])) 102 | for _, account := range accounts[1:] { 103 | addresses = append(addresses, account.PubKey().Address()) 104 | } 105 | 106 | // Distribute the funds to sub-accounts 107 | runAccounts, err := distributor.NewDistributor(ctx, p.cli).Distribute( 108 | accounts[0], 109 | addresses, 110 | p.cfg.ChainID, 111 | gasPrice, 112 | estimatedGas, 113 | ) 114 | if err != nil { 115 | return fmt.Errorf("unable to distribute funds, %w", err) 116 | } 117 | 118 | // Find which keys belong to the run accounts (not all initial accounts are run accounts) 119 | runKeys := make([]crypto.PrivKey, 0, len(runAccounts)) 120 | 121 | for _, runAccount := range runAccounts { 122 | for _, account := range accounts[1:] { 123 | if account.PubKey().Address() == runAccount.GetAddress() { 124 | runKeys = append(runKeys, account) 125 | } 126 | } 127 | } 128 | 129 | // Construct the transactions using the runtime 130 | txs, err := txRuntime.ConstructTransactions( 131 | runKeys, 132 | runAccounts, 133 | p.cfg.Transactions, 134 | maxGas, 135 | gasPrice, 136 | p.cfg.ChainID, 137 | p.cli.EstimateGas, 138 | ) 139 | if err != nil { 140 | return fmt.Errorf("unable to construct transactions, %w", err) 141 | } 142 | 143 | // Send the signed transactions in batches 144 | batchStart := time.Now() 145 | 146 | batchResult, err := txBatcher.BatchTransactions(txs, int(p.cfg.BatchSize)) 147 | if err != nil { 148 | return fmt.Errorf("unable to batch transactions %w", err) 149 | } 150 | 151 | // Collect the transaction results 152 | runResult, err := txCollector.GetRunResult( 153 | batchResult.TxHashes, 154 | batchResult.StartBlock, 155 | batchStart, 156 | ) 157 | if err != nil { 158 | return fmt.Errorf("unable to collect transactions, %w", err) 159 | } 160 | 161 | // Display [+ save the results] 162 | return p.handleResults(runResult) 163 | } 164 | 165 | // initializeAccounts initializes the accounts needed for the stress test run 166 | func (p *Pipeline) initializeAccounts() []crypto.PrivKey { 167 | fmt.Printf("\n🧮 Initializing Accounts 🧮\n\n") 168 | fmt.Printf("Generating sub-accounts...\n") 169 | 170 | var ( 171 | accounts = make([]crypto.PrivKey, p.cfg.SubAccounts+1) 172 | bar = progressbar.Default(int64(p.cfg.SubAccounts+1), "accounts initialized") 173 | 174 | seed = bip39.NewSeed(p.cfg.Mnemonic, "") 175 | ) 176 | 177 | // Register the accounts with the keybase 178 | for i := 0; i < int(p.cfg.SubAccounts)+1; i++ { 179 | accounts[i] = signer.GenerateKeyFromSeed(seed, uint32(i)) 180 | _ = bar.Add(1) //nolint:errcheck // No need to check 181 | } 182 | 183 | fmt.Printf("✅ Successfully generated %d accounts\n", len(accounts)) 184 | 185 | return accounts 186 | } 187 | 188 | // handleResults displays the results in the terminal, 189 | // and saves them to disk if an output path was specified 190 | func (p *Pipeline) handleResults(runResult *collector.RunResult) error { 191 | // Display the results in the terminal 192 | displayResults(runResult) 193 | 194 | // Check if the results need to be saved to disk 195 | if p.cfg.Output == "" { 196 | // No disk save necessary 197 | return nil 198 | } 199 | 200 | fmt.Printf("\n💾 Saving Results 💾\n\n") 201 | 202 | if err := saveResults(runResult, p.cfg.Output); err != nil { 203 | return fmt.Errorf("unable to save results, %w", err) 204 | } 205 | 206 | fmt.Printf("✅ Successfully saved results to %s\n", p.cfg.Output) 207 | 208 | return nil 209 | } 210 | 211 | // prepareRuntime prepares the runtime by pre-deploying 212 | // any pending transactions 213 | func prepareRuntime( 214 | ctx context.Context, 215 | mode runtime.Type, 216 | deployerKey crypto.PrivKey, 217 | chainID string, 218 | cli pipelineClient, 219 | txRuntime runtime.Runtime, 220 | currentMaxGas int64, 221 | gasPrice std.GasPrice, 222 | transactions uint64, 223 | ) (std.Coin, error) { 224 | // Get the deployer account 225 | deployer, err := cli.GetAccount(ctx, deployerKey.PubKey().Address().String()) 226 | if err != nil { 227 | return std.Coin{}, fmt.Errorf("unable to fetch deployer account, %w", err) 228 | } 229 | 230 | signCB := runtime.SignTransactionsCb(chainID, deployer, deployerKey) 231 | 232 | if mode != runtime.RealmCall { 233 | return txRuntime.CalculateRuntimeCosts(deployer, cli.EstimateGas, signCB, currentMaxGas, gasPrice, transactions) 234 | } 235 | 236 | fmt.Printf("\n✨ Starting Predeployment Procedure ✨\n\n") 237 | 238 | // Get the predeploy transactions 239 | predeployTxs, err := txRuntime.Initialize( 240 | deployer, 241 | signCB, 242 | cli.EstimateGas, 243 | currentMaxGas, 244 | gasPrice, 245 | ) 246 | if err != nil { 247 | return std.Coin{}, fmt.Errorf("unable to initialize runtime, %w", err) 248 | } 249 | 250 | bar := progressbar.Default(int64(len(predeployTxs)), "predeployed txs") 251 | 252 | // Execute the predeploy transactions 253 | for _, tx := range predeployTxs { 254 | if err := cli.BroadcastTransaction(ctx, tx); err != nil { 255 | return std.Coin{}, fmt.Errorf("unable to broadcast predeploy tx, %w", err) 256 | } 257 | 258 | _ = bar.Add(1) //nolint:errcheck // No need to check 259 | } 260 | 261 | fmt.Printf("✅ Successfully predeployed %d transactions\n", len(predeployTxs)) 262 | 263 | return txRuntime.CalculateRuntimeCosts(deployer, cli.EstimateGas, signCB, currentMaxGas, gasPrice, transactions) 264 | } 265 | -------------------------------------------------------------------------------- /ci/dagger/run-supernova/go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/gqlgen v0.17.75 h1:GwHJsptXWLHeY7JO8b7YueUI4w9Pom6wJTICosDtQuI= 2 | github.com/99designs/gqlgen v0.17.75/go.mod h1:p7gbTpdnHyl70hmSpM8XG8GiKwmCv+T5zkdY8U8bLog= 3 | github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= 4 | github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= 5 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= 6 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 7 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 8 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 9 | github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= 10 | github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 14 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 15 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 16 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 17 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 18 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 19 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 20 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 21 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 22 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 23 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 24 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= 25 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 26 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 29 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 30 | github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= 31 | github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= 32 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 33 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 34 | github.com/vektah/gqlparser/v2 v2.5.28 h1:bIulcl3LF69ba6EiZVGD88y4MkM+Jxrf3P2MX8xLRkY= 35 | github.com/vektah/gqlparser/v2 v2.5.28/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= 36 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 37 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 38 | go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 39 | go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 40 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= 41 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= 42 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= 43 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= 44 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= 45 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= 46 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= 47 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= 48 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= 49 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= 50 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= 51 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= 52 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= 53 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= 54 | go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= 55 | go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= 56 | go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= 57 | go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= 58 | go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= 59 | go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= 60 | go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= 61 | go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= 62 | go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= 63 | go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= 64 | go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= 65 | go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= 66 | go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 67 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 68 | go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= 69 | go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= 70 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 71 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 72 | golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= 73 | golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= 74 | golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= 75 | golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 76 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 77 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 78 | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= 79 | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= 80 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= 81 | google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= 82 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= 83 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 84 | google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= 85 | google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= 86 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 87 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 88 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 89 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 90 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gnolang/gno/supernova/tools 2 | 3 | go 1.23 4 | 5 | require github.com/golangci/golangci-lint v1.63.4 6 | 7 | require ( 8 | 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 9 | 4d63.com/gochecknoglobals v0.2.1 // indirect 10 | github.com/4meepo/tagalign v1.4.1 // indirect 11 | github.com/Abirdcfly/dupword v0.1.3 // indirect 12 | github.com/Antonboom/errname v1.0.0 // indirect 13 | github.com/Antonboom/nilnil v1.0.1 // indirect 14 | github.com/Antonboom/testifylint v1.5.2 // indirect 15 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect 16 | github.com/Crocmagnon/fatcontext v0.5.3 // indirect 17 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect 18 | github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect 19 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 20 | github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect 21 | github.com/alecthomas/go-check-sumtype v0.3.1 // indirect 22 | github.com/alexkohler/nakedret/v2 v2.0.5 // indirect 23 | github.com/alexkohler/prealloc v1.0.0 // indirect 24 | github.com/alingse/asasalint v0.0.11 // indirect 25 | github.com/alingse/nilnesserr v0.1.1 // indirect 26 | github.com/ashanbrown/forbidigo v1.6.0 // indirect 27 | github.com/ashanbrown/makezero v1.2.0 // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/bkielbasa/cyclop v1.2.3 // indirect 30 | github.com/blizzy78/varnamelen v0.8.0 // indirect 31 | github.com/bombsimon/wsl/v4 v4.5.0 // indirect 32 | github.com/breml/bidichk v0.3.2 // indirect 33 | github.com/breml/errchkjson v0.4.0 // indirect 34 | github.com/butuzov/ireturn v0.3.1 // indirect 35 | github.com/butuzov/mirror v1.3.0 // indirect 36 | github.com/catenacyber/perfsprint v0.7.1 // indirect 37 | github.com/ccojocar/zxcvbn-go v1.0.2 // indirect 38 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 39 | github.com/charithe/durationcheck v0.0.10 // indirect 40 | github.com/chavacava/garif v0.1.0 // indirect 41 | github.com/ckaznocha/intrange v0.3.0 // indirect 42 | github.com/curioswitch/go-reassign v0.3.0 // indirect 43 | github.com/daixiang0/gci v0.13.5 // indirect 44 | github.com/davecgh/go-spew v1.1.1 // indirect 45 | github.com/denis-tingaikin/go-header v0.5.0 // indirect 46 | github.com/ettle/strcase v0.2.0 // indirect 47 | github.com/fatih/color v1.18.0 // indirect 48 | github.com/fatih/structtag v1.2.0 // indirect 49 | github.com/firefart/nonamedreturns v1.0.5 // indirect 50 | github.com/fsnotify/fsnotify v1.5.4 // indirect 51 | github.com/fzipp/gocyclo v0.6.0 // indirect 52 | github.com/ghostiam/protogetter v0.3.8 // indirect 53 | github.com/go-critic/go-critic v0.11.5 // indirect 54 | github.com/go-toolsmith/astcast v1.1.0 // indirect 55 | github.com/go-toolsmith/astcopy v1.1.0 // indirect 56 | github.com/go-toolsmith/astequal v1.2.0 // indirect 57 | github.com/go-toolsmith/astfmt v1.1.0 // indirect 58 | github.com/go-toolsmith/astp v1.1.0 // indirect 59 | github.com/go-toolsmith/strparse v1.1.0 // indirect 60 | github.com/go-toolsmith/typep v1.1.0 // indirect 61 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 62 | github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect 63 | github.com/gobwas/glob v0.2.3 // indirect 64 | github.com/gofrs/flock v0.12.1 // indirect 65 | github.com/golang/protobuf v1.5.3 // indirect 66 | github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect 67 | github.com/golangci/go-printf-func-name v0.1.0 // indirect 68 | github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 // indirect 69 | github.com/golangci/misspell v0.6.0 // indirect 70 | github.com/golangci/plugin-module-register v0.1.1 // indirect 71 | github.com/golangci/revgrep v0.5.3 // indirect 72 | github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect 73 | github.com/google/go-cmp v0.6.0 // indirect 74 | github.com/gordonklaus/ineffassign v0.1.0 // indirect 75 | github.com/gostaticanalysis/analysisutil v0.7.1 // indirect 76 | github.com/gostaticanalysis/comment v1.4.2 // indirect 77 | github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect 78 | github.com/gostaticanalysis/nilerr v0.1.1 // indirect 79 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect 80 | github.com/hashicorp/go-version v1.7.0 // indirect 81 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 82 | github.com/hashicorp/hcl v1.0.0 // indirect 83 | github.com/hexops/gotextdiff v1.0.3 // indirect 84 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 85 | github.com/jgautheron/goconst v1.7.1 // indirect 86 | github.com/jingyugao/rowserrcheck v1.1.1 // indirect 87 | github.com/jjti/go-spancheck v0.6.4 // indirect 88 | github.com/julz/importas v0.2.0 // indirect 89 | github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect 90 | github.com/kisielk/errcheck v1.8.0 // indirect 91 | github.com/kkHAIKE/contextcheck v1.1.5 // indirect 92 | github.com/kulti/thelper v0.6.3 // indirect 93 | github.com/kunwardeep/paralleltest v1.0.10 // indirect 94 | github.com/kyoh86/exportloopref v0.1.11 // indirect 95 | github.com/lasiar/canonicalheader v1.1.2 // indirect 96 | github.com/ldez/exptostd v0.3.1 // indirect 97 | github.com/ldez/gomoddirectives v0.6.0 // indirect 98 | github.com/ldez/grignotin v0.7.0 // indirect 99 | github.com/ldez/tagliatelle v0.7.1 // indirect 100 | github.com/ldez/usetesting v0.4.2 // indirect 101 | github.com/leonklingele/grouper v1.1.2 // indirect 102 | github.com/macabu/inamedparam v0.1.3 // indirect 103 | github.com/magiconair/properties v1.8.6 // indirect 104 | github.com/maratori/testableexamples v1.0.0 // indirect 105 | github.com/maratori/testpackage v1.1.1 // indirect 106 | github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect 107 | github.com/mattn/go-colorable v0.1.13 // indirect 108 | github.com/mattn/go-isatty v0.0.20 // indirect 109 | github.com/mattn/go-runewidth v0.0.16 // indirect 110 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 111 | github.com/mgechev/revive v1.5.1 // indirect 112 | github.com/mitchellh/go-homedir v1.1.0 // indirect 113 | github.com/mitchellh/mapstructure v1.5.0 // indirect 114 | github.com/moricho/tparallel v0.3.2 // indirect 115 | github.com/nakabonne/nestif v0.3.1 // indirect 116 | github.com/nishanths/exhaustive v0.12.0 // indirect 117 | github.com/nishanths/predeclared v0.2.2 // indirect 118 | github.com/nunnatsa/ginkgolinter v0.18.4 // indirect 119 | github.com/olekukonko/tablewriter v0.0.5 // indirect 120 | github.com/pelletier/go-toml v1.9.5 // indirect 121 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 122 | github.com/pmezard/go-difflib v1.0.0 // indirect 123 | github.com/polyfloyd/go-errorlint v1.7.0 // indirect 124 | github.com/prometheus/client_golang v1.12.1 // indirect 125 | github.com/prometheus/client_model v0.2.0 // indirect 126 | github.com/prometheus/common v0.32.1 // indirect 127 | github.com/prometheus/procfs v0.7.3 // indirect 128 | github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect 129 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect 130 | github.com/quasilyte/gogrep v0.5.0 // indirect 131 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect 132 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect 133 | github.com/raeperd/recvcheck v0.2.0 // indirect 134 | github.com/rivo/uniseg v0.4.7 // indirect 135 | github.com/rogpeppe/go-internal v1.13.1 // indirect 136 | github.com/ryancurrah/gomodguard v1.3.5 // indirect 137 | github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect 138 | github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect 139 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect 140 | github.com/sashamelentyev/interfacebloat v1.1.0 // indirect 141 | github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect 142 | github.com/securego/gosec/v2 v2.21.4 // indirect 143 | github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect 144 | github.com/sirupsen/logrus v1.9.3 // indirect 145 | github.com/sivchari/containedctx v1.0.3 // indirect 146 | github.com/sivchari/tenv v1.12.1 // indirect 147 | github.com/sonatard/noctx v0.1.0 // indirect 148 | github.com/sourcegraph/go-diff v0.7.0 // indirect 149 | github.com/spf13/afero v1.11.0 // indirect 150 | github.com/spf13/cast v1.5.0 // indirect 151 | github.com/spf13/cobra v1.8.1 // indirect 152 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 153 | github.com/spf13/pflag v1.0.5 // indirect 154 | github.com/spf13/viper v1.12.0 // indirect 155 | github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect 156 | github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect 157 | github.com/stretchr/objx v0.5.2 // indirect 158 | github.com/stretchr/testify v1.10.0 // indirect 159 | github.com/subosito/gotenv v1.4.1 // indirect 160 | github.com/tdakkota/asciicheck v0.3.0 // indirect 161 | github.com/tetafro/godot v1.4.20 // indirect 162 | github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect 163 | github.com/timonwong/loggercheck v0.10.1 // indirect 164 | github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect 165 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect 166 | github.com/ultraware/funlen v0.2.0 // indirect 167 | github.com/ultraware/whitespace v0.2.0 // indirect 168 | github.com/uudashr/gocognit v1.2.0 // indirect 169 | github.com/uudashr/iface v1.3.0 // indirect 170 | github.com/xen0n/gosmopolitan v1.2.2 // indirect 171 | github.com/yagipy/maintidx v1.0.0 // indirect 172 | github.com/yeya24/promlinter v0.3.0 // indirect 173 | github.com/ykadowak/zerologlint v0.1.5 // indirect 174 | gitlab.com/bosi/decorder v0.4.2 // indirect 175 | go-simpler.org/musttag v0.13.0 // indirect 176 | go-simpler.org/sloglint v0.7.2 // indirect 177 | go.uber.org/atomic v1.7.0 // indirect 178 | go.uber.org/automaxprocs v1.6.0 // indirect 179 | go.uber.org/multierr v1.6.0 // indirect 180 | go.uber.org/zap v1.24.0 // indirect 181 | golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect 182 | golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect 183 | golang.org/x/mod v0.22.0 // indirect 184 | golang.org/x/sync v0.10.0 // indirect 185 | golang.org/x/sys v0.28.0 // indirect 186 | golang.org/x/text v0.20.0 // indirect 187 | golang.org/x/tools v0.28.0 // indirect 188 | google.golang.org/protobuf v1.34.2 // indirect 189 | gopkg.in/ini.v1 v1.67.0 // indirect 190 | gopkg.in/yaml.v2 v2.4.0 // indirect 191 | gopkg.in/yaml.v3 v3.0.1 // indirect 192 | honnef.co/go/tools v0.5.1 // indirect 193 | mvdan.cc/gofumpt v0.7.0 // indirect 194 | mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect 195 | ) 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 2 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= 4 | github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 5 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 6 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 7 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 8 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 9 | github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= 10 | github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= 11 | github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= 12 | github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= 13 | github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= 14 | github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= 15 | github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= 16 | github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= 17 | github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= 18 | github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= 19 | github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= 20 | github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= 21 | github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= 22 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 23 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 24 | github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= 25 | github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 26 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 27 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 28 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 29 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 30 | github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= 31 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 32 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 33 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 34 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 35 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 36 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 37 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 38 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 39 | github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= 40 | github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= 41 | github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= 42 | github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= 43 | github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= 44 | github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= 45 | github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= 46 | github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= 47 | github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= 48 | github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= 49 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= 50 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= 51 | github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= 52 | github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= 53 | github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= 54 | github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= 55 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= 56 | github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= 57 | github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= 58 | github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= 59 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 60 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 61 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 62 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 63 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= 65 | github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= 66 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 67 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= 68 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= 69 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 70 | github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= 71 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 72 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 73 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 74 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 75 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 76 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 77 | github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= 78 | github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= 79 | github.com/gnolang/gno v0.0.0-20250926084639-6974fdb8ae0e h1:4EufBiixviwmTCCJXS8A28oBLNygeCBE1PjiXPFZ6EI= 80 | github.com/gnolang/gno v0.0.0-20250926084639-6974fdb8ae0e/go.mod h1:j9wKq29meqwktEj2ReqPbSkeYUwoisfxHHVeV20lhtw= 81 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 82 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 83 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 84 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 85 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 86 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 87 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 88 | github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= 89 | github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= 90 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 91 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 92 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 93 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 94 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 95 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 96 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 97 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 98 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 99 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 100 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 101 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 102 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 103 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 104 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 106 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 108 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 109 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 110 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 111 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 112 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 113 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 114 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 115 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 116 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= 117 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= 118 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 119 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 120 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 121 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 122 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 123 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 124 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 125 | github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= 126 | github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 127 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 128 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 129 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 130 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 131 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 132 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 133 | github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= 134 | github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= 135 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 136 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 137 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 138 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 139 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 140 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 141 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 142 | github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= 143 | github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= 144 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 145 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 146 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 147 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 148 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 149 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 150 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 151 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 152 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 153 | github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= 154 | github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= 155 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 156 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 157 | github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= 158 | github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= 159 | github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= 160 | github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 161 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 162 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 163 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 164 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 165 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 166 | github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= 167 | github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 168 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= 169 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 170 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 171 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 172 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 173 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 174 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 175 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 176 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 177 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 178 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 179 | github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= 180 | github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= 181 | github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= 182 | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 183 | github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA= 184 | github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= 185 | github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= 186 | github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= 187 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 188 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 189 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 190 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 191 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 192 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 193 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 194 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 195 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 196 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= 197 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 198 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 199 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 200 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 201 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 202 | github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= 203 | github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= 204 | github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= 205 | github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= 206 | go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= 207 | go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= 208 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 209 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 210 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 211 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 212 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= 213 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= 214 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= 215 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= 216 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 217 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 218 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 219 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 220 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 221 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 222 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 223 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 224 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 225 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 226 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 227 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 228 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 229 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 230 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 231 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 232 | golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= 233 | golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= 234 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= 235 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 236 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 237 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 238 | golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= 239 | golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 240 | golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 241 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 242 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 244 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 246 | golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 247 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 248 | golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= 249 | golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 250 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 251 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 252 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 253 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 254 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 256 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 257 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 258 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 259 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 260 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 261 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 262 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 263 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 264 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 266 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 267 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 268 | golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= 269 | golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 270 | golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= 271 | golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= 272 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 273 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 274 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 275 | golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 276 | golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 277 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 278 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 279 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 280 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 281 | golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= 282 | golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 283 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 284 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 285 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 286 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 287 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= 288 | google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= 289 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= 290 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= 291 | google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= 292 | google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 293 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 294 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 295 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 296 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 297 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 298 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 299 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 300 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 301 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 302 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 303 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 304 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 305 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 306 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 307 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 308 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 309 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 310 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 311 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 312 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 313 | --------------------------------------------------------------------------------