Skip to content

Commit

Permalink
Merge pull request #208 from CosmWasm/history_store_196
Browse files Browse the repository at this point in the history
Use prefix store for contract history
  • Loading branch information
ethanfrey authored Jul 21, 2020
2 parents 5c5e0f2 + ad520ed commit 1f701b8
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 167 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) [\#196](https://github.com/CosmWasm/wasmd/issues/196) Move history of contract code migrations to their own prefix store
* (wasmd) [\#130](https://github.com/CosmWasm/wasmd/issues/130) Full history of contract code migrations
* (wasmd) [\#187](https://github.com/CosmWasm/wasmd/issues/187) Introduce wasmgovd binary
* (wasmd) [\#178](https://github.com/CosmWasm/wasmd/issues/178) Add cli support for wasm gov proposals
Expand Down
36 changes: 36 additions & 0 deletions api_migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Changes to the api

## [\#196](https://github.com/CosmWasm/wasmd/issues/196) - Move history of contract code migrations to their own prefix store

The `ContractDetails.initMsg` used in cosmJs was moved into a new entity `ContractCodeHistoryEntry`. They contain code updates to a contract.

### Route
This data is available via a new route `/wasm/contract/{contractAddr}/history`

### Response
A list of ContractCodeHistoryEntries with following fields:
* `operation` can be any of `"Init", "Migrate", "Genesis"`
* `code_id` uint64
* `msg` as raw json

### Errors
* 404 - for an unknown contract

### CLI
`wasmcli query wasm contract-history [bech32_address] to print all the code changes.`
Example:
`wasmcli query wasm contract-history cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c`
```json
[
{
"operation": "Init",
"code_id": 1,
"msg": "\"init-msg\""
},
{
"operation": "Migrate",
"code_id": 2,
"msg": "\"migrate-msg\""
}
]
```
27 changes: 27 additions & 0 deletions x/wasm/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command {
GetCmdListContractByCode(cdc),
GetCmdQueryCode(cdc),
GetCmdGetContractInfo(cdc),
GetCmdGetContractHistory(cdc),
GetCmdGetContractState(cdc),
)...)
return queryCmd
Expand Down Expand Up @@ -267,6 +268,32 @@ func GetCmdGetContractStateSmart(cdc *codec.Codec) *cobra.Command {
return cmd
}

// GetCmdGetContractHistory prints the code history for a given contract
func GetCmdGetContractHistory(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "contract-history [bech32_address]",
Short: "Prints out the code history for a contract given its address",
Long: "Prints out the code history for a contract given its address",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)

addr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}

route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String())
res, _, err := cliCtx.Query(route)
if err != nil {
return err
}
fmt.Println(string(res))
return nil
},
}
}

type argumentDecoder struct {
// dec is the default decoder
dec func(string) ([]byte, error)
Expand Down
26 changes: 24 additions & 2 deletions x/wasm/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/wasm/code/{codeID}/contracts", listContractsByCodeHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}", queryContractHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/state", queryContractStateAllHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/history", queryContractHistoryFn(cliCtx)).Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/smart/{query}", queryContractStateSmartHandlerFn(cliCtx)).Queries("encoding", "{encoding}").Methods("GET")
r.HandleFunc("/wasm/contract/{contractAddr}/raw/{key}", queryContractStateRawHandlerFn(cliCtx)).Queries("encoding", "{encoding}").Methods("GET")
}
Expand Down Expand Up @@ -153,7 +154,6 @@ func queryContractStateAllHandlerFn(cliCtx context.CLIContext) http.HandlerFunc
rest.PostProcessResponse(w, cliCtx, resultData)
}
}

func queryContractStateRawHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
decoder := newArgDecoder(hex.DecodeString)
Expand Down Expand Up @@ -230,6 +230,29 @@ func queryContractStateSmartHandlerFn(cliCtx context.CLIContext) http.HandlerFun
}
}

func queryContractHistoryFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["contractAddr"])
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

route := fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, keeper.QueryContractHistory, addr.String())
res, height, err := cliCtx.Query(route)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, json.RawMessage(res))
}
}

type argumentDecoder struct {
// dec is the default decoder
dec func(string) ([]byte, error)
Expand All @@ -241,7 +264,6 @@ func newArgDecoder(def func(string) ([]byte, error)) *argumentDecoder {
}

func (a *argumentDecoder) DecodeString(s string) ([]byte, error) {

switch a.encoding {
case "hex":
return hex.DecodeString(s)
Expand Down
1 change: 0 additions & 1 deletion x/wasm/internal/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
}
// redact contract info
contract.Created = nil
contract.ContractCodeHistory = nil

genState.Contracts = append(genState.Contracts, types.Contract{
ContractAddress: addr,
Expand Down
75 changes: 20 additions & 55 deletions x/wasm/internal/keeper/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json"
Expand Down Expand Up @@ -39,15 +40,18 @@ func TestGenesisExportImport(t *testing.T) {
codeInfo types.CodeInfo
contract types.ContractInfo
stateModels []types.Model
history []types.ContractCodeHistoryEntry
)
f.Fuzz(&codeInfo)
f.Fuzz(&contract)
f.Fuzz(&stateModels)
f.NilChance(0).Fuzz(&history)
codeID, err := srcKeeper.Create(srcCtx, codeInfo.Creator, wasmCode, codeInfo.Source, codeInfo.Builder, &codeInfo.InstantiateConfig)
require.NoError(t, err)
contract.CodeID = codeID
contractAddr := srcKeeper.generateContractAddress(srcCtx, codeID)
srcKeeper.setContractInfo(srcCtx, contractAddr, &contract)
srcKeeper.appendToContractHistory(srcCtx, contractAddr, history...)
srcKeeper.importContractState(srcCtx, contractAddr, stateModels)
}
var wasmParams types.Params
Expand Down Expand Up @@ -93,11 +97,17 @@ func TestGenesisExportImport(t *testing.T) {
for i := 0; srcIT.Valid(); i++ {
require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %s", srcStoreKeys[j].Name(), srcIT.Key())
require.Equal(t, srcIT.Key(), dstIT.Key(), i)
require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %s", srcStoreKeys[j].Name(), i, srcIT.Key())

isContractHistory := srcStoreKeys[j].Name() == types.StoreKey && bytes.HasPrefix(srcIT.Key(), types.ContractHistoryStorePrefix)
if !isContractHistory { // only skip history entries because we know they are different
require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", srcStoreKeys[j].Name(), i, srcIT.Key())
}
srcIT.Next()
dstIT.Next()
}
require.False(t, dstIT.Valid())
if !assert.False(t, dstIT.Valid()) {
t.Fatalf("dest Iterator still has key :%X", dstIT.Key())
}
}
}

Expand Down Expand Up @@ -348,53 +358,6 @@ func TestFailFastImport(t *testing.T) {
}
}

func TestExportShouldNotContainContractCodeHistory(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

wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
var (
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator = createFakeFundedAccount(ctx, accKeeper, deposit)
anyAddr = make([]byte, sdk.AddrLen)
)

firstCodeID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", &types.AllowEverybody)
require.NoError(t, err)
secondCodeID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", &types.AllowEverybody)
require.NoError(t, err)
initMsg := InitMsg{
Verifier: anyAddr,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)

// create instance
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
contractAddr, err := keeper.Instantiate(ctx, firstCodeID, creator, creator, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)

// and migrate to second code id
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
_, err = keeper.Migrate(ctx, contractAddr, creator, secondCodeID, initMsgBz)
require.NoError(t, err)
// and contract contains 2 history elements
contractInfo := keeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, contractInfo)
require.Len(t, contractInfo.ContractCodeHistory, 2)
// when exported
state := ExportGenesis(ctx, keeper)
require.NoError(t, state.ValidateBasic())
require.Len(t, state.Contracts, 1)
assert.Len(t, state.Contracts[0].ContractInfo.ContractCodeHistory, 0)
assert.Nil(t, state.Contracts[0].ContractInfo.Created)
}

