Skip to content

Commit c67cf14

Browse files
authored
Merge pull request cosmos#497 from CosmWasm/list_contract_323
Improve list contracts by code query
2 parents 9ebeb85 + 8c7967e commit c67cf14

18 files changed

+297
-494
lines changed

contrib/local/02-contracts.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ wasmd tx wasm instantiate "$CODE_ID" "$INIT" --admin=$(wasmd keys show validator
2525
--from validator --amount="100ustake" --label "local0.1.0" \
2626
--gas 1000000 -y --chain-id=testing -b block | jq
2727

28-
CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" -o json | jq -r '.contract_infos[-1].address')
28+
CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" -o json | jq -r '.contracts[-1]')
2929
echo "* Contract address: $CONTRACT"
3030
echo "### Query all"
3131
RESP=$(wasmd query wasm contract-state all "$CONTRACT" -o json)

contrib/local/03-grpc-queries.sh

+9-7
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@ set -o errexit -o nounset -o pipefail
44
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
55

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

911
echo "### List all codes"
10-
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 \
12+
RESP=$(grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
1113
localhost:9090 cosmwasm.wasm.v1beta1.Query/Codes)
1214
echo "$RESP" | jq
1315

1416
CODE_ID=$(echo "$RESP" | jq -r '.codeInfos[-1].codeId')
15-
echo "### List contract by code"
16-
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 \
17+
echo "### List contracts by code"
18+
RESP=$(grpcurl -plaintext -import-path $PROTO_THRD -import-path $PROTO_WASMD -proto $PROTO_WASMD_QUERY \
1719
-d "{\"codeId\": $CODE_ID}" localhost:9090 cosmwasm.wasm.v1beta1.Query/ContractsByCode )
1820
echo $RESP | jq
1921

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

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

2931
echo "Empty state due to 'burner' contract cleanup"

docs/proto/proto-docs.md

+1-19
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555

5656
- [cosmwasm/wasm/v1beta1/query.proto](#cosmwasm/wasm/v1beta1/query.proto)
5757
- [CodeInfoResponse](#cosmwasm.wasm.v1beta1.CodeInfoResponse)
58-
- [ContractInfoWithAddress](#cosmwasm.wasm.v1beta1.ContractInfoWithAddress)
5958
- [QueryAllContractStateRequest](#cosmwasm.wasm.v1beta1.QueryAllContractStateRequest)
6059
- [QueryAllContractStateResponse](#cosmwasm.wasm.v1beta1.QueryAllContractStateResponse)
6160
- [QueryCodeRequest](#cosmwasm.wasm.v1beta1.QueryCodeRequest)
@@ -814,23 +813,6 @@ CodeInfoResponse contains code meta data from CodeInfo
814813

815814

816815

817-
<a name="cosmwasm.wasm.v1beta1.ContractInfoWithAddress"></a>
818-
819-
### ContractInfoWithAddress
820-
ContractInfoWithAddress adds the address (key) to the ContractInfo
821-
representation
822-
823-
824-
| Field | Type | Label | Description |
825-
| ----- | ---- | ----- | ----------- |
826-
| `address` | [string](#string) | | |
827-
| `contract_info` | [ContractInfo](#cosmwasm.wasm.v1beta1.ContractInfo) | | |
828-
829-
830-
831-
832-
833-
834816
<a name="cosmwasm.wasm.v1beta1.QueryAllContractStateRequest"></a>
835817

836818
### QueryAllContractStateRequest
@@ -1020,7 +1002,7 @@ Query/ContractsByCode RPC method
10201002

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

10261008

proto/cosmwasm/wasm/v1beta1/query.proto

+3-14
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,12 @@ message QueryContractsByCodeRequest {
100100
cosmos.base.query.v1beta1.PageRequest pagination = 2;
101101
}
102102

103-
// ContractInfoWithAddress adds the address (key) to the ContractInfo
104-
// representation
105-
message ContractInfoWithAddress {
106-
option (gogoproto.equal) = true;
107-
108-
string address = 1;
109-
ContractInfo contract_info = 2 [
110-
(gogoproto.embed) = true,
111-
(gogoproto.nullable) = false,
112-
(gogoproto.jsontag) = ""
113-
];
114-
}
115103
// QueryContractsByCodeResponse is the response type for the
116104
// Query/ContractsByCode RPC method
117105
message QueryContractsByCodeResponse {
118-
repeated ContractInfoWithAddress contract_infos = 1
119-
[ (gogoproto.nullable) = false ];
106+
// contracts are a set of contract addresses
107+
repeated string contracts = 1;
108+
120109
// pagination defines the pagination in the response.
121110
cosmos.base.query.v1beta1.PageResponse pagination = 2;
122111
}

x/wasm/alias.go

-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ type (
125125
ContractInfo = types.ContractInfo
126126
CreatedAt = types.AbsoluteTxPosition
127127
Config = types.WasmConfig
128-
ContractInfoWithAddress = types.ContractInfoWithAddress
129128
CodeInfoResponse = types.CodeInfoResponse
130129
MessageHandler = keeper.SDKMessageHandler
131130
BankEncoder = keeper.BankEncoder

x/wasm/keeper/genesis_test.go

+22-24
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,14 @@ import (
66
"encoding/base64"
77
"errors"
88
"fmt"
9-
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
10-
"io/ioutil"
11-
"math/rand"
12-
"os"
13-
"testing"
14-
"time"
15-
169
"github.com/CosmWasm/wasmd/x/wasm/types"
1710
wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
1811
"github.com/cosmos/cosmos-sdk/store"
12+
"github.com/cosmos/cosmos-sdk/store/prefix"
1913
sdk "github.com/cosmos/cosmos-sdk/types"
2014
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
2115
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
16+
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
2217
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
2318
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
2419
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
@@ -30,6 +25,11 @@ import (
3025
"github.com/tendermint/tendermint/proto/tendermint/crypto"
3126
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
3227
dbm "github.com/tendermint/tm-db"
28+
"io/ioutil"
29+
"math/rand"
30+
"os"
31+
"testing"
32+
"time"
3333
)
3434

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

106-
// reset ContractInfo in source DB for comparison with dest DB
106+
// setup new instances
107+
dstKeeper, dstCtx, dstStoreKeys := setupKeeper(t)
108+
109+
// reset contract code index in source DB for comparison with dest DB
107110
wasmKeeper.IterateContractInfo(srcCtx, func(address sdk.AccAddress, info wasmTypes.ContractInfo) bool {
108-
wasmKeeper.deleteContractSecondIndex(srcCtx, address, &info)
109-
info.ResetFromGenesis(srcCtx)
110-
wasmKeeper.storeContractInfo(srcCtx, address, &info)
111+
wasmKeeper.removeFromContractCodeSecondaryIndex(srcCtx, address, wasmKeeper.getLastContractHistoryEntry(srcCtx, address))
112+
prefixStore := prefix.NewStore(srcCtx.KVStore(wasmKeeper.storeKey), types.GetContractCodeHistoryElementPrefix(address))
113+
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
114+
prefixStore.Delete(iter.Key())
115+
}
116+
x := &info
117+
newHistory := x.ResetFromGenesis(dstCtx)
118+
wasmKeeper.storeContractInfo(srcCtx, address, x)
119+
wasmKeeper.addToContractCodeSecondaryIndex(srcCtx, address, newHistory)
120+
wasmKeeper.appendToContractHistory(srcCtx, address, newHistory)
111121
return false
112122
})
113123

114124
// re-import
115-
dstKeeper, dstCtx, dstStoreKeys := setupKeeper(t)
116-
117125
var importState wasmTypes.GenesisState
118-
err = wasmKeeper.cdc.UnmarshalJSON(exportedGenesis, &importState)
126+
err = dstKeeper.cdc.UnmarshalJSON(exportedGenesis, &importState)
119127
require.NoError(t, err)
120128
InitGenesis(dstCtx, dstKeeper, importState, &StakingKeeperMock{}, TestHandler(contractKeeper))
121129

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

127135
for i := 0; srcIT.Valid(); i++ {
128-
isContractHistory := srcStoreKeys[j].Name() == types.StoreKey && bytes.HasPrefix(srcIT.Key(), types.ContractCodeHistoryElementPrefix)
129-
if isContractHistory {
130-
// only skip history entries because we know they are different
131-
// from genesis they are merged into 1 single entry
132-
srcIT.Next()
133-
if bytes.HasPrefix(dstIT.Key(), types.ContractCodeHistoryElementPrefix) {
134-
dstIT.Next()
135-
}
136-
continue
137-
}
138136
require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %x", srcStoreKeys[j].Name(), srcIT.Key())
139137
require.Equal(t, srcIT.Key(), dstIT.Key(), i)
140138
require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", srcStoreKeys[j].Name(), i, srcIT.Key())

x/wasm/keeper/keeper.go

+40-5
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,10 @@ func (k Keeper) instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.A
296296
}
297297

298298
// store contract before dispatch so that contract could be called back
299+
historyEntry := contractInfo.InitialHistory(initMsg)
300+
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
301+
k.appendToContractHistory(ctx, contractAddress, historyEntry)
299302
k.storeContractInfo(ctx, contractAddress, &contractInfo)
300-
k.appendToContractHistory(ctx, contractAddress, contractInfo.InitialHistory(initMsg))
301303

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

410412
// delete old secondary index entry
411-
k.deleteContractSecondIndex(ctx, contractAddress, contractInfo)
413+
k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress))
412414
// persist migration updates
413415
historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg)
414416
k.appendToContractHistory(ctx, contractAddress, historyEntry)
417+
k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry)
415418
k.storeContractInfo(ctx, contractAddress, contractInfo)
416419

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

510-
func (k Keeper) deleteContractSecondIndex(ctx sdk.Context, contractAddress sdk.AccAddress, contractInfo *types.ContractInfo) {
511-
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, contractInfo))
513+
// addToContractCodeSecondaryIndex adds element to the index for contracts-by-codeid queries
514+
func (k Keeper) addToContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
515+
store := ctx.KVStore(k.storeKey)
516+
store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry), []byte{})
517+
}
518+
519+
// removeFromContractCodeSecondaryIndex removes element to the index for contracts-by-codeid queries
520+
func (k Keeper) removeFromContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) {
521+
ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry))
522+
}
523+
524+
// IterateContractsByCode iterates over all contracts with given codeID ASC on code update time.
525+
func (k Keeper) IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) {
526+
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(codeID))
527+
for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() {
528+
key := iter.Key()
529+
if cb(key[types.AbsoluteTxPositionLen:]) {
530+
return
531+
}
532+
}
512533
}
513534

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

