From 2e17be598081fe6ecbff3c1a506b318439884986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Orkun=20K=C3=BCl=C3=A7e?= Date: Tue, 15 Nov 2022 13:18:19 +0300 Subject: [PATCH] Implement code info validation in gov_tx --- go.mod | 4 +- go.sum | 6 ++- x/wasm/client/cli/gov_tx.go | 69 ++++++++++++++++++++++---------- x/wasm/client/cli/gov_tx_test.go | 61 ++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 216c2e57e7..0b5b9461d0 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cosmos/iavl v0.19.3 github.com/cosmos/ibc-go/v3 v3.3.0 github.com/cosmos/interchain-accounts v0.1.0 + github.com/docker/distribution v2.8.1+incompatible github.com/dvsekhvalnov/jose2go v1.5.0 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 @@ -82,7 +83,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87 // indirect github.com/improbable-eng/grpc-web v0.14.1 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.15.9 // indirect github.com/lib/pq v1.10.6 // indirect @@ -95,6 +96,7 @@ require ( github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index 235a94278b..dad9b993a6 100644 --- a/go.sum +++ b/go.sum @@ -212,6 +212,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= @@ -443,8 +445,9 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/improbable-eng/grpc-web v0.14.1 h1:NrN4PY71A6tAz2sKDvC5JCauENWp0ykG8Oq1H3cpFvw= github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -584,6 +587,7 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= diff --git a/x/wasm/client/cli/gov_tx.go b/x/wasm/client/cli/gov_tx.go index 38816f5887..f1401ea9d6 100644 --- a/x/wasm/client/cli/gov_tx.go +++ b/x/wasm/client/cli/gov_tx.go @@ -4,6 +4,8 @@ import ( "bytes" "crypto/sha256" "fmt" + "github.com/docker/distribution/reference" + "net/url" "strconv" "strings" @@ -15,6 +17,7 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/pkg/errors" "github.com/spf13/cobra" + flag "github.com/spf13/pflag" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -63,27 +66,9 @@ func ProposalStoreCodeCmd() *cobra.Command { return err } - source, err := cmd.Flags().GetString(flagSource) + source, builder, codeHash, err := parseCodeInfoFlags(src.WASMByteCode, cmd.Flags()) if err != nil { - return fmt.Errorf("source: %s", err) - } - if source == "" { - return fmt.Errorf("source: must not be empty") - } - builder, err := cmd.Flags().GetString(flagBuilder) - if err != nil { - return fmt.Errorf("builder: %s", err) - } - if builder == "" { - return fmt.Errorf("builder: must not be empty") - } - codeHash, err := cmd.Flags().GetBytesHex(flagCodeHash) - if err != nil { - return fmt.Errorf("code-hash: %s", err) - } - checksum := sha256.Sum256(src.WASMByteCode) - if !bytes.Equal(checksum[:], codeHash[:]) { - return fmt.Errorf("code-hash mismatch: %X, checksum: %X", codeHash, checksum) + return err } content := types.StoreCodeProposal{ Title: proposalTitle, @@ -115,7 +100,7 @@ func ProposalStoreCodeCmd() *cobra.Command { cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional") cmd.Flags().Bool(flagUnpinCode, false, "Unpin code on upload, optional") cmd.Flags().String(flagSource, "", "Source is a valid absolute HTTPS URI to the contract's source code,") - cmd.Flags().String(flagBuilder, "", "Builder is a valid docker image name with tag, such as \"cosmwasm/workspace-optimizer:0.6.2\"") + cmd.Flags().String(flagBuilder, "", "Builder is a valid docker image name with tag, such as \"cosmwasm/workspace-optimizer:0.12.9\"") cmd.Flags().BytesHex(flagCodeHash, []byte{}, "CodeHash is the sha256 hash of the wasm code") // proposal flags @@ -125,6 +110,48 @@ func ProposalStoreCodeCmd() *cobra.Command { return cmd } +func parseCodeInfoFlags(wasm []byte, flags *flag.FlagSet) (string, string, []byte, error) { + source, err := flags.GetString(flagSource) + if err != nil { + return "", "", []byte{}, fmt.Errorf("source: %s", err) + } + builder, err := flags.GetString(flagBuilder) + if err != nil { + return "", "", []byte{}, fmt.Errorf("builder: %s", err) + } + codeHash, err := flags.GetBytesHex(flagCodeHash) + if err != nil { + return "", "", []byte{}, fmt.Errorf("codeHash: %s", err) + } + + // if any set require others to be set + if len(source) != 0 || len(builder) != 0 || len(codeHash) != 0 { + if source == "" { + return "", "", []byte{}, fmt.Errorf("source is required") + } + if _, err = url.ParseRequestURI(source); err != nil { + return "", "", []byte{}, fmt.Errorf("source: %s", err) + } + if builder == "" { + return "", "", []byte{}, fmt.Errorf("builder is required") + } + if _, err := reference.ParseDockerRef(builder); err != nil { + return "", "", []byte{}, fmt.Errorf("builder: %s", err) + } + if len(codeHash) == 0 { + return "", "", []byte{}, fmt.Errorf("code hash is required") + } + // wasm is unzipped in parseStoreCodeArgs + // checksum generation will be decoupled here + // reference https://github.com/CosmWasm/wasmvm/issues/359 + checksum := sha256.Sum256(wasm) + if !bytes.Equal(checksum[:], codeHash[:]) { + return "", "", []byte{}, fmt.Errorf("code-hash mismatch: %X, checksum: %X", codeHash, checksum) + } + } + return source, builder, codeHash, nil +} + func ProposalInstantiateContractCmd() *cobra.Command { cmd := &cobra.Command{ Use: "instantiate-contract [code_id_int64] [json_encoded_init_args] --label [text] --title [text] --description [text] --run-as [address] --admin [address,optional] --amount [coins,optional]", diff --git a/x/wasm/client/cli/gov_tx_test.go b/x/wasm/client/cli/gov_tx_test.go index 7c8600e07f..f5d227788f 100644 --- a/x/wasm/client/cli/gov_tx_test.go +++ b/x/wasm/client/cli/gov_tx_test.go @@ -1,6 +1,7 @@ package cli import ( + "os" "testing" "github.com/stretchr/testify/assert" @@ -95,3 +96,63 @@ func TestParseAccessConfigUpdates(t *testing.T) { }) } } + +func TestParseCodeInfoFlags(t *testing.T) { + correctSource := "https://github.com/CosmWasm/wasmd/blob/main/x/wasm/keeper/testdata/hackatom.wasm" + correctBuilderRef := "cosmwasm/workspace-optimizer:0.12.9" + + wasmBin, err := os.ReadFile("../../keeper/testdata/hackatom.wasm") + require.NoError(t, err) + + checksumStr := "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5" + + specs := map[string]struct { + args []string + expErr bool + }{ + "source missing": { + args: []string{"--builder=" + correctBuilderRef, "--code-hash=" + checksumStr}, + expErr: true, + }, + "builder missing": { + args: []string{"--code-source-url=" + correctSource, "--code-hash=" + checksumStr}, + expErr: true, + }, + "code hash missing": { + args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef}, + expErr: true, + }, + "source format wrong": { + args: []string{"--code-source-url=" + "format_wrong", "--builder=" + correctBuilderRef, "--code-hash=" + checksumStr}, + expErr: true, + }, + "builder format wrong": { + args: []string{"--code-source-url=" + correctSource, "--builder=" + "format//", "--code-hash=" + checksumStr}, + expErr: true, + }, + "code hash wrong": { + args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef, "--code-hash=" + "AA"}, + expErr: true, + }, + "happy path, none set": { + args: []string{}, + expErr: false, + }, + "happy path all set": { + args: []string{"--code-source-url=" + correctSource, "--builder=" + correctBuilderRef, "--code-hash=" + checksumStr}, + expErr: false, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + flags := ProposalStoreCodeCmd().Flags() + require.NoError(t, flags.Parse(spec.args)) + _, _, _, gotErr := parseCodeInfoFlags(wasmBin, flags) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + }) + } +}