Skip to content

Commit

Permalink
Merge pull request #497 from CosmWasm/list_contract_323
Browse files Browse the repository at this point in the history
Improve list contracts by code query
  • Loading branch information
ethanfrey authored Apr 27, 2021
2 parents 9ebeb85 + 8c7967e commit c67cf14
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 494 deletions.
2 changes: 1 addition & 1 deletion contrib/local/02-contracts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ wasmd tx wasm instantiate "$CODE_ID" "$INIT" --admin=$(wasmd keys show validator
--from validator --amount="100ustake" --label "local0.1.0" \
--gas 1000000 -y --chain-id=testing -b block | jq

CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" -o json | jq -r '.contract_infos[-1].address')
CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" -o json | jq -r '.contracts[-1]')
echo "* Contract address: $CONTRACT"
echo "### Query all"
RESP=$(wasmd query wasm contract-state all "$CONTRACT" -o json)
Expand Down
16 changes: 9 additions & 7 deletions contrib/local/03-grpc-queries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@ set -o errexit -o nounset -o pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

echo "-----------------------"
COSMOS_SDK_DIR=${COSMOS_SDK_DIR:-$(go list -f "{{ .Dir }}" -m github.com/cosmos/cosmos-sdk)}
PROTO_THRD="$DIR/../../third_party/proto"
PROTO_WASMD="$DIR/../../proto"
PROTO_WASMD_QUERY="$PROTO_WASMD/cosmwasm/wasm/v1beta1/query.proto"

echo "### List all codes"
RESP=$(grpcurl -plaintext -import-path $COSMOS_SDK_DIR/third_party/proto -import-path $COSMOS_SDK_DIR/proto -import-path . -proto ./x/wasm/types/query.proto \
RESP=$(grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
localhost:9090 cosmwasm.wasm.v1beta1.Query/Codes)
echo "$RESP" | jq

CODE_ID=$(echo "$RESP" | jq -r '.codeInfos[-1].codeId')
echo "### List contract by code"
RESP=$(grpcurl -plaintext -import-path $COSMOS_SDK_DIR/third_party/proto -import-path $COSMOS_SDK_DIR/proto -import-path . -proto ./x/wasm/types/query.proto \
echo "### List contracts by code"
RESP=$(grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
-d "{\"codeId\": $CODE_ID}" localhost:9090 cosmwasm.wasm.v1beta1.Query/ContractsByCode )
echo $RESP | jq

echo "### Show history for contract"
CONTRACT=$(echo $RESP | jq -r ".contractInfos[-1].address")
grpcurl -plaintext -import-path $COSMOS_SDK_DIR/third_party/proto -import-path $COSMOS_SDK_DIR/proto -import-path . -proto ./x/wasm/types/query.proto \
CONTRACT=$(echo $RESP | jq -r ".contracts[-1]")
grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
-d "{\"address\": \"$CONTRACT\"}" localhost:9090 cosmwasm.wasm.v1beta1.Query/ContractHistory | jq

echo "### Show contract state"
grpcurl -plaintext -import-path $COSMOS_SDK_DIR/third_party/proto -import-path $COSMOS_SDK_DIR/proto -import-path . -proto ./x/wasm/types/query.proto \
grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
-d "{\"address\": \"$CONTRACT\"}" localhost:9090 cosmwasm.wasm.v1beta1.Query/AllContractState | jq

echo "Empty state due to 'burner' contract cleanup"
20 changes: 1 addition & 19 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@

- [cosmwasm/wasm/v1beta1/query.proto](#cosmwasm/wasm/v1beta1/query.proto)
- [CodeInfoResponse](#cosmwasm.wasm.v1beta1.CodeInfoResponse)
- [ContractInfoWithAddress](#cosmwasm.wasm.v1beta1.ContractInfoWithAddress)
- [QueryAllContractStateRequest](#cosmwasm.wasm.v1beta1.QueryAllContractStateRequest)
- [QueryAllContractStateResponse](#cosmwasm.wasm.v1beta1.QueryAllContractStateResponse)
- [QueryCodeRequest](#cosmwasm.wasm.v1beta1.QueryCodeRequest)
Expand Down Expand Up @@ -814,23 +813,6 @@ CodeInfoResponse contains code meta data from CodeInfo



<a name="cosmwasm.wasm.v1beta1.ContractInfoWithAddress"></a>

### ContractInfoWithAddress
ContractInfoWithAddress adds the address (key) to the ContractInfo
representation


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `address` | [string](#string) | | |
| `contract_info` | [ContractInfo](#cosmwasm.wasm.v1beta1.ContractInfo) | | |






<a name="cosmwasm.wasm.v1beta1.QueryAllContractStateRequest"></a>

### QueryAllContractStateRequest
Expand Down Expand Up @@ -1020,7 +1002,7 @@ Query/ContractsByCode RPC method

| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `contract_infos` | [ContractInfoWithAddress](#cosmwasm.wasm.v1beta1.ContractInfoWithAddress) | repeated | |
| `contracts` | [string](#string) | repeated | contracts are a set of contract addresses |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines the pagination in the response. |


Expand Down
17 changes: 3 additions & 14 deletions proto/cosmwasm/wasm/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,12 @@ message QueryContractsByCodeRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// ContractInfoWithAddress adds the address (key) to the ContractInfo
// representation
message ContractInfoWithAddress {
option (gogoproto.equal) = true;

string address = 1;
ContractInfo contract_info = 2 [
(gogoproto.embed) = true,
(gogoproto.nullable) = false,
(gogoproto.jsontag) = ""
];
}
// QueryContractsByCodeResponse is the response type for the
// Query/ContractsByCode RPC method
message QueryContractsByCodeResponse {
repeated ContractInfoWithAddress contract_infos = 1
[ (gogoproto.nullable) = false ];
// contracts are a set of contract addresses
repeated string contracts = 1;

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
Expand Down
1 change: 0 additions & 1 deletion x/wasm/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ type (
ContractInfo = types.ContractInfo
CreatedAt = types.AbsoluteTxPosition
Config = types.WasmConfig
ContractInfoWithAddress = types.ContractInfoWithAddress
CodeInfoResponse = types.CodeInfoResponse
MessageHandler = keeper.SDKMessageHandler
BankEncoder = keeper.BankEncoder
Expand Down
46 changes: 22 additions & 24 deletions x/wasm/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ import (
"encoding/base64"
"errors"
"fmt"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"io/ioutil"
"math/rand"
"os"
"testing"
"time"

"github.com/CosmWasm/wasmd/x/wasm/types"
wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
Expand All @@ -30,6 +25,11 @@ import (
"github.com/tendermint/tendermint/proto/tendermint/crypto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
"io/ioutil"
"math/rand"
"os"
"testing"
"time"
)

const firstCodeID = 1
Expand Down Expand Up @@ -103,19 +103,27 @@ func TestGenesisExportImport(t *testing.T) {
exportedGenesis, err := wasmKeeper.cdc.MarshalJSON(exportedState)
require.NoError(t, err)

// reset ContractInfo in source DB for comparison with dest DB
// setup new instances
dstKeeper, dstCtx, dstStoreKeys := setupKeeper(t)

// reset contract code index in source DB for comparison with dest DB
wasmKeeper.IterateContractInfo(srcCtx, func(address sdk.AccAddress, info wasmTypes.ContractInfo) bool {
wasmKeeper.deleteContractSecondIndex(srcCtx, address, &info)
info.ResetFromGenesis(srcCtx)
wasmKeeper.storeContractInfo(srcCtx, address, &info)
wasmKeeper.removeFromContractCodeSecondaryIndex(srcCtx, address, wasmKeeper.getLastContractHistoryEntry(srcCtx, address))
prefixStore := prefix.NewStore(srcCtx.KVStore(wasmKeeper.storeKey), types.GetContractCodeHistoryElementPrefix(address))
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
prefixStore.Delete(iter.Key())
}
x := &info
newHistory := x.ResetFromGenesis(dstCtx)
wasmKeeper.storeContractInfo(srcCtx, address, x)
wasmKeeper.addToContractCodeSecondaryIndex(srcCtx, address, newHistory)
wasmKeeper.appendToContractHistory(srcCtx, address, newHistory)
return false
})

// re-import
dstKeeper, dstCtx, dstStoreKeys := setupKeeper(t)

var importState wasmTypes.GenesisState
err = wasmKeeper.cdc.UnmarshalJSON(exportedGenesis, &importState)
err = dstKeeper.cdc.UnmarshalJSON(exportedGenesis, &importState)
require.NoError(t, err)
InitGenesis(dstCtx, dstKeeper, importState, &StakingKeeperMock{}, TestHandler(contractKeeper))

Expand All @@ -125,16 +133,6 @@ func TestGenesisExportImport(t *testing.T) {
dstIT := dstCtx.KVStore(dstStoreKeys[j]).Iterator(nil, nil)

for i := 0; srcIT.Valid(); i++ {
isContractHistory := srcStoreKeys[j].Name() == types.StoreKey && bytes.HasPrefix(srcIT.Key(), types.ContractCodeHistoryElementPrefix)
if isContractHistory {
// only skip history entries because we know they are different
// from genesis they are merged into 1 single entry
srcIT.Next()
if bytes.HasPrefix(dstIT.Key(), types.ContractCodeHistoryElementPrefix) {
dstIT.Next()
}
continue
}
require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %x", srcStoreKeys[j].Name(), srcIT.Key())
require.Equal(t, srcIT.Key(), dstIT.Key(), i)
require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", srcStoreKeys[j].Name(), i, srcIT.Key())
Expand Down
45 changes: 40 additions & 5 deletions x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,10 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
}

// store contract before dispatch so that contract could be called back
historyEntry := contractInfo.InitialHistory(initMsg)
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
k.appendToContractHistory(ctx, contractAddress, historyEntry)
k.storeContractInfo(ctx, contractAddress, &contractInfo)
k.appendToContractHistory(ctx, contractAddress, contractInfo.InitialHistory(initMsg))

// dispatch submessages then messages
err = k.dispatchAll(ctx, contractAddress, contractInfo.IBCPortID, res.Submessages, res.Messages)
Expand Down Expand Up @@ -408,10 +410,11 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
ctx.EventManager().EmitEvents(events)

// delete old secondary index entry
k.deleteContractSecondIndex(ctx, contractAddress, contractInfo)
k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress))
// persist migration updates
historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg)
k.appendToContractHistory(ctx, contractAddress, historyEntry)
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
k.storeContractInfo(ctx, contractAddress, contractInfo)

// dispatch submessages then messages
Expand Down Expand Up @@ -507,8 +510,26 @@ func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply was
}, nil
}

func (k Keeper) deleteContractSecondIndex(ctx sdk.Context, contractAddress sdk.AccAddress, contractInfo *types.ContractInfo) {
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contractInfo))
// addToContractCodeSecondaryIndex adds element to the index for contracts-by-codeid queries
func (k Keeper) addToContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry), []byte{})
}

// removeFromContractCodeSecondaryIndex removes element to the index for contracts-by-codeid queries
func (k Keeper) removeFromContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry))
}

// IterateContractsByCode iterates over all contracts with given codeID ASC on code update time.
func (k Keeper) IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(codeID))
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
key := iter.Key()
if cb(key[types.AbsoluteTxPositionLen:]) {
return
}
}
}

func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error {
Expand Down Expand Up @@ -552,6 +573,19 @@ func (k Keeper) GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress)
return r
}

// getLastContractHistoryEntry returns the last element from history. To be used internally only as it panics when none exists
func (k Keeper) getLastContractHistoryEntry(ctx sdk.Context, contractAddr sdk.AccAddress) types.ContractCodeHistoryEntry {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr))
iter := prefixStore.ReverseIterator(nil, nil)
var r types.ContractCodeHistoryEntry
if !iter.Valid() {
// all contracts have a history
panic(fmt.Sprintf("no history for %s", contractAddr.String()))
}
k.cdc.MustUnmarshalBinaryBare(iter.Value(), &r)
return r
}

// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-smart")
Expand Down Expand Up @@ -623,10 +657,10 @@ func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress)
return store.Has(types.GetContractAddressKey(contractAddress))
}

// storeContractInfo persists the ContractInfo. No secondary index updated here.
func (k Keeper) storeContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(contract))
store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contract), []byte{})
}

func (k Keeper) IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) {
Expand Down Expand Up @@ -1003,6 +1037,7 @@ func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *
historyEntry := c.ResetFromGenesis(ctx)
k.appendToContractHistory(ctx, contractAddr, historyEntry)
k.storeContractInfo(ctx, contractAddr, c)
k.addToContractCodeSecondaryIndex(ctx, contractAddr, historyEntry)
return k.importContractState(ctx, contractAddr, state)
}

Expand Down
Loading

0 comments on commit c67cf14

Please sign in to comment.