576+
// getLastContractHistoryEntry returns the last element from history. To be used internally only as it panics when none exists
577+
func (k Keeper) getLastContractHistoryEntry(ctx sdk.Context, contractAddr sdk.AccAddress) types.ContractCodeHistoryEntry {
578+
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr))
579+
iter := prefixStore.ReverseIterator(nil, nil)
580+
var r types.ContractCodeHistoryEntry
581+
if !iter.Valid() {
582+
// all contracts have a history
583+
panic(fmt.Sprintf("no history for %s", contractAddr.String()))
584+
}
585+
k.cdc.MustUnmarshalBinaryBare(iter.Value(), &r)
586+
return r
587+
}
588+
555589
// QuerySmart queries the smart contract itself.
556590
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
557591
defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-smart")
@@ -623,10 +657,10 @@ func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress)
623657
return store.Has(types.GetContractAddressKey(contractAddress))
624658
}
625659

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

632666
func (k Keeper) IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) {
@@ -1003,6 +1037,7 @@ func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *
10031037
historyEntry := c.ResetFromGenesis(ctx)
10041038
k.appendToContractHistory(ctx, contractAddr, historyEntry)
10051039
k.storeContractInfo(ctx, contractAddr, c)
1040+
k.addToContractCodeSecondaryIndex(ctx, contractAddr, historyEntry)
10061041
return k.importContractState(ctx, contractAddr, state)
10071042
}
10081043

0 commit comments

Comments
 (0)