func TestImportContractWithCodeHistoryReset(t *testing.T) {
genesis := `
{
Expand Down Expand Up @@ -482,14 +445,16 @@ func TestImportContractWithCodeHistoryReset(t *testing.T) {
Admin: adminAddr,
Label: "ȀĴnZV芢毤",
Created: &types.AbsoluteTxPosition{BlockHeight: 0, TxIndex: 0},
ContractCodeHistory: []types.ContractCodeHistoryEntry{{
Operation: types.GenesisContractCodeHistoryType,
CodeID: 1,
Updated: types.NewAbsoluteTxPosition(ctx),
},
},
}
assert.Equal(t, expContractInfo, *gotContractInfo)

expHistory := []types.ContractCodeHistoryEntry{{
Operation: types.GenesisContractCodeHistoryType,
CodeID: 1,
Updated: types.NewAbsoluteTxPosition(ctx),
},
}
assert.Equal(t, expHistory, keeper.GetContractHistory(ctx, contractAddr))
}

func setupKeeper(t *testing.T) (Keeper, sdk.Context, []sdk.StoreKey, func()) {
Expand Down
36 changes: 27 additions & 9 deletions x/wasm/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A

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

k.appendToContractHistory(ctx, contractAddress, instance.InitialHistory(initMsg))
return contractAddress, nil
}

Expand Down Expand Up @@ -341,7 +341,8 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
events := types.ParseEvents(res.Log, contractAddress)
ctx.EventManager().EmitEvents(events)

contractInfo.AddMigration(ctx, newCodeID, msg)
historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg)
k.appendToContractHistory(ctx, contractAddress, historyEntry)
k.setContractInfo(ctx, contractAddress, contractInfo)

if err := k.dispatchMessages(ctx, contractAddress, res.Messages); err != nil {
Expand Down Expand Up @@ -376,6 +377,22 @@ func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAd
return nil
}

func (k Keeper) appendToContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress, newEntries ...types.ContractCodeHistoryEntry) {
entries := append(k.GetContractHistory(ctx, contractAddr), newEntries...)
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractHistoryStorePrefix)
prefixStore.Set(contractAddr, k.cdc.MustMarshalBinaryBare(&entries))
}

func (k Keeper) GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractHistoryStorePrefix)
var entries []types.ContractCodeHistoryEntry
bz := prefixStore.Get(contractAddr)
if bz != nil {
k.cdc.MustUnmarshalBinaryBare(bz, &entries)
}
return entries
}

// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: query")
Expand Down Expand Up @@ -613,17 +630,18 @@ func (k Keeper) importAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uin
return nil
}

func (k Keeper) importContract(ctx sdk.Context, address sdk.AccAddress, c *types.ContractInfo, state []types.Model) error {
func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *types.ContractInfo, state []types.Model) error {
if !k.containsCodeInfo(ctx, c.CodeID) {
return errors.Wrapf(types.ErrNotFound, "code id: %d", c.CodeID)
}
if k.containsContractInfo(ctx, address) {
return errors.Wrapf(types.ErrDuplicate, "contract: %s", address)
if k.containsContractInfo(ctx, contractAddr) {
return errors.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr)
}

c.ResetFromGenesis(ctx)
k.setContractInfo(ctx, address, c)
return k.importContractState(ctx, address, state)
historyEntry := c.ResetFromGenesis(ctx)
k.appendToContractHistory(ctx, contractAddr, historyEntry)
k.setContractInfo(ctx, contractAddr, c)
return k.importContractState(ctx, contractAddr, state)
}

func addrFromUint64(id uint64) sdk.AccAddress {
Expand Down
Loading

0 comments on commit 1f701b8

Please sign in to comment.