Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve list contracts by code query #497

Merged
merged 3 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]')
alpe marked this conversation as resolved.
Show resolved Hide resolved
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) {
alpe marked this conversation as resolved.
Show resolved Hide resolved
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 {
alpe marked this conversation as resolved.
Show resolved Hide resolved
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
58 changes: 54 additions & 4 deletions x/wasm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,20 +950,29 @@ func TestMigrateReplacesTheSecondIndex(t *testing.T) {
store := ctx.KVStore(keepers.WasmKeeper.storeKey)
oldContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract)
require.NotNil(t, oldContractInfo)
exists := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, oldContractInfo))
createHistoryEntry := types.ContractCodeHistoryEntry{
CodeID: example.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
}
exists := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
require.True(t, exists)

ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // increment for different block
// when do migrate
newCodeExample := StoreBurnerExampleContract(t, ctx, keepers)
migMsgBz := BurnerExampleInitMsg{Payout: example.CreatorAddr}.GetBytes(t)
_, err := keepers.ContractKeeper.Migrate(ctx, example.Contract, example.CreatorAddr, newCodeExample.CodeID, migMsgBz)
require.NoError(t, err)

// then the new index exists
newContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract)
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, newContractInfo))
migrateHistoryEntry := types.ContractCodeHistoryEntry{
CodeID: newCodeExample.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
}
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, migrateHistoryEntry))
require.True(t, exists)
// and the old index was removed
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, oldContractInfo))
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
require.False(t, exists)
}

Expand Down Expand Up @@ -1046,6 +1055,47 @@ func TestMigrateWithDispatchedMessage(t *testing.T) {
assert.Equal(t, deposit, balance)
}

func TestIterateContractsByCode(t *testing.T) {
alpe marked this conversation as resolved.
Show resolved Hide resolved
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
k, c := keepers.WasmKeeper, keepers.ContractKeeper
example1 := InstantiateHackatomExampleContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
example2 := InstantiateIBCReflectContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
initMsg := HackatomExampleInitMsg{
Verifier: RandomAccountAddress(t),
Beneficiary: RandomAccountAddress(t),
}.GetBytes(t)
contractAddr3, _, err := c.Instantiate(ctx, example1.CodeID, example1.CreatorAddr, nil, initMsg, "foo", nil)
require.NoError(t, err)
specs := map[string]struct {
codeID uint64
exp []sdk.AccAddress
}{
"multiple results": {
codeID: example1.CodeID,
exp: []sdk.AccAddress{example1.Contract, contractAddr3},
},
"single results": {
codeID: example2.CodeID,
exp: []sdk.AccAddress{example2.Contract},
},
"empty results": {
codeID: 99999,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
var gotAddr []sdk.AccAddress
k.IterateContractsByCode(ctx, spec.codeID, func(address sdk.AccAddress) bool {
gotAddr = append(gotAddr, address)
return false
})
assert.Equal(t, spec.exp, gotAddr)
})
}
}

type sudoMsg struct {
// This is a tongue-in-check demo command. This is not the intended purpose of Sudo.
// Here we show that some priviledged Go module can make a call that should never be exposed
Expand Down
Loading