chore: remove unreleased and not sdk dependent packages from v0.52.x 1/n (#21093)

This commit is contained in:
Julien Robert 2024-08-01 10:04:46 +02:00 committed by GitHub
parent c769b1312b
commit 99ac978524
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
412 changed files with 262 additions and 54192 deletions

View File

@ -14,8 +14,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: actions/setup-go@v5
with:
go-version: "1.22.2"
@ -31,9 +29,7 @@ jobs:
if: env.GIT_DIFF
id: lint_long
run: |
nix develop -c make lint
env:
NIX: 1
make lint
- uses: technote-space/get-diff-action@v6.1.2
if: steps.lint_long.outcome == 'skipped'
with:
@ -43,8 +39,7 @@ jobs:
- name: run linting (short)
if: steps.lint_long.outcome == 'skipped' && env.GIT_DIFF
run: |
nix develop -c make lint
make lint
env:
GIT_DIFF: ${{ env.GIT_DIFF }}
LINT_DIFF: 1
NIX: 1

View File

@ -740,74 +740,6 @@ jobs:
with:
projectBaseDir: tools/hubl/
test-store:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: actions/setup-go@v5
with:
go-version: "1.20"
check-latest: true
cache: true
cache-dependency-path: store/go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
store/**/*.go
store/go.mod
store/go.sum
- name: tests
if: env.GIT_DIFF
run: |
cd store
nix develop .. -c go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock rocksdb' ./...
- name: sonarcloud
if: ${{ env.GIT_DIFF && !github.event.pull_request.draft && env.SONAR_TOKEN != null }}
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
projectBaseDir: store/
test-store-v2:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: actions/setup-go@v5
with:
go-version: "1.22"
check-latest: true
cache: true
cache-dependency-path: store/v2/go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
store/v2/**/*.go
store/v2/go.mod
store/v2/go.sum
- name: test & coverage report creation
if: env.GIT_DIFF
run: |
cd store/v2
nix develop .. -c go test -mod=readonly -timeout 30m -coverprofile=coverage.out -covermode=atomic -tags='norace ledger test_ledger_mock rocksdb' ./...
- name: sonarcloud
if: ${{ env.GIT_DIFF && !github.event.pull_request.draft && env.SONAR_TOKEN != null }}
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
projectBaseDir: store/v2/
test-log:
runs-on: ubuntu-latest
steps:

View File

@ -14,78 +14,6 @@ concurrency:
cancel-in-progress: true
jobs:
server-v2:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
check-latest: true
cache: true
cache-dependency-path: go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
server/v2/*.go
server/v2/go.mod
server/v2/go.sum
server/v2/testdata/*.toml
- name: test & coverage report creation
if: env.GIT_DIFF
run: |
cd server/v2 && go test -mod=readonly -race -timeout 30m -tags='ledger test_ledger_mock'
stf:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
check-latest: true
cache: true
cache-dependency-path: go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
server/v2/stf/**/*.go
server/v2/stf/go.mod
server/v2/stf/go.sum
- name: test & coverage report creation
if: env.GIT_DIFF
run: |
cd server/v2/stf && go test -mod=readonly -race -timeout 30m -tags='ledger test_ledger_mock'
appmanager:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
check-latest: true
cache: true
cache-dependency-path: go.sum
- uses: technote-space/get-diff-action@v6.1.2
id: git_diff
with:
PATTERNS: |
server/v2/appmanager/**/*.go
server/v2/appmanager/go.mod
server/v2/appmanager/go.sum
- name: test & coverage report creation
if: env.GIT_DIFF
run: |
cd server/v2/appmanager && go test -mod=readonly -race -timeout 30m -tags='ledger test_ledger_mock'
cometbft:
runs-on: ubuntu-latest
strategy:

View File

@ -2,7 +2,7 @@ package flag
import (
"context"
"fmt"
"errors"
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
@ -38,7 +38,7 @@ func (c *coinValue) String() string {
func (c *coinValue) Set(stringValue string) error {
if strings.Contains(stringValue, ",") {
return fmt.Errorf("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
}
coin, err := coins.ParseCoin(stringValue)

View File

@ -1 +1 @@
{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk","to_address":"cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk","amount":[{"denom":"foo","amount":"1"}]}],"timeout_timestamp":"1970-01-01T00:00:00Z"},"auth_info":{"fee":{"gas_limit":"200000"}}}
{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk","to_address":"cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk","amount":[{"denom":"foo","amount":"1"}]}],"memo":"","timeout_height":"0","unordered":false,"timeout_timestamp":"1970-01-01T00:00:00Z","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""},"tip":null},"signatures":[]}

View File

@ -10,7 +10,7 @@ require (
cosmossdk.io/x/gov v0.0.0-20231113122742-912390d5fc4a
cosmossdk.io/x/tx v0.13.3
github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/cosmos/cosmos-sdk v0.51.0
github.com/cosmos/cosmos-sdk v0.52.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
google.golang.org/grpc v1.64.1
@ -26,6 +26,7 @@ require (
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/log v1.3.1 // indirect
cosmossdk.io/math v1.3.0
cosmossdk.io/schema v0.1.1 // indirect
cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc // indirect
cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000
cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000 // indirect
@ -119,6 +120,7 @@ require (
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@ -170,11 +172,6 @@ require (
pgregory.net/rapid v1.1.0 // indirect
)
require (
cosmossdk.io/schema v0.1.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
)
replace github.com/cosmos/cosmos-sdk => ./../../
// TODO remove post spinning out all modules
@ -182,7 +179,7 @@ replace (
cosmossdk.io/api => ./../../api
cosmossdk.io/core => ./../../core
cosmossdk.io/core/testing => ../../core/testing
cosmossdk.io/store => ./../../store
cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6 // main
cosmossdk.io/x/accounts => ./../../x/accounts
cosmossdk.io/x/auth => ./../../x/auth
cosmossdk.io/x/bank => ./../../x/bank

View File

@ -16,6 +16,8 @@ cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6 h1:lhyOHcIJU+IB6i5sO36DWC2r4QXDEk/bsno7jrTr28k=
cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6/go.mod h1:CY8wAToETz/dmuuKwf/qfXEImtey4jWdWWcoavfQWNw=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=

View File

@ -1,7 +1,7 @@
package coins
import (
"fmt"
"errors"
"regexp"
"strings"
@ -19,13 +19,13 @@ func ParseCoin(input string) (*basev1beta1.Coin, error) {
input = strings.TrimSpace(input)
if input == "" {
return nil, fmt.Errorf("empty input when parsing coin")
return nil, errors.New("empty input when parsing coin")
}
matches := coinRegex.FindStringSubmatch(input)
if len(matches) == 0 {
return nil, fmt.Errorf("invalid input format")
return nil, errors.New("invalid input format")
}
return &basev1beta1.Coin{

View File

@ -1,6 +1,7 @@
package prompt
import (
"errors"
"fmt"
"net/url"
@ -10,7 +11,7 @@ import (
// ValidatePromptNotEmpty validates that the input is not empty.
func ValidatePromptNotEmpty(input string) error {
if input == "" {
return fmt.Errorf("input cannot be empty")
return errors.New("input cannot be empty")
}
return nil

View File

@ -16,7 +16,7 @@ service Msg {
};
rpc Clawback(MsgClawbackRequest) returns (MsgClawbackResponse) {
option (cosmos_proto.method_added_in) = "cosmos-sdk v0.51.0";
option (cosmos_proto.method_added_in) = "cosmos-sdk v0.52.0 ";
}
}
@ -59,4 +59,4 @@ message MsgResponse {
message MsgClawbackRequest {}
message MsgClawbackResponse {}
message MsgClawbackResponse {}

View File

@ -4287,27 +4287,27 @@ var file_testpb_msg_proto_rawDesc = []byte{
0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x4d, 0x73, 0x67, 0x43,
0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x15,
0x0a, 0x13, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xab, 0x01, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x47, 0x0a,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xac, 0x01, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x47, 0x0a,
0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d,
0x73, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74,
0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16,
0xca, 0xb4, 0x2d, 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76,
0x30, 0x2e, 0x35, 0x30, 0x2e, 0x30, 0x12, 0x5b, 0x0a, 0x08, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61,
0x30, 0x2e, 0x35, 0x30, 0x2e, 0x30, 0x12, 0x5c, 0x0a, 0x08, 0x43, 0x6c, 0x61, 0x77, 0x62, 0x61,
0x63, 0x6b, 0x12, 0x1a, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x43,
0x6c, 0x61, 0x77, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0x2e, 0x4d, 0x73, 0x67, 0x43, 0x6c, 0x61, 0x77, 0x62,
0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0xca, 0xb4, 0x2d,
0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76, 0x30, 0x2e, 0x35,
0x31, 0x2e, 0x30, 0x42, 0x86, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73, 0x74,
0x70, 0x62, 0x42, 0x08, 0x4d, 0x73, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x36,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x54, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x54,
0x65, 0x73, 0x74, 0x70, 0x62, 0xca, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xe2, 0x02,
0x12, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0xca, 0xb4, 0x2d,
0x13, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x20, 0x76, 0x30, 0x2e, 0x35,
0x32, 0x2e, 0x30, 0x20, 0x42, 0x86, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x65, 0x73,
0x74, 0x70, 0x62, 0x42, 0x08, 0x4d, 0x73, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x54, 0x58, 0x58, 0xaa, 0x02, 0x06,
0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xca, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0xe2,
0x02, 0x12, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x54, 0x65, 0x73, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -123,7 +123,7 @@ func verifySignature(
return err
}
if !pubKey.VerifySignature(signBytes, data.Signature) {
return fmt.Errorf("unable to verify single signer signature")
return errors.New("unable to verify single signer signature")
}
return nil
default:

View File

@ -9,7 +9,6 @@ COPY api/go.mod api/go.sum /work/api/
COPY log/go.mod log/go.sum /work/log/
COPY core/go.mod core/go.sum /work/core/
COPY collections/go.mod collections/go.sum /work/collections/
COPY store/go.mod store/go.sum /work/store/
COPY depinject/go.mod depinject/go.sum /work/depinject/
COPY x/tx/go.mod x/tx/go.sum /work/x/tx/
COPY x/protocolpool/go.mod x/protocolpool/go.sum /work/x/protocolpool/
@ -24,9 +23,6 @@ COPY x/mint/go.mod x/mint/go.sum /work/x/mint/
COPY x/consensus/go.mod x/consensus/go.sum /work/x/consensus/
COPY x/accounts/go.mod x/accounts/go.sum /work/x/accounts/
COPY runtime/v2/go.mod runtime/v2/go.sum /work/runtime/v2/
COPY server/v2/go.mod server/v2/go.sum /work/server/v2/
COPY server/v2/appmanager/go.mod server/v2/appmanager/go.sum /work/server/v2/appmanager/
COPY server/v2/stf/go.mod server/v2/stf/go.sum /work/server/v2/stf/
COPY server/v2/cometbft/go.mod server/v2/cometbft/go.sum /work/server/v2/cometbft/
COPY core/testing/go.mod core/testing/go.sum /work/core/testing/

2
go.mod
View File

@ -188,7 +188,7 @@ replace (
cosmossdk.io/collections => ./collections
cosmossdk.io/core => ./core
cosmossdk.io/core/testing => ./core/testing
cosmossdk.io/store => ./store
cosmossdk.io/store => cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6 // main
cosmossdk.io/x/accounts => ./x/accounts
cosmossdk.io/x/auth => ./x/auth
cosmossdk.io/x/bank => ./x/bank

2
go.sum
View File

@ -14,6 +14,8 @@ cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6 h1:lhyOHcIJU+IB6i5sO36DWC2r4QXDEk/bsno7jrTr28k=
cosmossdk.io/store v1.0.0-rc.0.0.20240731205446-aee9803a0af6/go.mod h1:CY8wAToETz/dmuuKwf/qfXEImtey4jWdWWcoavfQWNw=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=

View File

@ -16,11 +16,6 @@ use (
./simapp
./tests
./tests/systemtests
./schema
./server/v2/stf
./server/v2/appmanager
./store
./store/v2
./runtime/v2
./tools/cosmovisor
./tools/confix

View File

@ -1,37 +0,0 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) \#<issue-number> message
The issue numbers will later be link-ified during the release process so you do
not have to worry about including a link manually, but you can if you wish.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
"CLI Breaking" for breaking CLI commands.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]

View File

@ -1,41 +0,0 @@
# PostgreSQL Indexer
The PostgreSQL indexer can fully index the current state for all modules that implement `cosmossdk.io/schema.HasModuleCodec`.
implement `cosmossdk.io/schema.HasModuleCodec`.
## Table, Column and Enum Naming
`ObjectType`s names are converted to table names prefixed with the module name and an underscore. i.e. the `ObjectType` `foo` in module `bar` will be stored in a table named `bar_foo`.
Column names are identical to field names. All identifiers are quoted with double quotes so that they are case-sensitive and won't clash with any reserved names.
Like, table names, enum types are prefixed with the module name and an underscore.
## Schema Type Mapping
The mapping of `cosmossdk.io/schema` `Kind`s to PostgreSQL types is as follows:
| Kind | PostgreSQL Type | Notes |
|---------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `StringKind` | `TEXT` | |
| `BoolKind` | `BOOLEAN` | |
| `BytesKind` | `BYTEA` | |
| `Int8Kind` | `SMALLINT` | |
| `Int16Kind` | `SMALLINT` | |
| `Int32Kind` | `INTEGER` | |
| `Int64Kind` | `BIGINT` | |
| `Uint8Kind` | `SMALLINT` | |
| `Uint16Kind` | `INTEGER` | |
| `Uint32Kind` | `BIGINT` | |
| `Uint64Kind` | `NUMERIC` | |
| `Float32Kind` | `REAL` | |
| `Float64Kind` | `DOUBLE PRECISION` | |
| `IntegerStringKind` | `NUMERIC` | |
| `DecimalStringKind` | `NUMERIC` | |
| `JSONKind` | `JSONB` | |
| `Bech32AddressKind` | `TEXT` | addresses are converted to strings with the specified address prefix |
| `TimeKind` | `BIGINT` and `TIMESTAMPTZ` | time types are stored as two columns, one with the `_nanos` suffix with full nanoseconds precision, and another as a `TIMESTAMPTZ` generated column with microsecond precision |
| `DurationKind` | `BIGINT` | durations are stored as a single column in nanoseconds |
| `EnumKind` | `<module_name>_<enum_name>` | a custom enum type is created for each module prefixed with the module name it pertains to |

View File

@ -1,8 +0,0 @@
package postgres
// BaseSQL is the base SQL that is always included in the schema.
const BaseSQL = `
CREATE OR REPLACE FUNCTION nanos_to_timestamptz(nanos bigint) RETURNS timestamptz AS $$
SELECT to_timestamp(nanos / 1000000000) + (nanos / 1000000000) * INTERVAL '1 microsecond'
$$ LANGUAGE SQL IMMUTABLE;
`

View File

@ -1,120 +0,0 @@
package postgres
import (
"fmt"
"io"
"cosmossdk.io/schema"
)
// createColumnDefinition writes a column definition within a CREATE TABLE statement for the field.
func (tm *ObjectIndexer) createColumnDefinition(writer io.Writer, field schema.Field) error {
_, err := fmt.Fprintf(writer, "%q ", field.Name)
if err != nil {
return err
}
simple := simpleColumnType(field.Kind)
if simple != "" {
_, err = fmt.Fprintf(writer, "%s", simple)
if err != nil {
return err
}
return writeNullability(writer, field.Nullable)
} else {
switch field.Kind {
case schema.EnumKind:
_, err = fmt.Fprintf(writer, "%q", enumTypeName(tm.moduleName, field.EnumDefinition))
if err != nil {
return err
}
case schema.TimeKind:
// for time fields, we generate two columns:
// - one with nanoseconds precision for lossless storage, suffixed with _nanos
// - one as a timestamptz (microsecond precision) for ease of use, that is GENERATED
nanosColName := fmt.Sprintf("%s_nanos", field.Name)
_, err = fmt.Fprintf(writer, "TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz(%q)) STORED,\n\t", nanosColName)
if err != nil {
return err
}
_, err = fmt.Fprintf(writer, `%q BIGINT`, nanosColName)
if err != nil {
return err
}
default:
return fmt.Errorf("unexpected kind: %v, this should have been handled earlier", field.Kind)
}
return writeNullability(writer, field.Nullable)
}
}
// writeNullability writes column nullability.
func writeNullability(writer io.Writer, nullable bool) error {
if nullable {
_, err := fmt.Fprintf(writer, " NULL,\n\t")
return err
} else {
_, err := fmt.Fprintf(writer, " NOT NULL,\n\t")
return err
}
}
// simpleColumnType returns the postgres column type for the kind for simple types.
func simpleColumnType(kind schema.Kind) string {
//nolint:goconst // adding constants for these postgres type names would impede readability
switch kind {
case schema.StringKind:
return "TEXT"
case schema.BoolKind:
return "BOOLEAN"
case schema.BytesKind:
return "BYTEA"
case schema.Int8Kind:
return "SMALLINT"
case schema.Int16Kind:
return "SMALLINT"
case schema.Int32Kind:
return "INTEGER"
case schema.Int64Kind:
return "BIGINT"
case schema.Uint8Kind:
return "SMALLINT"
case schema.Uint16Kind:
return "INTEGER"
case schema.Uint32Kind:
return "BIGINT"
case schema.Uint64Kind:
return "NUMERIC"
case schema.IntegerStringKind:
return "NUMERIC"
case schema.DecimalStringKind:
return "NUMERIC"
case schema.Float32Kind:
return "REAL"
case schema.Float64Kind:
return "DOUBLE PRECISION"
case schema.JSONKind:
return "JSONB"
case schema.DurationKind:
return "BIGINT"
case schema.Bech32AddressKind:
return "TEXT"
default:
return ""
}
}
// updatableColumnName is the name of the insertable/updatable column name for the field.
// This is the field name in most cases, except for time columns which are stored as nanos
// and then converted to timestamp generated columns.
func (tm *ObjectIndexer) updatableColumnName(field schema.Field) (name string, err error) {
name = field.Name
if field.Kind == schema.TimeKind {
name = fmt.Sprintf("%s_nanos", name)
}
name = fmt.Sprintf("%q", name)
return
}

View File

@ -1,14 +0,0 @@
package postgres
import (
"context"
"database/sql"
)
// DBConn is an interface that abstracts the *sql.DB, *sql.Tx and *sql.Conn types.
type DBConn interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}

View File

@ -1,96 +0,0 @@
package postgres
import (
"context"
"fmt"
"io"
"strings"
)
// CreateTable creates the table for the object type.
func (tm *ObjectIndexer) CreateTable(ctx context.Context, conn DBConn) error {
buf := new(strings.Builder)
err := tm.CreateTableSql(buf)
if err != nil {
return err
}
sqlStr := buf.String()
if tm.options.Logger != nil {
tm.options.Logger(fmt.Sprintf("Creating table %s", tm.TableName()), sqlStr)
}
_, err = conn.ExecContext(ctx, sqlStr)
return err
}
// CreateTableSql generates a CREATE TABLE statement for the object type.
func (tm *ObjectIndexer) CreateTableSql(writer io.Writer) error {
_, err := fmt.Fprintf(writer, "CREATE TABLE IF NOT EXISTS %q (\n\t", tm.TableName())
if err != nil {
return err
}
isSingleton := false
if len(tm.typ.KeyFields) == 0 {
isSingleton = true
_, err = fmt.Fprintf(writer, "_id INTEGER NOT NULL CHECK (_id = 1),\n\t")
if err != nil {
return err
}
} else {
for _, field := range tm.typ.KeyFields {
err = tm.createColumnDefinition(writer, field)
if err != nil {
return err
}
}
}
for _, field := range tm.typ.ValueFields {
err = tm.createColumnDefinition(writer, field)
if err != nil {
return err
}
}
// add _deleted column when we have RetainDeletions set and enabled
if !tm.options.DisableRetainDeletions && tm.typ.RetainDeletions {
_, err = fmt.Fprintf(writer, "_deleted BOOLEAN NOT NULL DEFAULT FALSE,\n\t")
if err != nil {
return err
}
}
var pKeys []string
if !isSingleton {
for _, field := range tm.typ.KeyFields {
name, err := tm.updatableColumnName(field)
if err != nil {
return err
}
pKeys = append(pKeys, name)
}
} else {
pKeys = []string{"_id"}
}
_, err = fmt.Fprintf(writer, "PRIMARY KEY (%s)", strings.Join(pKeys, ", "))
if err != nil {
return err
}
_, err = fmt.Fprintf(writer, "\n);\n")
if err != nil {
return err
}
// we GRANT SELECT on the table to PUBLIC so that the table is automatically available
// for querying using off-the-shelf tools like pg_graphql, Postgrest, Postgraphile, etc.
// without any login permissions
_, err = fmt.Fprintf(writer, "GRANT SELECT ON TABLE %q TO PUBLIC;", tm.TableName())
if err != nil {
return err
}
return nil
}

View File

@ -1,94 +0,0 @@
package postgres
import (
"os"
"cosmossdk.io/indexer/postgres/internal/testdata"
"cosmossdk.io/schema"
)
func ExampleObjectIndexer_CreateTableSql_allKinds() {
exampleCreateTable(testdata.AllKindsObject)
// Output:
// CREATE TABLE IF NOT EXISTS "test_all_kinds" (
// "id" BIGINT NOT NULL,
// "ts" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("ts_nanos")) STORED,
// "ts_nanos" BIGINT NOT NULL,
// "string" TEXT NOT NULL,
// "bytes" BYTEA NOT NULL,
// "int8" SMALLINT NOT NULL,
// "uint8" SMALLINT NOT NULL,
// "int16" SMALLINT NOT NULL,
// "uint16" INTEGER NOT NULL,
// "int32" INTEGER NOT NULL,
// "uint32" BIGINT NOT NULL,
// "int64" BIGINT NOT NULL,
// "uint64" NUMERIC NOT NULL,
// "integer" NUMERIC NOT NULL,
// "decimal" NUMERIC NOT NULL,
// "bool" BOOLEAN NOT NULL,
// "time" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("time_nanos")) STORED,
// "time_nanos" BIGINT NOT NULL,
// "duration" BIGINT NOT NULL,
// "float32" REAL NOT NULL,
// "float64" DOUBLE PRECISION NOT NULL,
// "bech32address" TEXT NOT NULL,
// "enum" "test_my_enum" NOT NULL,
// "json" JSONB NOT NULL,
// PRIMARY KEY ("id", "ts_nanos")
// );
// GRANT SELECT ON TABLE "test_all_kinds" TO PUBLIC;
}
func ExampleObjectIndexer_CreateTableSql_singleton() {
exampleCreateTable(testdata.SingletonObject)
// Output:
// CREATE TABLE IF NOT EXISTS "test_singleton" (
// _id INTEGER NOT NULL CHECK (_id = 1),
// "foo" TEXT NOT NULL,
// "bar" INTEGER NULL,
// "an_enum" "test_my_enum" NOT NULL,
// PRIMARY KEY (_id)
// );
// GRANT SELECT ON TABLE "test_singleton" TO PUBLIC;
}
func ExampleObjectIndexer_CreateTableSql_vote() {
exampleCreateTable(testdata.VoteObject)
// Output:
// CREATE TABLE IF NOT EXISTS "test_vote" (
// "proposal" BIGINT NOT NULL,
// "address" TEXT NOT NULL,
// "vote" "test_vote_type" NOT NULL,
// _deleted BOOLEAN NOT NULL DEFAULT FALSE,
// PRIMARY KEY ("proposal", "address")
// );
// GRANT SELECT ON TABLE "test_vote" TO PUBLIC;
}
func ExampleObjectIndexer_CreateTableSql_vote_no_retain_delete() {
exampleCreateTableOpt(testdata.VoteObject, true)
// Output:
// CREATE TABLE IF NOT EXISTS "test_vote" (
// "proposal" BIGINT NOT NULL,
// "address" TEXT NOT NULL,
// "vote" "test_vote_type" NOT NULL,
// PRIMARY KEY ("proposal", "address")
// );
// GRANT SELECT ON TABLE "test_vote" TO PUBLIC;
}
func exampleCreateTable(objectType schema.ObjectType) {
exampleCreateTableOpt(objectType, false)
}
func exampleCreateTableOpt(objectType schema.ObjectType, noRetainDelete bool) {
tm := NewObjectIndexer("test", objectType, Options{
Logger: func(msg, sql string, params ...interface{}) {},
DisableRetainDeletions: noRetainDelete,
})
err := tm.CreateTableSql(os.Stdout)
if err != nil {
panic(err)
}
}

View File

@ -1,92 +0,0 @@
package postgres
import (
"context"
"database/sql"
"fmt"
"io"
"strings"
"cosmossdk.io/schema"
)
// CreateEnumType creates an enum type in the database.
func (m *ModuleIndexer) CreateEnumType(ctx context.Context, conn DBConn, enum schema.EnumDefinition) error {
typeName := enumTypeName(m.moduleName, enum)
row := conn.QueryRowContext(ctx, "SELECT 1 FROM pg_type WHERE typname = $1", typeName)
var res interface{}
if err := row.Scan(&res); err != nil {
if err != sql.ErrNoRows {
return fmt.Errorf("failed to check if enum type %q exists: %v", typeName, err) //nolint:errorlint // using %v for go 1.12 compat
}
} else {
// the enum type already exists
return nil
}
buf := new(strings.Builder)
err := CreateEnumTypeSql(buf, m.moduleName, enum)
if err != nil {
return err
}
sqlStr := buf.String()
if m.options.Logger != nil {
m.options.Logger("Creating enum type", sqlStr)
}
_, err = conn.ExecContext(ctx, sqlStr)
return err
}
// CreateEnumTypeSql generates a CREATE TYPE statement for the enum definition.
func CreateEnumTypeSql(writer io.Writer, moduleName string, enum schema.EnumDefinition) error {
_, err := fmt.Fprintf(writer, "CREATE TYPE %q AS ENUM (", enumTypeName(moduleName, enum))
if err != nil {
return err
}
for i, value := range enum.Values {
if i > 0 {
_, err = fmt.Fprintf(writer, ", ")
if err != nil {
return err
}
}
_, err = fmt.Fprintf(writer, "'%s'", value)
if err != nil {
return err
}
}
_, err = fmt.Fprintf(writer, ");")
return err
}
// enumTypeName returns the name of the enum type scoped to the module.
func enumTypeName(moduleName string, enum schema.EnumDefinition) string {
return fmt.Sprintf("%s_%s", moduleName, enum.Name)
}
// createEnumTypesForFields creates enum types for all the fields that have enum kind in the module schema.
func (m *ModuleIndexer) createEnumTypesForFields(ctx context.Context, conn DBConn, fields []schema.Field) error {
for _, field := range fields {
if field.Kind != schema.EnumKind {
continue
}
if _, ok := m.definedEnums[field.EnumDefinition.Name]; ok {
// if the enum type is already defined, skip
// we assume validation already happened
continue
}
err := m.CreateEnumType(ctx, conn, field.EnumDefinition)
if err != nil {
return err
}
m.definedEnums[field.EnumDefinition.Name] = field.EnumDefinition
}
return nil
}

View File

@ -1,16 +0,0 @@
package postgres
import (
"os"
"cosmossdk.io/indexer/postgres/internal/testdata"
)
func ExampleCreateEnumTypeSql() {
err := CreateEnumTypeSql(os.Stdout, "test", testdata.MyEnum)
if err != nil {
panic(err)
}
// Output:
// CREATE TYPE "test_my_enum" AS ENUM ('a', 'b', 'c');
}

View File

@ -1,11 +0,0 @@
module cosmossdk.io/indexer/postgres
// NOTE: we are staying on an earlier version of golang to avoid problems building
// with older codebases.
go 1.12
// NOTE: cosmossdk.io/schema should be the only dependency here
// so there are no problems building this with any version of the SDK.
// This module should only use the golang standard library (database/sql)
// and cosmossdk.io/indexer/base.
require cosmossdk.io/schema v0.1.1

View File

@ -1,2 +0,0 @@
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=

View File

@ -1,80 +0,0 @@
package postgres
import (
"context"
"database/sql"
"fmt"
"cosmossdk.io/schema/appdata"
)
type Config struct {
// DatabaseURL is the PostgreSQL connection URL to use to connect to the database.
DatabaseURL string `json:"database_url"`
// DatabaseDriver is the PostgreSQL database/sql driver to use. This defaults to "pgx".
DatabaseDriver string `json:"database_driver"`
// DisableRetainDeletions disables the retain deletions functionality even if it is set in an object type schema.
DisableRetainDeletions bool `json:"disable_retain_deletions"`
}
type SqlLogger = func(msg, sql string, params ...interface{})
func StartIndexer(ctx context.Context, logger SqlLogger, config Config) (appdata.Listener, error) {
if config.DatabaseURL == "" {
return appdata.Listener{}, fmt.Errorf("missing database URL")
}
driver := config.DatabaseDriver
if driver == "" {
driver = "pgx"
}
db, err := sql.Open(driver, config.DatabaseURL)
if err != nil {
return appdata.Listener{}, err
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return appdata.Listener{}, err
}
// commit base schema
_, err = tx.Exec(BaseSQL)
if err != nil {
return appdata.Listener{}, err
}
moduleIndexers := map[string]*ModuleIndexer{}
opts := Options{
DisableRetainDeletions: config.DisableRetainDeletions,
Logger: logger,
}
return appdata.Listener{
InitializeModuleData: func(data appdata.ModuleInitializationData) error {
moduleName := data.ModuleName
modSchema := data.Schema
_, ok := moduleIndexers[moduleName]
if ok {
return fmt.Errorf("module %s already initialized", moduleName)
}
mm := NewModuleIndexer(moduleName, modSchema, opts)
moduleIndexers[moduleName] = mm
return mm.InitializeSchema(ctx, tx)
},
Commit: func(data appdata.CommitData) error {
err = tx.Commit()
if err != nil {
return err
}
tx, err = db.BeginTx(ctx, nil)
return err
},
}, nil
}

View File

@ -1,98 +0,0 @@
package testdata
import "cosmossdk.io/schema"
var ExampleSchema schema.ModuleSchema
var AllKindsObject schema.ObjectType
func init() {
AllKindsObject = schema.ObjectType{
Name: "all_kinds",
KeyFields: []schema.Field{
{
Name: "id",
Kind: schema.Int64Kind,
},
{
Name: "ts",
Kind: schema.TimeKind,
},
},
}
for i := schema.InvalidKind + 1; i <= schema.MAX_VALID_KIND; i++ {
field := schema.Field{
Name: i.String(),
Kind: i,
}
switch i {
case schema.EnumKind:
field.EnumDefinition = MyEnum
case schema.Bech32AddressKind:
field.AddressPrefix = "foo"
default:
}
AllKindsObject.ValueFields = append(AllKindsObject.ValueFields, field)
}
ExampleSchema = schema.ModuleSchema{
ObjectTypes: []schema.ObjectType{
AllKindsObject,
SingletonObject,
VoteObject,
},
}
}
var SingletonObject = schema.ObjectType{
Name: "singleton",
ValueFields: []schema.Field{
{
Name: "foo",
Kind: schema.StringKind,
},
{
Name: "bar",
Kind: schema.Int32Kind,
Nullable: true,
},
{
Name: "an_enum",
Kind: schema.EnumKind,
EnumDefinition: MyEnum,
},
},
}
var VoteObject = schema.ObjectType{
Name: "vote",
KeyFields: []schema.Field{
{
Name: "proposal",
Kind: schema.Int64Kind,
},
{
Name: "address",
Kind: schema.Bech32AddressKind,
},
},
ValueFields: []schema.Field{
{
Name: "vote",
Kind: schema.EnumKind,
EnumDefinition: schema.EnumDefinition{
Name: "vote_type",
Values: []string{"yes", "no", "abstain"},
},
},
},
RetainDeletions: true,
}
var MyEnum = schema.EnumDefinition{
Name: "my_enum",
Values: []string{"a", "b", "c"},
}

View File

@ -1,61 +0,0 @@
package postgres
import (
"context"
"fmt"
"cosmossdk.io/schema"
)
// ModuleIndexer manages the tables for a module.
type ModuleIndexer struct {
moduleName string
schema schema.ModuleSchema
tables map[string]*ObjectIndexer
definedEnums map[string]schema.EnumDefinition
options Options
}
// NewModuleIndexer creates a new ModuleIndexer for the given module schema.
func NewModuleIndexer(moduleName string, modSchema schema.ModuleSchema, options Options) *ModuleIndexer {
return &ModuleIndexer{
moduleName: moduleName,
schema: modSchema,
tables: map[string]*ObjectIndexer{},
definedEnums: map[string]schema.EnumDefinition{},
options: options,
}
}
// InitializeSchema creates tables for all object types in the module schema and creates enum types.
func (m *ModuleIndexer) InitializeSchema(ctx context.Context, conn DBConn) error {
// create enum types
for _, typ := range m.schema.ObjectTypes {
err := m.createEnumTypesForFields(ctx, conn, typ.KeyFields)
if err != nil {
return err
}
err = m.createEnumTypesForFields(ctx, conn, typ.ValueFields)
if err != nil {
return err
}
}
// create tables for all object types
for _, typ := range m.schema.ObjectTypes {
tm := NewObjectIndexer(m.moduleName, typ, m.options)
m.tables[typ.Name] = tm
err := tm.CreateTable(ctx, conn)
if err != nil {
return fmt.Errorf("failed to create table for %s in module %s: %v", typ.Name, m.moduleName, err) //nolint:errorlint // using %v for go 1.12 compat
}
}
return nil
}
// ObjectIndexers returns the object indexers for the module.
func (m *ModuleIndexer) ObjectIndexers() map[string]*ObjectIndexer {
return m.tables
}

View File

@ -1,44 +0,0 @@
package postgres
import (
"fmt"
"cosmossdk.io/schema"
)
// ObjectIndexer is a helper struct that generates SQL for a given object type.
type ObjectIndexer struct {
moduleName string
typ schema.ObjectType
valueFields map[string]schema.Field
allFields map[string]schema.Field
options Options
}
// NewObjectIndexer creates a new ObjectIndexer for the given object type.
func NewObjectIndexer(moduleName string, typ schema.ObjectType, options Options) *ObjectIndexer {
allFields := make(map[string]schema.Field)
valueFields := make(map[string]schema.Field)
for _, field := range typ.KeyFields {
allFields[field.Name] = field
}
for _, field := range typ.ValueFields {
valueFields[field.Name] = field
allFields[field.Name] = field
}
return &ObjectIndexer{
moduleName: moduleName,
typ: typ,
allFields: allFields,
valueFields: valueFields,
options: options,
}
}
// TableName returns the name of the table for the object type scoped to its module.
func (tm *ObjectIndexer) TableName() string {
return fmt.Sprintf("%s_%s", tm.moduleName, tm.typ.Name)
}

View File

@ -1,10 +0,0 @@
package postgres
// Options are the options for module and object indexers.
type Options struct {
// DisableRetainDeletions disables retain deletions functionality even on object types that have it set.
DisableRetainDeletions bool
// Logger is the logger for the indexer to use.
Logger SqlLogger
}

View File

@ -1,16 +0,0 @@
sonar.projectKey=cosmos-sdk-indexer-postgres
sonar.organization=cosmos
sonar.projectName=Cosmos SDK - Postgres Indexer
sonar.project.monorepo.enabled=true
sonar.sources=.
sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gw.go
sonar.coverage.exclusions=**/*_test.go,**/testutil/**,**/*.pb.go,**/*.pb.gw.go,**/*.pulsar.go,test_helpers.go,docs/**
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out
sonar.sourceEncoding=UTF-8
sonar.scm.provider=git
sonar.scm.forceReloadAll=true

View File

@ -1,3 +0,0 @@
# PostgreSQL Indexer Tests
The majority of tests for the PostgreSQL indexer are stored in this separate `tests` go module to keep the main indexer module free of dependencies on any particular PostgreSQL driver. This allows users to choose their own driver and integrate the indexer free of any dependency conflict concerns.

View File

@ -1,33 +0,0 @@
module cosmossdk.io/indexer/postgres/testing
require (
cosmossdk.io/indexer/postgres v0.0.0-00010101000000-000000000000
cosmossdk.io/schema v0.1.1
github.com/fergusstrange/embedded-postgres v1.27.0
github.com/hashicorp/consul/sdk v0.16.1
github.com/jackc/pgx/v5 v5.6.0
github.com/stretchr/testify v1.9.0
gotest.tools/v3 v3.5.1
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace cosmossdk.io/indexer/postgres => ../.
go 1.22

View File

@ -1,56 +0,0 @@
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fergusstrange/embedded-postgres v1.27.0 h1:RAlpWL194IhEpPgeJceTM0ifMJKhiSVxBVIDYB1Jee8=
github.com/fergusstrange/embedded-postgres v1.27.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@ -1,87 +0,0 @@
package tests
import (
"context"
"fmt"
"os"
"strings"
"testing"
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
"github.com/hashicorp/consul/sdk/freeport"
_ "github.com/jackc/pgx/v5/stdlib" // this is where we get our pgx database driver from
"github.com/stretchr/testify/require"
"gotest.tools/v3/golden"
"cosmossdk.io/indexer/postgres"
"cosmossdk.io/indexer/postgres/internal/testdata"
"cosmossdk.io/schema/appdata"
)
func TestInitSchema(t *testing.T) {
t.Run("default", func(t *testing.T) {
testInitSchema(t, false, "init_schema.txt")
})
t.Run("retain deletions disabled", func(t *testing.T) {
testInitSchema(t, true, "init_schema_no_retain_delete.txt")
})
}
func testInitSchema(t *testing.T, disableRetainDeletions bool, goldenFileName string) {
t.Helper()
connectionUrl := createTestDB(t)
buf := &strings.Builder{}
logger := func(msg, sql string, params ...interface{}) {
_, err := fmt.Fprintln(buf, msg)
require.NoError(t, err)
_, err = fmt.Fprintln(buf, sql)
require.NoError(t, err)
if len(params) != 0 {
_, err = fmt.Fprintln(buf, "Params:", params)
require.NoError(t, err)
}
_, err = fmt.Fprintln(buf)
require.NoError(t, err)
}
listener, err := postgres.StartIndexer(context.Background(), logger, postgres.Config{
DatabaseURL: connectionUrl,
DisableRetainDeletions: disableRetainDeletions,
})
require.NoError(t, err)
require.NotNil(t, listener.InitializeModuleData)
require.NoError(t, listener.InitializeModuleData(appdata.ModuleInitializationData{
ModuleName: "test",
Schema: testdata.ExampleSchema,
}))
require.NotNil(t, listener.Commit)
require.NoError(t, listener.Commit(appdata.CommitData{}))
golden.Assert(t, buf.String(), goldenFileName)
}
func createTestDB(t *testing.T) (connectionUrl string) {
t.Helper()
tempDir, err := os.MkdirTemp("", "postgres-indexer-test")
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(tempDir))
})
dbPort := freeport.GetOne(t)
pgConfig := embeddedpostgres.DefaultConfig().
Port(uint32(dbPort)).
DataPath(tempDir)
connectionUrl = pgConfig.GetConnectionURL()
pg := embeddedpostgres.NewDatabase(pgConfig)
require.NoError(t, pg.Start())
t.Cleanup(func() {
require.NoError(t, pg.Stop())
})
return
}

View File

@ -1,56 +0,0 @@
Creating enum type
CREATE TYPE "test_my_enum" AS ENUM ('a', 'b', 'c');
Creating enum type
CREATE TYPE "test_vote_type" AS ENUM ('yes', 'no', 'abstain');
Creating table test_all_kinds
CREATE TABLE IF NOT EXISTS "test_all_kinds" (
"id" BIGINT NOT NULL,
"ts" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("ts_nanos")) STORED,
"ts_nanos" BIGINT NOT NULL,
"string" TEXT NOT NULL,
"bytes" BYTEA NOT NULL,
"int8" SMALLINT NOT NULL,
"uint8" SMALLINT NOT NULL,
"int16" SMALLINT NOT NULL,
"uint16" INTEGER NOT NULL,
"int32" INTEGER NOT NULL,
"uint32" BIGINT NOT NULL,
"int64" BIGINT NOT NULL,
"uint64" NUMERIC NOT NULL,
"integer" NUMERIC NOT NULL,
"decimal" NUMERIC NOT NULL,
"bool" BOOLEAN NOT NULL,
"time" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("time_nanos")) STORED,
"time_nanos" BIGINT NOT NULL,
"duration" BIGINT NOT NULL,
"float32" REAL NOT NULL,
"float64" DOUBLE PRECISION NOT NULL,
"bech32address" TEXT NOT NULL,
"enum" "test_my_enum" NOT NULL,
"json" JSONB NOT NULL,
PRIMARY KEY ("id", "ts_nanos")
);
GRANT SELECT ON TABLE "test_all_kinds" TO PUBLIC;
Creating table test_singleton
CREATE TABLE IF NOT EXISTS "test_singleton" (
_id INTEGER NOT NULL CHECK (_id = 1),
"foo" TEXT NOT NULL,
"bar" INTEGER NULL,
"an_enum" "test_my_enum" NOT NULL,
PRIMARY KEY (_id)
);
GRANT SELECT ON TABLE "test_singleton" TO PUBLIC;
Creating table test_vote
CREATE TABLE IF NOT EXISTS "test_vote" (
"proposal" BIGINT NOT NULL,
"address" TEXT NOT NULL,
"vote" "test_vote_type" NOT NULL,
_deleted BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY ("proposal", "address")
);
GRANT SELECT ON TABLE "test_vote" TO PUBLIC;

View File

@ -1,55 +0,0 @@
Creating enum type
CREATE TYPE "test_my_enum" AS ENUM ('a', 'b', 'c');
Creating enum type
CREATE TYPE "test_vote_type" AS ENUM ('yes', 'no', 'abstain');
Creating table test_all_kinds
CREATE TABLE IF NOT EXISTS "test_all_kinds" (
"id" BIGINT NOT NULL,
"ts" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("ts_nanos")) STORED,
"ts_nanos" BIGINT NOT NULL,
"string" TEXT NOT NULL,
"bytes" BYTEA NOT NULL,
"int8" SMALLINT NOT NULL,
"uint8" SMALLINT NOT NULL,
"int16" SMALLINT NOT NULL,
"uint16" INTEGER NOT NULL,
"int32" INTEGER NOT NULL,
"uint32" BIGINT NOT NULL,
"int64" BIGINT NOT NULL,
"uint64" NUMERIC NOT NULL,
"integer" NUMERIC NOT NULL,
"decimal" NUMERIC NOT NULL,
"bool" BOOLEAN NOT NULL,
"time" TIMESTAMPTZ GENERATED ALWAYS AS (nanos_to_timestamptz("time_nanos")) STORED,
"time_nanos" BIGINT NOT NULL,
"duration" BIGINT NOT NULL,
"float32" REAL NOT NULL,
"float64" DOUBLE PRECISION NOT NULL,
"bech32address" TEXT NOT NULL,
"enum" "test_my_enum" NOT NULL,
"json" JSONB NOT NULL,
PRIMARY KEY ("id", "ts_nanos")
);
GRANT SELECT ON TABLE "test_all_kinds" TO PUBLIC;
Creating table test_singleton
CREATE TABLE IF NOT EXISTS "test_singleton" (
_id INTEGER NOT NULL CHECK (_id = 1),
"foo" TEXT NOT NULL,
"bar" INTEGER NULL,
"an_enum" "test_my_enum" NOT NULL,
PRIMARY KEY (_id)
);
GRANT SELECT ON TABLE "test_singleton" TO PUBLIC;
Creating table test_vote
CREATE TABLE IF NOT EXISTS "test_vote" (
"proposal" BIGINT NOT NULL,
"address" TEXT NOT NULL,
"vote" "test_vote_type" NOT NULL,
PRIMARY KEY ("proposal", "address")
);
GRANT SELECT ON TABLE "test_vote" TO PUBLIC;

View File

@ -5,6 +5,9 @@ import (
"encoding/json"
"fmt"
"io"
"path/filepath"
"github.com/spf13/viper"
"cosmossdk.io/core/appmodule"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
@ -13,6 +16,7 @@ import (
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/stf"
"cosmossdk.io/server/v2/stf/branch"
"cosmossdk.io/store/v2/db"
rootstore "cosmossdk.io/store/v2/root"
)
@ -22,6 +26,7 @@ import (
type AppBuilder[T transaction.Tx] struct {
app *App[T]
storeOptions *rootstore.FactoryOptions
viper *viper.Viper
// the following fields are used to overwrite the default
branch func(state store.ReaderMap) store.WriterMap
@ -119,6 +124,30 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) {
}
a.app.stf = stf
v := a.viper
home := v.GetString(FlagHome)
storeOpts := rootstore.DefaultStoreOptions()
if s := v.Sub("store.options"); s != nil {
if err := s.Unmarshal(&storeOpts); err != nil {
return nil, fmt.Errorf("failed to store options: %w", err)
}
}
scRawDb, err := db.NewDB(db.DBType(v.GetString("store.app-db-backend")), "application", filepath.Join(home, "data"), nil)
if err != nil {
panic(err)
}
storeOptions := &rootstore.FactoryOptions{
Logger: a.app.logger,
RootDir: home,
Options: storeOpts,
StoreKeys: append(a.app.storeKeys, "stf"),
SCRawDB: scRawDb,
}
a.storeOptions = storeOptions
rs, err := rootstore.CreateRootStore(a.storeOptions)
if err != nil {
return nil, fmt.Errorf("failed to create root store: %w", err)

View File

@ -7,9 +7,6 @@ replace (
cosmossdk.io/api => ../../api
cosmossdk.io/core => ../../core
cosmossdk.io/core/testing => ../../core/testing
cosmossdk.io/server/v2/appmanager => ../../server/v2/appmanager
cosmossdk.io/server/v2/stf => ../../server/v2/stf
cosmossdk.io/store/v2 => ../../store/v2
cosmossdk.io/x/accounts => ../../x/accounts
cosmossdk.io/x/auth => ../../x/auth
cosmossdk.io/x/bank => ../../x/bank
@ -22,16 +19,17 @@ replace (
require (
cosmossdk.io/api v0.7.5
cosmossdk.io/core v0.12.1-0.20240726110027-5c90246b3f9f
cosmossdk.io/core v0.12.1-0.20231114100755-569e3ff6a0d7
cosmossdk.io/depinject v1.0.0
cosmossdk.io/log v1.3.1
cosmossdk.io/server/v2/appmanager v0.0.0-20240726110027-5c90246b3f9f
cosmossdk.io/server/v2/stf v0.0.0-20240726110027-5c90246b3f9f
cosmossdk.io/store/v2 v2.0.0-20240726110027-5c90246b3f9f
cosmossdk.io/server/v2/appmanager v0.0.0-20240731205446-aee9803a0af6 // main
cosmossdk.io/server/v2/stf v0.0.0-20240731205446-aee9803a0af6 // main
cosmossdk.io/store/v2 v2.0.0-20240731205446-aee9803a0af6 // main
cosmossdk.io/x/tx v0.13.3
github.com/cosmos/gogoproto v1.5.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
google.golang.org/grpc v1.65.0
github.com/spf13/viper v1.19.0
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc
google.golang.org/grpc v1.64.1
google.golang.org/protobuf v1.34.2
)
@ -39,14 +37,13 @@ require (
buf.build/gen/go/cometbft/cometbft/protocolbuffers/go v1.34.2-20240701160653-fedbb9acfd2f.2 // indirect
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2 // indirect
cosmossdk.io/core/testing v0.0.0-00010101000000-000000000000 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.1 // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
@ -55,7 +52,8 @@ require (
github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/getsentry/sentry-go v0.28.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
@ -63,15 +61,19 @@ require (
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.3 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/linxGnu/grocksdb v1.9.2 // indirect
github.com/linxGnu/grocksdb v1.8.14 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/gomega v1.34.0 // indirect
github.com/onsi/gomega v1.28.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
@ -80,18 +82,26 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -4,10 +4,16 @@ buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88e
buf.build/gen/go/cosmos/gogo-proto/protocolbuffers/go v1.34.2-20240130113600-88ef6483f90f.2/go.mod h1:HqcXMSa5qnNuakaMUo+hWhF51mKbcrZxGl9Vp5EeJXc=
cosmossdk.io/depinject v1.0.0 h1:dQaTu6+O6askNXO06+jyeUAnF2/ssKwrrszP9t5q050=
cosmossdk.io/depinject v1.0.0/go.mod h1:zxK/h3HgHoA/eJVtiSsoaRaRA2D5U4cJ5thIG4ssbB8=
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 h1:IQNdY2kB+k+1OM2DvqFG1+UgeU1JzZrWtwuWzI3ZfwA=
cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5/go.mod h1:0CuYKkFHxc1vw2JC+t21THBCALJVROrWVR/3PQ1urpc=
cosmossdk.io/log v1.3.1 h1:UZx8nWIkfbbNEWusZqzAx3ZGvu54TZacWib3EzUYmGI=
cosmossdk.io/log v1.3.1/go.mod h1:2/dIomt8mKdk6vl3OWJcPk2be3pGOS8OQaLUM/3/tCM=
cosmossdk.io/server/v2/appmanager v0.0.0-20240731205446-aee9803a0af6 h1:vrHmVjfEjEwQh90dim272gYq7OFILg4Yrv3XzreMpe4=
cosmossdk.io/server/v2/appmanager v0.0.0-20240731205446-aee9803a0af6/go.mod h1:Xm5IOSjw45Sew7fiVckaTCIU5oQPs20V+54NOqR3H4o=
cosmossdk.io/server/v2/stf v0.0.0-20240731205446-aee9803a0af6 h1:F8yfqCf1cAwuZZnIxinmzr/2nmLjhK9K/BJfBjW3nJ0=
cosmossdk.io/server/v2/stf v0.0.0-20240731205446-aee9803a0af6/go.mod h1:IUbZp79IZ4NCR2eNXA0utcQOS8lz34BvsAWTeCGwGAM=
cosmossdk.io/store/v2 v2.0.0-20240731205446-aee9803a0af6 h1:/ffIfMKzoCVUI38t5Vq3BNW9U8exRMxK5QgS/ujn0lA=
cosmossdk.io/store/v2 v2.0.0-20240731205446-aee9803a0af6/go.mod h1:aG3brMLcldPsdhfkdCaisGDIe+tXTNWdUDt5JYsRDl8=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
@ -29,14 +35,12 @@ 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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw=
github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
@ -66,8 +70,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -125,6 +129,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -132,8 +138,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -143,8 +149,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8=
github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ=
github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -154,6 +162,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -162,9 +172,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
@ -175,10 +184,12 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os=
github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo=
github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@ -214,19 +225,39 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
@ -236,14 +267,16 @@ github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EU
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -312,12 +345,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f h1:RARaIm8pxYuxyNPbBQf5igT7XdOyCNtat1qAT2ZxjU4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5 h1:SbSDUWW1PAO24TNpLdeheoYPd7kllICcLU52x6eD4kQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240709173604-40e1e62336c5/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -334,6 +367,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -342,6 +377,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=

View File

@ -166,7 +166,7 @@ func (m *MM[T]) InitGenesisJSON(
case appmodulev2.HasGenesis:
m.logger.Debug("running initialization for module", "module", moduleName)
if err := module.InitGenesis(ctx, genesisData[moduleName]); err != nil {
return err
return fmt.Errorf("init module %s: %w", moduleName, err)
}
case appmodulev2.HasABCIGenesis:
m.logger.Debug("running initialization for module", "module", moduleName)
@ -410,7 +410,7 @@ func (m *MM[T]) RunMigrations(ctx context.Context, fromVM appmodulev2.VersionMap
// The module manager assumes only one module will update the validator set, and it can't be a new module.
if len(moduleValUpdates) > 0 {
return nil, fmt.Errorf("validator InitGenesis update is already set by another module")
return nil, errors.New("validator InitGenesis update is already set by another module")
}
}
}

View File

@ -6,6 +6,7 @@ import (
"slices"
"github.com/cosmos/gogoproto/proto"
"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
@ -28,7 +29,6 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2/services"
"cosmossdk.io/server/v2/stf"
rootstorev2 "cosmossdk.io/store/v2/root"
)
var (
@ -151,7 +151,7 @@ type AppInputs struct {
InterfaceRegistrar registry.InterfaceRegistrar
LegacyAmino legacy.Amino
Logger log.Logger
StoreOptions *rootstorev2.FactoryOptions `optional:"true"`
Viper *viper.Viper `optional:"true"`
}
func SetupAppBuilder(inputs AppInputs) {
@ -162,10 +162,8 @@ func SetupAppBuilder(inputs AppInputs) {
app.moduleManager.RegisterInterfaces(inputs.InterfaceRegistrar)
app.moduleManager.RegisterLegacyAminoCodec(inputs.LegacyAmino)
if inputs.StoreOptions != nil {
inputs.AppBuilder.storeOptions = inputs.StoreOptions
inputs.AppBuilder.storeOptions.StoreKeys = inputs.AppBuilder.app.storeKeys
inputs.AppBuilder.storeOptions.StoreKeys = append(inputs.AppBuilder.storeOptions.StoreKeys, "stf")
if inputs.Viper != nil {
inputs.AppBuilder.viper = inputs.Viper
}
}

View File

@ -12,7 +12,10 @@ import (
"cosmossdk.io/x/tx/signing"
)
const ModuleName = "runtime"
const (
ModuleName = "runtime"
FlagHome = "home"
)
// ValidateProtoAnnotations validates that the proto annotations are correct.
// More specifically, it verifies:

View File

@ -1,37 +0,0 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) \#<issue-number> message
The issue numbers will later be link-ified during the release process so you do
not have to worry about including a link manually, but you can if you wish.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
"CLI Breaking" for breaking CLI commands.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]

View File

@ -1,13 +0,0 @@
# Logical State Schema Framework
The `cosmossdk.io/schema` base module is designed to provide a stable, **zero-dependency** base layer for specifying the **logical representation of module state schemas** and implementing **state indexing**. This is intended to be used primarily for indexing modules in external databases and providing a standard human-readable state representation for genesis import and export.
The schema defined in this library does not aim to be general purpose and cover all types of schemas, such as those used for defining transactions. For instance, this schema does not include many types of composite objects such as nested objects and arrays. Rather, the schema defined here aims to cover _state_ schemas only which are implemented as key-value pairs and usually have direct mappings to relational database tables or objects in a document store.
Also, this schema does not cover physical state layout and byte-level encoding, but simply describes a common logical format.
## `HasModuleCodec` Interface
Any module which supports logical decoding and/or encoding should implement the `HasModuleCodec` interface. This interface provides a way to get the codec for the module, which can be used to decode the module's state and/or apply logical updates.
State frameworks such as `collections` or `orm` should directly provide `ModuleCodec` implementations so that this functionality basically comes for free if a compatible framework is used. Modules that do not use one of these frameworks can choose to manually implement logical decoding and/or encoding.

View File

@ -1,32 +0,0 @@
# App Data
The `appdata` package defines the basic types for streaming blockchain event and state data to external listeners, with a specific focus on supporting logical decoding and indexing of state.
A blockchain data source should accept a `Listener` instance and invoke the provided callbacks in the correct order. A downstream listener should provide a `Listener` instance and perform operations based on the data passed to its callbacks.
## `Listener` Callback Order
`Listener` callbacks should be called in this order
```mermaid
sequenceDiagram
actor Source
actor Target
Source ->> Target: Initialize
Source -->> Target: InitializeModuleSchema
loop Block
Source ->> Target: StartBlock
Source ->> Target: OnBlockHeader
Source -->> Target: OnTx
Source -->> Target: OnEvent
Source -->> Target: OnKVPair
Source -->> Target: OnObjectUpdate
Source ->> Target: Commit
end
```
`Initialize` must be called before any other method and should only be invoked once. `InitializeModuleSchema` should be called at most once for every module with logical data.
Sources will generally only call `InitializeModuleSchema` and `OnObjectUpdate` if they have native logical decoding capabilities. Usually, the indexer framework will provide this functionality based on `OnKVPair` data and `schema.HasModuleCodec` implementations.
`StartBlock` and `OnBlockHeader` should be called only once at the beginning of a block, and `Commit` should be called only once at the end of a block. The `OnTx`, `OnEvent`, `OnKVPair` and `OnObjectUpdate` must be called after `OnBlockHeader`, may be called multiple times within a block and indexers should not assume that the order is logical unless `InitializationData.HasEventAlignedWrites` is true.

View File

@ -1,97 +0,0 @@
package appdata
import (
"encoding/json"
"cosmossdk.io/schema"
)
// ModuleInitializationData represents data for related to module initialization, in particular
// the module's schema.
type ModuleInitializationData struct {
// ModuleName is the name of the module.
ModuleName string
// Schema is the schema of the module.
Schema schema.ModuleSchema
}
// StartBlockData represents the data that is passed to a listener when a block is started.
type StartBlockData struct {
// Height is the height of the block.
Height uint64
// Bytes is the raw byte representation of the block header. It may be nil if the source does not provide it.
HeaderBytes ToBytes
// JSON is the JSON representation of the block header. It should generally be a JSON object.
// It may be nil if the source does not provide it.
HeaderJSON ToJSON
}
// TxData represents the raw transaction data that is passed to a listener.
type TxData struct {
// TxIndex is the index of the transaction in the block.
TxIndex int32
// Bytes is the raw byte representation of the transaction.
Bytes ToBytes
// JSON is the JSON representation of the transaction. It should generally be a JSON object.
JSON ToJSON
}
// EventData represents event data that is passed to a listener.
type EventData struct {
// TxIndex is the index of the transaction in the block to which this event is associated.
// It should be set to a negative number if the event is not associated with a transaction.
// Canonically -1 should be used to represent begin block processing and -2 should be used to
// represent end block processing.
TxIndex int32
// MsgIndex is the index of the message in the transaction to which this event is associated.
// If TxIndex is negative, this index could correspond to the index of the message in
// begin or end block processing if such indexes exist, or it can be set to zero.
MsgIndex uint32
// EventIndex is the index of the event in the message to which this event is associated.
EventIndex uint32
// Type is the type of the event.
Type string
// Data is the JSON representation of the event data. It should generally be a JSON object.
Data ToJSON
}
// ToBytes is a function that lazily returns the raw byte representation of data.
type ToBytes = func() ([]byte, error)
// ToJSON is a function that lazily returns the JSON representation of data.
type ToJSON = func() (json.RawMessage, error)
// KVPairData represents a batch of key-value pair data that is passed to a listener.
type KVPairData struct {
Updates []ModuleKVPairUpdate
}
// ModuleKVPairUpdate represents a key-value pair update for a specific module.
type ModuleKVPairUpdate struct {
// ModuleName is the name of the module that the key-value pair belongs to.
ModuleName string
// Update is the key-value pair update.
Update schema.KVPairUpdate
}
// ObjectUpdateData represents object update data that is passed to a listener.
type ObjectUpdateData struct {
// ModuleName is the name of the module that the update corresponds to.
ModuleName string
// Updates are the object updates.
Updates []schema.ObjectUpdate
}
// CommitData represents commit data. It is empty for now, but fields could be added later.
type CommitData struct{}

View File

@ -1,41 +0,0 @@
package appdata
// Listener is an interface that defines methods for listening to both raw and logical blockchain data.
// It is valid for any of the methods to be nil, in which case the listener will not be called for that event.
// Listeners should understand the guarantees that are provided by the source they are listening to and
// understand which methods will or will not be called. For instance, most blockchains will not do logical
// decoding of data out of the box, so the InitializeModuleData and OnObjectUpdate methods will not be called.
// These methods will only be called when listening logical decoding is setup.
type Listener struct {
// InitializeModuleData should be called whenever the blockchain process starts OR whenever
// logical decoding of a module is initiated. An indexer listening to this event
// should ensure that they have performed whatever initialization steps (such as database
// migrations) required to receive OnObjectUpdate events for the given module. If the
// indexer's schema is incompatible with the module's on-chain schema, the listener should return
// an error. Module names must conform to the NameFormat regular expression.
InitializeModuleData func(ModuleInitializationData) error
// StartBlock is called at the beginning of processing a block.
StartBlock func(StartBlockData) error
// OnTx is called when a transaction is received.
OnTx func(TxData) error
// OnEvent is called when an event is received.
OnEvent func(EventData) error
// OnKVPair is called when a key-value has been written to the store for a given module.
// Module names must conform to the NameFormat regular expression.
OnKVPair func(updates KVPairData) error
// OnObjectUpdate is called whenever an object is updated in a module's state. This is only called
// when logical data is available. It should be assumed that the same data in raw form
// is also passed to OnKVPair. Module names must conform to the NameFormat regular expression.
OnObjectUpdate func(ObjectUpdateData) error
// Commit is called when state is committed, usually at the end of a block. Any
// indexers should commit their data when this is called and return an error if
// they are unable to commit. Data sources MUST call Commit when data is committed,
// otherwise it should be assumed that indexers have not persisted their state.
Commit func(CommitData) error
}

View File

@ -1,61 +0,0 @@
package appdata
// Packet is the interface that all listener data structures implement so that this data can be "packetized"
// and processed in a stream, possibly asynchronously.
type Packet interface {
apply(*Listener) error
}
// SendPacket sends a packet to a listener invoking the appropriate callback for this packet if one is registered.
func (l Listener) SendPacket(p Packet) error {
return p.apply(&l)
}
func (m ModuleInitializationData) apply(l *Listener) error {
if l.InitializeModuleData == nil {
return nil
}
return l.InitializeModuleData(m)
}
func (b StartBlockData) apply(l *Listener) error {
if l.StartBlock == nil {
return nil
}
return l.StartBlock(b)
}
func (t TxData) apply(l *Listener) error {
if l.OnTx == nil {
return nil
}
return l.OnTx(t)
}
func (e EventData) apply(l *Listener) error {
if l.OnEvent == nil {
return nil
}
return l.OnEvent(e)
}
func (k KVPairData) apply(l *Listener) error {
if l.OnKVPair == nil {
return nil
}
return l.OnKVPair(k)
}
func (o ObjectUpdateData) apply(l *Listener) error {
if l.OnObjectUpdate == nil {
return nil
}
return l.OnObjectUpdate(o)
}
func (c CommitData) apply(l *Listener) error {
if l.Commit == nil {
return nil
}
return l.Commit(c)
}

View File

@ -1,40 +0,0 @@
package schema
// HasModuleCodec is an interface that modules can implement to provide a ModuleCodec.
// Usually these modules would also implement appmodule.AppModule, but that is not included
// to keep this package free of any dependencies.
type HasModuleCodec interface {
// ModuleCodec returns a ModuleCodec for the module.
ModuleCodec() (ModuleCodec, error)
}
// ModuleCodec is a struct that contains the schema and a KVDecoder for a module.
type ModuleCodec struct {
// Schema is the schema for the module. It is required.
Schema ModuleSchema
// KVDecoder is a function that decodes a key-value pair into an ObjectUpdate.
// If it is nil, the module doesn't support state decoding directly.
KVDecoder KVDecoder
}
// KVDecoder is a function that decodes a key-value pair into one or more ObjectUpdate's.
// If the KV-pair doesn't represent object updates, the function should return nil as the first
// and no error. The error result should only be non-nil when the decoder expected
// to parse a valid update and was unable to. In the case of an error, the decoder may return
// a non-nil value for the first return value, which can indicate which parts of the update
// were decodable to aid debugging.
type KVDecoder = func(KVPairUpdate) ([]ObjectUpdate, error)
// KVPairUpdate represents a key-value pair set or delete.
type KVPairUpdate struct {
// Key is the key of the key-value pair.
Key []byte
// Value is the value of the key-value pair. It should be ignored when Delete is true.
Value []byte
// Delete is a flag that indicates that the key-value pair was deleted. If it is false,
// then it is assumed that this has been a set operation.
Delete bool
}

View File

@ -1,458 +0,0 @@
package decoding
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"testing"
"cosmossdk.io/schema"
"cosmossdk.io/schema/appdata"
)
func TestMiddleware(t *testing.T) {
tl := newTestFixture(t)
listener, err := Middleware(tl.Listener, tl.resolver, MiddlewareOptions{})
if err != nil {
t.Fatal("unexpected error", err)
}
tl.setListener(listener)
tl.bankMod.Mint("bob", "foo", 100)
err = tl.bankMod.Send("bob", "alice", "foo", 50)
if err != nil {
t.Fatal("unexpected error", err)
}
tl.oneMod.SetValue("abc")
expectedBank := []schema.ObjectUpdate{
{
TypeName: "supply",
Key: []interface{}{"foo"},
Value: uint64(100),
},
{
TypeName: "balances",
Key: []interface{}{"bob", "foo"},
Value: uint64(100),
},
{
TypeName: "balances",
Key: []interface{}{"bob", "foo"},
Value: uint64(50),
},
{
TypeName: "balances",
Key: []interface{}{"alice", "foo"},
Value: uint64(50),
},
}
if !reflect.DeepEqual(tl.bankUpdates, expectedBank) {
t.Fatalf("expected %v, got %v", expectedBank, tl.bankUpdates)
}
expectedOne := []schema.ObjectUpdate{
{TypeName: "item", Value: "abc"},
}
if !reflect.DeepEqual(tl.oneValueUpdates, expectedOne) {
t.Fatalf("expected %v, got %v", expectedOne, tl.oneValueUpdates)
}
}
func TestMiddleware_filtered(t *testing.T) {
tl := newTestFixture(t)
listener, err := Middleware(tl.Listener, tl.resolver, MiddlewareOptions{
ModuleFilter: func(moduleName string) bool {
return moduleName == "one" //nolint:goconst // adding constants for this would impede readability
},
})
if err != nil {
t.Fatal("unexpected error", err)
}
tl.setListener(listener)
tl.bankMod.Mint("bob", "foo", 100)
tl.oneMod.SetValue("abc")
if len(tl.bankUpdates) != 0 {
t.Fatalf("expected no bank updates")
}
expectedOne := []schema.ObjectUpdate{
{TypeName: "item", Value: "abc"},
}
if !reflect.DeepEqual(tl.oneValueUpdates, expectedOne) {
t.Fatalf("expected %v, got %v", expectedOne, tl.oneValueUpdates)
}
}
func TestSync(t *testing.T) {
tl := newTestFixture(t)
tl.bankMod.Mint("bob", "foo", 100)
err := tl.bankMod.Send("bob", "alice", "foo", 50)
if err != nil {
t.Fatal("unexpected error", err)
}
tl.oneMod.SetValue("def")
err = Sync(tl.Listener, tl.multiStore, tl.resolver, SyncOptions{})
if err != nil {
t.Fatal("unexpected error", err)
}
expected := []schema.ObjectUpdate{
{
TypeName: "balances",
Key: []interface{}{"alice", "foo"},
Value: uint64(50),
},
{
TypeName: "balances",
Key: []interface{}{"bob", "foo"},
Value: uint64(50),
},
{
TypeName: "supply",
Key: []interface{}{"foo"},
Value: uint64(100),
},
}
if !reflect.DeepEqual(tl.bankUpdates, expected) {
t.Fatalf("expected %v, got %v", expected, tl.bankUpdates)
}
expectedOne := []schema.ObjectUpdate{
{TypeName: "item", Value: "def"},
}
if !reflect.DeepEqual(tl.oneValueUpdates, expectedOne) {
t.Fatalf("expected %v, got %v", expectedOne, tl.oneValueUpdates)
}
}
func TestSync_filtered(t *testing.T) {
tl := newTestFixture(t)
tl.bankMod.Mint("bob", "foo", 100)
tl.oneMod.SetValue("def")
err := Sync(tl.Listener, tl.multiStore, tl.resolver, SyncOptions{
ModuleFilter: func(moduleName string) bool {
return moduleName == "one"
},
})
if err != nil {
t.Fatal("unexpected error", err)
}
if len(tl.bankUpdates) != 0 {
t.Fatalf("expected no bank updates")
}
expectedOne := []schema.ObjectUpdate{
{TypeName: "item", Value: "def"},
}
if !reflect.DeepEqual(tl.oneValueUpdates, expectedOne) {
t.Fatalf("expected %v, got %v", expectedOne, tl.oneValueUpdates)
}
}
type testFixture struct {
appdata.Listener
bankUpdates []schema.ObjectUpdate
oneValueUpdates []schema.ObjectUpdate
resolver DecoderResolver
multiStore *testMultiStore
bankMod *exampleBankModule
oneMod *oneValueModule
}
func newTestFixture(t *testing.T) *testFixture {
t.Helper()
res := &testFixture{}
res.Listener = appdata.Listener{
InitializeModuleData: func(data appdata.ModuleInitializationData) error {
var expected schema.ModuleSchema
switch data.ModuleName {
case "bank":
expected = exampleBankSchema
case "one":
expected = oneValueModSchema
default:
t.Fatalf("unexpected module %s", data.ModuleName)
}
if !reflect.DeepEqual(data.Schema, expected) {
t.Errorf("expected %v, got %v", expected, data.Schema)
}
return nil
},
OnObjectUpdate: func(data appdata.ObjectUpdateData) error {
switch data.ModuleName {
case "bank":
res.bankUpdates = append(res.bankUpdates, data.Updates...)
case "one":
res.oneValueUpdates = append(res.oneValueUpdates, data.Updates...)
default:
t.Errorf("unexpected module %s", data.ModuleName)
}
return nil
},
}
res.multiStore = newTestMultiStore()
res.bankMod = &exampleBankModule{
store: res.multiStore.newTestStore(t, "bank"),
}
res.oneMod = &oneValueModule{
store: res.multiStore.newTestStore(t, "one"),
}
modSet := map[string]interface{}{
"bank": res.bankMod,
"one": res.oneMod,
}
res.resolver = ModuleSetDecoderResolver(modSet)
return res
}
func (f *testFixture) setListener(listener appdata.Listener) {
f.bankMod.store.listener = listener
f.oneMod.store.listener = listener
}
type testMultiStore struct {
stores map[string]*testStore
}
type testStore struct {
t *testing.T
modName string
store map[string][]byte
listener appdata.Listener
}
func newTestMultiStore() *testMultiStore {
return &testMultiStore{
stores: map[string]*testStore{},
}
}
var _ SyncSource = &testMultiStore{}
func (ms *testMultiStore) IterateAllKVPairs(moduleName string, fn func(key, value []byte) error) error {
s, ok := ms.stores[moduleName]
if !ok {
return fmt.Errorf("don't have state for module %s", moduleName)
}
var keys []string
for key := range s.store {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
err := fn([]byte(key), s.store[key])
if err != nil {
return err
}
}
return nil
}
func (ms *testMultiStore) newTestStore(t *testing.T, modName string) *testStore {
t.Helper()
s := &testStore{
t: t,
modName: modName,
store: map[string][]byte{},
}
ms.stores[modName] = s
return s
}
func (t testStore) Get(key []byte) []byte {
return t.store[string(key)]
}
func (t testStore) GetUInt64(key []byte) uint64 {
bz := t.store[string(key)]
if len(bz) == 0 {
return 0
}
x, err := strconv.ParseUint(string(bz), 10, 64)
if err != nil {
t.t.Fatalf("unexpected error: %v", err)
}
return x
}
func (t testStore) Set(key, value []byte) {
if t.listener.OnKVPair != nil {
err := t.listener.OnKVPair(appdata.KVPairData{Updates: []appdata.ModuleKVPairUpdate{
{
ModuleName: t.modName,
Update: schema.KVPairUpdate{
Key: key,
Value: value,
},
},
}})
if err != nil {
t.t.Fatalf("unexpected error: %v", err)
}
}
t.store[string(key)] = value
}
func (t testStore) SetUInt64(key []byte, value uint64) {
t.Set(key, []byte(strconv.FormatUint(value, 10)))
}
type exampleBankModule struct {
store *testStore
}
func (e exampleBankModule) Mint(acct, denom string, amount uint64) {
key := supplyKey(denom)
e.store.SetUInt64(key, e.store.GetUInt64(key)+amount)
e.addBalance(acct, denom, amount)
}
func (e exampleBankModule) Send(from, to, denom string, amount uint64) error {
err := e.subBalance(from, denom, amount)
if err != nil {
return nil
}
e.addBalance(to, denom, amount)
return nil
}
func (e exampleBankModule) GetBalance(acct, denom string) uint64 {
return e.store.GetUInt64(balanceKey(acct, denom))
}
func (e exampleBankModule) GetSupply(denom string) uint64 {
return e.store.GetUInt64(supplyKey(denom))
}
func balanceKey(acct, denom string) []byte {
return []byte(fmt.Sprintf("balance/%s/%s", acct, denom))
}
func supplyKey(denom string) []byte {
return []byte(fmt.Sprintf("supply/%s", denom))
}
func (e exampleBankModule) addBalance(acct, denom string, amount uint64) {
key := balanceKey(acct, denom)
e.store.SetUInt64(key, e.store.GetUInt64(key)+amount)
}
func (e exampleBankModule) subBalance(acct, denom string, amount uint64) error {
key := balanceKey(acct, denom)
cur := e.store.GetUInt64(key)
if cur < amount {
return fmt.Errorf("insufficient balance")
}
e.store.SetUInt64(key, cur-amount)
return nil
}
var exampleBankSchema = schema.ModuleSchema{
ObjectTypes: []schema.ObjectType{
{
Name: "balances",
KeyFields: []schema.Field{
{
Name: "account",
Kind: schema.StringKind,
},
{
Name: "denom",
Kind: schema.StringKind,
},
},
ValueFields: []schema.Field{
{
Name: "amount",
Kind: schema.Uint64Kind,
},
},
},
},
}
func (e exampleBankModule) ModuleCodec() (schema.ModuleCodec, error) {
return schema.ModuleCodec{
Schema: exampleBankSchema,
KVDecoder: func(update schema.KVPairUpdate) ([]schema.ObjectUpdate, error) {
key := string(update.Key)
value, err := strconv.ParseUint(string(update.Value), 10, 64)
if err != nil {
return nil, err
}
if strings.HasPrefix(key, "balance/") {
parts := strings.Split(key, "/")
return []schema.ObjectUpdate{{
TypeName: "balances",
Key: []interface{}{parts[1], parts[2]},
Value: value,
}}, nil
} else if strings.HasPrefix(key, "supply/") {
parts := strings.Split(key, "/")
return []schema.ObjectUpdate{{
TypeName: "supply",
Key: []interface{}{parts[1]},
Value: value,
}}, nil
} else {
return nil, fmt.Errorf("unexpected key: %s", key)
}
},
}, nil
}
var _ schema.HasModuleCodec = exampleBankModule{}
type oneValueModule struct {
store *testStore
}
var oneValueModSchema = schema.ModuleSchema{
ObjectTypes: []schema.ObjectType{
{
Name: "item",
ValueFields: []schema.Field{
{Name: "value", Kind: schema.StringKind},
},
},
},
}
func (i oneValueModule) ModuleCodec() (schema.ModuleCodec, error) {
return schema.ModuleCodec{
Schema: oneValueModSchema,
KVDecoder: func(update schema.KVPairUpdate) ([]schema.ObjectUpdate, error) {
if string(update.Key) != "key" {
return nil, fmt.Errorf("unexpected key: %v", update.Key)
}
return []schema.ObjectUpdate{
{TypeName: "item", Value: string(update.Value)},
}, nil
},
}, nil
}
func (i oneValueModule) SetValue(x string) {
i.store.Set([]byte("key"), []byte(x))
}
var _ schema.HasModuleCodec = oneValueModule{}

View File

@ -1,106 +0,0 @@
package decoding
import (
"cosmossdk.io/schema"
"cosmossdk.io/schema/appdata"
)
type MiddlewareOptions struct {
ModuleFilter func(moduleName string) bool
}
// Middleware decodes raw data passed to the listener as kv-updates into decoded object updates. Module initialization
// is done lazily as modules are encountered in the kv-update stream.
func Middleware(target appdata.Listener, resolver DecoderResolver, opts MiddlewareOptions) (appdata.Listener, error) {
initializeModuleData := target.InitializeModuleData
onObjectUpdate := target.OnObjectUpdate
// no-op if not listening to decoded data
if initializeModuleData == nil && onObjectUpdate == nil {
return target, nil
}
onKVPair := target.OnKVPair
moduleCodecs := map[string]*schema.ModuleCodec{}
target.OnKVPair = func(data appdata.KVPairData) error {
// first forward kv pair updates
if onKVPair != nil {
err := onKVPair(data)
if err != nil {
return err
}
}
for _, kvUpdate := range data.Updates {
// look for an existing codec
pcdc, ok := moduleCodecs[kvUpdate.ModuleName]
if !ok {
if opts.ModuleFilter != nil && !opts.ModuleFilter(kvUpdate.ModuleName) {
// we don't care about this module so store nil and continue
moduleCodecs[kvUpdate.ModuleName] = nil
continue
}
// look for a new codec
cdc, found, err := resolver.LookupDecoder(kvUpdate.ModuleName)
if err != nil {
return err
}
if !found {
// store nil to indicate we've seen this module and don't have a codec
// and keep processing the kv updates
moduleCodecs[kvUpdate.ModuleName] = nil
continue
}
pcdc = &cdc
moduleCodecs[kvUpdate.ModuleName] = pcdc
if initializeModuleData != nil {
err = initializeModuleData(appdata.ModuleInitializationData{
ModuleName: kvUpdate.ModuleName,
Schema: cdc.Schema,
})
if err != nil {
return err
}
}
}
if pcdc == nil {
// we've already seen this module and can't decode
continue
}
if onObjectUpdate == nil || pcdc.KVDecoder == nil {
// not listening to updates or can't decode so continue
continue
}
updates, err := pcdc.KVDecoder(kvUpdate.Update)
if err != nil {
return err
}
if len(updates) == 0 {
// no updates
continue
}
err = target.OnObjectUpdate(appdata.ObjectUpdateData{
ModuleName: kvUpdate.ModuleName,
Updates: updates,
})
if err != nil {
return err
}
}
return nil
}
return target, nil
}

View File

@ -1,66 +0,0 @@
package decoding
import (
"sort"
"cosmossdk.io/schema"
)
// DecoderResolver is an interface that allows indexers to discover and use module decoders.
type DecoderResolver interface {
// IterateAll iterates over all available module decoders.
IterateAll(func(moduleName string, cdc schema.ModuleCodec) error) error
// LookupDecoder looks up a specific module decoder.
LookupDecoder(moduleName string) (decoder schema.ModuleCodec, found bool, err error)
}
// ModuleSetDecoderResolver returns DecoderResolver that will discover modules implementing
// DecodeableModule in the provided module set.
func ModuleSetDecoderResolver(moduleSet map[string]interface{}) DecoderResolver {
return &moduleSetDecoderResolver{
moduleSet: moduleSet,
}
}
type moduleSetDecoderResolver struct {
moduleSet map[string]interface{}
}
func (a moduleSetDecoderResolver) IterateAll(f func(string, schema.ModuleCodec) error) error {
keys := make([]string, 0, len(a.moduleSet))
for k := range a.moduleSet {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
module := a.moduleSet[k]
dm, ok := module.(schema.HasModuleCodec)
if ok {
decoder, err := dm.ModuleCodec()
if err != nil {
return err
}
err = f(k, decoder)
if err != nil {
return err
}
}
}
return nil
}
func (a moduleSetDecoderResolver) LookupDecoder(moduleName string) (schema.ModuleCodec, bool, error) {
mod, ok := a.moduleSet[moduleName]
if !ok {
return schema.ModuleCodec{}, false, nil
}
dm, ok := mod.(schema.HasModuleCodec)
if !ok {
return schema.ModuleCodec{}, false, nil
}
decoder, err := dm.ModuleCodec()
return decoder, true, err
}

View File

@ -1,124 +0,0 @@
package decoding
import (
"errors"
"testing"
"cosmossdk.io/schema"
)
type modA struct{}
func (m modA) ModuleCodec() (schema.ModuleCodec, error) {
return schema.ModuleCodec{
Schema: schema.ModuleSchema{ObjectTypes: []schema.ObjectType{{Name: "A"}}},
}, nil
}
type modB struct{}
func (m modB) ModuleCodec() (schema.ModuleCodec, error) {
return schema.ModuleCodec{
Schema: schema.ModuleSchema{ObjectTypes: []schema.ObjectType{{Name: "B"}}},
}, nil
}
type modC struct{}
var moduleSet = map[string]interface{}{
"modA": modA{},
"modB": modB{},
"modC": modC{},
}
var testResolver = ModuleSetDecoderResolver(moduleSet)
func TestModuleSetDecoderResolver_IterateAll(t *testing.T) {
objectTypes := map[string]bool{}
err := testResolver.IterateAll(func(moduleName string, cdc schema.ModuleCodec) error {
objectTypes[cdc.Schema.ObjectTypes[0].Name] = true
return nil
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(objectTypes) != 2 {
t.Fatalf("expected 2 object types, got %d", len(objectTypes))
}
if !objectTypes["A"] {
t.Fatalf("expected object type A")
}
if !objectTypes["B"] {
t.Fatalf("expected object type B")
}
}
func TestModuleSetDecoderResolver_LookupDecoder(t *testing.T) {
decoder, found, err := testResolver.LookupDecoder("modA")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !found {
t.Fatalf("expected to find decoder for modA")
}
if decoder.Schema.ObjectTypes[0].Name != "A" {
t.Fatalf("expected object type A, got %s", decoder.Schema.ObjectTypes[0].Name)
}
decoder, found, err = testResolver.LookupDecoder("modB")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !found {
t.Fatalf("expected to find decoder for modB")
}
if decoder.Schema.ObjectTypes[0].Name != "B" {
t.Fatalf("expected object type B, got %s", decoder.Schema.ObjectTypes[0].Name)
}
decoder, found, err = testResolver.LookupDecoder("modC")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if found {
t.Fatalf("expected not to find decoder")
}
decoder, found, err = testResolver.LookupDecoder("modD")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if found {
t.Fatalf("expected not to find decoder")
}
}
type modD struct{}
func (m modD) ModuleCodec() (schema.ModuleCodec, error) {
return schema.ModuleCodec{}, errors.New("an error")
}
func TestModuleSetDecoderResolver_IterateAll_Error(t *testing.T) {
resolver := ModuleSetDecoderResolver(map[string]interface{}{
"modD": modD{},
})
err := resolver.IterateAll(func(moduleName string, cdc schema.ModuleCodec) error {
if moduleName == "modD" {
t.Fatalf("expected error")
}
return nil
})
if err == nil {
t.Fatalf("expected error")
}
}

View File

@ -1,63 +0,0 @@
package decoding
import (
"cosmossdk.io/schema"
"cosmossdk.io/schema/appdata"
)
// SyncSource is an interface that allows indexers to start indexing modules with pre-existing state.
// It should generally be a wrapper around the key-value store.
type SyncSource interface {
// IterateAllKVPairs iterates over all key-value pairs for a given module.
IterateAllKVPairs(moduleName string, fn func(key, value []byte) error) error
}
// SyncOptions are the options for Sync.
type SyncOptions struct {
ModuleFilter func(moduleName string) bool
}
// Sync synchronizes existing state from the sync source to the listener using the resolver to decode data.
func Sync(listener appdata.Listener, source SyncSource, resolver DecoderResolver, opts SyncOptions) error {
initializeModuleData := listener.InitializeModuleData
onObjectUpdate := listener.OnObjectUpdate
// no-op if not listening to decoded data
if initializeModuleData == nil && onObjectUpdate == nil {
return nil
}
return resolver.IterateAll(func(moduleName string, cdc schema.ModuleCodec) error {
if opts.ModuleFilter != nil && !opts.ModuleFilter(moduleName) {
// ignore this module
return nil
}
if initializeModuleData != nil {
err := initializeModuleData(appdata.ModuleInitializationData{
ModuleName: moduleName,
Schema: cdc.Schema,
})
if err != nil {
return err
}
}
if onObjectUpdate == nil || cdc.KVDecoder == nil {
return nil
}
return source.IterateAllKVPairs(moduleName, func(key, value []byte) error {
updates, err := cdc.KVDecoder(schema.KVPairUpdate{Key: key, Value: value})
if err != nil {
return err
}
if len(updates) == 0 {
return nil
}
return onObjectUpdate(appdata.ObjectUpdateData{ModuleName: moduleName, Updates: updates})
})
})
}

View File

@ -1,80 +0,0 @@
package schema
import (
"errors"
"fmt"
)
// EnumDefinition represents the definition of an enum type.
type EnumDefinition struct {
// Name is the name of the enum type. It must conform to the NameFormat regular expression.
// Its name must be unique between all enum types and object types in the module.
// The same enum, however, can be used in multiple object types and fields as long as the
// definition is identical each time
Name string
// Values is a list of distinct, non-empty values that are part of the enum type.
// Each value must conform to the NameFormat regular expression.
Values []string
}
// Validate validates the enum definition.
func (e EnumDefinition) Validate() error {
if !ValidateName(e.Name) {
return fmt.Errorf("invalid enum definition name %q", e.Name)
}
if len(e.Values) == 0 {
return errors.New("enum definition values cannot be empty")
}
seen := make(map[string]bool, len(e.Values))
for i, v := range e.Values {
if !ValidateName(v) {
return fmt.Errorf("invalid enum definition value %q at index %d for enum %s", v, i, e.Name)
}
if seen[v] {
return fmt.Errorf("duplicate enum definition value %q for enum %s", v, e.Name)
}
seen[v] = true
}
return nil
}
// ValidateValue validates that the value is a valid enum value.
func (e EnumDefinition) ValidateValue(value string) error {
for _, v := range e.Values {
if v == value {
return nil
}
}
return fmt.Errorf("value %q is not a valid enum value for %s", value, e.Name)
}
// checkEnumCompatibility checks that the enum values are consistent across object types and fields.
func checkEnumCompatibility(enumValueMap map[string]map[string]bool, field Field) error {
if field.Kind != EnumKind {
return nil
}
enum := field.EnumDefinition
if existing, ok := enumValueMap[enum.Name]; ok {
if len(existing) != len(enum.Values) {
return fmt.Errorf("enum %q has different number of values in different object types", enum.Name)
}
for _, value := range enum.Values {
if !existing[value] {
return fmt.Errorf("enum %q has different values in different object types", enum.Name)
}
}
} else {
valueMap := map[string]bool{}
for _, value := range enum.Values {
valueMap[value] = true
}
enumValueMap[enum.Name] = valueMap
}
return nil
}

View File

@ -1,106 +0,0 @@
package schema
import (
"strings"
"testing"
)
func TestEnumDefinition_Validate(t *testing.T) {
tests := []struct {
name string
enum EnumDefinition
errContains string
}{
{
name: "valid enum",
enum: EnumDefinition{
Name: "test",
Values: []string{"a", "b", "c"},
},
errContains: "",
},
{
name: "empty name",
enum: EnumDefinition{
Name: "",
Values: []string{"a", "b", "c"},
},
errContains: "invalid enum definition name",
},
{
name: "empty values",
enum: EnumDefinition{
Name: "test",
Values: []string{},
},
errContains: "enum definition values cannot be empty",
},
{
name: "empty value",
enum: EnumDefinition{
Name: "test",
Values: []string{"a", "", "c"},
},
errContains: "invalid enum definition value",
},
{
name: "duplicate value",
enum: EnumDefinition{
Name: "test",
Values: []string{"a", "b", "a"},
},
errContains: "duplicate enum definition value \"a\" for enum test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.enum.Validate()
if tt.errContains == "" {
if err != nil {
t.Errorf("expected valid enum definition to pass validation, got: %v", err)
}
} else {
if err == nil {
t.Errorf("expected invalid enum definition to fail validation, got nil error")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("expected error to contain %s, got: %v", tt.errContains, err)
}
}
})
}
}
func TestEnumDefinition_ValidateValue(t *testing.T) {
enum := EnumDefinition{
Name: "test",
Values: []string{"a", "b", "c"},
}
tests := []struct {
value string
errContains string
}{
{"a", ""},
{"b", ""},
{"c", ""},
{"d", "value \"d\" is not a valid enum value for test"},
}
for _, tt := range tests {
t.Run(tt.value, func(t *testing.T) {
err := enum.ValidateValue(tt.value)
if tt.errContains == "" {
if err != nil {
t.Errorf("expected valid enum value to pass validation, got: %v", err)
}
} else {
if err == nil {
t.Errorf("expected invalid enum value to fail validation, got nil error")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("expected error to contain %s, got: %v", tt.errContains, err)
}
}
})
}
}

View File

@ -1,67 +0,0 @@
package schema
import "fmt"
// Field represents a field in an object type.
type Field struct {
// Name is the name of the field. It must conform to the NameFormat regular expression.
Name string
// Kind is the basic type of the field.
Kind Kind
// Nullable indicates whether null values are accepted for the field. Key fields CANNOT be nullable.
Nullable bool
// EnumDefinition is the definition of the enum type and is only valid when Kind is EnumKind.
// The same enum types can be reused in the same module schema, but they always must contain
// the same values for the same enum name. This possibly introduces some duplication of
// definitions but makes it easier to reason about correctness and validation in isolation.
EnumDefinition EnumDefinition
}
// Validate validates the field.
func (c Field) Validate() error {
// valid name
if !ValidateName(c.Name) {
return fmt.Errorf("invalid field name %q", c.Name)
}
// valid kind
if err := c.Kind.Validate(); err != nil {
return fmt.Errorf("invalid field kind for %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12
}
// enum definition only valid with EnumKind
if c.Kind == EnumKind {
if err := c.EnumDefinition.Validate(); err != nil {
return fmt.Errorf("invalid enum definition for field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12
}
} else if c.Kind != EnumKind && (c.EnumDefinition.Name != "" || c.EnumDefinition.Values != nil) {
return fmt.Errorf("enum definition is only valid for field %q with type EnumKind", c.Name)
}
return nil
}
// ValidateValue validates that the value conforms to the field's kind and nullability.
// Unlike Kind.ValidateValue, it also checks that the value conforms to the EnumDefinition
// if the field is an EnumKind.
func (c Field) ValidateValue(value interface{}) error {
if value == nil {
if !c.Nullable {
return fmt.Errorf("field %q cannot be null", c.Name)
}
return nil
}
err := c.Kind.ValidateValueType(value)
if err != nil {
return fmt.Errorf("invalid value for field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12
}
if c.Kind == EnumKind {
return c.EnumDefinition.ValidateValue(value.(string))
}
return nil
}

View File

@ -1,166 +0,0 @@
package schema
import (
"strings"
"testing"
)
func TestField_Validate(t *testing.T) {
tests := []struct {
name string
field Field
errContains string
}{
{
name: "valid field",
field: Field{
Name: "field1",
Kind: StringKind,
},
errContains: "",
},
{
name: "empty name",
field: Field{
Name: "",
Kind: StringKind,
},
errContains: "invalid field name",
},
{
name: "invalid kind",
field: Field{
Name: "field1",
Kind: InvalidKind,
},
errContains: "invalid field kind",
},
{
name: "invalid enum definition",
field: Field{
Name: "field1",
Kind: EnumKind,
},
errContains: "invalid enum definition",
},
{
name: "enum definition with non-EnumKind",
field: Field{
Name: "field1",
Kind: StringKind,
EnumDefinition: EnumDefinition{Name: "enum"},
},
errContains: "enum definition is only valid for field \"field1\" with type EnumKind",
},
{
name: "valid enum",
field: Field{
Name: "field1",
Kind: EnumKind,
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.field.Validate()
if tt.errContains == "" {
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
} else {
if err == nil {
t.Errorf("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("expected error contains: %s, got: %v", tt.errContains, err)
}
}
})
}
}
func TestField_ValidateValue(t *testing.T) {
tests := []struct {
name string
field Field
value interface{}
errContains string
}{
{
name: "valid field",
field: Field{
Name: "field1",
Kind: StringKind,
},
value: "value",
errContains: "",
},
{
name: "null non-nullable field",
field: Field{
Name: "field1",
Kind: StringKind,
Nullable: false,
},
value: nil,
errContains: "cannot be null",
},
{
name: "null nullable field",
field: Field{
Name: "field1",
Kind: StringKind,
Nullable: true,
},
value: nil,
errContains: "",
},
{
name: "invalid value",
field: Field{
Name: "field1",
Kind: StringKind,
},
value: 1,
errContains: "invalid value for field \"field1\"",
},
{
name: "valid enum",
field: Field{
Name: "field1",
Kind: EnumKind,
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
},
value: "a",
errContains: "",
},
{
name: "invalid enum",
field: Field{
Name: "field1",
Kind: EnumKind,
EnumDefinition: EnumDefinition{Name: "enum", Values: []string{"a", "b"}},
},
value: "c",
errContains: "not a valid enum value",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.field.ValidateValue(tt.value)
if tt.errContains == "" {
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
} else {
if err == nil {
t.Errorf("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("expected error contains: %s, got: %v", tt.errContains, err)
}
}
})
}
}

View File

@ -1,71 +0,0 @@
package schema
import "fmt"
// ValidateObjectKey validates that the value conforms to the set of fields as a Key in an ObjectUpdate.
// See ObjectUpdate.Key for documentation on the requirements of such keys.
func ValidateObjectKey(keyFields []Field, value interface{}) error {
return validateFieldsValue(keyFields, value)
}
// ValidateObjectValue validates that the value conforms to the set of fields as a Value in an ObjectUpdate.
// See ObjectUpdate.Value for documentation on the requirements of such values.
func ValidateObjectValue(valueFields []Field, value interface{}) error {
valueUpdates, ok := value.(ValueUpdates)
if !ok {
return validateFieldsValue(valueFields, value)
}
values := map[string]interface{}{}
err := valueUpdates.Iterate(func(fieldname string, value interface{}) bool {
values[fieldname] = value
return true
})
if err != nil {
return err
}
for _, field := range valueFields {
v, ok := values[field.Name]
if !ok {
continue
}
if err := field.ValidateValue(v); err != nil {
return err
}
delete(values, field.Name)
}
if len(values) > 0 {
return fmt.Errorf("unexpected values in ValueUpdates: %v", values)
}
return nil
}
func validateFieldsValue(fields []Field, value interface{}) error {
if len(fields) == 0 {
return nil
}
if len(fields) == 1 {
return fields[0].ValidateValue(value)
}
values, ok := value.([]interface{})
if !ok {
return fmt.Errorf("expected slice of values for key fields, got %T", value)
}
if len(fields) != len(values) {
return fmt.Errorf("expected %d key fields, got %d values", len(fields), len(value.([]interface{})))
}
for i, field := range fields {
if err := field.ValidateValue(values[i]); err != nil {
return err
}
}
return nil
}

View File

@ -1,143 +0,0 @@
package schema
import (
"strings"
"testing"
)
func TestValidateForKeyFields(t *testing.T) {
tests := []struct {
name string
keyFields []Field
key interface{}
errContains string
}{
{
name: "no key fields",
keyFields: nil,
key: nil,
},
{
name: "single key field, valid",
keyFields: object1Type.KeyFields,
key: "hello",
errContains: "",
},
{
name: "single key field, invalid",
keyFields: object1Type.KeyFields,
key: []interface{}{"value"},
errContains: "invalid value",
},
{
name: "multiple key fields, valid",
keyFields: object2Type.KeyFields,
key: []interface{}{"hello", int32(42)},
},
{
name: "multiple key fields, not a slice",
keyFields: object2Type.KeyFields,
key: map[string]interface{}{"field1": "hello", "field2": "42"},
errContains: "expected slice of values",
},
{
name: "multiple key fields, wrong number of values",
keyFields: object2Type.KeyFields,
key: []interface{}{"hello"},
errContains: "expected 2 key fields",
},
{
name: "multiple key fields, invalid value",
keyFields: object2Type.KeyFields,
key: []interface{}{"hello", "abc"},
errContains: "invalid value",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateObjectKey(tt.keyFields, tt.key)
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}
func TestValidateForValueFields(t *testing.T) {
tests := []struct {
name string
valueFields []Field
value interface{}
errContains string
}{
{
name: "no value fields",
valueFields: nil,
value: nil,
},
{
name: "single value field, valid",
valueFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
value: "hello",
errContains: "",
},
{
name: "value updates, empty",
valueFields: object3Type.ValueFields,
value: MapValueUpdates(map[string]interface{}{}),
},
{
name: "value updates, 1 field valid",
valueFields: object3Type.ValueFields,
value: MapValueUpdates(map[string]interface{}{
"field1": "hello",
}),
},
{
name: "value updates, 2 fields, 1 invalid",
valueFields: object3Type.ValueFields,
value: MapValueUpdates(map[string]interface{}{
"field1": "hello",
"field2": "abc",
}),
errContains: "expected int32",
},
{
name: "value updates, extra value",
valueFields: object3Type.ValueFields,
value: MapValueUpdates(map[string]interface{}{
"field1": "hello",
"field2": int32(42),
"field3": "extra",
}),
errContains: "unexpected values",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateObjectValue(tt.valueFields, tt.value)
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}

View File

@ -1,7 +0,0 @@
module cosmossdk.io/schema
// NOTE: this go.mod should have zero dependencies and remain on go 1.12 to stay compatible
// with all known production releases of the Cosmos SDK. This is to ensure that all historical
// apps could be patched to support indexing if desired.
go 1.12

View File

@ -1,15 +0,0 @@
# Indexer Framework
# Defining an Indexer
Indexer implementations should be registered with the `indexer.Register` function with a unique type name. Indexers take the configuration options defined by `indexer.Config` which defines a common set of configuration options as well as indexer-specific options under the `config` sub-key. Indexers do not need to manage the common filtering options specified in `Config` - the indexer manager will manage these for the indexer. Indexer implementations just need to return a correct `InitResult` response.
# Integrating the Indexer Manager
The indexer manager should be used for managing all indexers and should be integrated directly with applications wishing to support indexing. The `StartManager` function is used to start the manager. The configuration options for the manager and all indexer targets should be passed as the ManagerOptions.Config field and should match the json structure of ManagerConfig. An example configuration section in `app.toml` might look like this:
```toml
[indexer.target.postgres]
type = "postgres"
config.database_url = "postgres://user:password@localhost:5432/dbname"
```

View File

@ -1,78 +0,0 @@
package indexer
import (
"context"
"cosmossdk.io/schema/appdata"
"cosmossdk.io/schema/logutil"
)
// Config species the configuration passed to an indexer initialization function.
// It includes both common configuration options related to include or excluding
// parts of the data stream as well as indexer specific options under the config
// subsection.
//
// NOTE: it is an error for an indexer to change its common options, such as adding
// or removing indexed modules, after the indexer has been initialized because this
// could result in an inconsistent state.
type Config struct {
// Type is the name of the indexer type as registered with Register.
Type string `json:"type"`
// Config are the indexer specific config options specified by the user.
Config map[string]interface{} `json:"config"`
// ExcludeState specifies that the indexer will not receive state updates.
ExcludeState bool `json:"exclude_state"`
// ExcludeEvents specifies that the indexer will not receive events.
ExcludeEvents bool `json:"exclude_events"`
// ExcludeTxs specifies that the indexer will not receive transaction's.
ExcludeTxs bool `json:"exclude_txs"`
// ExcludeBlockHeaders specifies that the indexer will not receive block headers,
// although it will still receive StartBlock and Commit callbacks, just without
// the header data.
ExcludeBlockHeaders bool `json:"exclude_block_headers"`
// IncludeModules specifies a list of modules whose state the indexer will
// receive state updates for.
// Only one of include or exclude modules should be specified.
IncludeModules []string `json:"include_modules"`
// ExcludeModules specifies a list of modules whose state the indexer will not
// receive state updates for.
// Only one of include or exclude modules should be specified.
ExcludeModules []string `json:"exclude_modules"`
}
type InitFunc = func(InitParams) (InitResult, error)
// InitParams is the input to the indexer initialization function.
type InitParams struct {
// Config is the indexer config.
Config Config
// Context is the context that the indexer should use to listen for a shutdown signal via Context.Done(). Other
// parameters may also be passed through context from the app if necessary.
Context context.Context
// Logger is a logger the indexer can use to write log messages.
Logger logutil.Logger
}
// InitResult is the indexer initialization result and includes the indexer's listener implementation.
type InitResult struct {
// Listener is the indexer's app data listener.
Listener appdata.Listener
// LastBlockPersisted indicates the last block that the indexer persisted (if it is persisting data). It
// should be 0 if the indexer has no data stored and wants to start syncing state. It should be -1 if the indexer
// does not care to persist state at all and is just listening for some other streaming purpose. If the indexer
// has persisted state and has missed some blocks, a runtime error will occur to prevent the indexer from continuing
// in an invalid state. If an indexer starts indexing after a chain's genesis (returning 0), the indexer manager
// will attempt to perform a catch-up sync of state. Historical events will not be replayed, but an accurate
// representation of the current state at the height at which indexing began can be reproduced.
LastBlockPersisted int64
}

View File

@ -1,44 +0,0 @@
package indexer
import (
"context"
"cosmossdk.io/schema/appdata"
"cosmossdk.io/schema/decoding"
"cosmossdk.io/schema/logutil"
)
// ManagerOptions are the options for starting the indexer manager.
type ManagerOptions struct {
// Config is the user configuration for all indexing. It should generally be an instance of map[string]interface{}
// and match the json structure of ManagerConfig. The manager will attempt to convert it to ManagerConfig.
Config interface{}
// Resolver is the decoder resolver that will be used to decode the data. It is required.
Resolver decoding.DecoderResolver
// SyncSource is a representation of the current state of key-value data to be used in a catch-up sync.
// Catch-up syncs will be performed at initialization when necessary. SyncSource is optional but if
// it is omitted, indexers will only be able to start indexing state from genesis.
SyncSource decoding.SyncSource
// Logger is the logger that indexers can use to write logs. It is optional.
Logger logutil.Logger
// Context is the context that indexers should use for shutdown signals via Context.Done(). It can also
// be used to pass down other parameters to indexers if necessary. If it is omitted, context.Background
// will be used.
Context context.Context
}
// ManagerConfig is the configuration of the indexer manager and contains the configuration for each indexer target.
type ManagerConfig struct {
// Target is a map of named indexer targets to their configuration.
Target map[string]Config
}
// StartManager starts the indexer manager with the given options. The state machine should write all relevant app data to
// the returned listener.
func StartManager(opts ManagerOptions) (appdata.Listener, error) {
panic("TODO: this will be implemented in a follow-up PR, this function is just a stub to demonstrate the API")
}

View File

@ -1,14 +0,0 @@
package indexer
import "fmt"
// Register registers an indexer type with the given initialization function.
func Register(indexerType string, initFunc InitFunc) {
if _, ok := indexerRegistry[indexerType]; ok {
panic(fmt.Sprintf("indexer %s already registered", indexerType))
}
indexerRegistry[indexerType] = initFunc
}
var indexerRegistry = map[string]InitFunc{}

View File

@ -1,26 +0,0 @@
package indexer
import "testing"
func TestRegister(t *testing.T) {
Register("test", func(params InitParams) (InitResult, error) {
return InitResult{}, nil
})
if indexerRegistry["test"] == nil {
t.Fatalf("expected to find indexer")
}
if indexerRegistry["test2"] != nil {
t.Fatalf("expected not to find indexer")
}
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected to panic")
}
}()
Register("test", func(params InitParams) (InitResult, error) {
return InitResult{}, nil
})
}

View File

@ -1,363 +0,0 @@
package schema
import (
"encoding/json"
"fmt"
"regexp"
"time"
"unicode/utf8"
)
// Kind represents the basic type of a field in an object.
// Each kind defines the types of go values which should be accepted
// by listeners and generated by decoders when providing entity updates.
type Kind int
const (
// InvalidKind indicates that an invalid type.
InvalidKind Kind = iota
// StringKind is a string type and values of this type must be of the go type string
// containing valid UTF-8 and cannot contain null characters.
StringKind
// BytesKind is a bytes type and values of this type must be of the go type []byte.
BytesKind
// Int8Kind is an int8 type and values of this type must be of the go type int8.
Int8Kind
// Uint8Kind is a uint8 type and values of this type must be of the go type uint8.
Uint8Kind
// Int16Kind is an int16 type and values of this type must be of the go type int16.
Int16Kind
// Uint16Kind is a uint16 type and values of this type must be of the go type uint16.
Uint16Kind
// Int32Kind is an int32 type and values of this type must be of the go type int32.
Int32Kind
// Uint32Kind is a uint32 type and values of this type must be of the go type uint32.
Uint32Kind
// Int64Kind is an int64 type and values of this type must be of the go type int64.
Int64Kind
// Uint64Kind is a uint64 type and values of this type must be of the go type uint64.
Uint64Kind
// IntegerStringKind represents an arbitrary precision integer number. Values of this type must
// be of the go type string and formatted as base10 integers, specifically matching to
// the IntegerFormat regex.
IntegerStringKind
// DecimalStringKind represents an arbitrary precision decimal or integer number. Values of this type
// must be of the go type string and match the DecimalFormat regex.
DecimalStringKind
// BoolKind is a boolean type and values of this type must be of the go type bool.
BoolKind
// TimeKind is a time type and values of this type must be of the go type time.Time.
TimeKind
// DurationKind is a duration type and values of this type must be of the go type time.Duration.
DurationKind
// Float32Kind is a float32 type and values of this type must be of the go type float32.
Float32Kind
// Float64Kind is a float64 type and values of this type must be of the go type float64.
Float64Kind
// AddressKind represents an account address and must be of type []byte. Addresses usually have a
// human-readable rendering, such as bech32, and tooling should provide a way for apps to define a
// string encoder for friendly user-facing display.
AddressKind
// EnumKind is an enum type and values of this type must be of the go type string.
// Fields of this type are expected to set the EnumDefinition field in the field definition to the enum
// definition.
EnumKind
// JSONKind is a JSON type and values of this type should be of go type json.RawMessage and represent
// valid JSON.
JSONKind
)
// MAX_VALID_KIND is the maximum valid kind value.
const MAX_VALID_KIND = JSONKind
const (
// IntegerFormat is a regex that describes the format integer number strings must match. It specifies
// that integers may have at most 100 digits.
IntegerFormat = `^-?[0-9]{1,100}$`
// DecimalFormat is a regex that describes the format decimal number strings must match. It specifies
// that decimals may have at most 50 digits before and after the decimal point and may have an optional
// exponent of up to 2 digits. These restrictions ensure that the decimal can be accurately represented
// by a wide variety of implementations.
DecimalFormat = `^-?[0-9]{1,50}(\.[0-9]{1,50})?([eE][-+]?[0-9]{1,2})?$`
)
// Validate returns an errContains if the kind is invalid.
func (t Kind) Validate() error {
if t <= InvalidKind {
return fmt.Errorf("unknown type: %d", t)
}
if t > JSONKind {
return fmt.Errorf("invalid type: %d", t)
}
return nil
}
// String returns a string representation of the kind.
func (t Kind) String() string {
switch t {
case StringKind:
return "string"
case BytesKind:
return "bytes"
case Int8Kind:
return "int8"
case Uint8Kind:
return "uint8"
case Int16Kind:
return "int16"
case Uint16Kind:
return "uint16"
case Int32Kind:
return "int32"
case Uint32Kind:
return "uint32"
case Int64Kind:
return "int64"
case Uint64Kind:
return "uint64"
case DecimalStringKind:
return "decimal"
case IntegerStringKind:
return "integer"
case BoolKind:
return "bool"
case TimeKind:
return "time"
case DurationKind:
return "duration"
case Float32Kind:
return "float32"
case Float64Kind:
return "float64"
case AddressKind:
return "bech32address"
case EnumKind:
return "enum"
case JSONKind:
return "json"
default:
return fmt.Sprintf("invalid(%d)", t)
}
}
// ValidateValueType returns an errContains if the value does not conform to the expected go type.
// Some fields may accept nil values, however, this method does not have any notion of
// nullability. This method only validates that the go type of the value is correct for the kind
// and does not validate string or json formats. Kind.ValidateValue does a more thorough validation
// of number and json string formatting.
func (t Kind) ValidateValueType(value interface{}) error {
switch t {
case StringKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case BytesKind:
_, ok := value.([]byte)
if !ok {
return fmt.Errorf("expected []byte, got %T", value)
}
case Int8Kind:
_, ok := value.(int8)
if !ok {
return fmt.Errorf("expected int8, got %T", value)
}
case Uint8Kind:
_, ok := value.(uint8)
if !ok {
return fmt.Errorf("expected uint8, got %T", value)
}
case Int16Kind:
_, ok := value.(int16)
if !ok {
return fmt.Errorf("expected int16, got %T", value)
}
case Uint16Kind:
_, ok := value.(uint16)
if !ok {
return fmt.Errorf("expected uint16, got %T", value)
}
case Int32Kind:
_, ok := value.(int32)
if !ok {
return fmt.Errorf("expected int32, got %T", value)
}
case Uint32Kind:
_, ok := value.(uint32)
if !ok {
return fmt.Errorf("expected uint32, got %T", value)
}
case Int64Kind:
_, ok := value.(int64)
if !ok {
return fmt.Errorf("expected int64, got %T", value)
}
case Uint64Kind:
_, ok := value.(uint64)
if !ok {
return fmt.Errorf("expected uint64, got %T", value)
}
case IntegerStringKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case DecimalStringKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case BoolKind:
_, ok := value.(bool)
if !ok {
return fmt.Errorf("expected bool, got %T", value)
}
case TimeKind:
_, ok := value.(time.Time)
if !ok {
return fmt.Errorf("expected time.Time, got %T", value)
}
case DurationKind:
_, ok := value.(time.Duration)
if !ok {
return fmt.Errorf("expected time.Duration, got %T", value)
}
case Float32Kind:
_, ok := value.(float32)
if !ok {
return fmt.Errorf("expected float32, got %T", value)
}
case Float64Kind:
_, ok := value.(float64)
if !ok {
return fmt.Errorf("expected float64, got %T", value)
}
case AddressKind:
_, ok := value.([]byte)
if !ok {
return fmt.Errorf("expected []byte, got %T", value)
}
case EnumKind:
_, ok := value.(string)
if !ok {
return fmt.Errorf("expected string, got %T", value)
}
case JSONKind:
_, ok := value.(json.RawMessage)
if !ok {
return fmt.Errorf("expected json.RawMessage, got %T", value)
}
default:
return fmt.Errorf("invalid type: %d", t)
}
return nil
}
// ValidateValue returns an errContains if the value does not conform to the expected go type and format.
// It is more thorough, but slower, than Kind.ValidateValueType and validates that Integer, Decimal and JSON
// values are formatted correctly. It cannot validate enum values because Kind's do not have enum schemas.
func (t Kind) ValidateValue(value interface{}) error {
err := t.ValidateValueType(value)
if err != nil {
return err
}
switch t {
case StringKind:
str := value.(string)
if !utf8.ValidString(str) {
return fmt.Errorf("expected valid utf-8 string, got %s", value)
}
// check for null characters
for _, r := range str {
if r == 0 {
return fmt.Errorf("expected string without null characters, got %s", value)
}
}
case IntegerStringKind:
if !integerRegex.Match([]byte(value.(string))) {
return fmt.Errorf("expected base10 integer, got %s", value)
}
case DecimalStringKind:
if !decimalRegex.Match([]byte(value.(string))) {
return fmt.Errorf("expected decimal number, got %s", value)
}
case JSONKind:
if !json.Valid(value.(json.RawMessage)) {
return fmt.Errorf("expected valid JSON, got %s", value)
}
default:
return nil
}
return nil
}
var (
integerRegex = regexp.MustCompile(IntegerFormat)
decimalRegex = regexp.MustCompile(DecimalFormat)
)
// KindForGoValue finds the simplest kind that can represent the given go value. It will not, however,
// return kinds such as IntegerStringKind, DecimalStringKind, AddressKind, or EnumKind which all can be
// represented as strings.
func KindForGoValue(value interface{}) Kind {
switch value.(type) {
case string:
return StringKind
case []byte:
return BytesKind
case int8:
return Int8Kind
case uint8:
return Uint8Kind
case int16:
return Int16Kind
case uint16:
return Uint16Kind
case int32:
return Int32Kind
case uint32:
return Uint32Kind
case int64:
return Int64Kind
case uint64:
return Uint64Kind
case float32:
return Float32Kind
case float64:
return Float64Kind
case bool:
return BoolKind
case time.Time:
return TimeKind
case time.Duration:
return DurationKind
case json.RawMessage:
return JSONKind
default:
return InvalidKind
}
}

View File

@ -1,265 +0,0 @@
package schema
import (
"encoding/json"
"fmt"
"testing"
"time"
)
func TestKind_Validate(t *testing.T) {
for kind := InvalidKind + 1; kind <= MAX_VALID_KIND; kind++ {
if err := kind.Validate(); err != nil {
t.Errorf("expected valid kind %s to pass validation, got: %v", kind, err)
}
}
invalidKinds := []Kind{
Kind(-1),
InvalidKind,
Kind(100),
}
for _, kind := range invalidKinds {
if err := kind.Validate(); err == nil {
t.Errorf("expected invalid kind %s to fail validation, got: %v", kind, err)
}
}
}
func TestKind_ValidateValueType(t *testing.T) {
tests := []struct {
kind Kind
value interface{}
valid bool
}{
{kind: StringKind, value: "hello", valid: true},
{kind: StringKind, value: []byte("hello"), valid: false},
{kind: BytesKind, value: []byte("hello"), valid: true},
{kind: BytesKind, value: "hello", valid: false},
{kind: Int8Kind, value: int8(1), valid: true},
{kind: Int8Kind, value: int16(1), valid: false},
{kind: Uint8Kind, value: uint8(1), valid: true},
{kind: Uint8Kind, value: uint16(1), valid: false},
{kind: Int16Kind, value: int16(1), valid: true},
{kind: Int16Kind, value: int32(1), valid: false},
{kind: Uint16Kind, value: uint16(1), valid: true},
{kind: Uint16Kind, value: uint32(1), valid: false},
{kind: Int32Kind, value: int32(1), valid: true},
{kind: Int32Kind, value: int64(1), valid: false},
{kind: Uint32Kind, value: uint32(1), valid: true},
{kind: Uint32Kind, value: uint64(1), valid: false},
{kind: Int64Kind, value: int64(1), valid: true},
{kind: Int64Kind, value: int32(1), valid: false},
{kind: Uint64Kind, value: uint64(1), valid: true},
{kind: Uint64Kind, value: uint32(1), valid: false},
{kind: IntegerStringKind, value: "1", valid: true},
{kind: IntegerStringKind, value: int32(1), valid: false},
{kind: DecimalStringKind, value: "1.0", valid: true},
{kind: DecimalStringKind, value: "1", valid: true},
{kind: DecimalStringKind, value: "1.1e4", valid: true},
{kind: DecimalStringKind, value: int32(1), valid: false},
{kind: AddressKind, value: []byte("hello"), valid: true},
{kind: AddressKind, value: 1, valid: false},
{kind: BoolKind, value: true, valid: true},
{kind: BoolKind, value: false, valid: true},
{kind: BoolKind, value: 1, valid: false},
{kind: EnumKind, value: "hello", valid: true},
{kind: EnumKind, value: 1, valid: false},
{kind: TimeKind, value: time.Now(), valid: true},
{kind: TimeKind, value: "hello", valid: false},
{kind: DurationKind, value: time.Second, valid: true},
{kind: DurationKind, value: "hello", valid: false},
{kind: Float32Kind, value: float32(1.0), valid: true},
{kind: Float32Kind, value: float64(1.0), valid: false},
{kind: Float64Kind, value: float64(1.0), valid: true},
{kind: Float64Kind, value: float32(1.0), valid: false},
{kind: JSONKind, value: json.RawMessage("{}"), valid: true},
{kind: JSONKind, value: "hello", valid: false},
{kind: InvalidKind, value: "hello", valid: false},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
err := tt.kind.ValidateValueType(tt.value)
if tt.valid && err != nil {
t.Errorf("test %d: expected valid value %v for kind %s to pass validation, got: %v", i, tt.value, tt.kind, err)
}
if !tt.valid && err == nil {
t.Errorf("test %d: expected invalid value %v for kind %s to fail validation, got: %v", i, tt.value, tt.kind, err)
}
})
}
// nils get rejected
for kind := InvalidKind + 1; kind <= MAX_VALID_KIND; kind++ {
if err := kind.ValidateValueType(nil); err == nil {
t.Errorf("expected nil value to fail validation for kind %s", kind)
}
}
}
func TestKind_ValidateValue(t *testing.T) {
tests := []struct {
kind Kind
value interface{}
valid bool
}{
// check a few basic cases that should get caught be ValidateValueType
{StringKind, "hello", true},
{Int64Kind, int64(1), true},
{Int32Kind, "abc", false},
{BytesKind, nil, false},
// string must be valid UTF-8
{StringKind, string([]byte{0xff, 0xfe, 0xfd}), false},
// strings with null characters are invalid
{StringKind, string([]byte{1, 2, 0, 3}), false},
// check integer, decimal and json more thoroughly
{IntegerStringKind, "1", true},
{IntegerStringKind, "0", true},
{IntegerStringKind, "10", true},
{IntegerStringKind, "-100", true},
{IntegerStringKind, "1.0", false},
{IntegerStringKind, "00", true}, // leading zeros are allowed
{IntegerStringKind, "001", true},
{IntegerStringKind, "-01", true},
// 100 digits
{IntegerStringKind, "1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", true},
// more than 100 digits
{IntegerStringKind, "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", false},
{IntegerStringKind, "", false},
{IntegerStringKind, "abc", false},
{IntegerStringKind, "abc100", false},
{DecimalStringKind, "1.0", true},
{DecimalStringKind, "0.0", true},
{DecimalStringKind, "-100.075", true},
{DecimalStringKind, "1002346.000", true},
{DecimalStringKind, "0", true},
{DecimalStringKind, "10", true},
{DecimalStringKind, "-100", true},
{DecimalStringKind, "1", true},
{DecimalStringKind, "1.0e4", true},
{DecimalStringKind, "1.0e-4", true},
{DecimalStringKind, "1.0e+4", true},
{DecimalStringKind, "1.0e", false},
{DecimalStringKind, "1.0e4.0", false},
{DecimalStringKind, "1.0e-4.0", false},
{DecimalStringKind, "1.0e+4.0", false},
{DecimalStringKind, "-1.0e-4", true},
{DecimalStringKind, "-1.0e+4", true},
{DecimalStringKind, "-1.0E4", true},
{DecimalStringKind, "1E-9", true},
{DecimalStringKind, "1E-99", true},
{DecimalStringKind, "1E+9", true},
{DecimalStringKind, "1E+99", true},
// 50 digits before and after the decimal point
{DecimalStringKind, "10000000000000000000000000000000000000000000000000.10000000000000000000000000000000000000000000000001", true},
// too many digits before the decimal point
{DecimalStringKind, "10000000000000000000000000000000000000000000000000000000000000000000000000", false},
// too many digits after the decimal point
{DecimalStringKind, "1.0000000000000000000000000000000000000000000000000000000000000000000000001", false},
// exponent too big
{DecimalStringKind, "1E-999", false},
{DecimalStringKind, "", false},
{DecimalStringKind, "abc", false},
{DecimalStringKind, "abc", false},
{JSONKind, json.RawMessage(`{"a":10}`), true},
{JSONKind, json.RawMessage("10"), true},
{JSONKind, json.RawMessage("10.0"), true},
{JSONKind, json.RawMessage("true"), true},
{JSONKind, json.RawMessage("null"), true},
{JSONKind, json.RawMessage(`"abc"`), true},
{JSONKind, json.RawMessage(`[1,true,0.1,"abc",{"b":3}]`), true},
{JSONKind, json.RawMessage(`"abc`), false},
{JSONKind, json.RawMessage(`tru`), false},
{JSONKind, json.RawMessage(`[`), false},
{JSONKind, json.RawMessage(`{`), false},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("test %v %s", tt.kind, tt.value), func(t *testing.T) {
err := tt.kind.ValidateValue(tt.value)
if tt.valid && err != nil {
t.Errorf("test %d: expected valid value %v for kind %s to pass validation, got: %v", i, tt.value, tt.kind, err)
}
if !tt.valid && err == nil {
t.Errorf("test %d: expected invalid value %v for kind %s to fail validation, got: %v", i, tt.value, tt.kind, err)
}
})
}
}
func TestKind_String(t *testing.T) {
tests := []struct {
kind Kind
want string
}{
{StringKind, "string"},
{BytesKind, "bytes"},
{Int8Kind, "int8"},
{Uint8Kind, "uint8"},
{Int16Kind, "int16"},
{Uint16Kind, "uint16"},
{Int32Kind, "int32"},
{Uint32Kind, "uint32"},
{Int64Kind, "int64"},
{Uint64Kind, "uint64"},
{IntegerStringKind, "integer"},
{DecimalStringKind, "decimal"},
{BoolKind, "bool"},
{TimeKind, "time"},
{DurationKind, "duration"},
{Float32Kind, "float32"},
{Float64Kind, "float64"},
{JSONKind, "json"},
{EnumKind, "enum"},
{AddressKind, "bech32address"},
{InvalidKind, "invalid(0)"},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("test %s", tt.kind), func(t *testing.T) {
if got := tt.kind.String(); got != tt.want {
t.Errorf("test %d: Kind.String() = %v, want %v", i, got, tt.want)
}
})
}
}
func TestKindForGoValue(t *testing.T) {
tests := []struct {
value interface{}
want Kind
}{
{"hello", StringKind},
{[]byte("hello"), BytesKind},
{int8(1), Int8Kind},
{uint8(1), Uint8Kind},
{int16(1), Int16Kind},
{uint16(1), Uint16Kind},
{int32(1), Int32Kind},
{uint32(1), Uint32Kind},
{int64(1), Int64Kind},
{uint64(1), Uint64Kind},
{float32(1.0), Float32Kind},
{float64(1.0), Float64Kind},
{true, BoolKind},
{time.Now(), TimeKind},
{time.Second, DurationKind},
{json.RawMessage("{}"), JSONKind},
{map[string]interface{}{"a": 1}, InvalidKind},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
if got := KindForGoValue(tt.value); got != tt.want {
t.Errorf("test %d: KindForGoValue(%v) = %v, want %v", i, tt.value, got, tt.want)
}
// for valid kinds check valid value
if tt.want.Validate() == nil {
if err := tt.want.ValidateValue(tt.value); err != nil {
t.Errorf("test %d: expected valid value %v for kind %s to pass validation, got: %v", i, tt.value, tt.want, err)
}
}
})
}
}

View File

@ -1,35 +0,0 @@
// Package logutil defines the Logger interface expected by indexer implementations.
// It is implemented by cosmossdk.io/log which is not imported to minimize dependencies.
package logutil
// Logger is the logger interface expected by indexer implementations.
type Logger interface {
// Info takes a message and a set of key/value pairs and logs with level INFO.
// The key of the tuple must be a string.
Info(msg string, keyVals ...interface{})
// Warn takes a message and a set of key/value pairs and logs with level WARN.
// The key of the tuple must be a string.
Warn(msg string, keyVals ...interface{})
// Error takes a message and a set of key/value pairs and logs with level ERR.
// The key of the tuple must be a string.
Error(msg string, keyVals ...interface{})
// Debug takes a message and a set of key/value pairs and logs with level DEBUG.
// The key of the tuple must be a string.
Debug(msg string, keyVals ...interface{})
}
// NoopLogger is a logger that doesn't do anything.
type NoopLogger struct{}
func (n NoopLogger) Info(string, ...interface{}) {}
func (n NoopLogger) Warn(string, ...interface{}) {}
func (n NoopLogger) Error(string, ...interface{}) {}
func (n NoopLogger) Debug(string, ...interface{}) {}
var _ Logger = NoopLogger{}

View File

@ -1,31 +0,0 @@
package schema
import "fmt"
// ModuleSchema represents the logical schema of a module for purposes of indexing and querying.
type ModuleSchema struct {
// ObjectTypes describe the types of objects that are part of the module's schema.
ObjectTypes []ObjectType
}
// Validate validates the module schema.
func (s ModuleSchema) Validate() error {
enumValueMap := map[string]map[string]bool{}
for _, objType := range s.ObjectTypes {
if err := objType.validate(enumValueMap); err != nil {
return err
}
}
return nil
}
// ValidateObjectUpdate validates that the update conforms to the module schema.
func (s ModuleSchema) ValidateObjectUpdate(update ObjectUpdate) error {
for _, objType := range s.ObjectTypes {
if objType.Name == update.TypeName {
return objType.ValidateObjectUpdate(update)
}
}
return fmt.Errorf("object type %q not found in module schema", update.TypeName)
}

View File

@ -1,229 +0,0 @@
package schema
import (
"strings"
"testing"
)
func TestModuleSchema_Validate(t *testing.T) {
tests := []struct {
name string
moduleSchema ModuleSchema
errContains string
}{
{
name: "valid module schema",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
},
},
errContains: "",
},
{
name: "invalid object type",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
},
},
errContains: "invalid object type name",
},
{
name: "same enum with missing values",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "k",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b"},
},
},
},
ValueFields: []Field{
{
Name: "v",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b", "c"},
},
},
},
},
},
},
errContains: "different number of values",
},
{
name: "same enum with different values",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "k",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b"},
},
},
},
},
{
Name: "object2",
KeyFields: []Field{
{
Name: "k",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "c"},
},
},
},
},
},
},
errContains: "different values",
},
{
name: "same enum",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "k",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b"},
},
},
},
},
{
Name: "object2",
KeyFields: []Field{
{
Name: "k",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b"},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.moduleSchema.Validate()
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}
func TestModuleSchema_ValidateObjectUpdate(t *testing.T) {
tests := []struct {
name string
moduleSchema ModuleSchema
objectUpdate ObjectUpdate
errContains string
}{
{
name: "valid object update",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
},
},
objectUpdate: ObjectUpdate{
TypeName: "object1",
Key: "abc",
},
errContains: "",
},
{
name: "object type not found",
moduleSchema: ModuleSchema{
ObjectTypes: []ObjectType{
{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
},
},
objectUpdate: ObjectUpdate{
TypeName: "object2",
Key: "abc",
},
errContains: "object type \"object2\" not found in module schema",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.moduleSchema.ValidateObjectUpdate(tt.objectUpdate)
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}

View File

@ -1,15 +0,0 @@
package schema
import "regexp"
// NameFormat is the regular expression that a name must match.
// A name must start with a letter or underscore and can only contain letters, numbers, and underscores.
// A name must be at least one character long and can be at most 64 characters long.
const NameFormat = `^[a-zA-Z_][a-zA-Z0-9_]{0,63}$`
var nameRegex = regexp.MustCompile(NameFormat)
// ValidateName checks if the given name is a valid name conforming to NameFormat.
func ValidateName(name string) bool {
return nameRegex.MatchString(name)
}

View File

@ -1,31 +0,0 @@
package schema
import "testing"
func TestValidateName(t *testing.T) {
tests := []struct {
name string
valid bool
}{
{"", false},
{"a", true},
{"A", true},
{"_", true},
{"abc123_def789", true},
{"0", false},
{"a0", true},
{"a_", true},
{"$a", false},
{"a b", false},
{"pretty_unnecessarily_long_but_valid_name", true},
{"totally_unnecessarily_long_and_invalid_name_sdgkhwersdglkhweriqwery3258", false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if ValidateName(test.name) != test.valid {
t.Errorf("expected %v for name %q", test.valid, test.name)
}
})
}
}

View File

@ -1,100 +0,0 @@
package schema
import "fmt"
// ObjectType describes an object type a module schema.
type ObjectType struct {
// Name is the name of the object type. It must be unique within the module schema
// and conform to the NameFormat regular expression.
Name string
// KeyFields is a list of fields that make up the primary key of the object.
// It can be empty in which case indexers should assume that this object is
// a singleton and only has one value. Field names must be unique within the
// object between both key and value fields. Key fields CANNOT be nullable.
KeyFields []Field
// ValueFields is a list of fields that are not part of the primary key of the object.
// It can be empty in the case where all fields are part of the primary key.
// Field names must be unique within the object between both key and value fields.
ValueFields []Field
// RetainDeletions is a flag that indicates whether the indexer should retain
// deleted rows in the database and flag them as deleted rather than actually
// deleting the row. For many types of data in state, the data is deleted even
// though it is still valid in order to save space. Indexers will want to have
// the option of retaining such data and distinguishing from other "true" deletions.
RetainDeletions bool
}
// Validate validates the object type.
func (o ObjectType) Validate() error {
return o.validate(map[string]map[string]bool{})
}
// validate validates the object type with an enumValueMap that can be
// shared across a whole module schema.
func (o ObjectType) validate(enumValueMap map[string]map[string]bool) error {
if !ValidateName(o.Name) {
return fmt.Errorf("invalid object type name %q", o.Name)
}
fieldNames := map[string]bool{}
for _, field := range o.KeyFields {
if err := field.Validate(); err != nil {
return fmt.Errorf("invalid key field %q: %v", field.Name, err) //nolint:errorlint // false positive due to using go1.12
}
if field.Nullable {
return fmt.Errorf("key field %q cannot be nullable", field.Name)
}
if fieldNames[field.Name] {
return fmt.Errorf("duplicate field name %q", field.Name)
}
fieldNames[field.Name] = true
if err := checkEnumCompatibility(enumValueMap, field); err != nil {
return err
}
}
for _, field := range o.ValueFields {
if err := field.Validate(); err != nil {
return fmt.Errorf("invalid value field %q: %v", field.Name, err) //nolint:errorlint // false positive due to using go1.12
}
if fieldNames[field.Name] {
return fmt.Errorf("duplicate field name %q", field.Name)
}
fieldNames[field.Name] = true
if err := checkEnumCompatibility(enumValueMap, field); err != nil {
return err
}
}
if len(o.KeyFields) == 0 && len(o.ValueFields) == 0 {
return fmt.Errorf("object type %q has no key or value fields", o.Name)
}
return nil
}
// ValidateObjectUpdate validates that the update conforms to the object type.
func (o ObjectType) ValidateObjectUpdate(update ObjectUpdate) error {
if o.Name != update.TypeName {
return fmt.Errorf("object type name %q does not match update type name %q", o.Name, update.TypeName)
}
if err := ValidateObjectKey(o.KeyFields, update.Key); err != nil {
return fmt.Errorf("invalid key for object type %q: %v", update.TypeName, err) //nolint:errorlint // false positive due to using go1.12
}
if update.Delete {
return nil
}
return ValidateObjectValue(o.ValueFields, update.Value)
}

View File

@ -1,270 +0,0 @@
package schema
import (
"strings"
"testing"
)
var object1Type = ObjectType{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
}
var object2Type = ObjectType{
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
{
Name: "field2",
Kind: Int32Kind,
},
},
}
var object3Type = ObjectType{
Name: "object3",
ValueFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
{
Name: "field2",
Kind: Int32Kind,
},
},
}
var object4Type = ObjectType{
Name: "object4",
KeyFields: []Field{
{
Name: "field1",
Kind: Int32Kind,
},
},
ValueFields: []Field{
{
Name: "field2",
Kind: StringKind,
},
},
}
func TestObjectType_Validate(t *testing.T) {
tests := []struct {
name string
objectType ObjectType
errContains string
}{
{
name: "valid object type",
objectType: object1Type,
errContains: "",
},
{
name: "empty object type name",
objectType: ObjectType{
Name: "",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
errContains: "invalid object type name",
},
{
name: "invalid key field",
objectType: ObjectType{
Name: "object1",
KeyFields: []Field{
{
Name: "",
Kind: StringKind,
},
},
},
errContains: "invalid field name",
},
{
name: "invalid value field",
objectType: ObjectType{
Name: "object1",
ValueFields: []Field{
{
Kind: StringKind,
},
},
},
errContains: "invalid field name",
},
{
name: "no fields",
objectType: ObjectType{Name: "object0"},
errContains: "has no key or value fields",
},
{
name: "duplicate field",
objectType: ObjectType{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
ValueFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
},
},
errContains: "duplicate field name",
},
{
name: "duplicate field 22",
objectType: ObjectType{
Name: "object1",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
},
{
Name: "field1",
Kind: StringKind,
},
},
},
errContains: "duplicate field name",
},
{
name: "nullable key field",
objectType: ObjectType{
Name: "objectNullKey",
KeyFields: []Field{
{
Name: "field1",
Kind: StringKind,
Nullable: true,
},
},
},
errContains: "key field \"field1\" cannot be nullable",
},
{
name: "duplicate incompatible enum",
objectType: ObjectType{
Name: "objectWithEnums",
KeyFields: []Field{
{
Name: "key",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"a", "b"},
},
},
},
ValueFields: []Field{
{
Name: "value",
Kind: EnumKind,
EnumDefinition: EnumDefinition{
Name: "enum1",
Values: []string{"c", "b"},
},
},
},
},
errContains: "enum \"enum1\" has different values",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.objectType.Validate()
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}
func TestObjectType_ValidateObjectUpdate(t *testing.T) {
tests := []struct {
name string
objectType ObjectType
object ObjectUpdate
errContains string
}{
{
name: "wrong name",
objectType: object1Type,
object: ObjectUpdate{
TypeName: "object2",
Key: "hello",
},
errContains: "does not match update type name",
},
{
name: "invalid value",
objectType: object1Type,
object: ObjectUpdate{
TypeName: "object1",
Key: 123,
},
errContains: "invalid value",
},
{
name: "valid update",
objectType: object4Type,
object: ObjectUpdate{
TypeName: "object4",
Key: int32(123),
Value: "hello",
},
},
{
name: "valid deletion",
objectType: object4Type,
object: ObjectUpdate{
TypeName: "object4",
Key: int32(123),
Value: "ignored!",
Delete: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.objectType.ValidateObjectUpdate(tt.object)
if tt.errContains == "" {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Fatalf("expected error to contain %q, got: %v", tt.errContains, err)
}
}
})
}
}

View File

@ -1,61 +0,0 @@
package schema
import "sort"
// ObjectUpdate represents an update operation on an object in a module's state.
type ObjectUpdate struct {
// TypeName is the name of the object type in the module's schema.
TypeName string
// Key returns the value of the primary key of the object and must conform to these constraints with respect
// that the schema that is defined for the object:
// - if key represents a single field, then the value must be valid for the first field in that
// field list. For instance, if there is one field in the key of type String, then the value must be of
// type string
// - if key represents multiple fields, then the value must be a slice of values where each value is valid
// for the corresponding field in the field list. For instance, if there are two fields in the key of
// type String, String, then the value must be a slice of two strings.
// If the key has no fields, meaning that this is a singleton object, then this value is ignored and can be nil.
Key interface{}
// Value returns the non-primary key fields of the object and can either conform to the same constraints
// as ObjectUpdate.Key or it may be and instance of ValueUpdates. ValueUpdates can be used as a performance
// optimization to avoid copying the values of the object into the update and/or to omit unchanged fields.
// If this is a delete operation, then this value is ignored and can be nil.
Value interface{}
// Delete is a flag that indicates whether this update is a delete operation. If true, then the Value field
// is ignored and can be nil.
Delete bool
}
// ValueUpdates is an interface that represents the value fields of an object update. fields that
// were not updated may be excluded from the update. Consumers should be aware that implementations
// may not filter out fields that were unchanged. However, if a field is omitted from the update
// it should be considered unchanged.
type ValueUpdates interface {
// Iterate iterates over the fields and values in the object update. The function should return
// true to continue iteration or false to stop iteration. Each field value should conform
// to the requirements of that field's type in the schema. Iterate returns an error if
// it was unable to decode the values properly (which could be the case in lazy evaluation).
Iterate(func(col string, value interface{}) bool) error
}
// MapValueUpdates is a map-based implementation of ValueUpdates which always iterates
// over keys in sorted order.
type MapValueUpdates map[string]interface{}
// Iterate implements the ValueUpdates interface.
func (m MapValueUpdates) Iterate(fn func(col string, value interface{}) bool) error {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if !fn(k, m[k]) {
return nil
}
}
return nil
}

View File

@ -1,52 +0,0 @@
package schema
import "testing"
func TestMapValueUpdates_Iterate(t *testing.T) {
updates := MapValueUpdates(map[string]interface{}{
"a": "abc",
"b": 123,
})
got := map[string]interface{}{}
err := updates.Iterate(func(fieldname string, value interface{}) bool {
got[fieldname] = value
return true
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(got) != 2 {
t.Errorf("expected 2 updates, got: %v", got)
}
if got["a"] != "abc" {
t.Errorf("expected a=abc, got: %v", got)
}
if got["b"] != 123 {
t.Errorf("expected b=123, got: %v", got)
}
got = map[string]interface{}{}
err = updates.Iterate(func(fieldname string, value interface{}) bool {
if len(got) == 1 {
return false
}
got[fieldname] = value
return true
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(got) != 1 {
t.Errorf("expected 1 updates, got: %v", got)
}
// should have gotten the first field in order
if got["a"] != "abc" {
t.Errorf("expected a=abc, got: %v", got)
}
}

View File

@ -1,16 +0,0 @@
sonar.projectKey=cosmos-sdk-schema
sonar.organization=cosmos
sonar.projectName=Cosmos SDK - Schema
sonar.project.monorepo.enabled=true
sonar.sources=.
sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gw.go
sonar.coverage.exclusions=**/*_test.go,**/testutil/**,**/*.pb.go,**/*.pb.gw.go,**/*.pulsar.go,test_helpers.go,docs/**
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out
sonar.sourceEncoding=UTF-8
sonar.scm.provider=git
sonar.scm.forceReloadAll=true

View File

@ -1,95 +0,0 @@
package grpc
import (
"errors"
"fmt"
gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/grpc/encoding"
"google.golang.org/protobuf/proto"
_ "cosmossdk.io/api/amino" // Import amino.proto file for reflection
appmanager "cosmossdk.io/core/app"
)
type protoCodec struct {
interfaceRegistry appmanager.InterfaceRegistry
}
// newProtoCodec returns a reference to a new ProtoCodec
func newProtoCodec(interfaceRegistry appmanager.InterfaceRegistry) *protoCodec {
return &protoCodec{
interfaceRegistry: interfaceRegistry,
}
}
// Marshal implements BinaryMarshaler.Marshal method.
// NOTE: this function must be used with a concrete type which
// implements proto.Message. For interface please use the codec.MarshalInterface
func (pc *protoCodec) Marshal(o gogoproto.Message) ([]byte, error) {
// Size() check can catch the typed nil value.
if o == nil || gogoproto.Size(o) == 0 {
// return empty bytes instead of nil, because nil has special meaning in places like store.Set
return []byte{}, nil
}
return gogoproto.Marshal(o)
}
// Unmarshal implements BinaryMarshaler.Unmarshal method.
// NOTE: this function must be used with a concrete type which
// implements proto.Message. For interface please use the codec.UnmarshalInterface
func (pc *protoCodec) Unmarshal(bz []byte, ptr gogoproto.Message) error {
err := gogoproto.Unmarshal(bz, ptr)
if err != nil {
return err
}
// err = codectypes.UnpackInterfaces(ptr, pc.interfaceRegistry) // TODO: identify if needed for grpc
// if err != nil {
// return err
// }
return nil
}
func (pc *protoCodec) Name() string {
return "cosmos-sdk-grpc-codec"
}
// GRPCCodec returns the gRPC Codec for this specific ProtoCodec
func (pc *protoCodec) GRPCCodec() encoding.Codec {
return &grpcProtoCodec{cdc: pc}
}
// grpcProtoCodec is the implementation of the gRPC proto codec.
type grpcProtoCodec struct {
cdc appmanager.ProtoCodec
}
var errUnknownProtoType = errors.New("codec: unknown proto type") // sentinel error
func (c grpcProtoCodec) Marshal(v any) ([]byte, error) {
switch m := v.(type) {
case proto.Message:
protov2MarshalOpts := proto.MarshalOptions{Deterministic: true}
return protov2MarshalOpts.Marshal(m)
case gogoproto.Message:
return c.cdc.Marshal(m)
default:
return nil, fmt.Errorf("%w: cannot marshal type %T", errUnknownProtoType, v)
}
}
func (c grpcProtoCodec) Unmarshal(data []byte, v any) error {
switch m := v.(type) {
case proto.Message:
return proto.Unmarshal(data, m)
case gogoproto.Message:
return c.cdc.Unmarshal(data, m)
default:
return fmt.Errorf("%w: cannot unmarshal type %T", errUnknownProtoType, v)
}
}
func (c grpcProtoCodec) Name() string {
return "cosmos-sdk-grpc-codec"
}

View File

@ -1,51 +0,0 @@
package grpc
import "math"
func DefaultConfig() *Config {
return &Config{
Enable: true,
// DefaultGRPCAddress defines the default address to bind the gRPC server to.
Address: "localhost:9090",
// DefaultGRPCMaxRecvMsgSize defines the default gRPC max message size in
// bytes the server can receive.
MaxRecvMsgSize: 1024 * 1024 * 10,
// DefaultGRPCMaxSendMsgSize defines the default gRPC max message size in
// bytes the server can send.
MaxSendMsgSize: math.MaxInt32,
}
}
// Config defines configuration for the gRPC server.
type Config struct {
// Enable defines if the gRPC server should be enabled.
Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the gRPC server should be enabled."`
// Address defines the API server to listen on
Address string `mapstructure:"address" toml:"address" comment:"Address defines the gRPC server address to bind to."`
// MaxRecvMsgSize defines the max message size in bytes the server can receive.
// The default value is 10MB.
MaxRecvMsgSize int `mapstructure:"max-recv-msg-size" toml:"max-recv-msg-size" comment:"MaxRecvMsgSize defines the max message size in bytes the server can receive.\nThe default value is 10MB."`
// MaxSendMsgSize defines the max message size in bytes the server can send.
// The default value is math.MaxInt32.
MaxSendMsgSize int `mapstructure:"max-send-msg-size" toml:"max-send-msg-size" comment:"MaxSendMsgSize defines the max message size in bytes the server can send.\nThe default value is math.MaxInt32."`
}
// CfgOption is a function that allows to overwrite the default server configuration.
type CfgOption func(*Config)
// OverwriteDefaultConfig overwrites the default config with the new config.
func OverwriteDefaultConfig(newCfg *Config) CfgOption {
return func(cfg *Config) {
*cfg = *newCfg
}
}
// Disable the grpc-gateway server by default (default enabled).
func Disable() CfgOption {
return func(cfg *Config) {
cfg.Enable = false
}
}

View File

@ -1,12 +0,0 @@
package grpc
import "fmt"
// start flags are prefixed with the server name
// as the config in prefixed with the server name
// this allows viper to properly bind the flags
func prefix(f string) string {
return fmt.Sprintf("%s.%s", ServerName, f)
}
var FlagAddress = prefix("address")

View File

@ -1,5 +0,0 @@
// Package gogoreflection implements gRPC reflection for gogoproto consumers
// the normal reflection library does not work as it points to a different
// singleton registry. The API and codebase is taken from the official gRPC
// reflection repository.
package gogoreflection

View File

@ -1,76 +0,0 @@
package gogoreflection
import (
"reflect"
_ "github.com/cosmos/gogoproto/gogoproto" // required so it does register the gogoproto file descriptor
gogoproto "github.com/cosmos/gogoproto/proto"
_ "github.com/cosmos/cosmos-proto" // look above
"github.com/golang/protobuf/proto" //nolint:staticcheck // migrate in a future pr
)
func getFileDescriptor(filePath string) []byte {
// Since we got well known descriptors which are not registered into gogoproto
// registry but are instead registered into the proto one, we need to check both.
fd := gogoproto.FileDescriptor(filePath)
if len(fd) != 0 {
return fd
}
return proto.FileDescriptor(filePath) //nolint:staticcheck // keep for backward compatibility
}
func getMessageType(name string) reflect.Type {
typ := gogoproto.MessageType(name)
if typ != nil {
return typ
}
return proto.MessageType(name) //nolint:staticcheck // keep for backward compatibility
}
func getExtension(extID int32, m proto.Message) *gogoproto.ExtensionDesc {
// check first in gogoproto registry
for id, desc := range gogoproto.RegisteredExtensions(m) {
if id == extID {
return desc
}
}
// check into proto registry
for id, desc := range proto.RegisteredExtensions(m) { //nolint:staticcheck // keep for backward compatibility
if id == extID {
return &gogoproto.ExtensionDesc{
ExtendedType: desc.ExtendedType, //nolint:staticcheck // keep for backward compatibility
ExtensionType: desc.ExtensionType, //nolint:staticcheck // keep for backward compatibility
Field: desc.Field, //nolint:staticcheck // keep for backward compatibility
Name: desc.Name, //nolint:staticcheck // keep for backward compatibility
Tag: desc.Tag, //nolint:staticcheck // keep for backward compatibility
Filename: desc.Filename, //nolint:staticcheck // keep for backward compatibility
}
}
}
return nil
}
func getExtensionsNumbers(m proto.Message) []int32 {
gogoProtoExts := gogoproto.RegisteredExtensions(m)
out := make([]int32, 0, len(gogoProtoExts))
for id := range gogoProtoExts {
out = append(out, id)
}
if len(out) != 0 {
return out
}
protoExts := proto.RegisteredExtensions(m) //nolint:staticcheck // kept for backwards compatibility
out = make([]int32, 0, len(protoExts))
for id := range protoExts {
out = append(out, id)
}
return out
}

View File

@ -1,22 +0,0 @@
package gogoreflection
import (
"testing"
"google.golang.org/protobuf/runtime/protoimpl"
)
func TestRegistrationFix(t *testing.T) {
res := getFileDescriptor("gogoproto/gogo.proto")
rawDesc, err := decompress(res)
if err != nil {
t.Fatal(err)
}
fd := protoimpl.DescBuilder{
RawDescriptor: rawDesc,
}.Build()
if fd.File.Extensions().Len() == 0 {
t.Fatal("unexpected parsing")
}
}

View File

@ -1,483 +0,0 @@
/*
*
* Copyright 2016 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
Package gogoreflection implements server reflection service.
The service implemented is defined in:
https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1alpha/reflection.proto.
To register server reflection on a gRPC server:
import "google.golang.org/grpc/reflection"
s := grpc.NewServer()
pb.RegisterYourOwnServer(s, &server{})
// Register reflection service on gRPC server.
reflection.Register(s)
s.Serve(lis)
*/
package gogoreflection // import "google.golang.org/grpc/reflection"
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
"reflect"
"sort"
"strings"
"sync"
gogoproto "github.com/cosmos/gogoproto/proto"
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/core/log"
)
type serverReflectionServer struct {
rpb.UnimplementedServerReflectionServer
s *grpc.Server
methods []string
initSymbols sync.Once
serviceNames []string
symbols map[string]*dpb.FileDescriptorProto // map of fully-qualified names to files
log log.Logger
}
// Register registers the server reflection service on the given gRPC server.
func Register(s *grpc.Server, methods []string, logger log.Logger) {
rpb.RegisterServerReflectionServer(s, &serverReflectionServer{
s: s,
methods: methods,
log: logger,
})
}
// protoMessage is used for type assertion on proto messages.
// Generated proto message implements function Descriptor(), but Descriptor()
// is not part of interface proto.Message. This interface is needed to
// call Descriptor().
type protoMessage interface {
Descriptor() ([]byte, []int)
}
func (s *serverReflectionServer) getSymbols() (svcNames []string, symbolIndex map[string]*dpb.FileDescriptorProto) {
s.initSymbols.Do(func() {
s.symbols = map[string]*dpb.FileDescriptorProto{}
services, fds := s.getServices(s.methods)
s.serviceNames = services
processed := map[string]struct{}{}
for _, fd := range fds {
s.processFile(fd, processed)
}
sort.Strings(s.serviceNames)
})
return s.serviceNames, s.symbols
}
func (s *serverReflectionServer) processFile(fd *dpb.FileDescriptorProto, processed map[string]struct{}) {
filename := fd.GetName()
if _, ok := processed[filename]; ok {
return
}
processed[filename] = struct{}{}
prefix := fd.GetPackage()
for _, msg := range fd.MessageType {
s.processMessage(fd, prefix, msg)
}
for _, en := range fd.EnumType {
s.processEnum(fd, prefix, en)
}
for _, ext := range fd.Extension {
s.processField(fd, prefix, ext)
}
for _, svc := range fd.Service {
svcName := fqn(prefix, svc.GetName())
s.symbols[svcName] = fd
for _, meth := range svc.Method {
name := fqn(svcName, meth.GetName())
s.symbols[name] = fd
}
}
for _, dep := range fd.Dependency {
fdenc := getFileDescriptor(dep)
fdDep, err := decodeFileDesc(fdenc)
if err != nil {
continue
}
s.processFile(fdDep, processed)
}
}
func (s *serverReflectionServer) processMessage(fd *dpb.FileDescriptorProto, prefix string, msg *dpb.DescriptorProto) {
msgName := fqn(prefix, msg.GetName())
s.symbols[msgName] = fd
for _, nested := range msg.NestedType {
s.processMessage(fd, msgName, nested)
}
for _, en := range msg.EnumType {
s.processEnum(fd, msgName, en)
}
for _, ext := range msg.Extension {
s.processField(fd, msgName, ext)
}
for _, fld := range msg.Field {
s.processField(fd, msgName, fld)
}
for _, oneof := range msg.OneofDecl {
oneofName := fqn(msgName, oneof.GetName())
s.symbols[oneofName] = fd
}
}
func (s *serverReflectionServer) processEnum(fd *dpb.FileDescriptorProto, prefix string, en *dpb.EnumDescriptorProto) {
enName := fqn(prefix, en.GetName())
s.symbols[enName] = fd
for _, val := range en.Value {
valName := fqn(enName, val.GetName())
s.symbols[valName] = fd
}
}
func (s *serverReflectionServer) processField(fd *dpb.FileDescriptorProto, prefix string, fld *dpb.FieldDescriptorProto) {
fldName := fqn(prefix, fld.GetName())
s.symbols[fldName] = fd
}
func fqn(prefix, name string) string {
if prefix == "" {
return name
}
return prefix + "." + name
}
// fileDescForType gets the file descriptor for the given type.
// The given type should be a proto message.
func (s *serverReflectionServer) fileDescForType(st reflect.Type) (*dpb.FileDescriptorProto, error) {
m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(protoMessage)
if !ok {
return nil, fmt.Errorf("failed to create message from type: %v", st)
}
enc, _ := m.Descriptor()
return decodeFileDesc(enc)
}
// decodeFileDesc does decompression and unmarshalling on the given
// file descriptor byte slice.
func decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
raw, err := decompress(enc)
if err != nil {
return nil, fmt.Errorf("failed to decompress enc: %w", err)
}
fd := new(dpb.FileDescriptorProto)
if err := gogoproto.Unmarshal(raw, fd); err != nil {
return nil, fmt.Errorf("bad descriptor: %w", err)
}
return fd, nil
}
// decompress does gzip decompression.
func decompress(b []byte) ([]byte, error) {
r, err := gzip.NewReader(bytes.NewReader(b))
if err != nil {
return nil, fmt.Errorf("bad gzipped descriptor: %w", err)
}
out, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("bad gzipped descriptor: %w", err)
}
return out, nil
}
func typeForName(name string) (reflect.Type, error) {
pt := getMessageType(name)
if pt == nil {
return nil, fmt.Errorf("unknown type: %q", name)
}
st := pt.Elem()
return st, nil
}
func fileDescContainingExtension(st reflect.Type, ext int32) (*dpb.FileDescriptorProto, error) {
m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(gogoproto.Message)
if !ok {
return nil, fmt.Errorf("failed to create message from type: %v", st)
}
extDesc := getExtension(ext, m)
if extDesc == nil {
return nil, fmt.Errorf("failed to find registered extension for extension number %v", ext)
}
return decodeFileDesc(getFileDescriptor(extDesc.Filename))
}
func (s *serverReflectionServer) allExtensionNumbersForType(st reflect.Type) ([]int32, error) {
m, ok := reflect.Zero(reflect.PtrTo(st)).Interface().(gogoproto.Message)
if !ok {
return nil, fmt.Errorf("failed to create message from type: %v", st)
}
out := getExtensionsNumbers(m)
return out, nil
}
// fileDescWithDependencies returns a slice of serialized fileDescriptors in
// wire format ([]byte). The fileDescriptors will include fd and all the
// transitive dependencies of fd with names not in sentFileDescriptors.
func fileDescWithDependencies(fd *dpb.FileDescriptorProto, sentFileDescriptors map[string]bool) ([][]byte, error) {
r := [][]byte{}
queue := []*dpb.FileDescriptorProto{fd}
for len(queue) > 0 {
currentfd := queue[0]
queue = queue[1:]
if sent := sentFileDescriptors[currentfd.GetName()]; len(r) == 0 || !sent {
sentFileDescriptors[currentfd.GetName()] = true
currentfdEncoded, err := gogoproto.Marshal(currentfd)
if err != nil {
return nil, err
}
r = append(r, currentfdEncoded)
}
for _, dep := range currentfd.Dependency {
fdenc := getFileDescriptor(dep)
fdDep, err := decodeFileDesc(fdenc)
if err != nil {
continue
}
queue = append(queue, fdDep)
}
}
return r, nil
}
// fileDescEncodingByFilename finds the file descriptor for given filename,
// finds all of its previously unsent transitive dependencies, does marshaling
// on them, and returns the marshaled result.
func (s *serverReflectionServer) fileDescEncodingByFilename(name string, sentFileDescriptors map[string]bool) ([][]byte, error) {
enc := getFileDescriptor(name)
if enc == nil {
return nil, fmt.Errorf("unknown file: %v", name)
}
fd, err := decodeFileDesc(enc)
if err != nil {
return nil, err
}
return fileDescWithDependencies(fd, sentFileDescriptors)
}
// fileDescEncodingContainingSymbol finds the file descriptor containing the
// given symbol, finds all of its previously unsent transitive dependencies,
// does marshaling on them, and returns the marshaled result. The given symbol
// can be a type, a service or a method.
func (s *serverReflectionServer) fileDescEncodingContainingSymbol(name string, sentFileDescriptors map[string]bool) ([][]byte, error) {
_, symbols := s.getSymbols()
fd := symbols[name]
if fd == nil {
// Check if it's a type name that was not present in the
// transitive dependencies of the registered services.
if st, err := typeForName(name); err == nil {
fd, err = s.fileDescForType(st)
if err != nil {
return nil, err
}
}
}
if fd == nil {
return nil, fmt.Errorf("unknown symbol: %v", name)
}
return fileDescWithDependencies(fd, sentFileDescriptors)
}
// fileDescEncodingContainingExtension finds the file descriptor containing
// given extension, finds all of its previously unsent transitive dependencies,
// does marshaling on them, and returns the marshaled result.
func (s *serverReflectionServer) fileDescEncodingContainingExtension(typeName string, extNum int32, sentFileDescriptors map[string]bool) ([][]byte, error) {
st, err := typeForName(typeName)
if err != nil {
return nil, err
}
fd, err := fileDescContainingExtension(st, extNum)
if err != nil {
return nil, err
}
return fileDescWithDependencies(fd, sentFileDescriptors)
}
// allExtensionNumbersForTypeName returns all extension numbers for the given type.
func (s *serverReflectionServer) allExtensionNumbersForTypeName(name string) ([]int32, error) {
st, err := typeForName(name)
if err != nil {
return nil, err
}
extNums, err := s.allExtensionNumbersForType(st)
if err != nil {
return nil, err
}
return extNums, nil
}
// ServerReflectionInfo is the reflection service handler.
func (s *serverReflectionServer) ServerReflectionInfo(stream rpb.ServerReflection_ServerReflectionInfoServer) error {
sentFileDescriptors := make(map[string]bool)
for {
in, err := stream.Recv()
if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
return err
}
out := &rpb.ServerReflectionResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
ValidHost: in.Host, //nolint:staticcheck // SA1019: we want to keep using v1alpha
OriginalRequest: in,
}
switch req := in.MessageRequest.(type) {
case *rpb.ServerReflectionRequest_FileByFilename:
b, err := s.fileDescEncodingByFilename(req.FileByFilename, sentFileDescriptors) //nolint:staticcheck // SA1019: we want to keep using v1alpha
if err != nil {
out.MessageResponse = &rpb.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &rpb.ErrorResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
ErrorCode: int32(codes.NotFound),
ErrorMessage: err.Error(),
},
}
} else {
out.MessageResponse = &rpb.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &rpb.FileDescriptorResponse{FileDescriptorProto: b}, //nolint:staticcheck // SA1019: we want to keep using v1alpha
}
}
case *rpb.ServerReflectionRequest_FileContainingSymbol:
b, err := s.fileDescEncodingContainingSymbol(req.FileContainingSymbol, sentFileDescriptors) //nolint:staticcheck // SA1019: we want to keep using v1alpha
if err != nil {
out.MessageResponse = &rpb.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &rpb.ErrorResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
ErrorCode: int32(codes.NotFound),
ErrorMessage: err.Error(),
},
}
} else {
out.MessageResponse = &rpb.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &rpb.FileDescriptorResponse{FileDescriptorProto: b}, //nolint:staticcheck // SA1019: we want to keep using v1alpha
}
}
case *rpb.ServerReflectionRequest_FileContainingExtension:
typeName := req.FileContainingExtension.ContainingType //nolint:staticcheck // SA1019: we want to keep using v1alpha
extNum := req.FileContainingExtension.ExtensionNumber //nolint:staticcheck // SA1019: we want to keep using v1alpha
b, err := s.fileDescEncodingContainingExtension(typeName, extNum, sentFileDescriptors)
if err != nil {
out.MessageResponse = &rpb.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &rpb.ErrorResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
ErrorCode: int32(codes.NotFound),
ErrorMessage: err.Error(),
},
}
} else {
out.MessageResponse = &rpb.ServerReflectionResponse_FileDescriptorResponse{
FileDescriptorResponse: &rpb.FileDescriptorResponse{FileDescriptorProto: b}, //nolint:staticcheck // SA1019: we want to keep using v1alpha
}
}
case *rpb.ServerReflectionRequest_AllExtensionNumbersOfType:
extNums, err := s.allExtensionNumbersForTypeName(req.AllExtensionNumbersOfType) //nolint:staticcheck // SA1019: we want to keep using v1alpha
if err != nil {
out.MessageResponse = &rpb.ServerReflectionResponse_ErrorResponse{
ErrorResponse: &rpb.ErrorResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
ErrorCode: int32(codes.NotFound),
ErrorMessage: err.Error(),
},
}
} else {
out.MessageResponse = &rpb.ServerReflectionResponse_AllExtensionNumbersResponse{
AllExtensionNumbersResponse: &rpb.ExtensionNumberResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
BaseTypeName: req.AllExtensionNumbersOfType, //nolint:staticcheck // SA1019: we want to keep using v1alpha
ExtensionNumber: extNums,
},
}
}
case *rpb.ServerReflectionRequest_ListServices:
svcNames, _ := s.getSymbols()
serviceResponses := make([]*rpb.ServiceResponse, len(svcNames)) //nolint:staticcheck // SA1019: we want to keep using v1alpha
for i, n := range svcNames {
serviceResponses[i] = &rpb.ServiceResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
Name: n,
}
}
out.MessageResponse = &rpb.ServerReflectionResponse_ListServicesResponse{
ListServicesResponse: &rpb.ListServiceResponse{ //nolint:staticcheck // SA1019: we want to keep using v1alpha
Service: serviceResponses,
},
}
default:
return status.Errorf(codes.InvalidArgument, "invalid MessageRequest: %v", in.MessageRequest)
}
if err := stream.Send(out); err != nil {
return err
}
}
}
// getServices gets the unique list of services given a list of methods.
func (s *serverReflectionServer) getServices(methods []string) (svcs []string, fds []*dpb.FileDescriptorProto) {
registry, err := gogoproto.MergedRegistry()
if err != nil {
s.log.Error("unable to load merged registry", "err", err)
return nil, nil
}
seenSvc := map[protoreflect.FullName]struct{}{}
for _, methodName := range methods {
methodName = strings.Join(strings.Split(methodName[1:], "/"), ".")
md, err := registry.FindDescriptorByName(protoreflect.FullName(methodName))
if err != nil {
s.log.Error("unable to load method descriptor", "method", methodName, "err", err)
continue
}
svc := md.(protoreflect.MethodDescriptor).Parent()
if _, seen := seenSvc[svc.FullName()]; !seen {
svcs = append(svcs, string(svc.FullName()))
file := svc.ParentFile()
fds = append(fds, protodesc.ToFileDescriptorProto(file))
}
}
return
}

