diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f425cac0..c44522070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#1712](https://github.com/NibiruChain/nibiru/pull/1712) - refactor(inflation): turn inflation off by default * [#1713](https://github.com/NibiruChain/nibiru/pull/1713) - chore(build-release): use new rocksdb libraries and link required libs * [#1715](https://github.com/NibiruChain/nibiru/pull/1715) - fix(build): revert! to working build + cherry-picks +* [#1731](https://github.com/NibiruChain/nibiru/pull/1731) - feat(cli): add cli command to decode stargate base64 messages #### Dapp modules: perp, spot, etc @@ -82,6 +83,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Dependencies - Bump `google.golang.org/grpc` from 1.59.0 to 1.60.0 ([#1720](https://github.com/NibiruChain/nibiru/pull/1720)) - Bump `golang.org/x/crypto` from 0.15.0 to 0.17.0 ([#1724](https://github.com/NibiruChain/nibiru/pull/1724)) +- Bump `github.com/holiman/uint256` from 1.2.3 to 1.2.4 ([#1730](https://github.com/NibiruChain/nibiru/pull/1730)) +- Bump `github.com/dvsekhvalnov/jose2go` from 1.5.0 to 1.6.0 ([#1733](https://github.com/NibiruChain/nibiru/pull/1733)) * Bump `github.com/spf13/cast` from 1.5.1 to 1.6.0 ([#1689](https://github.com/NibiruChain/nibiru/pull/1689)) * Bump `cosmossdk.io/math` from 1.1.2 to 1.2.0 ([#1676](https://github.com/NibiruChain/nibiru/pull/1676)) diff --git a/CHAOSNET.md b/CHAOSNET.md index f6c75b97c..862858136 100644 --- a/CHAOSNET.md +++ b/CHAOSNET.md @@ -4,10 +4,12 @@ - [What is Chaosnet?](#what-is-chaosnet) - [How to run "chaosnet"](#how-to-run-chaosnet) - [How to force pull images from the registry](#how-to-force-pull-images-from-the-registry) + - [IBC Commands](#ibc-commands) - [Endpoints](#endpoints) - [FAQ](#faq) - [`make chaosnet` says that "Additional property name is not allowed"](#make-chaosnet-says-that-additional-property-name-is-not-allowed) - [Does data persist between runs?](#does-data-persist-between-runs) + - [My `make chaosnet` takes forever to run](#my-make-chaosnet-takes-forever-to-run) ## What is Chaosnet? @@ -36,6 +38,8 @@ Enter your GitHub username for the `username` field, and your personal access to 4. Run `make chaosnet` +Note that this will take a while the first time you run it, as it will need to pull all the images from the registry, build the chaonset image locally, and set up the IBC channel (which has a lot of round trip packet commits). + ## How to force pull images from the registry By default, most images (heart-monitor, liquidator, etc.) are cached locally and won't re-fetch from upstream registries. To force a pull, you can run @@ -44,13 +48,185 @@ By default, most images (heart-monitor, liquidator, etc.) are cached locally and make chaosnet-build ``` +## IBC Commands + +To send an IBC transfer from nibiru-0 to nibiru-1, run: + +1. SSH into nibiru-0 + + ```sh + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru-0 /bin/ash + ``` + +2. Transfer tokens from nibiru-0 to nibiru-1 + + ```sh + nibid tx ibc-transfer transfer transfer \ + channel-0 \ + nibi18mxturdh0mjw032c3zslgkw63cukkl4q5skk8g \ + 1000000unibi \ + --from validator \ + --fees 5000unibi \ + --yes | jq + ``` + +3. In a new shell, SSH into nibiru-1 + + ```sh + docker compose -f ./contrib/docker-compose/docker-compose-chaosnet.yml exec -it nibiru-1 /bin/ash + ``` + +4. Query the balance of nibiru-1 + + ```sh + # set the config since nibiru-1 has different ports + nibid config node "http://localhost:36657" + + nibid q bank balances $(nibid keys show validator -a) | jq + ``` + + Output: + + ```json + { + "balances": [ + { + "denom": "ibc/9BEE732637B12723D26E365D19CCB624587CE6254799EEE7C5F77B587BD677B0", + "amount": "1000000" + }, + { + "denom": "unibi", + "amount": "9999100000000" + } + ], + "pagination": { + "next_key": null, + "total": "0" + } + } + ``` + +5. Send tokens from nibiru-1 to nibiru-0 + + ```sh + nibid tx ibc-transfer transfer transfer \ + channel-0 \ + nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl \ + 5555unibi \ + --from validator \ + --fees 5000unibi \ + --yes | jq + ``` + +6. Go back to the nibiru-0 and query the balance + + ```sh + nibid q bank balances $(nibid keys show validator -a) | jq + ``` + + Output: + + ```json + { + "balances": [ + { + "denom": "ibc/9BEE732637B12723D26E365D19CCB624587CE6254799EEE7C5F77B587BD677B0", + "amount": "5555" + }, + { + "denom": "unibi", + "amount": "9999098995000" + } + ], + "pagination": { + "next_key": null, + "total": "0" + } + } + ``` + +7. Send IBC tokens back to nibiru-1 + + ```sh + nibid tx ibc-transfer transfer transfer \ + channel-0 \ + nibi18mxturdh0mjw032c3zslgkw63cukkl4q5skk8g \ + 5555ibc/9BEE732637B12723D26E365D19CCB624587CE6254799EEE7C5F77B587BD677B0 \ + --from validator \ + --fees 5000unibi \ + --yes | jq + ``` + +8. Verify tokens are sent + + ```sh + nibid q bank balances $(nibid keys show validator -a) | jq + ``` + + Output: + + ```json + { + "balances": [ + { + "denom": "unibi", + "amount": "9999098990000" + } + ], + "pagination": { + "next_key": null, + "total": "0" + } + } + ``` + +9. Back in the nibiru-1 shell, send tokens back to nibiru-0 + + ```sh + nibid tx ibc-transfer transfer transfer \ + channel-0 \ + nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl \ + 1000000ibc/9BEE732637B12723D26E365D19CCB624587CE6254799EEE7C5F77B587BD677B0 \ + --from validator \ + --fees 5000unibi \ + --yes | jq + ``` + +10. Verify tokens are sent + + ```sh + nibid q bank balances $(nibid keys show validator -a) | jq + ``` + + Output: + + ```json + { + "balances": [ + { + "denom": "unibi", + "amount": "9999099990000" + } + ], + "pagination": { + "next_key": null, + "total": "0" + } + } + ``` + ## Endpoints - `http://localhost:5555` -> GraphQL server -- `http://localhost:26657` -> Tendermint RPC server -- `tcp://localhost:9090` -> Cosmos SDK gRPC server -- `http://localhost:1317` -> Cosmos SDK LCD (REST) server - `http://localhost:8000` -> Faucet server (HTTP POST only) +- +- `http://localhost:26657` -> nibiru-0 Tendermint RPC server +- `tcp://localhost:9090` -> nibiru-0 Cosmos SDK gRPC server +- `http://localhost:1317` -> nibiru-0 Cosmos SDK LCD (REST) server +- +- `http://localhost:36657` -> nibiru-1 Tendermint RPC server +- `tcp://localhost:19090` -> nibiru-1 Cosmos SDK gRPC server +- `http://localhost:11317` -> nibiru-1 Cosmos SDK LCD (REST) server ## FAQ @@ -61,3 +237,7 @@ Make sure to update your docker application to version >=23.0.1 ### Does data persist between runs? No, all volumes are deleted and recreated every time you run `make chaosnet`. This is to ensure that you always start with a clean network. + +### My `make chaosnet` takes forever to run + +It usually takes a few minutes to set everything up and create the IBC channels. If it takes more than 5 minutes, then check the logs of the chaosnet containers to see if any step failed. Reach out to for help. diff --git a/cmd/nibid/cmd/decode_base64.go b/cmd/nibid/cmd/decode_base64.go new file mode 100644 index 000000000..968f07679 --- /dev/null +++ b/cmd/nibid/cmd/decode_base64.go @@ -0,0 +1,126 @@ +package cmd + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + sdkcodec "github.com/cosmos/cosmos-sdk/codec" + + wasmvm "github.com/CosmWasm/wasmvm/types" +) + +// YieldStargateMsgs parses the JSON and sends wasmvm.StargateMsg objects to a channel +func YieldStargateMsgs(jsonBz []byte) ([]wasmvm.StargateMsg, error) { + var data interface{} + if err := json.Unmarshal(jsonBz, &data); err != nil { + return nil, err + } + + var msgs []wasmvm.StargateMsg + parseStargateMsgs(data, &msgs) + return msgs, nil +} + +func parseStargateMsgs(jsonData any, msgs *[]wasmvm.StargateMsg) { + switch v := jsonData.(type) { + case map[string]interface{}: + if typeURL, ok := v["type_url"].(string); ok { + if value, ok := v["value"].(string); ok { + *msgs = append(*msgs, wasmvm.StargateMsg{ + TypeURL: typeURL, + Value: []byte(value), + }) + } + } + for _, value := range v { + parseStargateMsgs(value, msgs) + } + case []interface{}: + for _, value := range v { + parseStargateMsgs(value, msgs) + } + } +} + +type StargateMsgDecoded struct { + TypeURL string `json:"type_url"` + Value string `json:"value"` +} + +func DecodeBase64StargateMsgs( + jsonBz []byte, context client.Context, +) (newSgMsgs []StargateMsgDecoded, err error) { + codec := context.Codec + + var data interface{} + if err := json.Unmarshal(jsonBz, &data); err != nil { + return []StargateMsgDecoded{}, err + } + + sgMsgs, err := YieldStargateMsgs(jsonBz) + if err != nil { + return + } + for _, sgMsg := range sgMsgs { + valueStr := string(sgMsg.Value) + value := strings.Replace(string(sgMsg.Value), `\"`, `"`, -1) + value = strings.Replace(value, `"{`, `{`, -1) + value = strings.Replace(value, `}"`, `}`, -1) + + if _, err := base64.StdEncoding.DecodeString(valueStr); err == nil { + protoMsg, err := context.InterfaceRegistry.Resolve(sgMsg.TypeURL) + if err != nil { + return newSgMsgs, err + } + + decodedBz, _ := base64.StdEncoding.Strict().DecodeString(string(sgMsg.Value)) + concrete := protoMsg.(sdkcodec.ProtoMarshaler) + + err = codec.Unmarshal(decodedBz, concrete) + if err != nil { + return newSgMsgs, err + } + + outBytes, err := codec.MarshalJSON(concrete) + if err != nil { + return newSgMsgs, err + } + + newSgMsgs = append(newSgMsgs, StargateMsgDecoded{sgMsg.TypeURL, string(outBytes)}) + } else if _, err := json.Marshal(value); err == nil { + newSgMsgs = append(newSgMsgs, StargateMsgDecoded{sgMsg.TypeURL, string(sgMsg.Value)}) + } else { + return newSgMsgs, fmt.Errorf( + "parse error: encountered wasmvm.StargateMsg with unexpected format: %s", sgMsg) + } + } + return newSgMsgs, nil +} + +// DecodeBase64Cmd creates a cobra command for base64 decoding. +func DecodeBase64Cmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "base64-decode", + Short: "Decode a base64-encoded protobuf message", + Long: `Decode a base64-encoded protobuf message from JSON input. +The input should be a JSON object with 'type_url' and 'value' fields.`, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + outMessage, err := DecodeBase64StargateMsgs([]byte(args[0]), clientCtx) + fmt.Println(outMessage) + + return err + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + + return cmd +} diff --git a/cmd/nibid/cmd/decode_base64_test.go b/cmd/nibid/cmd/decode_base64_test.go new file mode 100644 index 000000000..c5e3c1e4e --- /dev/null +++ b/cmd/nibid/cmd/decode_base64_test.go @@ -0,0 +1,120 @@ +package cmd_test + +import ( + "context" + "testing" + + "github.com/NibiruChain/nibiru/app" + + "github.com/cometbft/cometbft/libs/log" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + nibid "github.com/NibiruChain/nibiru/cmd/nibid/cmd" +) + +func TestBase64Decode(t *testing.T) { + type TestCase struct { + name string + json_message string + expectError bool + } + + executeTest := func(t *testing.T, testCase TestCase) { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + home := t.TempDir() + logger := log.NewNopLogger() + cfg, err := genutiltest.CreateDefaultTendermintConfig(home) + require.NoError(t, err) + + appCodec := app.MakeEncodingConfig().Marshaler + err = genutiltest.ExecInitCmd( + testModuleBasicManager, home, appCodec) + require.NoError(t, err) + + serverCtx := server.NewContext(viper.New(), cfg, logger) + clientCtx := (client.Context{}. + WithCodec(appCodec). + WithHomeDir(home). + WithInterfaceRegistry(app.MakeEncodingConfig().InterfaceRegistry)) + + ctx := context.Background() + ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) + ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) + + cmd := nibid.DecodeBase64Cmd(home) + cmd.SetArgs([]string{ + tc.json_message, + }) + + if tc.expectError { + require.Error(t, cmd.ExecuteContext(ctx)) + } else { + require.NoError(t, cmd.ExecuteContext(ctx)) + } + }) + } + + testCases := []TestCase{ + { + name: "valid message", + json_message: ` + { + "stargate": { + "type_url": "/cosmos.staking.v1beta1.MsgUndelegate", + "value": "Cj9uaWJpMTdwOXJ6d25uZnhjanAzMnVuOXVnN3loaHpndGtodmw5amZrc3p0Z3c1dWg2OXdhYzJwZ3N5bjcwbmoSMm5pYml2YWxvcGVyMXdqNWtma25qa3BjNmpkMzByeHRtOHRweGZqZjd4cWx3eDM4YzdwGgwKBXVuaWJpEgMxMTE=" + } + }`, + expectError: false, + }, + { + name: "valid message", + json_message: ` + { + "stargate": { + "type_url": "/cosmos.staking.v1beta1.MsgUndelegate", + "value": "Cj9uaWJpMTdwOXJ6d25uZnhjanAzMnVuOXVnN3loaHpndGtodmw5amZrc3p0Z3c1dWg2OXdhYzJwZ3N5bjcwbmoSMm5pYml2YWxvcGVyMXdqNWtma25qa3BjNmpkMzByeHRtOHRweGZqZjd4cWx3eDM4YzdwGgwKBXVuaWJpEgMxMTE=" + }, + "another": { + "type_url": "/cosmos.staking.v1beta1.MsgDelegate", + "value": {"delegator_address":"cosmos1eckjje8r8s48kv0pndgtwvehveedlzlnnshl3e", "validator_address":"cosmos1n6ndsc04xh2hqf506nhvhcggj0qwguf8ks06jj", "amount":{"denom":"unibi","amount":"42"} } + } + }`, + expectError: false, + }, + { + name: "valid message", + json_message: ` + { + "another": { + "type_url": "/cosmos.staking.v1beta1.MsgDelegate", + "value": "{\"delegator_address\":\"cosmos1eckjje8r8s48kv0pndgtwvehveedlzlnnshl3e\", \"validator_address\":\"cosmos1n6ndsc04xh2hqf506nhvhcggj0qwguf8ks06jj\", \"amount\":{\"denom\":\"unibi\",\"amount\":\"42\"} }" + } + }`, + expectError: false, + }, + { + name: "empty message", + json_message: ` + { + + }`, + expectError: false, + }, + { + name: "invalid json", + json_message: ` + + }`, + expectError: true, + }, + } + + for _, testCase := range testCases { + executeTest(t, testCase) + } +} diff --git a/cmd/nibid/cmd/root.go b/cmd/nibid/cmd/root.go index f4798e305..140c3d758 100644 --- a/cmd/nibid/cmd/root.go +++ b/cmd/nibid/cmd/root.go @@ -139,6 +139,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig app.EncodingConfig) { rootCmd.AddCommand( InitCmd(app.ModuleBasics, app.DefaultNodeHome), AddGenesisAccountCmd(app.DefaultNodeHome), + DecodeBase64Cmd(app.DefaultNodeHome), tmcli.NewCompletionCmd(rootCmd, true), testnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}), debug.Cmd(), diff --git a/go.mod b/go.mod index 437b15744..b76d57b30 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 - github.com/holiman/uint256 v1.2.3 + github.com/holiman/uint256 v1.2.4 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/rakyll/statik v0.1.7 @@ -85,7 +85,7 @@ require ( github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getsentry/sentry-go v0.23.0 // indirect diff --git a/go.sum b/go.sum index acd7b1beb..83f9f266b 100644 --- a/go.sum +++ b/go.sum @@ -477,8 +477,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= -github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= +github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -780,8 +780,8 @@ github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7H github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=