diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 94c05f784..71057a522 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -36,8 +36,6 @@ jobs: binaries: | - /go/bin/layerd heighliner-tag: v1.7.5 - additional-args: | - --go-version 1.24.13 --alpine-version 3.22 - name: Publish Primary Tarball as Artifact uses: actions/upload-artifact@v4 @@ -72,8 +70,6 @@ jobs: binaries: | - /go/bin/layerd heighliner-tag: v1.7.5 - additional-args: | - --go-version 1.24.13 --alpine-version 3.22 - name: Publish IBC Tarball as Artifact uses: actions/upload-artifact@v4 @@ -97,7 +93,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - name: Generate Matrix id: set-matrix @@ -129,7 +125,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - name: Download Primary Tarball uses: actions/download-artifact@v4 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4dad66b69..878566518 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -20,9 +20,9 @@ jobs: run: go clean -modcache - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - name: Build run: go build -v ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9f07c0c85..0e871ae51 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,9 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: - go-version: "1.24" + go-version: "1.25" check-latest: true - uses: technote-space/get-diff-action@v6.1.2 id: git_diff diff --git a/.golangci.yml b/.golangci.yml index 7f6708ed3..14129c31a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,30 +1,20 @@ +version: "2" run: + build-tags: + - e2e + - ledger + - test_ledger_mock tests: true - timeout: 15m - sort-results: true allow-parallel-runners: true - exclude-dir: testutil/testdata - - -build-tags: - - e2e - - ledger - - test_ledger_mock - - linters: - disable-all: true + default: none enable: - dogsled - errcheck - errorlint - # - copyloopvar - - gci - goconst - gocritic - - gofumpt - gosec - - gosimple - govet - ineffassign - misspell @@ -32,127 +22,154 @@ linters: - nolintlint - revive - staticcheck - - stylecheck - thelper - - typecheck - unconvert - unused - -issues: - exclude-files: + settings: + dogsled: + max-blank-identifiers: 8 + gocritic: + disabled-checks: + - regexpMust + - appendAssign + - ifElseChain + gosec: + includes: + - G102 + - G103 + - G104 + - G106 + - G107 + - G108 + - G109 + - G110 + - G111 + - G112 + - G113 + - G114 + - G201 + - G202 + - G203 + - G204 + - G301 + - G302 + - G303 + - G304 + - G305 + - G306 + - G307 + - G401 + - G402 + - G403 + - G404 + - G501 + - G502 + - G503 + - G504 + - G505 + - G601 + misspell: + locale: US + nolintlint: + require-explanation: true + require-specific: false + allow-unused: false + revive: + rules: + - name: redefines-builtin-id + disabled: true + staticcheck: + checks: + - all + - -QF1001 + - -QF1002 + - -QF1003 + - -QF1004 + - -QF1005 + - -QF1006 + - -QF1007 + - -QF1008 + - -QF1009 + - -QF1010 + - -QF1011 + - -QF1012 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - gosec + text: Use of weak random number generator + - linters: + - staticcheck + text: 'ST1003:' + - linters: + - staticcheck + text: 'ST1016:' + - linters: + - staticcheck + path: migrations + text: 'SA1019:' + - linters: + - staticcheck + text: 'SA1019: codec.NewAminoCodec is deprecated' + - linters: + - staticcheck + text: 'SA1019: legacybech32.MustMarshalPubKey' + - linters: + - staticcheck + text: 'SA1019: legacybech32.MarshalPubKey' + - linters: + - staticcheck + text: 'SA1019: legacybech32.UnmarshalPubKey' + - linters: + - staticcheck + text: 'SA1019: params.SendEnabled is deprecated' + - linters: + - nolintlint + text: leading space + paths: - server/grpc/gogoreflection/fix_registration.go - - "fix_registration.go" - - ".*\\.pb\\.go$" - - ".*\\.pb\\.gw\\.go$" - - ".*\\.pulsar\\.go$" + - fix_registration.go + - .*\.pb\.go$ + - .*\.pb\.gw\.go$ + - .*\.pulsar\.go$ - crypto/keys/secp256k1/internal/* - # - types/coin_regex.go - exclude-rules: - - text: "Use of weak random number generator" - linters: - - gosec - - text: "ST1003:" - linters: - - stylecheck - # FIXME: Disabled until golangci-lint updates stylecheck with this fix: - # https://github.com/dominikh/go-tools/issues/389 - - text: "ST1016:" - linters: - - stylecheck - - path: "migrations" - text: "SA1019:" - linters: - - staticcheck - - text: "SA1019: codec.NewAminoCodec is deprecated" # TODO remove once migration path is set out - linters: - - staticcheck - - text: "SA1019: legacybech32.MustMarshalPubKey" # TODO remove once ready to remove from the sdk - linters: - - staticcheck - - text: "SA1019: legacybech32.MarshalPubKey" # TODO remove once ready to remove from the sdk - linters: - - staticcheck - - text: "SA1019: legacybech32.UnmarshalPubKey" # TODO remove once ready to remove from the sdk - linters: - - staticcheck - - text: "SA1019: params.SendEnabled is deprecated" # TODO remove once ready to remove from the sdk - linters: - - staticcheck - - text: "leading space" - linters: - - nolintlint + - testutil/testdata + - third_party$ + - builtin$ + - examples$ +issues: max-issues-per-linter: 10000 max-same-issues: 10000 - -linters-settings: - gci: - custom-order: true - sections: - - standard # Standard section: captures all standard packages. - - default # Default section: contains all imports that could not be matched to another section type. - - prefix(cosmossdk.io) - - prefix(github.com/cosmos/cosmos-sdk) - revive: - rules: - - name: redefines-builtin-id - disabled: true - - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - # Default: [] - means include all rules - includes: - # - G101 # Look for hard coded credentials - - G102 # Bind to all interfaces - - G103 # Audit the use of unsafe block - - G104 # Audit errors not checked - - G106 # Audit the use of ssh.InsecureIgnoreHostKey - - G107 # Url provided to HTTP request as taint input - - G108 # Profiling endpoint automatically exposed on /debug/pprof - - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 - - G110 # Potential DoS vulnerability via decompression bomb - - G111 # Potential directory traversal - - G112 # Potential slowloris attack - - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) - - G114 # Use of net/http serve function that has no support for setting timeouts - - G201 # SQL query construction using format string - - G202 # SQL query construction using string concatenation - - G203 # Use of unescaped data in HTML templates - - G204 # Audit use of command execution - - G301 # Poor file permissions used when creating a directory - - G302 # Poor file permissions used with chmod - - G303 # Creating tempfile using a predictable path - - G304 # File path provided as taint input - - G305 # File traversal when extracting zip/tar archive - - G306 # Poor file permissions used when writing to a new file - - G307 # Deferring a method which returns an error - - G401 # Detect the usage of DES, RC4, MD5 or SHA1 - - G402 # Look for bad TLS connection settings - - G403 # Ensure minimum RSA key length of 2048 bits - - G404 # Insecure random number source (rand) - - G501 # Import blocklist: crypto/md5 - - G502 # Import blocklist: crypto/des - - G503 # Import blocklist: crypto/rc4 - - G504 # Import blocklist: net/http/cgi - - G505 # Import blocklist: crypto/sha1 - - G601 # Implicit memory aliasing of items from a range statement - misspell: - locale: US - gofumpt: - extra-rules: true - dogsled: - max-blank-identifiers: 8 - maligned: - suggest-new: true - nolintlint: - allow-unused: false - allow-leading-space: true - require-explanation: true - require-specific: false - gosimple: - checks: ["all"] - gocritic: - disabled-checks: - - regexpMust - - appendAssign - - ifElseChain +formatters: + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - default + - prefix(cosmossdk.io) + - prefix(github.com/cosmos/cosmos-sdk) + custom-order: true + gofumpt: + extra-rules: true + exclusions: + generated: lax + paths: + - server/grpc/gogoreflection/fix_registration.go + - fix_registration.go + - .*\.pb\.go$ + - .*\.pb\.gw\.go$ + - .*\.pulsar\.go$ + - crypto/keys/secp256k1/internal/* + - testutil/testdata + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 74591c247..e5aa8fd32 100644 --- a/Makefile +++ b/Makefile @@ -79,8 +79,7 @@ build-with-checksum: build-linux-with-checksum build-darwin-with-checksum ### Linting ### ############################################################################### # Golangci-lint version -golangci_version=v1.64.0 -golangci_toolchain=go1.24.13 +golangci_version=v2.11.4 #? setup-pre-commit: Set pre-commit git hook setup-pre-commit: @@ -92,7 +91,7 @@ setup-pre-commit: #? lint-install: Install golangci-lint lint-install: @echo "--> Installing golangci-lint $(golangci_version)" - @GOTOOLCHAIN=$(golangci_toolchain) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) + @go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(golangci_version) #? lint: Run golangci-lint lint: @@ -296,7 +295,7 @@ local-image: ifeq (,$(shell which heighliner)) echo 'heighliner' binary not found. Consider running `make get-heighliner` else - heighliner build -c layer --local --dockerfile cosmos --go-version 1.24.13 --alpine-version 3.22 --build-target "make install" --binaries "/go/bin/layerd" + heighliner build -c layer --local --dockerfile cosmos --build-target "make install" --binaries "/go/bin/layerd" endif get-localic: diff --git a/app/app.go b/app/app.go index c4975fd2d..478c97757 100644 --- a/app/app.go +++ b/app/app.go @@ -636,7 +636,16 @@ func New( ), ) - voteExtHandler := NewVoteExtHandler(app.Logger(), app.AppCodec(), app.OracleKeeper, app.BridgeKeeper) + // Attempt to initialize vote extension signer using a configured remote signer if available. + voteExtSigner, err := RemoteVoteExtensionSigner(appCodec) + if err != nil { + panic(fmt.Sprintf("failed to initialize remote vote extension signer: %v", err)) + } + if voteExtSigner == nil { + app.Logger().Info("bridge vote extension signer deferred; will be built on first ExtendVote if CometBFT invokes this node as a validator") + } + + voteExtHandler := NewVoteExtHandler(app.Logger(), app.AppCodec(), app.OracleKeeper, app.BridgeKeeper, voteExtSigner) app.BaseApp.SetExtendVoteHandler(voteExtHandler.ExtendVoteHandler) app.BaseApp.SetVerifyVoteExtensionHandler(voteExtHandler.VerifyVoteExtensionHandler) diff --git a/app/extend_vote.go b/app/extend_vote.go index 0d629d6ab..d2629332e 100644 --- a/app/extend_vote.go +++ b/app/extend_vote.go @@ -10,12 +10,12 @@ import ( "fmt" "io" "os" + "sync" "syscall" "time" abci "github.com/cometbft/cometbft/abci/types" "github.com/ethereum/go-ethereum/common" - "github.com/spf13/viper" bridgetypes "github.com/tellor-io/layer/x/bridge/types" oracletypes "github.com/tellor-io/layer/x/oracle/types" registrytypes "github.com/tellor-io/layer/x/registry/types" @@ -24,7 +24,6 @@ import ( "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -63,7 +62,13 @@ type VoteExtHandler struct { oracleKeeper OracleKeeper bridgeKeeper BridgeKeeper codec codec.Codec - kr keyring.Keyring + + // signer may be nil at startup when the keyring path is configured + // (or when nothing is configured at all). It is populated on + // the first ExtendVote invocation via ensureSigner. + signerInitOnce sync.Once + signerInitErr error + signer VoteExtensionSigner } type OracleAttestation struct { @@ -87,15 +92,37 @@ type BridgeVoteExtension struct { ValsetSignature BridgeValsetSignature } -func NewVoteExtHandler(logger log.Logger, appCodec codec.Codec, oracleKeeper OracleKeeper, bridgeKeeper BridgeKeeper) *VoteExtHandler { +func NewVoteExtHandler(logger log.Logger, appCodec codec.Codec, oracleKeeper OracleKeeper, bridgeKeeper BridgeKeeper, signer VoteExtensionSigner) *VoteExtHandler { return &VoteExtHandler{ oracleKeeper: oracleKeeper, bridgeKeeper: bridgeKeeper, logger: logger, codec: appCodec, + signer: signer, } } +// ensureSigner is called at the top of ExtendVote. If a signer was +// supplied at startup (remote signer path), it is a noop. Otherwise it +// attempts to build a KeyringSigner from viper once, caches the result, +// and returns any error to the caller. Subsequent calls return the +// cached state. +func (h *VoteExtHandler) ensureSigner() error { + if h.signer != nil { + return nil + } + h.signerInitOnce.Do(func() { + h.logger.Info("init of bridge vote extension signer (keyring path)") + signer, err := NewKeyringSignerFromViperIfSet(h.codec) + if err != nil { + h.signerInitErr = err + return + } + h.signer = signer + }) + return h.signerInitErr +} + func (h *VoteExtHandler) ForceProcessTermination(format string, args ...interface{}) { h.logger.Error(format, args...) // Send SIGABRT to the current process @@ -109,9 +136,14 @@ func (h *VoteExtHandler) ForceProcessTermination(format string, args ...interfac } func (h *VoteExtHandler) ExtendVoteHandler(ctx sdk.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { + if err := h.ensureSigner(); err != nil { + h.ForceProcessTermination("CRITICAL: CometBFT invoked ExtendVote but the bridge signer is unavailable: %v. ", err) + return nil, err + } + voteExt := BridgeVoteExtension{} - operatorAddress, errOp := h.GetOperatorAddress() + operatorAddress, errOp := h.signer.GetOperatorAddress(ctx) if errOp != nil { h.logger.Error("ExtendVoteHandler: failed to get operator address", "error", errOp) h.ForceProcessTermination("CRITICAL: failed to get operator address: %v", errOp) @@ -122,19 +154,12 @@ func (h *VoteExtHandler) ExtendVoteHandler(ctx sdk.Context, req *abci.RequestExt initialSigA, initialSigB, err := h.SignInitialMessage(operatorAddress) if err != nil { h.logger.Info("ExtendVoteHandler: failed to sign initial message", "error", err) - bz, err := json.Marshal(voteExt) - if err != nil { - h.logger.Error("ExtendVoteHandler: failed to marshal vote extension", "error", err) - return &abci.ResponseExtendVote{}, err - } - return &abci.ResponseExtendVote{VoteExtension: bz}, nil + return h.marshalVoteExt(voteExt) } - // include the initial sig in the vote extension - initialSignature := InitialSignature{ + voteExt.InitialSignature = InitialSignature{ SignatureA: initialSigA, SignatureB: initialSigB, } - voteExt.InitialSignature = initialSignature } // generate oracle attestations and include them via vote extensions blockHeight := ctx.BlockHeight() - 1 @@ -142,52 +167,37 @@ func (h *VoteExtHandler) ExtendVoteHandler(ctx sdk.Context, req *abci.RequestExt if err != nil { if !errors.Is(err, collections.ErrNotFound) { h.logger.Error("ExtendVoteHandler: failed to get attestation requests", "error", err) - bz, err := json.Marshal(voteExt) - if err != nil { - h.logger.Error("ExtendVoteHandler: failed to marshal vote extension", "error", err) - return &abci.ResponseExtendVote{}, err - } - return &abci.ResponseExtendVote{VoteExtension: bz}, nil + return h.marshalVoteExt(voteExt) } - } else { - snapshots := attestationRequests.Requests - // iterate through snapshots and generate sigs - if len(snapshots) > 0 { - for _, snapshot := range snapshots { - sig, err := h.SignMessage(snapshot.Snapshot) - if err != nil { - h.logger.Error("ExtendVoteHandler: failed to sign message", "error", err) - bz, err := json.Marshal(voteExt) - if err != nil { - h.logger.Error("ExtendVoteHandler: failed to marshal vote extension", "error", err) - return &abci.ResponseExtendVote{}, err - } - return &abci.ResponseExtendVote{VoteExtension: bz}, nil - } - oracleAttestation := OracleAttestation{ - Snapshot: snapshot.Snapshot, - Attestation: sig, - } - voteExt.OracleAttestations = append(voteExt.OracleAttestations, oracleAttestation) + } else if len(attestationRequests.Requests) > 0 { + for _, snapshot := range attestationRequests.Requests { + sig, err := h.signer.Sign(ctx, snapshot.Snapshot) + if err != nil { + h.logger.Error("ExtendVoteHandler: failed to sign attestation", "error", err) + return h.marshalVoteExt(voteExt) } + voteExt.OracleAttestations = append(voteExt.OracleAttestations, OracleAttestation{ + Snapshot: snapshot.Snapshot, + Attestation: sig, + }) } } // include the valset sig in the vote extension sig, timestamp, err := h.CheckAndSignValidatorCheckpoint(ctx) if err != nil { h.logger.Error("ExtendVoteHandler: failed to sign validator checkpoint", "error", err) - bz, err := json.Marshal(voteExt) - if err != nil { - h.logger.Error("ExtendVoteHandler: failed to marshal vote extension", "error", err) - return &abci.ResponseExtendVote{}, fmt.Errorf("failed to marshal vote extension: %w", err) - } - return &abci.ResponseExtendVote{VoteExtension: bz}, nil + return h.marshalVoteExt(voteExt) } - valsetSignature := BridgeValsetSignature{ + voteExt.ValsetSignature = BridgeValsetSignature{ Signature: sig, Timestamp: timestamp, } - voteExt.ValsetSignature = valsetSignature + + return h.marshalVoteExt(voteExt) +} + +// marshalVoteExt marshals the vote extension, returning an error only if marshaling itself fails. +func (h *VoteExtHandler) marshalVoteExt(voteExt BridgeVoteExtension) (*abci.ResponseExtendVote, error) { bz, err := json.Marshal(voteExt) if err != nil { h.logger.Error("ExtendVoteHandler: failed to marshal vote extension", "error", err) @@ -260,25 +270,6 @@ func (h *VoteExtHandler) VerifyVoteExtensionHandler(ctx sdk.Context, req *abci.R return &abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil } -func (h *VoteExtHandler) SignMessage(msg []byte) ([]byte, error) { - kr, err := h.GetKeyring() - if err != nil { - h.ForceProcessTermination("CRITICAL: failed to get keyring: %v", err) - return nil, err // won't reach here - } - keyName := viper.GetString("key-name") - if keyName == "" { - h.ForceProcessTermination("CRITICAL: key name not found, please set --key-name flag") - return nil, errors.New("missing key name") // won't reach here - } - sig, _, err := kr.Sign(keyName, msg, 1) - if err != nil { - h.ForceProcessTermination("CRITICAL: failed to sign message: %v", err) - return nil, err // won't reach here - } - return sig, nil -} - func (h *VoteExtHandler) SignInitialMessage(operatorAddress string) ([]byte, []byte, error) { messageA := fmt.Sprintf("TellorLayer: Initial bridge signature A for operator %s", operatorAddress) messageB := fmt.Sprintf("TellorLayer: Initial bridge signature B for operator %s", operatorAddress) @@ -296,59 +287,18 @@ func (h *VoteExtHandler) SignInitialMessage(operatorAddress string) ([]byte, []b msgHashBBytes := msgHashBBytes32[:] // sign message - sigA, err := h.SignMessage(msgHashABytes) + sigA, err := h.signer.Sign(context.Background(), msgHashABytes) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to sign message A: %w", err) } - sigB, err := h.SignMessage(msgHashBBytes) + sigB, err := h.signer.Sign(context.Background(), msgHashBBytes) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to sign message B: %w", err) } return sigA, sigB, nil } -func (h *VoteExtHandler) GetOperatorAddress() (string, error) { - kr, err := h.GetKeyring() - if err != nil { - h.logger.Error("GetOperatorAddress: failed to get keyring", "error", err) - return "", fmt.Errorf("failed to get keyring: %w", err) - } - keyName := viper.GetString("key-name") - if keyName == "" { - h.logger.Error("GetOperatorAddress: key name not found") - return "", fmt.Errorf("key name not found, please set --key-name flag") - } - // list all keys - krlist, err := kr.List() - if err != nil { - h.logger.Error("GetOperatorAddress: failed to list keys", "error", err) - return "", fmt.Errorf("failed to list keys: %w", err) - } - if len(krlist) == 0 { - h.logger.Error("GetOperatorAddress: no keys found in keyring") - return "", fmt.Errorf("no keys found in keyring") - } - - // Fetch the operator key from the keyring. - info, err := kr.Key(keyName) - if err != nil { - h.logger.Error("GetOperatorAddress: failed to get operator key", "keyName", keyName, "error", err) - return "", fmt.Errorf("failed to get operator key: %w", err) - } - // Output the public key associated with the operator key. - key, _ := info.GetPubKey() - - // Convert the operator's public key to a Bech32 validator address - config := sdk.GetConfig() - bech32PrefixValAddr := config.GetBech32ValidatorAddrPrefix() - bech32ValAddr, err := sdk.Bech32ifyAddressBytes(bech32PrefixValAddr, key.Address().Bytes()) - if err != nil { - return "", fmt.Errorf("failed to convert operator public key to Bech32 validator address: %w", err) - } - return bech32ValAddr, nil -} - func (h *VoteExtHandler) CheckAndSignValidatorCheckpoint(ctx context.Context) (signature []byte, timestamp uint64, err error) { // get latest checkpoint index latestCheckpointIdx, err := h.bridgeKeeper.GetLatestCheckpointIndex(ctx) @@ -363,7 +313,7 @@ func (h *VoteExtHandler) CheckAndSignValidatorCheckpoint(ctx context.Context) (s return nil, 0, err } - operatorAddress, err := h.GetOperatorAddress() + operatorAddress, err := h.signer.GetOperatorAddress(ctx) if err != nil { h.logger.Error("failed to get operator address", "error", err) return nil, 0, err @@ -373,26 +323,24 @@ func (h *VoteExtHandler) CheckAndSignValidatorCheckpoint(ctx context.Context) (s h.logger.Error("failed to get validator did sign checkpoint", "error", err) return nil, 0, err } - if didSign { - return nil, 0, nil - } else if valIndex < 0 { + if didSign || valIndex < 0 { return nil, 0, nil - } else { - // sign the latest checkpoint - checkpointParams, err := h.bridgeKeeper.GetValidatorCheckpointParamsFromStorage(ctx, latestCheckpointTimestamp.Timestamp) - if err != nil { - h.logger.Error("failed to get checkpoint params", "error", err) - return nil, 0, err - } - checkpoint := checkpointParams.Checkpoint - checkpointString := hex.EncodeToString(checkpoint) - signature, err := h.EncodeAndSignMessage(checkpointString) - if err != nil { - h.logger.Error("failed to encode and sign message", "error", err) - return nil, 0, err - } - return signature, latestCheckpointTimestamp.Timestamp, nil } + + // sign the latest checkpoint + checkpointParams, err := h.bridgeKeeper.GetValidatorCheckpointParamsFromStorage(ctx, latestCheckpointTimestamp.Timestamp) + if err != nil { + h.logger.Error("failed to get checkpoint params", "error", err) + return nil, 0, err + } + checkpoint := checkpointParams.Checkpoint + checkpointString := hex.EncodeToString(checkpoint) + signature, err = h.EncodeAndSignMessage(checkpointString) + if err != nil { + h.logger.Error("failed to encode and sign message", "error", err) + return nil, 0, err + } + return signature, latestCheckpointTimestamp.Timestamp, nil } func (h *VoteExtHandler) GetValidatorIndexInValset(ctx context.Context, evmAddress []byte, valset *bridgetypes.BridgeValidatorSet) (int, error) { @@ -411,40 +359,10 @@ func (h *VoteExtHandler) EncodeAndSignMessage(checkpointString string) ([]byte, h.logger.Error("Failed to decode checkpoint", "error", err) return nil, err } - signature, err := h.SignMessage(checkpoint) + signature, err := h.signer.Sign(context.Background(), checkpoint) if err != nil { h.logger.Error("Failed to sign message", "error", err) return nil, err } return signature, nil } - -func (h *VoteExtHandler) InitKeyring() (keyring.Keyring, error) { - krBackend := viper.GetString("keyring-backend") - if krBackend == "" { - return nil, fmt.Errorf("keyring-backend not set, please use --keyring-backend flag") - } - krDir := viper.GetString("keyring-dir") - if krDir == "" { - krDir = viper.GetString("home") - } - if krDir == "" { - return nil, fmt.Errorf("keyring directory not set, please use --home or --keyring-dir flag") - } - kr, err := keyring.New(sdk.KeyringServiceName(), krBackend, krDir, os.Stdin, h.codec) - if err != nil { - return nil, err - } - return kr, nil -} - -func (h *VoteExtHandler) GetKeyring() (keyring.Keyring, error) { - if h.kr == nil { - kr, err := h.InitKeyring() - if err != nil { - return nil, err - } - h.kr = kr - } - return h.kr, nil -} diff --git a/app/extend_vote_test.go b/app/extend_vote_test.go index 48539f32b..8fd55a65c 100644 --- a/app/extend_vote_test.go +++ b/app/extend_vote_test.go @@ -1,6 +1,8 @@ package app_test import ( + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -10,7 +12,7 @@ import ( "github.com/agiledragon/gomonkey/v2" abci "github.com/cometbft/cometbft/abci/types" "github.com/ethereum/go-ethereum/common" - "github.com/spf13/viper" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "github.com/tellor-io/layer/app" "github.com/tellor-io/layer/app/mocks" @@ -18,21 +20,17 @@ import ( "github.com/tellor-io/layer/testutil/sample" bridgetypes "github.com/tellor-io/layer/x/bridge/types" - "cosmossdk.io/api/cosmos/crypto/secp256k1" "cosmossdk.io/collections" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" ) type VoteExtensionTestSuite struct { suite.Suite ctx sdk.Context - kr *mocks.Keyring cdc codec.Codec } @@ -41,22 +39,23 @@ func (s *VoteExtensionTestSuite) SetupTest() { s.cdc = codec.NewProtoCodec(registry) s.ctx = testutils.CreateTestContext(s.T()) - viper.Reset() } -func (s *VoteExtensionTestSuite) CreateHandlerAndMocks() (*app.VoteExtHandler, *mocks.OracleKeeper, *mocks.BridgeKeeper, *mocks.StakingKeeper) { +func (s *VoteExtensionTestSuite) CreateHandlerAndMocks() (*app.VoteExtHandler, *mocks.OracleKeeper, *mocks.BridgeKeeper, *mocks.StakingKeeper, *mocks.VoteExtensionSigner) { oracleKeeper := mocks.NewOracleKeeper(s.T()) bridgeKeeper := mocks.NewBridgeKeeper(s.T()) stakingKeeper := mocks.NewStakingKeeper(s.T()) + signer := mocks.NewVoteExtensionSigner(s.T()) handler := app.NewVoteExtHandler( log.NewNopLogger(), s.cdc, oracleKeeper, bridgeKeeper, + signer, ) - return handler, oracleKeeper, bridgeKeeper, stakingKeeper + return handler, oracleKeeper, bridgeKeeper, stakingKeeper, signer } func TestVoteExtensionTestSuite(t *testing.T) { @@ -66,7 +65,7 @@ func TestVoteExtensionTestSuite(t *testing.T) { // TODO: turn into test case array func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler() { require := s.Require() - h, _, bk, _ := s.CreateHandlerAndMocks() + h, _, bk, _, _ := s.CreateHandlerAndMocks() res, err := h.VerifyVoteExtensionHandler(s.ctx, &abci.RequestVerifyVoteExtension{}) require.NoError(err) @@ -230,7 +229,7 @@ func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler() { func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsUnknownFields() { require := s.Require() - h, _, bk, _ := s.CreateHandlerAndMocks() + h, _, bk, _, _ := s.CreateHandlerAndMocks() s.ctx = s.ctx.WithBlockHeight(3) attReq := bridgetypes.AttestationRequests{ @@ -273,7 +272,7 @@ func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsUnknownFields() func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsTrailingJSONData() { require := s.Require() - h, _, _, _ := s.CreateHandlerAndMocks() + h, _, _, _, _ := s.CreateHandlerAndMocks() s.ctx = s.ctx.WithBlockHeight(3) validVE := &app.BridgeVoteExtension{ @@ -302,7 +301,7 @@ func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsTrailingJSONDat func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsOversizedRawVE() { require := s.Require() - h, _, _, _ := s.CreateHandlerAndMocks() + h, _, _, _, _ := s.CreateHandlerAndMocks() // create a payload larger than maxVoteExtensionSize (512KB) oversized := make([]byte, 512*1024+1) @@ -315,7 +314,7 @@ func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsOversizedRawVE( func (s *VoteExtensionTestSuite) TestVerifyVoteExtHandler_RejectsOversizedAttestationFields() { require := s.Require() - h, _, bk, _ := s.CreateHandlerAndMocks() + h, _, bk, _, _ := s.CreateHandlerAndMocks() s.ctx = s.ctx.WithBlockHeight(3) attReq := bridgetypes.AttestationRequests{ @@ -363,7 +362,7 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { type testCase struct { name string - setupMocks func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) + setupMocks func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) expectedPanic bool validateResponse func(*abci.ResponseExtendVote) } @@ -371,12 +370,10 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { testCases := []testCase{ { name: "err on SignInitialMessage", - setupMocks: func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetEVMAddressByOperator", ctx, oppAddr).Return(nil, collections.ErrNotFound) - patches.ApplyMethod(reflect.TypeOf(h), "SignInitialMessage", func(_ *app.VoteExtHandler) ([]byte, []byte, error) { + patches.ApplyMethod(reflect.TypeOf(h), "SignInitialMessage", func(_ *app.VoteExtHandler, operatorAddress string) ([]byte, []byte, error) { return nil, nil, errors.New("error!") }) return bk, patches @@ -388,10 +385,8 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { }, { name: "err on GetOperatorAddress", - setupMocks: func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return "", errors.New("error!") - }) + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + signer.On("GetOperatorAddress", mock.Anything).Return("", errors.New("error!")) return bk, patches }, expectedPanic: true, @@ -401,12 +396,10 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { }, { name: "err on GetAttestationRequestsByHeight", - setupMocks: func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetEVMAddressByOperator", ctx, oppAddr).Return(nil, collections.ErrNotFound) - patches.ApplyMethod(reflect.TypeOf(h), "SignInitialMessage", func(_ *app.VoteExtHandler) ([]byte, []byte, error) { + patches.ApplyMethod(reflect.TypeOf(h), "SignInitialMessage", func(_ *app.VoteExtHandler, operatorAddress string) ([]byte, []byte, error) { return []byte("signatureA"), []byte("signatureB"), nil }) bk.On("GetAttestationRequestsByHeight", ctx, uint64(2)).Return((*bridgetypes.AttestationRequests)(nil), errors.New("error!")) @@ -417,12 +410,34 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { require.NotNil(resp) }, }, + { + name: "no EVM address, real SignInitialMessage succeeds, no attestations", + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) + bk.On("GetEVMAddressByOperator", ctx, oppAddr).Return(nil, collections.ErrNotFound) + // Let real SignInitialMessage run — mock signer.Sign with exact expected hashes + hashA := sha256.Sum256([]byte(fmt.Sprintf("TellorLayer: Initial bridge signature A for operator %s", oppAddr))) + hashB := sha256.Sum256([]byte(fmt.Sprintf("TellorLayer: Initial bridge signature B for operator %s", oppAddr))) + signer.On("Sign", mock.Anything, hashA[:]).Return([]byte("sigA"), nil).Once() + signer.On("Sign", mock.Anything, hashB[:]).Return([]byte("sigB"), nil).Once() + bk.On("GetAttestationRequestsByHeight", ctx, uint64(2)).Return(nil, collections.ErrNotFound) + bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(0), errors.New("no checkpoint")) + return bk, patches + }, + expectedPanic: false, + validateResponse: func(resp *abci.ResponseExtendVote) { + require.NotNil(resp) + // Verify the initial signatures were included in the vote extension + var voteExt app.BridgeVoteExtension + require.NoError(json.Unmarshal(resp.VoteExtension, &voteExt)) + require.Equal([]byte("sigA"), voteExt.InitialSignature.SignatureA) + require.Equal([]byte("sigB"), voteExt.InitialSignature.SignatureB) + }, + }, { name: "err signing checkpoint", - setupMocks: func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetEVMAddressByOperator", ctx, oppAddr).Return(evmAddr.Bytes(), nil) attReq := bridgetypes.AttestationRequests{ Requests: []*bridgetypes.AttestationRequest{ @@ -432,9 +447,7 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { }, } bk.On("GetAttestationRequestsByHeight", ctx, uint64(2)).Return(&attReq, nil) - patches.ApplyMethod(reflect.TypeOf(h), "SignMessage", func(_ *app.VoteExtHandler, msg []byte) ([]byte, error) { - return []byte("signedMsg"), nil - }) + signer.On("Sign", mock.Anything, []byte("snapshot")).Return([]byte("signedMsg"), nil) bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(0), errors.New("error")) return bk, patches }, @@ -446,20 +459,18 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { { name: "no errors", // order: - // 1. h.GetOperatorAddress() + // 1. h.signer.GetOperatorAddress() // 2. h.bk.GetEVMAddressByOperator() // 3. h.bk.GetAttestationRequestsByHeight() - // 4. h.SignMessage() + // 4. h.signer.Sign() // 5. h.CheckAndSignValidatorCheckpoint() // 5a. h.bk.GetLatestCheckpointIndex() // 5b. h.bk.GetValidatorTimestampByIdxFromStorage() - // 5c. h.GetOperatorAddress + // 5c. h.signer.GetOperatorAddress() // 5d. h.bk.GetValidatorDidSignCheckpoint() - setupMocks: func(bk *mocks.BridgeKeeper, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { - // 1. - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + setupMocks: func(bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, h *app.VoteExtHandler, patches *gomonkey.Patches) (*mocks.BridgeKeeper, *gomonkey.Patches) { + // 1 + 5c. + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) // 2. bk.On("GetEVMAddressByOperator", ctx, oppAddr).Return(evmAddr.Bytes(), nil) attReq := bridgetypes.AttestationRequests{ @@ -472,10 +483,7 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { // 3. bk.On("GetAttestationRequestsByHeight", ctx, uint64(2)).Return(&attReq, nil) // 4. - patches.ApplyMethod(reflect.TypeOf(h), "SignMessage", - func(_ *app.VoteExtHandler, msg []byte) ([]byte, error) { - return []byte("signedMsg"), nil - }) + signer.On("Sign", mock.Anything, []byte("snapshot")).Return([]byte("signedMsg"), nil) // 5a. bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(1), nil).Once() checkpointTimestamp := bridgetypes.CheckpointTimestamp{ @@ -483,10 +491,6 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { } // 5b. bk.On("GetValidatorTimestampByIdxFromStorage", ctx, uint64(1)).Return(checkpointTimestamp, nil) - // 5c. - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) // 5d. bk.On("GetValidatorDidSignCheckpoint", ctx, oppAddr, uint64(1)).Return(true, int64(1), nil) @@ -505,9 +509,9 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { s.T().Cleanup(func() { patches.Reset() }) - h, _, bk, _ := s.CreateHandlerAndMocks() + h, _, bk, _, signer := s.CreateHandlerAndMocks() if tc.setupMocks != nil { - bk, patches = tc.setupMocks(bk, h, patches) + bk, patches = tc.setupMocks(bk, signer, h, patches) require.NotNil(bk) require.NotNil(patches) } @@ -536,169 +540,64 @@ func (s *VoteExtensionTestSuite) TestExtendVoteHandler() { } } -func (s *VoteExtensionTestSuite) TestGetKeyring() { - require := s.Require() - h, _, _, _ := s.CreateHandlerAndMocks() - - viper.Set("keyring-backend", "test") - viper.Set("keyring-dir", s.T().TempDir()) - kr, err := h.GetKeyring() - require.NoError(err) - require.NotNil(kr) - - s.T().Cleanup(func() { - viper.Reset() - }) -} - -func (s *VoteExtensionTestSuite) TestGetOperatorAddress() { +func (s *VoteExtensionTestSuite) TestSignInitialMessage() { require := s.Require() - type testCase struct { - name string - setupMocks func(h *app.VoteExtHandler, patches *gomonkey.Patches) - expectedError bool - expectedErrorMsg string - } + operatorAddr := "operatorAddr1" + expectedHashA := sha256.Sum256([]byte(fmt.Sprintf("TellorLayer: Initial bridge signature A for operator %s", operatorAddr))) + expectedHashB := sha256.Sum256([]byte(fmt.Sprintf("TellorLayer: Initial bridge signature B for operator %s", operatorAddr))) - testCases := []testCase{ + testCases := []struct { + name string + setupSigner func(signer *mocks.VoteExtensionSigner) + expectedSigA []byte + expectedSigB []byte + expectedError string + }{ { - name: "err getting keyname", - setupMocks: func(h *app.VoteExtHandler, patches *gomonkey.Patches) { - s.kr = mocks.NewKeyring(s.T()) - tempDir := s.T().TempDir() - viper.Set("keyring-backend", "test") - viper.Set("keyring-dir", tempDir) - viper.Set("key-name", "") + name: "success", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + signer.On("Sign", mock.Anything, expectedHashA[:]).Return([]byte("signedMsgA"), nil).Once() + signer.On("Sign", mock.Anything, expectedHashB[:]).Return([]byte("signedMsgB"), nil).Once() }, - expectedError: true, - expectedErrorMsg: "key name not found, please set --key-name flag", + expectedSigA: []byte("signedMsgA"), + expectedSigB: []byte("signedMsgB"), }, { - name: "empty keyring", - setupMocks: func(h *app.VoteExtHandler, patches *gomonkey.Patches) { - tempDir := s.T().TempDir() - viper.Set("key-name", "testkey") - viper.Set("keyring-backend", "test") - viper.Set("keyring-dir", tempDir) - mockKr := mocks.NewKeyring(s.T()) - mockKr.On("List").Return([]*keyring.Record{}, nil) - - patches.ApplyMethod(reflect.TypeOf(h), "GetKeyring", - func(_ *app.VoteExtHandler) (keyring.Keyring, error) { - return mockKr, nil - }) + name: "error signing message A", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + signer.On("Sign", mock.Anything, expectedHashA[:]).Return(nil, errors.New("sign A failed")).Once() }, - expectedError: true, - expectedErrorMsg: "no keys found in keyring", + expectedError: "failed to sign message A", }, { - name: "kr.Key error", - setupMocks: func(h *app.VoteExtHandler, patches *gomonkey.Patches) { - tempDir := s.T().TempDir() - viper.Set("keyring-backend", "test") - viper.Set("keyring-dir", tempDir) - viper.Set("key-name", "testkey") - pubKey := &secp256k1.PubKey{Key: []byte("pubkey")} - anyPubKey, err := codectypes.NewAnyWithValue(pubKey) - require.NoError(err) - mockKr := mocks.NewKeyring(s.T()) - mockKr.On("List").Return([]*keyring.Record{ - {Name: "testkey", PubKey: anyPubKey}, - }, nil) - mockKr.On("Key", "testkey").Return(nil, errors.New("error!")) - - patches.ApplyMethod(reflect.TypeOf(h), "GetKeyring", - func(_ *app.VoteExtHandler) (keyring.Keyring, error) { - return mockKr, nil - }) + name: "error signing message B", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + signer.On("Sign", mock.Anything, expectedHashA[:]).Return([]byte("signedMsgA"), nil).Once() + signer.On("Sign", mock.Anything, expectedHashB[:]).Return(nil, errors.New("sign B failed")).Once() }, - expectedError: true, - expectedErrorMsg: "failed to get operator key:", - }, - { - name: "success", - setupMocks: func(h *app.VoteExtHandler, patches *gomonkey.Patches) { - tempDir := s.T().TempDir() - viper.Set("keyring-backend", "test") - viper.Set("keyring-dir", tempDir) - viper.Set("key-name", "testkey") - priv := hd.Secp256k1.Generate()([]byte("test")) - pubKey := priv.PubKey() - anyPubKey, err := codectypes.NewAnyWithValue(pubKey) - require.NoError(err) - localItem, err := keyring.NewLocalRecord( - "testkey", - priv, - pubKey, - ) - require.NoError(err) - mockKr := mocks.NewKeyring(s.T()) - mockKr.On("List").Return([]*keyring.Record{ - {Name: "testkey", PubKey: anyPubKey}, - }, nil) - - mockKr.On("Key", "testkey").Return(&keyring.Record{ - Name: "testkey", - PubKey: anyPubKey, - Item: localItem.Item, - }, nil) - patches.ApplyMethod(reflect.TypeOf(h), "GetKeyring", - func(_ *app.VoteExtHandler) (keyring.Keyring, error) { - return mockKr, nil - }) - }, - expectedError: false, + expectedError: "failed to sign message B", }, } for _, tc := range testCases { s.Run(tc.name, func() { - patches := gomonkey.NewPatches() - patches.Reset() - h, _, _, _ := s.CreateHandlerAndMocks() - s.T().Cleanup(func() { - patches.Reset() - viper.Reset() - }) - if tc.setupMocks != nil { - tc.setupMocks(h, patches) - } - resp, err := h.GetOperatorAddress() - if tc.expectedError { + h, _, _, _, signer := s.CreateHandlerAndMocks() + tc.setupSigner(signer) + + sigA, sigB, err := h.SignInitialMessage(operatorAddr) + if tc.expectedError != "" { require.Error(err) - require.Contains(err.Error(), tc.expectedErrorMsg) + require.Contains(err.Error(), tc.expectedError) } else { require.NoError(err) - require.NotEmpty(resp) + require.Equal(tc.expectedSigA, sigA) + require.Equal(tc.expectedSigB, sigB) } - s.T().Cleanup(func() { - patches.Reset() - }) }) } } -func (s *VoteExtensionTestSuite) TestSignInitialMessage() { - require := s.Require() - h, _, _, _ := s.CreateHandlerAndMocks() - - patches := gomonkey.NewPatches() - patches.Reset() - patches.ApplyMethod(reflect.TypeOf(h), "SignMessage", func(_ *app.VoteExtHandler, msg []byte) ([]byte, error) { - return []byte("signedMsg"), nil - }) - - sigA, sigB, err := h.SignInitialMessage("operatorAddr1") - require.NoError(err) - require.NotEmpty(sigA) - require.NotEmpty(sigB) - - s.T().Cleanup(func() { - patches.Reset() - }) -} - func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { require := s.Require() ctx := s.ctx.WithBlockHeight(2) @@ -707,21 +606,19 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { testCases := []struct { name string - setupMocks func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) + setupMocks func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) expectedSig []byte expectedTimestamp uint64 expectedError error }{ { name: "Validator already signed", - setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) { + setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) { bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(1), nil).Once() bk.On("GetValidatorTimestampByIdxFromStorage", ctx, uint64(1)).Return(bridgetypes.CheckpointTimestamp{ Timestamp: 10, }, nil).Once() - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetValidatorDidSignCheckpoint", ctx, oppAddr, uint64(10)).Return(true, int64(1), nil).Once() }, expectedSig: nil, @@ -730,7 +627,7 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { }, { name: "Error getting latest checkpoint index", - setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) { + setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) { bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(0), errors.New("index error!")) }, expectedSig: nil, @@ -739,7 +636,7 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { }, { name: "Error getting validator timestamp", - setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) { + setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) { bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(1), nil) bk.On("GetValidatorTimestampByIdxFromStorage", ctx, uint64(1)).Return(bridgetypes.CheckpointTimestamp{}, errors.New("timestamp error!")) }, @@ -749,14 +646,12 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { }, { name: "Error checking if validator signed", - setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) { + setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) { bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(1), nil) bk.On("GetValidatorTimestampByIdxFromStorage", ctx, uint64(1)).Return(bridgetypes.CheckpointTimestamp{ Timestamp: 10, }, nil) - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetValidatorDidSignCheckpoint", ctx, oppAddr, uint64(10)).Return(false, int64(0), errors.New("sig check error!")) }, expectedSig: nil, @@ -765,14 +660,12 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { }, { name: "No errors", - setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, patches *gomonkey.Patches) { + setupMocks: func(h *app.VoteExtHandler, bk *mocks.BridgeKeeper, signer *mocks.VoteExtensionSigner, patches *gomonkey.Patches) { bk.On("GetLatestCheckpointIndex", ctx).Return(uint64(1), nil).Once() bk.On("GetValidatorTimestampByIdxFromStorage", ctx, uint64(1)).Return(bridgetypes.CheckpointTimestamp{ Timestamp: 10, }, nil).Once() - patches.ApplyMethod(reflect.TypeOf(h), "GetOperatorAddress", func(_ *app.VoteExtHandler) (string, error) { - return oppAddr, nil - }) + signer.On("GetOperatorAddress", mock.Anything).Return(oppAddr, nil) bk.On("GetValidatorDidSignCheckpoint", ctx, oppAddr, uint64(10)).Return(false, int64(1), nil).Once() bk.On("GetValidatorCheckpointParamsFromStorage", ctx, uint64(10)).Return(bridgetypes.ValidatorCheckpointParams{ Checkpoint: []byte("checkpoint"), @@ -791,9 +684,9 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { s.Run(tc.name, func() { patches := gomonkey.NewPatches() patches.Reset() - h, _, bk, _ := s.CreateHandlerAndMocks() + h, _, bk, _, signer := s.CreateHandlerAndMocks() if tc.setupMocks != nil { - tc.setupMocks(h, bk, patches) + tc.setupMocks(h, bk, signer, patches) } sig, timestamp, err := h.CheckAndSignValidatorCheckpoint(ctx) if tc.expectedError != nil { @@ -815,51 +708,52 @@ func (s *VoteExtensionTestSuite) TestCheckAndSignValidatorCheckpoint() { func (s *VoteExtensionTestSuite) TestEncodeAndSignMessage() { require := s.Require() + expectedBytes, _ := hex.DecodeString("0123456789abcdef") + tests := []struct { - name string - checkpointString string - mockSignature []byte - mockError error - expectedSignature []byte - expectedError string + name string + checkpointString string + setupSigner func(signer *mocks.VoteExtensionSigner) + expectedSignature []byte + expectedError string + signShouldBeCalled bool }{ { - name: "Valid checkpoint", - checkpointString: "0123456789abcdef", - mockSignature: []byte("signedMsg"), - mockError: nil, - expectedSignature: []byte("signedMsg"), - expectedError: "", + name: "Valid checkpoint", + checkpointString: "0123456789abcdef", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + signer.On("Sign", mock.Anything, expectedBytes).Return([]byte("signedMsg"), nil).Once() + }, + expectedSignature: []byte("signedMsg"), + signShouldBeCalled: true, }, { - name: "Invalid hex string", - checkpointString: "invalid hex", - mockSignature: nil, - mockError: nil, - expectedSignature: nil, - expectedError: "encoding/hex: invalid byte", + name: "Invalid hex string", + checkpointString: "invalid hex", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + // Sign should not be called when hex decoding fails + }, + expectedSignature: nil, + expectedError: "encoding/hex: invalid byte", + signShouldBeCalled: false, }, { - name: "SignMessage error", - checkpointString: "0123456789abcdef", - mockSignature: nil, - mockError: errors.New("signing error"), - expectedSignature: nil, - expectedError: "signing error", + name: "Sign error", + checkpointString: "0123456789abcdef", + setupSigner: func(signer *mocks.VoteExtensionSigner) { + signer.On("Sign", mock.Anything, expectedBytes).Return(nil, errors.New("signing error")).Once() + }, + expectedSignature: nil, + expectedError: "signing error", + signShouldBeCalled: true, }, } for _, tt := range tests { - fmt.Println(tt.name) s.Run(tt.name, func() { - h, _, _, _ := s.CreateHandlerAndMocks() - patches := gomonkey.NewPatches() - patches.Reset() - patches.ApplyMethod(reflect.TypeOf(h), "SignMessage", - func(_ *app.VoteExtHandler, msg []byte) ([]byte, error) { - return tt.mockSignature, tt.mockError - }) - fmt.Println(tt.name) + h, _, _, _, signer := s.CreateHandlerAndMocks() + tt.setupSigner(signer) + signature, err := h.EncodeAndSignMessage(tt.checkpointString) if tt.expectedError != "" { @@ -869,9 +763,10 @@ func (s *VoteExtensionTestSuite) TestEncodeAndSignMessage() { require.NoError(err) } require.Equal(tt.expectedSignature, signature) - s.T().Cleanup(func() { - patches.Reset() - }) + + if !tt.signShouldBeCalled { + signer.AssertNotCalled(s.T(), "Sign", mock.Anything, mock.Anything) + } }) } } @@ -936,7 +831,7 @@ func (s *VoteExtensionTestSuite) TestGetValidatorIndexInValset() { for _, tc := range testCases { s.Run(tc.name, func() { - h, _, _, _ := s.CreateHandlerAndMocks() + h, _, _, _, _ := s.CreateHandlerAndMocks() index, err := h.GetValidatorIndexInValset(ctx, tc.evmAddr, tc.valset) if tc.expectedError != nil { diff --git a/app/keyring_signer.go b/app/keyring_signer.go new file mode 100644 index 000000000..959fe8bab --- /dev/null +++ b/app/keyring_signer.go @@ -0,0 +1,115 @@ +package app + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// KeyringSignerConfig holds resolved config for the keyring signer. +type KeyringSignerConfig struct { + KeyName string + KeyringBackend string + KeyringDir string +} + +// KeyringSigner implements VoteExtensionSigner using the Cosmos SDK keyring. +type KeyringSigner struct { + cfg KeyringSignerConfig + codec codec.Codec + kr keyring.Keyring + operatorAddress string // cached at startup +} + +// NewKeyringSignerFromViper reads config from viper at startup. +func NewKeyringSignerFromViper(appCodec codec.Codec) (*KeyringSigner, error) { + keyName := viper.GetString("key-name") + if keyName == "" { + return nil, errors.New("key-name not set, please use --key-name flag") + } + + krBackend := viper.GetString("keyring-backend") + if krBackend == "" { + return nil, errors.New("keyring-backend not set, please use --keyring-backend flag") + } + + krDir := viper.GetString("keyring-dir") + if krDir == "" { + krDir = viper.GetString("home") + } + if krDir == "" { + return nil, errors.New("keyring directory not set, please use --home or --keyring-dir flag") + } + + kr, err := keyring.New(sdk.KeyringServiceName(), krBackend, krDir, os.Stdin, appCodec) + if err != nil { + return nil, fmt.Errorf("failed to initialize keyring: %w", err) + } + + s := &KeyringSigner{ + cfg: KeyringSignerConfig{ + KeyName: keyName, + KeyringBackend: krBackend, + KeyringDir: krDir, + }, + codec: appCodec, + kr: kr, + } + + addr, err := s.resolveOperatorAddress() + if err != nil { + return nil, fmt.Errorf("failed to resolve operator address at startup: %w", err) + } + s.operatorAddress = addr + + return s, nil +} + +// Sign +func (s *KeyringSigner) Sign(_ context.Context, msg []byte) ([]byte, error) { + const secp256k1SignType = 1 + sig, _, err := s.kr.Sign(s.cfg.KeyName, msg, secp256k1SignType) + if err != nil { + return nil, fmt.Errorf("keyring sign failed: %w", err) + } + return sig, nil +} + +// GetOperatorAddress +func (s *KeyringSigner) GetOperatorAddress(_ context.Context) (string, error) { + if s.operatorAddress == "" { + return "", errors.New("operator address not initialized") + } + return s.operatorAddress, nil +} + +// resolveOperatorAddress derives the bech32 validator address from the keyring public key. +func (s *KeyringSigner) resolveOperatorAddress() (string, error) { + record, err := s.kr.Key(s.cfg.KeyName) + if err != nil { + return "", fmt.Errorf("failed to get key %q from keyring: %w", s.cfg.KeyName, err) + } + + pubKey, err := record.GetPubKey() + if err != nil { + return "", fmt.Errorf("failed to get public key for %q: %w", s.cfg.KeyName, err) + } + + config := sdk.GetConfig() + bech32ValAddr, err := sdk.Bech32ifyAddressBytes( + config.GetBech32ValidatorAddrPrefix(), + pubKey.Address().Bytes(), + ) + if err != nil { + return "", fmt.Errorf("failed to bech32-encode validator address: %w", err) + } + + return bech32ValAddr, nil +} diff --git a/app/mocks/VoteExtensionSigner.go b/app/mocks/VoteExtensionSigner.go new file mode 100644 index 000000000..c8d602161 --- /dev/null +++ b/app/mocks/VoteExtensionSigner.go @@ -0,0 +1,77 @@ +// Code generated by mockery v2.23.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// VoteExtensionSigner is an autogenerated mock type for the VoteExtensionSigner type +type VoteExtensionSigner struct { + mock.Mock +} + +// Sign provides a mock function with given fields: ctx, msg +func (_m *VoteExtensionSigner) Sign(ctx context.Context, msg []byte) ([]byte, error) { + ret := _m.Called(ctx, msg) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) ([]byte, error)); ok { + return rf(ctx, msg) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte) []byte); ok { + r0 = rf(ctx, msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOperatorAddress provides a mock function with given fields: ctx +func (_m *VoteExtensionSigner) GetOperatorAddress(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewVoteExtensionSigner creates a new instance of VoteExtensionSigner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewVoteExtensionSigner(t interface { + mock.TestingT + Cleanup(func()) +}) *VoteExtensionSigner { + mock := &VoteExtensionSigner{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/app/remote_signer_grpc.go b/app/remote_signer_grpc.go new file mode 100644 index 000000000..f459a22eb --- /dev/null +++ b/app/remote_signer_grpc.go @@ -0,0 +1,178 @@ +package app + +import ( + "context" + "fmt" + "time" + + signerv1 "github.com/tellor-io/bridge-remote-signer/api/gen/signer/v1" + bridgetls "github.com/tellor-io/bridge-remote-signer/api/tls" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + + cosmossecp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GRPCSignerConfig holds the connection config for the remote signing sidecar. +type GRPCSignerConfig struct { + // Address is the sidecar's gRPC address, e.g. "dns:///sidecar-host:9191" + Address string + + // CACert is the path to the CA certificate + CACert string + + // ClientCert is the path to the validator's client TLS certificate + ClientCert string + + // ClientKey is the path to the validator's client TLS private key + ClientKey string + + // ServerName must match the CN in the sidecar's server certificate + ServerName string + + // RequestTimeout is the per-RPC deadline. + // Must be less than CometBFT's vote extension timeout. + // Default: 2s + RequestTimeout time.Duration +} + +// GRPCRemoteSigner implements RemoteSigner by delegating Sign to the sidecar +// and deriving GetOperatorAddress from the sidecar's public key locally. +type GRPCRemoteSigner struct { + cfg GRPCSignerConfig + conn *grpc.ClientConn + client signerv1.BridgeSignerClient + operatorAddress string // derived from sidecar's public key at startup, cached +} + +// NewGRPCRemoteSigner dials the sidecar, fetches the public key, derives +// the operator address locally, and caches it. +// No private key ever exists on the validator node. +func NewGRPCRemoteSigner(cfg GRPCSignerConfig) (*GRPCRemoteSigner, error) { + if cfg.Address == "" { + return nil, fmt.Errorf("bridge signer address is required") + } + if cfg.RequestTimeout == 0 { + cfg.RequestTimeout = 2 * time.Second + } + + // Build mTLS credentials. + creds, err := bridgetls.NewClientCredentials( + cfg.CACert, + cfg.ClientCert, + cfg.ClientKey, + cfg.ServerName, + ) + if err != nil { + return nil, fmt.Errorf("failed to build mTLS credentials: %w", err) + } + + // Dial the sidecar. grpc.NewClient does not block — + // the actual TCP connection is established on first use. + conn, err := grpc.NewClient( + cfg.Address, + grpc.WithTransportCredentials(creds), + ) + if err != nil { + return nil, fmt.Errorf("failed to create gRPC client for sidecar at %q: %w", cfg.Address, err) + } + + s := &GRPCRemoteSigner{ + cfg: cfg, + conn: conn, + client: signerv1.NewBridgeSignerClient(conn), + } + + // Fetch the public key from the sidecar at startup. + // Derive the operator address locally + // Fails fast if sidecar is unreachable or key is misconfigured. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + pubKeyResp, err := s.client.GetPublicKey(ctx, &signerv1.GetPublicKeyRequest{}) + if err != nil { + conn.Close() + return nil, fmt.Errorf("failed to get public key from sidecar at %q: %w", cfg.Address, err) + } + + // Derive bech32 operator address from the public key using the Cosmos SDK. + // No private key needed — only the compressed public key. + operatorAddress, err := deriveOperatorAddressFromPubKey(pubKeyResp.PublicKey) + if err != nil { + conn.Close() + return nil, fmt.Errorf("failed to derive operator address from sidecar public key: %w", err) + } + + s.operatorAddress = operatorAddress + + return s, nil +} + +// Sign implements RemoteSigner. +// Delegates to the sidecar over mTLS-secured gRPC. +func (s *GRPCRemoteSigner) Sign(ctx context.Context, msg []byte) ([]byte, error) { + if len(msg) != 32 { + return nil, fmt.Errorf("Sign: msg must be exactly 32 bytes, got %d", len(msg)) + } + + rpcCtx, cancel := context.WithTimeout(ctx, s.cfg.RequestTimeout) + defer cancel() + + resp, err := s.client.Sign(rpcCtx, &signerv1.SignRequest{Msg: msg}) + if err != nil { + return nil, fmt.Errorf("Sign RPC failed: %w", err) + } + + if len(resp.Signature) != 65 { + return nil, fmt.Errorf("Sign: sidecar returned invalid signature length %d, expected 65", len(resp.Signature)) + } + + return resp.Signature[:64], nil +} + +// GetOperatorAddress implements RemoteSigner. +// Returns the cached operator address derived from the sidecar's public key. +// No network call — derived once at startup and cached. +func (s *GRPCRemoteSigner) GetOperatorAddress(_ context.Context) (string, error) { + if s.operatorAddress == "" { + return "", fmt.Errorf("operator address not initialized") + } + return s.operatorAddress, nil +} + +// Close closes the underlying gRPC connection. +func (s *GRPCRemoteSigner) Close() error { + if s.conn != nil { + return s.conn.Close() + } + return nil +} + +// IsReady reports whether the gRPC connection to the sidecar is healthy. +func (s *GRPCRemoteSigner) IsReady() bool { + state := s.conn.GetState() + return state == connectivity.Ready || state == connectivity.Idle +} + +// deriveOperatorAddressFromPubKey derives the bech32 validator operator address +// from a raw compressed secp256k1 public key using the Cosmos SDK. +// Uses SHA256 + RIPEMD160 (standard Cosmos address derivation). +func deriveOperatorAddressFromPubKey(compressedPubKey []byte) (string, error) { + if len(compressedPubKey) != 33 { + return "", fmt.Errorf("expected 33-byte compressed public key, got %d", len(compressedPubKey)) + } + + cosmosPubKey := &cosmossecp256k1.PubKey{Key: compressedPubKey} + + config := sdk.GetConfig() + bech32ValAddr, err := sdk.Bech32ifyAddressBytes( + config.GetBech32ValidatorAddrPrefix(), + cosmosPubKey.Address().Bytes(), + ) + if err != nil { + return "", fmt.Errorf("failed to bech32-encode validator address: %w", err) + } + + return bech32ValAddr, nil +} diff --git a/app/vote_extension_signer.go b/app/vote_extension_signer.go new file mode 100644 index 000000000..b9ea787a3 --- /dev/null +++ b/app/vote_extension_signer.go @@ -0,0 +1,48 @@ +package app + +import ( + "context" + "errors" + "fmt" + + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/codec" +) + +// VoteExtensionSigner is the interface VoteExtHandler uses for all bridge signing. +// Implemented by KeyringSigner or GRPCRemoteSigner. +type VoteExtensionSigner interface { + // Sign signs a 32-byte message and returns a secp256k1 signature. + Sign(ctx context.Context, msg []byte) ([]byte, error) + + // GetOperatorAddress returns the bech32 validator operator address. + GetOperatorAddress(ctx context.Context) (string, error) +} + +func RemoteVoteExtensionSigner(appCodec codec.Codec) (VoteExtensionSigner, error) { + if viper.GetBool("remote-signer-enabled") { + addr := viper.GetString("remote-signer-addr") + if addr == "" { + return nil, fmt.Errorf("--remote-signer-addr is required when --remote-signer-enabled is set") + } + return NewGRPCRemoteSigner(GRPCSignerConfig{ + Address: addr, + CACert: viper.GetString("remote-signer-ca-cert"), + ClientCert: viper.GetString("remote-signer-client-cert"), + ClientKey: viper.GetString("remote-signer-client-key"), + ServerName: viper.GetString("remote-signer-server-name"), + }) + } + return nil, nil +} + +// NewKeyringSignerFromViperIfSet is the lazy entry point used by the +// vote extension handler on first invocation. It reads --key-name from +// viper and tries to build a KeyringSigner if set. +func NewKeyringSignerFromViperIfSet(appCodec codec.Codec) (VoteExtensionSigner, error) { + if viper.GetString("key-name") == "" { + return nil, errors.New("no bridge signer configured; set --key-name or --remote-signer-enabled") + } + return NewKeyringSignerFromViper(appCodec) +} diff --git a/cmd/layerd/cmd/root_option.go b/cmd/layerd/cmd/root_option.go index e1d7a95b7..65f8f5db0 100644 --- a/cmd/layerd/cmd/root_option.go +++ b/cmd/layerd/cmd/root_option.go @@ -24,7 +24,13 @@ func GetOptionWithCustomStartCmd() *RootCmdOption { option := newRootCmdOption() f := func(cmd *cobra.Command) { cmd.Flags().String("keyring-backend", "test", "Select keyring's backend (os|file|kwallet|pass|test)") - cmd.Flags().String("key-name", "alice", "Select key name") + cmd.Flags().String("key-name", "", "Select key name") + cmd.Flags().Bool("remote-signer-enabled", false, "Use bridge-signer sidecar instead of local keyring") + cmd.Flags().String("remote-signer-addr", "", "gRPC address of the bridge-signer sidecar") + cmd.Flags().String("remote-signer-ca-cert", "", "Path to CA cert for verifying the sidecar") + cmd.Flags().String("remote-signer-client-cert", "", "Path to validator's client TLS cert") + cmd.Flags().String("remote-signer-client-key", "", "Path to validator's client TLS key") + cmd.Flags().String("remote-signer-server-name", "bridge-signer", "Expected CN in sidecar's TLS cert") } option.setCustomizeStartCmd(f) return option diff --git a/e2e/go.mod b/e2e/go.mod index f918c3b45..57b7f2c94 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -1,8 +1,6 @@ module github.com/tellor-io/layer/e2e -go 1.24.0 - -toolchain go1.24.13 +go 1.25.9 require ( cosmossdk.io/math v1.5.3 @@ -61,12 +59,13 @@ require ( github.com/btcsuite/btcd v0.22.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect - github.com/bytedance/sonic v1.13.2 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/cockroachdb/errors v1.12.0 // indirect github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect @@ -279,7 +278,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index ee40c4491..c4e39db39 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -325,11 +325,12 @@ github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pY github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -356,9 +357,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -857,10 +857,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -1181,6 +1179,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -1888,8 +1888,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1967,7 +1967,6 @@ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/e2e/startup_using_wrong_key_test.go b/e2e/startup_using_wrong_key_test.go index 923251369..2feda8be9 100644 --- a/e2e/startup_using_wrong_key_test.go +++ b/e2e/startup_using_wrong_key_test.go @@ -163,6 +163,15 @@ func TestStartupUsingWrongKey(t *testing.T) { fmt.Println("✅ Wrong key is now named 'validator' (what the daemon uses for vote extensions)") fmt.Println("✅ This simulates the real-world scenario where validator uses wrong key for vote extensions") + // Restart the validator so the signer caches the wrong key's operator address at startup + fmt.Println("\n=== Restarting validator 0 to pick up wrong key ===") + require.NoError(validators[0].Node.StopContainer(ctx)) + require.NoError(validators[0].Node.StartContainer(ctx)) + + // Wait for the node to catch up + err = testutil.WaitForBlocks(ctx, 5, validators[1].Node) + require.NoError(err) + // Verify that the validator can still sign blocks normally fmt.Println("\n=== Verifying validator can still sign blocks ===") diff --git a/go.mod b/go.mod index 277935712..4efd43f70 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/tellor-io/layer -go 1.24.0 - -toolchain go1.24.13 +go 1.25.9 require ( cosmossdk.io/api v0.9.2 @@ -18,7 +16,7 @@ require ( cosmossdk.io/x/feegrant v0.1.1 cosmossdk.io/x/tx v0.14.0 cosmossdk.io/x/upgrade v0.1.4 - github.com/agiledragon/gomonkey/v2 v2.12.0 + github.com/agiledragon/gomonkey/v2 v2.14.0 github.com/bufbuild/buf v1.32.0 github.com/cometbft/cometbft v0.38.17 github.com/cosmos/cosmos-db v1.1.3 @@ -42,6 +40,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/strangelove-ventures/globalfee v0.50.1 github.com/stretchr/testify v1.11.1 + github.com/tellor-io/bridge-remote-signer/api v0.0.0-20260414141258-1b00a304f995 github.com/vektra/mockery/v2 v2.23.1 go.uber.org/mock v0.5.2 golang.org/x/text v0.32.0 @@ -49,7 +48,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 google.golang.org/grpc v1.79.3 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -89,13 +88,14 @@ require ( github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee // indirect github.com/bufbuild/protovalidate-go v0.6.2 // indirect github.com/bufbuild/protoyaml-go v0.1.9 // indirect - github.com/bytedance/sonic v1.13.2 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chigopher/pathlib v0.12.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -287,4 +287,5 @@ replace ( github.com/cosmos/iavl => github.com/cosmos/iavl v1.2.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/tellor-io/bridge-remote-signer/api => github.com/tellor-io/bridge-remote-signer/api v0.0.0-20260414141258-1b00a304f995 ) diff --git a/go.sum b/go.sum index 120d835cf..ec8f1962a 100644 --- a/go.sum +++ b/go.sum @@ -269,8 +269,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agiledragon/gomonkey/v2 v2.12.0 h1:ek0dYu9K1rSV+TgkW5LvNNPRWyDZVIxGMCFI6Pz9o38= -github.com/agiledragon/gomonkey/v2 v2.12.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/agiledragon/gomonkey/v2 v2.14.0 h1:FASzes6sjtD0hRo5lu0g796qKL03bOHCgcIA/4am9QM= +github.com/agiledragon/gomonkey/v2 v2.14.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -320,11 +320,12 @@ github.com/bufbuild/protovalidate-go v0.6.2 h1:U/V3CGF0kPlR12v41rjO4DrYZtLcS4ZON github.com/bufbuild/protovalidate-go v0.6.2/go.mod h1:4BR3rKEJiUiTy+sqsusFn2ladOf0kYmA2Reo6BHSBgQ= github.com/bufbuild/protoyaml-go v0.1.9 h1:anV5UtF1Mlvkkgp4NWA6U/zOnJFng8Orq4Vf3ZUQHBU= github.com/bufbuild/protoyaml-go v0.1.9/go.mod h1:KCBItkvZOK/zwGueLdH1Wx1RLyFn5rCH7YjQrdty2Wc= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -355,9 +356,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -834,12 +834,10 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -1114,12 +1112,16 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tellor-io/bridge-remote-signer/api v0.0.0-20260414141258-1b00a304f995 h1:FTVy6227XWH7lMVgL3XUCOAj0KyZUk5IUiWaCYXYMcg= +github.com/tellor-io/bridge-remote-signer/api v0.0.0-20260414141258-1b00a304f995/go.mod h1:hkVexbxMzIkBrKx7r1GOjt/Fjt4pap43QzWzcFs9zG8= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= @@ -1808,8 +1810,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1853,7 +1855,6 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/scripts/prometheus-exporter/prometheus-exporter.go b/scripts/prometheus-exporter/prometheus-exporter.go index c8b42fbf2..47b44d2c4 100644 --- a/scripts/prometheus-exporter/prometheus-exporter.go +++ b/scripts/prometheus-exporter/prometheus-exporter.go @@ -629,7 +629,7 @@ func getPricesHandler(db *sql.DB) http.HandlerFunc { } if offset != "" { - query += fmt.Sprintf(" OFFSET $%d", argIndex) + query += fmt.Sprintf(" OFFSET $%d", argIndex) //nolint:gosec // G202: only appends $N placeholder; values are passed via db.Query args, not interpolated args = append(args, offset) } @@ -798,7 +798,7 @@ func getPricesByMarketHandler(db *sql.DB) http.HandlerFunc { } if offset != "" { - query += fmt.Sprintf(" OFFSET $%d", argIndex) + query += fmt.Sprintf(" OFFSET $%d", argIndex) //nolint:gosec // G202: only appends $N placeholder; values are passed via db.Query args, not interpolated args = append(args, offset) } @@ -898,7 +898,7 @@ func getPricesByExchangeHandler(db *sql.DB) http.HandlerFunc { } if offset != "" { - query += fmt.Sprintf(" OFFSET $%d", argIndex) + query += fmt.Sprintf(" OFFSET $%d", argIndex) //nolint:gosec // G202: only appends $N placeholder; values are passed via db.Query args, not interpolated args = append(args, offset) } @@ -1060,7 +1060,7 @@ func getPricesByRangeHandler(db *sql.DB) http.HandlerFunc { } if offset != "" { - query += fmt.Sprintf(" OFFSET $%d", argIndex) + query += fmt.Sprintf(" OFFSET $%d", argIndex) //nolint:gosec // G202: only appends $N placeholder; values are passed via db.Query args, not interpolated args = append(args, offset) } diff --git a/scripts/spam-tests/spam-reports.go b/scripts/spam-tests/spam-reports.go index 1e29373fb..f1f4b846e 100644 --- a/scripts/spam-tests/spam-reports.go +++ b/scripts/spam-tests/spam-reports.go @@ -177,7 +177,7 @@ func CreateNewAccountsAndFundReporters(numOfReporters int) (map[string]ReporterI } func GetAddressFromKeyName(key_name string) string { - cmd := exec.Command(COMMAND_PATH, "keys", "show", key_name, "-a", "--keyring-backend", "test", "--home", fmt.Sprintf("%s/%s", LAYER_PATH, key_name)) //nolint:all // has been tested manually + cmd := exec.Command(COMMAND_PATH, "keys", "show", key_name, "-a", "--keyring-backend", "test", "--home", fmt.Sprintf("%s/%s", LAYER_PATH, key_name)) output, err := cmd.CombinedOutput() if err != nil { log.Fatalf("cmd.Run() failed: %v\n", err)