View File

@ -1,200 +0,0 @@
package grpc
import (
"context"
"errors"
"fmt"
"io"
"net"
"strconv"
"github.com/cosmos/gogoproto/proto"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/exp/maps"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
serverv2 "cosmossdk.io/server/v2"
"cosmossdk.io/server/v2/api/grpc/gogoreflection"
)
const (
ServerName = "grpc"
BlockHeightHeader = "x-cosmos-block-height"
)
type Server[T transaction.Tx] struct {
logger log.Logger
config *Config
cfgOptions []CfgOption
grpcSrv *grpc.Server
}
// New creates a new grpc server.
func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] {
return &Server[T]{
cfgOptions: cfgOptions,
}
}
// Init returns a correctly configured and initialized gRPC server.
// Note, the caller is responsible for starting the server.
func (s *Server[T]) Init(appI serverv2.AppI[T], v *viper.Viper, logger log.Logger) error {
cfg := s.Config().(*Config)
if v != nil {
if err := serverv2.UnmarshalSubConfig(v, s.Name(), &cfg); err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
}
methodsMap := appI.GetGPRCMethodsToMessageMap()
grpcSrv := grpc.NewServer(
grpc.ForceServerCodec(newProtoCodec(appI.InterfaceRegistry()).GRPCCodec()),
grpc.MaxSendMsgSize(cfg.MaxSendMsgSize),
grpc.MaxRecvMsgSize(cfg.MaxRecvMsgSize),
grpc.UnknownServiceHandler(
makeUnknownServiceHandler(methodsMap, appI.GetAppManager()),
),
)
// Reflection allows external clients to see what services and methods the gRPC server exposes.
gogoreflection.Register(grpcSrv, maps.Keys(methodsMap), logger.With("sub-module", "grpc-reflection"))
s.grpcSrv = grpcSrv
s.config = cfg
s.logger = logger.With(log.ModuleKey, s.Name())
return nil
}
func (s *Server[T]) StartCmdFlags() *pflag.FlagSet {
flags := pflag.NewFlagSet(s.Name(), pflag.ExitOnError)
flags.String(FlagAddress, "localhost:9090", "Listen address")
return flags
}
func makeUnknownServiceHandler(messageMap map[string]func() proto.Message, querier interface {
Query(ctx context.Context, version uint64, msg proto.Message) (proto.Message, error)
},
) grpc.StreamHandler {
return func(srv any, stream grpc.ServerStream) error {
method, ok := grpc.MethodFromServerStream(stream)
if !ok {
return status.Error(codes.InvalidArgument, "unable to get method")
}
makeMsg, exists := messageMap[method]
if !exists {
return status.Errorf(codes.Unimplemented, "gRPC method %s is not handled", method)
}
for {
req := makeMsg()
err := stream.RecvMsg(req)
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
// extract height header
ctx := stream.Context()
height, err := getHeightFromCtx(ctx)
if err != nil {
return status.Errorf(codes.InvalidArgument, "invalid get height from context: %v", err)
}
resp, err := querier.Query(ctx, height, req)
if err != nil {
return err
}
err = stream.SendMsg(resp)
if err != nil {
return err
}
}
}
}
func getHeightFromCtx(ctx context.Context) (uint64, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return 0, nil
}
values := md.Get(BlockHeightHeader)
if len(values) == 0 {
return 0, nil
}
if len(values) != 1 {
return 0, fmt.Errorf("gRPC height metadata must be of length 1, got: %d", len(values))
}
heightStr := values[0]
height, err := strconv.ParseUint(heightStr, 10, 64)
if err != nil {
return 0, fmt.Errorf("unable to parse height string from gRPC metadata %s: %w", heightStr, err)
}
return height, nil
}
func (s *Server[T]) Name() string {
return ServerName
}
func (s *Server[T]) Config() any {
if s.config == nil || s.config == (&Config{}) {
cfg := DefaultConfig()
// overwrite the default config with the provided options
for _, opt := range s.cfgOptions {
opt(cfg)
}
return cfg
}
return s.config
}
func (s *Server[T]) Start(ctx context.Context) error {
if !s.config.Enable {
return nil
}
listener, err := net.Listen("tcp", s.config.Address)
if err != nil {
return fmt.Errorf("failed to listen on address %s: %w", s.config.Address, err)
}
errCh := make(chan error)
// Start the gRPC in an external goroutine as Serve is blocking and will return
// an error upon failure, which we'll send on the error channel that will be
// consumed by the for block below.
go func() {
s.logger.Info("starting gRPC server...", "address", s.config.Address)
errCh <- s.grpcSrv.Serve(listener)
}()
// Start a blocking select to wait for an indication to stop the server or that
// the server failed to start properly.
err = <-errCh
s.logger.Error("failed to start gRPC server", "err", err)
return err
}
func (s *Server[T]) Stop(ctx context.Context) error {
if !s.config.Enable {
return nil
}
s.logger.Info("stopping gRPC server...", "address", s.config.Address)
s.grpcSrv.GracefulStop()
return nil
}

