Skip to content

Commit

Permalink
Migrate contract backend functionality with go-cosmwasm stub impl (#122)
Browse files Browse the repository at this point in the history
* Start migration server side

* Return migration response and emit events

* Dispatch migrate contract messages

* Rebase to 0.9 and minor updates

* Review feedback

* Update changelog

* Add msg test
  • Loading branch information
alpe committed Jun 4, 2020
1 parent bc76bb0 commit 81d8560
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]

### Features
* (wasmd) [\#122](https://github.com/CosmWasm/wasmd/pull/122]) Migrate contract backend functionality with go-cosmwasm stub impl
* (wasmd)[\#2](https://github.com/cosmwasm/wasmd/pull/22) Improve wasm contract queries (all, raw, smart)
* (wasmd) [\#119](https://github.com/cosmwasm/wasmd/pull/119) Add support for the `--inter-block-cache` CLI
flag and configuration.
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ require (
github.com/tendermint/go-amino v0.15.1
github.com/tendermint/tendermint v0.33.3
github.com/tendermint/tm-db v0.5.1
go.etcd.io/bbolt v1.3.4 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
)

replace github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
github.com/CosmWasm/go-cosmwasm v0.0.0-20200603080847-883f85520aac h1:Em+R1ZTKgnIT6bYhEqk8/e1IXuZDXvs7FyuUktNzWtg=
github.com/CosmWasm/go-cosmwasm v0.0.0-20200603080847-883f85520aac/go.mod h1:gAFCwllx97ejI+m9SqJQrmd2SBW7HA0fOjvWWJjM2uc=
github.com/CosmWasm/go-cosmwasm v0.8.1-0.20200603124627-0af410d57fa1 h1:yTh6KEZXpVtjkhRQQSKOpS4YbRNIz7VFcace5czQkDw=
github.com/CosmWasm/go-cosmwasm v0.8.1-0.20200603124627-0af410d57fa1/go.mod h1:gAFCwllx97ejI+m9SqJQrmd2SBW7HA0fOjvWWJjM2uc=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
Expand Down Expand Up @@ -460,6 +458,8 @@ github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
Expand Down Expand Up @@ -537,6 +537,9 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtD
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down
1 change: 1 addition & 0 deletions x/wasm/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type (
MsgStoreCode = types.MsgStoreCode
MsgInstantiateContract = types.MsgInstantiateContract
MsgExecuteContract = types.MsgExecuteContract
MsgMigrateContract = types.MsgMigrateContract
Model = types.Model
CodeInfo = types.CodeInfo
ContractInfo = types.ContractInfo
Expand Down
24 changes: 23 additions & 1 deletion x/wasm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func NewHandler(k Keeper) sdk.Handler {
case *MsgExecuteContract:
return handleExecute(ctx, k, msg)

case *MsgMigrateContract:
return handleMigration(ctx, k, msg)
case MsgMigrateContract:
return handleMigration(ctx, k, &msg)

default:
errMsg := fmt.Sprintf("unrecognized wasm message type: %T", msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
Expand Down Expand Up @@ -80,7 +85,7 @@ func handleStoreCode(ctx sdk.Context, k Keeper, msg *MsgStoreCode) (*sdk.Result,
}

func handleInstantiate(ctx sdk.Context, k Keeper, msg *MsgInstantiateContract) (*sdk.Result, error) {
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.InitMsg, msg.Label, msg.InitFunds)
contractAddr, err := k.Instantiate(ctx, msg.Code, msg.Sender, msg.Admin, msg.InitMsg, msg.Label, msg.InitFunds)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -117,3 +122,20 @@ func handleExecute(ctx sdk.Context, k Keeper, msg *MsgExecuteContract) (*sdk.Res
res.Events = append(events, ourEvent)
return &res, nil
}

func handleMigration(ctx sdk.Context, k Keeper, msg *MsgMigrateContract) (*sdk.Result, error) {
res, err := k.Migrate(ctx, msg.Contract, msg.Sender, msg.Code, msg.MigrateMsg)
if err != nil {
return nil, err
}

events := filterMessageEvents(ctx.EventManager())
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
sdk.NewAttribute(AttributeSigner, msg.Sender.String()),
sdk.NewAttribute(AttributeKeyContract, msg.Contract.String()),
)
res.Events = append(events, ourEvent)
return res, nil
}
57 changes: 54 additions & 3 deletions x/wasm/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package keeper

import (
"encoding/binary"
"github.com/cosmos/cosmos-sdk/x/staking"
"path/filepath"

"github.com/cosmos/cosmos-sdk/x/staking"

wasm "github.com/CosmWasm/go-cosmwasm"
wasmTypes "github.com/CosmWasm/go-cosmwasm/types"
"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -92,7 +93,7 @@ func isSimulationMode(ctx sdk.Context) bool {
}

// Instantiate creates an instance of a WASM contract
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, error) {
// create contract address
contractAddress := k.generateContractAddress(ctx, codeID)
existingAcct := k.accountKeeper.GetAccount(ctx, contractAddress)
Expand Down Expand Up @@ -155,7 +156,7 @@ func (k Keeper) Instantiate(ctx sdk.Context, codeID uint64, creator sdk.AccAddre

// persist instance
createdAt := types.NewCreatedAt(ctx)
instance := types.NewContractInfo(codeID, creator, initMsg, label, createdAt)
instance := types.NewContractInfo(codeID, creator, admin, initMsg, label, createdAt)
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(instance))

return contractAddress, nil
Expand Down Expand Up @@ -204,6 +205,56 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
return value, nil
}

// Migrate allows to upgrade a contract to a new code with data migration.
func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte) (*sdk.Result, error) {
contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if contractInfo.Admin == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "migration not supported by this contract")
}
if !contractInfo.Admin.Equals(caller) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "no permission")
}
newCodeInfo := k.GetCodeInfo(ctx, newCodeID)
if newCodeInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown code")
}