View File

@ -1,28 +0,0 @@
package grpcgateway
func DefaultConfig() *Config {
return &Config{
Enable: true,
}
}
type Config struct {
// Enable defines if the gRPC-gateway should be enabled.
Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the gRPC-gateway should be enabled."`
}
type CfgOption func(*Config)
// OverwriteDefaultConfig overwrites the default config with the new config.
func OverwriteDefaultConfig(newCfg *Config) CfgOption {
return func(cfg *Config) {
*cfg = *newCfg
}
}
// Disable the grpc server by default (default enabled).
func Disable() CfgOption {
return func(cfg *Config) {
cfg.Enable = false
}
}

View File

@ -1,145 +0,0 @@
package grpcgateway
import (
"context"
"fmt"
"net/http"
"strings"
gateway "github.com/cosmos/gogogateway"
"github.com/cosmos/gogoproto/jsonpb"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/viper"
"google.golang.org/grpc"
"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
serverv2 "cosmossdk.io/server/v2"
)
var _ serverv2.ServerComponent[transaction.Tx] = (*GRPCGatewayServer[transaction.Tx])(nil)
const (
ServerName = "grpc-gateway"
// GRPCBlockHeightHeader is the gRPC header for block height.
GRPCBlockHeightHeader = "x-cosmos-block-height"
)
type GRPCGatewayServer[T transaction.Tx] struct {
logger log.Logger
config *Config
cfgOptions []CfgOption
GRPCSrv *grpc.Server
GRPCGatewayRouter *runtime.ServeMux
}
// New creates a new gRPC-gateway server.
func New[T transaction.Tx](grpcSrv *grpc.Server, ir jsonpb.AnyResolver, cfgOptions ...CfgOption) *GRPCGatewayServer[T] {
// The default JSON marshaller used by the gRPC-Gateway is unable to marshal non-nullable non-scalar fields.
// Using the gogo/gateway package with the gRPC-Gateway WithMarshaler option fixes the scalar field marshaling issue.
marshalerOption := &gateway.JSONPb{
EmitDefaults: true,
Indent: "",
OrigName: true,
AnyResolver: ir,
}
return &GRPCGatewayServer[T]{
GRPCSrv: grpcSrv,
GRPCGatewayRouter: runtime.NewServeMux(
// Custom marshaler option is required for gogo proto
runtime.WithMarshalerOption(runtime.MIMEWildcard, marshalerOption),
// This is necessary to get error details properly
// marshaled in unary requests.
runtime.WithProtoErrorHandler(runtime.DefaultHTTPProtoErrorHandler),
// Custom header matcher for mapping request headers to
// GRPC metadata
runtime.WithIncomingHeaderMatcher(CustomGRPCHeaderMatcher),
),
cfgOptions: cfgOptions,
}
}
func (g *GRPCGatewayServer[T]) Name() string {
return ServerName
}
func (s *GRPCGatewayServer[T]) Config() any {
if s.config == nil || s.config == (&Config{}) {
cfg := DefaultConfig()
// overwrite the default config with the provided options
for _, opt := range s.cfgOptions {
opt(cfg)
}
return cfg
}
return s.config
}
func (s *GRPCGatewayServer[T]) Init(appI serverv2.AppI[transaction.Tx], v *viper.Viper, logger log.Logger) error {
cfg := s.Config().(*Config)
if v != nil {
if err := serverv2.UnmarshalSubConfig(v, s.Name(), &cfg); err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
}
// Register the gRPC-Gateway server.
// appI.RegisterGRPCGatewayRoutes(s.GRPCGatewayRouter, s.GRPCSrv)
s.logger = logger
s.config = cfg
return nil
}
func (s *GRPCGatewayServer[T]) Start(ctx context.Context) error {
if !s.config.Enable {
return nil
}
// TODO start a normal Go http server (and do not leverage comet's like https://github.com/cosmos/cosmos-sdk/blob/9df6019de6ee7999fe9864bac836deb2f36dd44a/server/api/server.go#L98)
return nil
}
func (s *GRPCGatewayServer[T]) Stop(ctx context.Context) error {
if !s.config.Enable {
return nil
}
return nil
}
// Register implements registers a grpc-gateway server
func (s *GRPCGatewayServer[T]) Register(r mux.Router) error {
// configure grpc-gatway server
r.PathPrefix("/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Fall back to grpc gateway server.
s.GRPCGatewayRouter.ServeHTTP(w, req)
}))
return nil
}
// CustomGRPCHeaderMatcher for mapping request headers to
// GRPC metadata.
// HTTP headers that start with 'Grpc-Metadata-' are automatically mapped to
// gRPC metadata after removing prefix 'Grpc-Metadata-'. We can use this
// CustomGRPCHeaderMatcher if headers don't start with `Grpc-Metadata-`
func CustomGRPCHeaderMatcher(key string) (string, bool) {
switch strings.ToLower(key) {
case GRPCBlockHeightHeader:
return GRPCBlockHeightHeader, true
default:
return runtime.DefaultHeaderMatcher(key)
}
}

View File

@ -1,42 +0,0 @@
package telemetry
type Config struct {
// Prefixed with keys to separate services
ServiceName string `mapstructure:"service-name" toml:"service-name" comment:"Prefixed with keys to separate services."`
// Enabled enables the application telemetry functionality. When enabled,
// an in-memory sink is also enabled by default. Operators may also enabled
// other sinks such as Prometheus.
Enabled bool `mapstructure:"enabled" toml:"enabled" comment:"Enabled enables the application telemetry functionality. When enabled, an in-memory sink is also enabled by default. Operators may also enabled other sinks such as Prometheus."`
// Enable prefixing gauge values with hostname
EnableHostname bool `mapstructure:"enable-hostname" toml:"enable-hostname" comment:"Enable prefixing gauge values with hostname."`
// Enable adding hostname to labels
EnableHostnameLabel bool `mapstructure:"enable-hostname-label" toml:"enable-hostname-label" comment:"Enable adding hostname to labels."`
// Enable adding service to labels
EnableServiceLabel bool `mapstructure:"enable-service-label" toml:"enable-service-label" comment:"Enable adding service to labels."`
// PrometheusRetentionTime, when positive, enables a Prometheus metrics sink.
// It defines the retention duration in seconds.
PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time" toml:"prometheus-retention-time" comment:"PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. It defines the retention duration in seconds."`
// GlobalLabels defines a global set of name/value label tuples applied to all
// metrics emitted using the wrapper functions defined in telemetry package.
//
// Example:
// [["chain_id", "cosmoshub-1"]]
GlobalLabels [][]string `mapstructure:"global-labels" toml:"global-labels" comment:"GlobalLabels defines a global set of name/value label tuples applied to all metrics emitted using the wrapper functions defined in telemetry package.\n Example:\n [[\"chain_id\", \"cosmoshub-1\"]]"`
// MetricsSink defines the type of metrics backend to use.
MetricsSink string `mapstructure:"type" toml:"metrics-sink" comment:"MetricsSink defines the type of metrics backend to use. Default is in memory"`
// StatsdAddr defines the address of a statsd server to send metrics to.
// Only utilized if MetricsSink is set to "statsd" or "dogstatsd".
StatsdAddr string `mapstructure:"statsd-addr" toml:"stats-addr" comment:"StatsdAddr defines the address of a statsd server to send metrics to. Only utilized if MetricsSink is set to \"statsd\" or \"dogstatsd\"."`
// DatadogHostname defines the hostname to use when emitting metrics to
// Datadog. Only utilized if MetricsSink is set to "dogstatsd".
DatadogHostname string `mapstructure:"datadog-hostname" toml:"data-dog-hostname" comment:"DatadogHostname defines the hostname to use when emitting metrics to Datadog. Only utilized if MetricsSink is set to \"dogstatsd\"."`
}