var noDeposit sdk.Coins
params := types.NewEnv(ctx, caller, noDeposit, contractAddress)

// prepare querier
querier := QueryHandler{
Ctx: ctx,
Plugins: k.queryPlugins,
}

prefixStoreKey := types.GetContractStorePrefixKey(contractAddress)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)
gas := gasForContract(ctx)
res, gasUsed, err := k.wasmer.Migrate(newCodeInfo.CodeHash, params, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas)
consumeGas(ctx, gasUsed)
if err != nil {
return nil, sdkerrors.Wrap(types.ErrMigrationFailed, err.Error())
}

// emit all events from this contract migration itself
value := types.CosmosResult(*res, contractAddress)
ctx.EventManager().EmitEvents(value.Events)
value.Events = nil

contractInfo.UpdateCodeID(ctx, newCodeID)
k.setContractInfo(ctx, contractAddress, *contractInfo)

if err := k.dispatchMessages(ctx, contractAddress, res.Messages); err != nil {
return nil, sdkerrors.Wrap(err, "dispatch")
}

return &value, nil
}

// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit))
Expand Down
129 changes: 123 additions & 6 deletions x/wasm/internal/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"bytes"
"encoding/binary"
"encoding/json"
"io/ioutil"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
stypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -195,7 +197,7 @@ func TestInstantiate(t *testing.T) {
gasBefore := ctx.GasMeter().GasConsumed()

// create with no balance is also legal
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 1", nil)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())

Expand Down Expand Up @@ -228,7 +230,7 @@ func TestInstantiateWithNonExistingCodeID(t *testing.T) {
require.NoError(t, err)

const nonExistingCodeID = 9999
addr, err := keeper.Instantiate(ctx, nonExistingCodeID, creator, initMsgBz, "demo contract 2", nil)
addr, err := keeper.Instantiate(ctx, nonExistingCodeID, creator, nil, initMsgBz, "demo contract 2", nil)
require.True(t, types.ErrNotFound.Is(err), err)
require.Nil(t, addr)
}
Expand Down Expand Up @@ -259,7 +261,7 @@ func TestExecute(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 3", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())

Expand Down Expand Up @@ -353,7 +355,7 @@ func TestExecuteWithPanic(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 4", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 4", deposit)
require.NoError(t, err)

// let's make sure we get a reasonable error, no panic/crash
Expand Down Expand Up @@ -387,7 +389,7 @@ func TestExecuteWithCpuLoop(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 5", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 5", deposit)
require.NoError(t, err)

// make sure we set a limit before calling
Expand Down Expand Up @@ -429,7 +431,7 @@ func TestExecuteWithStorageLoop(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 6", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 6", deposit)
require.NoError(t, err)

// make sure we set a limit before calling
Expand All @@ -451,6 +453,121 @@ func TestExecuteWithStorageLoop(t *testing.T) {
require.True(t, false, "We must panic before this line")
}

func TestMigrate(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper

deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)

wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)

originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
newContractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
require.NotEqual(t, originalContractID, newContractID)

_, _, anyAddr := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
admin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
codeID uint64
migrateMsg []byte
expErr *sdkerrors.Error
}{
"all good with same code id": {
admin: creator,
caller: creator,
codeID: originalContractID,
},
"all good with new code id": {
admin: creator,
caller: creator,
codeID: newContractID,
},
"all good with admin set": {
admin: fred,
caller: fred,
codeID: newContractID,
},
"prevent migration when admin was not set on instantiate": {
caller: creator,
codeID: originalContractID,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent migration when not admin": {
caller: creator,
admin: fred,
codeID: originalContractID,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing code id": {
admin: creator,
caller: creator,
codeID: 99999,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail with non existing contract addr": {
admin: creator,
caller: creator,
overrideContractAddr: anyAddr,
codeID: originalContractID,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail when migration caused error": {
admin: creator,
caller: creator,
codeID: originalContractID,
migrateMsg: bytes.Repeat([]byte{0x1}, 7), // condition hard coded in stub: >6 = error
expErr: types.ErrMigrationFailed,
},
}
var (
builtIntoGoCosmWasmStubGas = sdk.Gas(10000)
builtIntoGoCosmWasmStubData = []byte(("my-migration-response-data"))
)
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
addr, err := keeper.Instantiate(ctx, originalContractID, creator, spec.admin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
}
gasBefore := ctx.GasMeter().GasConsumed()
res, err := keeper.Migrate(ctx, addr, spec.caller, spec.codeID, spec.migrateMsg)
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
gasAfter := ctx.GasMeter().GasConsumed()
assert.Greater(t, gasAfter-gasBefore, builtIntoGoCosmWasmStubGas/GasMultiplier)
assert.Equal(t, builtIntoGoCosmWasmStubData, res.Data)
cInfo := keeper.GetContractInfo(ctx, addr)
assert.Equal(t, spec.codeID, cInfo.CodeID)
assert.Equal(t, originalContractID, cInfo.PreviousCodeID)
assert.Equal(t, types.NewCreatedAt(ctx), cInfo.LastUpdated)
// TODO: check contract store was updated by migration code (impl also in contract)
// TODO: check any messages dispatched proper
// TODO: check events?
})
}
}

type InitMsg struct {
Verifier sdk.AccAddress `json:"verifier"`
Beneficiary sdk.AccAddress `json:"beneficiary"`
Expand Down
4 changes: 2 additions & 2 deletions x/wasm/internal/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestQueryContractState(t *testing.T) {
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract to query", deposit)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract to query", deposit)
require.NoError(t, err)

contractModel := []types.Model{
Expand Down Expand Up @@ -187,7 +187,7 @@ func TestListContractByCodeOrdering(t *testing.T) {
ctx = setBlock(ctx, h)
h++
}
_, err = keeper.Instantiate(ctx, codeID, creator, initMsgBz, fmt.Sprintf("contract %d", i), topUp)
_, err = keeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp)
require.NoError(t, err)
}

Expand Down
Loading

0 comments on commit 81d8560

Please sign in to comment.