View File

@ -1,188 +0,0 @@
package telemetry
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/hashicorp/go-metrics"
"github.com/hashicorp/go-metrics/datadog"
metricsprom "github.com/hashicorp/go-metrics/prometheus"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/expfmt"
)
// GlobalLabels defines the set of global labels that will be applied to all
// metrics emitted using the telemetry package function wrappers.
var GlobalLabels = []metrics.Label{} // nolint: ignore // false positive
// NewLabel creates a new instance of Label with name and value
func NewLabel(name, value string) metrics.Label {
return metrics.Label{Name: name, Value: value}
}
// Metrics supported format types.
const (
FormatDefault = ""
FormatPrometheus = "prometheus"
FormatText = "text"
ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8`
MetricSinkInMem = "mem"
MetricSinkStatsd = "statsd"
MetricSinkDogsStatsd = "dogstatsd"
)
// DisplayableSink is an interface that defines a method for displaying metrics.
type DisplayableSink interface {
DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error)
}
// Metrics defines a wrapper around application telemetry functionality. It allows
// metrics to be gathered at any point in time. When creating a Metrics object,
// internally, a global metrics is registered with a set of sinks as configured
// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a
// dump of formatted recent metrics will be sent to STDERR.
type Metrics struct {
sink metrics.MetricSink
prometheusEnabled bool
}
// GatherResponse is the response type of registered metrics
type GatherResponse struct {
Metrics []byte
ContentType string
}
// New creates a new instance of Metrics
func New(cfg Config) (_ *Metrics, rerr error) {
if !cfg.Enabled {
return nil, nil
}
if numGlobalLabels := len(cfg.GlobalLabels); numGlobalLabels > 0 {
parsedGlobalLabels := make([]metrics.Label, numGlobalLabels)
for i, gl := range cfg.GlobalLabels {
parsedGlobalLabels[i] = NewLabel(gl[0], gl[1])
}
GlobalLabels = parsedGlobalLabels
}
metricsConf := metrics.DefaultConfig(cfg.ServiceName)
metricsConf.EnableHostname = cfg.EnableHostname
metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel
var (
sink metrics.MetricSink
err error
)
switch cfg.MetricsSink {
case MetricSinkStatsd:
sink, err = metrics.NewStatsdSink(cfg.StatsdAddr)
case MetricSinkDogsStatsd:
sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname)
default:
memSink := metrics.NewInmemSink(10*time.Second, time.Minute)
sink = memSink
inMemSig := metrics.DefaultInmemSignal(memSink)
defer func() {
if rerr != nil {
inMemSig.Stop()
}
}()
}
if err != nil {
return nil, err
}
m := &Metrics{sink: sink}
fanout := metrics.FanoutSink{sink}
if cfg.PrometheusRetentionTime > 0 {
m.prometheusEnabled = true
prometheusOpts := metricsprom.PrometheusOpts{
Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second,
}
promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts)
if err != nil {
return nil, err
}
fanout = append(fanout, promSink)
}
if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil {
return nil, err
}
return m, nil
}
// Gather collects all registered metrics and returns a GatherResponse where the
// metrics are encoded depending on the type. Metrics are either encoded via
// Prometheus or JSON if in-memory.
func (m *Metrics) Gather(format string) (GatherResponse, error) {
switch format {
case FormatPrometheus:
return m.gatherPrometheus()
case FormatText:
return m.gatherGeneric()
case FormatDefault:
return m.gatherGeneric()
default:
return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format)
}
}
// gatherPrometheus collects Prometheus metrics and returns a GatherResponse.
// If Prometheus metrics are not enabled, it returns an error.
func (m *Metrics) gatherPrometheus() (GatherResponse, error) {
if !m.prometheusEnabled {
return GatherResponse{}, fmt.Errorf("prometheus metrics are not enabled")
}
metricsFamilies, err := prometheus.DefaultGatherer.Gather()
if err != nil {
return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err)
}
buf := &bytes.Buffer{}
defer buf.Reset()
e := expfmt.NewEncoder(buf, expfmt.NewFormat(expfmt.TypeTextPlain))
for _, mf := range metricsFamilies {
if err := e.Encode(mf); err != nil {
return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err)
}
}
return GatherResponse{ContentType: ContentTypeText, Metrics: buf.Bytes()}, nil
}
// gatherGeneric collects generic metrics and returns a GatherResponse.
func (m *Metrics) gatherGeneric() (GatherResponse, error) {
gm, ok := m.sink.(DisplayableSink)
if !ok {
return GatherResponse{}, fmt.Errorf("non in-memory metrics sink does not support generic format")
}
summary, err := gm.DisplayMetrics(nil, nil)
if err != nil {
return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err)
}
content, err := json.Marshal(summary)
if err != nil {
return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err)
}
return GatherResponse{ContentType: "application/json", Metrics: content}, nil
}

View File

@ -1,47 +0,0 @@
package telemetry
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
)
func RegisterMetrics(r mux.Router, cfg Config) (*Metrics, error) {
m, err := New(cfg)
if err != nil {
return nil, err
}
metricsHandler := func(w http.ResponseWriter, r *http.Request) {
format := strings.TrimSpace(r.FormValue("format"))
gr, err := m.Gather(format)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
bz, err := json.Marshal(errorResponse{Code: 400, Error: fmt.Sprintf("failed to gather metrics: %s", err)})
if err != nil {
return
}
_, _ = w.Write(bz)
return
}
w.Header().Set("Content-Type", gr.ContentType)
_, _ = w.Write(gr.Metrics)
}
r.HandleFunc("/metrics", metricsHandler).Methods("GET")
return m, nil
}
// errorResponse defines the attributes of a JSON error response.
type errorResponse struct {
Code int `json:"code,omitempty"`
Error string `json:"error"`
}

View File

@ -1,172 +0,0 @@
package appmanager
import (
"bytes"
"context"
"encoding/json"
"fmt"
appmanager "cosmossdk.io/core/app"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/core/transaction"
)
// Store defines the underlying storage behavior needed by AppManager.
type Store interface {
// StateLatest returns a readonly view over the latest
// committed state of the store. Alongside the version
// associated with it.
StateLatest() (uint64, corestore.ReaderMap, error)
// StateAt returns a readonly view over the provided
// state. Must error when the version does not exist.
StateAt(version uint64) (corestore.ReaderMap, error)
}
// AppManager is a coordinator for all things related to an application
type AppManager[T transaction.Tx] struct {
config Config
db Store
initGenesis InitGenesis
exportGenesis ExportGenesis
stf StateTransitionFunction[T]
}
func (a AppManager[T]) InitGenesis(
ctx context.Context,
blockRequest *appmanager.BlockRequest[T],
initGenesisJSON []byte,
txDecoder transaction.Codec[T],
) (*appmanager.BlockResponse, corestore.WriterMap, error) {
v, zeroState, err := a.db.StateLatest()
if err != nil {
return nil, nil, fmt.Errorf("unable to get latest state: %w", err)
}
if v != 0 { // TODO: genesis state may be > 0, we need to set version on store
return nil, nil, fmt.Errorf("cannot init genesis on non-zero state")
}
var genTxs []T
genesisState, err := a.stf.RunWithCtx(ctx, zeroState, func(ctx context.Context) error {
return a.initGenesis(ctx, bytes.NewBuffer(initGenesisJSON), func(jsonTx json.RawMessage) error {
genTx, err := txDecoder.DecodeJSON(jsonTx)
if err != nil {
return fmt.Errorf("failed to decode genesis transaction: %w", err)
}
genTxs = append(genTxs, genTx)
return nil
})
})
if err != nil {
return nil, nil, fmt.Errorf("failed to import genesis state: %w", err)
}
// run block
// TODO: in an ideal world, genesis state is simply an initial state being applied
// unaware of what that state means in relation to every other
blockRequest.Txs = genTxs
blockResponse, blockZeroState, err := a.stf.DeliverBlock(ctx, blockRequest, genesisState)
if err != nil {
return blockResponse, nil, fmt.Errorf("failed to deliver block %d: %w", blockRequest.Height, err)
}
// after executing block 0, we extract the changes and apply them to the genesis state.
blockZeroStateChanges, err := blockZeroState.GetStateChanges()
if err != nil {
return nil, nil, fmt.Errorf("failed to get block zero state changes: %w", err)
}
err = genesisState.ApplyStateChanges(blockZeroStateChanges)
if err != nil {
return nil, nil, fmt.Errorf("failed to apply block zero state changes to genesis state: %w", err)
}
return blockResponse, genesisState, err
// consensus server will need to set the version of the store
}
// ExportGenesis exports the genesis state of the application.
func (a AppManager[T]) ExportGenesis(ctx context.Context, version uint64) ([]byte, error) {
bz, err := a.exportGenesis(ctx, version)
if err != nil {
return nil, fmt.Errorf("failed to export genesis state: %w", err)
}
return bz, nil
}
func (a AppManager[T]) DeliverBlock(
ctx context.Context,
block *appmanager.BlockRequest[T],
) (*appmanager.BlockResponse, corestore.WriterMap, error) {
latestVersion, currentState, err := a.db.StateLatest()
if err != nil {
return nil, nil, fmt.Errorf("unable to create new state for height %d: %w", block.Height, err)
}
if latestVersion+1 != block.Height {
return nil, nil, fmt.Errorf("invalid DeliverBlock height wanted %d, got %d", latestVersion+1, block.Height)
}
blockResponse, newState, err := a.stf.DeliverBlock(ctx, block, currentState)
if err != nil {
return nil, nil, fmt.Errorf("block delivery failed: %w", err)
}
return blockResponse, newState, nil
}
// ValidateTx will validate the tx against the latest storage state. This means that
// only the stateful validation will be run, not the execution portion of the tx.
// If full execution is needed, Simulate must be used.
func (a AppManager[T]) ValidateTx(ctx context.Context, tx T) (appmanager.TxResult, error) {
_, latestState, err := a.db.StateLatest()
if err != nil {
return appmanager.TxResult{}, err
}
return a.stf.ValidateTx(ctx, latestState, a.config.ValidateTxGasLimit, tx), nil
}
// Simulate runs validation and execution flow of a Tx.
func (a AppManager[T]) Simulate(ctx context.Context, tx T) (appmanager.TxResult, corestore.WriterMap, error) {
_, state, err := a.db.StateLatest()
if err != nil {
return appmanager.TxResult{}, nil, err
}
result, cs := a.stf.Simulate(ctx, state, a.config.SimulationGasLimit, tx) // TODO: check if this is done in the antehandler
return result, cs, nil
}
// Query queries the application at the provided version.
// CONTRACT: Version must always be provided, if 0, get latest
func (a AppManager[T]) Query(ctx context.Context, version uint64, request transaction.Msg) (transaction.Msg, error) {
// if version is provided attempt to do a height query.
if version != 0 {
queryState, err := a.db.StateAt(version)
if err != nil {
return nil, err
}
return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request)
}
// otherwise rely on latest available state.
_, queryState, err := a.db.StateLatest()
if err != nil {
return nil, err
}
return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request)
}
// QueryWithState executes a query with the provided state. This allows to process a query
// independently of the db state. For example, it can be used to process a query with temporary
// and uncommitted state
func (a AppManager[T]) QueryWithState(
ctx context.Context,
state corestore.ReaderMap,
request transaction.Msg,
) (transaction.Msg, error) {
return a.stf.Query(ctx, state, a.config.QueryGasLimit, request)
}

View File

@ -1,40 +0,0 @@
package appmanager
import (
"cosmossdk.io/core/transaction"
)
// Builder is a struct that represents the application builder for managing transactions.
// It contains various fields and methods for initializing the application and handling transactions.
type Builder[T transaction.Tx] struct {
STF StateTransitionFunction[T] // The state transition function for processing transactions.
DB Store // The database for storing application data.
// Gas limits for validating, querying, and simulating transactions.
ValidateTxGasLimit uint64
QueryGasLimit uint64
SimulationGasLimit uint64
// InitGenesis is a function that initializes the application state from a genesis file.
// It takes a context, a source reader for the genesis file, and a transaction handler function.
InitGenesis InitGenesis
// ExportGenesis is a function that exports the application state to a genesis file.
// It takes a context and a version number for the genesis file.
ExportGenesis ExportGenesis
}
// Build creates a new instance of AppManager with the provided configuration and returns it.
// It initializes the AppManager with the given database, export state, import state, initGenesis function, and state transition function.
func (b Builder[T]) Build() (*AppManager[T], error) {
return &AppManager[T]{
config: Config{
ValidateTxGasLimit: b.ValidateTxGasLimit,
QueryGasLimit: b.QueryGasLimit,
SimulationGasLimit: b.SimulationGasLimit,
},
db: b.DB,
initGenesis: b.InitGenesis,
exportGenesis: b.ExportGenesis,
stf: b.STF,
}, nil
}

View File

@ -1,9 +0,0 @@
package appmanager
// Config represents the configuration options for the app manager.
// TODO: implement comments for toml
type Config struct {
ValidateTxGasLimit uint64 `mapstructure:"validate-tx-gas-limit"` // TODO: check how this works on app mempool
QueryGasLimit uint64 `mapstructure:"query-gas-limit"`
SimulationGasLimit uint64 `mapstructure:"simulation-gas-limit"`
}

View File

@ -1,14 +0,0 @@
package appmanager
import (
"context"
"encoding/json"
"io"
)
type (
// ExportGenesis is a function type that represents the export of the genesis state.
ExportGenesis func(ctx context.Context, version uint64) ([]byte, error)
// InitGenesis is a function type that represents the initialization of the genesis state.
InitGenesis func(ctx context.Context, src io.Reader, txHandler func(json.RawMessage) error) error
)

View File

@ -1,14 +0,0 @@
module cosmossdk.io/server/v2/appmanager
go 1.21
replace cosmossdk.io/core => ../../../core
require cosmossdk.io/core v0.12.0
require (
github.com/cosmos/gogoproto v1.5.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

View File

@ -1,10 +0,0 @@
github.com/cosmos/gogoproto v1.5.0 h1:SDVwzEqZDDBoslaeZg+dGE55hdzHfgUA40pEanMh52o=
github.com/cosmos/gogoproto v1.5.0/go.mod h1:iUM31aofn3ymidYG6bUR5ZFrk+Om8p5s754eMUcyp8I=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

Some files were not shown because too many files have changed in this diff Show More