Use prefix store for contract history

This commit is contained in:
Alex Peters 2020-07-20 12:39:59 +02:00
parent 1c4d4d8f0e
commit 72a1a45030
No known key found for this signature in database
GPG Key ID: BD28388D49EE708D
14 changed files with 109 additions and 148 deletions

View File

@ -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

View File

@ -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,

View File

@ -1,6 +1,7 @@
package keeper
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json"
@ -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
@ -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())
}
}
}
@ -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 := `
{
@ -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()) {

View File

@ -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
}
@ -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 {
@ -376,6 +377,27 @@ 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) {
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)
}
entries = append(entries, newEntries...)
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")
@ -613,17 +635,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 {

View File

@ -285,7 +285,7 @@ func TestInstantiate(t *testing.T) {
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", nil)
codeID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
@ -301,28 +301,27 @@ func TestInstantiate(t *testing.T) {
gasBefore := ctx.GasMeter().GasConsumed()
// create with no balance is also legal
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 1", nil)
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
gasAfter := ctx.GasMeter().GasConsumed()
require.Equal(t, uint64(0x1175b), gasAfter-gasBefore)
require.Equal(t, uint64(0x12313), gasAfter-gasBefore)
// ensure it is stored properly
info := keeper.GetContractInfo(ctx, addr)
info := keeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, info)
assert.Equal(t, info.Creator, creator)
assert.Equal(t, info.CodeID, contractID)
assert.Equal(t, info.CodeID, codeID)
assert.Equal(t, info.Label, "demo contract 1")
exp := []types.ContractCodeHistoryEntry{{
Operation: types.InitContractCodeHistoryType,
CodeID: contractID,
CodeID: codeID,
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: json.RawMessage(initMsgBz),
}}
assert.Equal(t, exp, info.ContractCodeHistory)
assert.Equal(t, exp, keeper.getContractHistory(ctx, contractAddr))
}
func TestInstantiateWithDeposit(t *testing.T) {
@ -535,7 +534,7 @@ func TestExecute(t *testing.T) {
// make sure gas is properly deducted from ctx
gasAfter := ctx.GasMeter().GasConsumed()
require.Equal(t, uint64(0x11c49), gasAfter-gasBefore)
require.Equal(t, uint64(0x11aa2), gasAfter-gasBefore)
// ensure bob now exists and got both payments released
bobAcct = accKeeper.GetAccount(ctx, bob)
@ -873,17 +872,17 @@ func TestMigrate(t *testing.T) {
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
addr, err := keeper.Instantiate(ctx, originalCodeID, creator, spec.admin, initMsgBz, "demo contract", nil)
contractAddr, err := keeper.Instantiate(ctx, originalCodeID, creator, spec.admin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
contractAddr = spec.overrideContractAddr
}
_, err = keeper.Migrate(ctx, addr, spec.caller, spec.codeID, spec.migrateMsg)
_, err = keeper.Migrate(ctx, contractAddr, 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
}
cInfo := keeper.GetContractInfo(ctx, addr)
cInfo := keeper.GetContractInfo(ctx, contractAddr)
assert.Equal(t, spec.codeID, cInfo.CodeID)
expHistory := []types.ContractCodeHistoryEntry{{
@ -897,9 +896,9 @@ func TestMigrate(t *testing.T) {
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: spec.migrateMsg,
}}
assert.Equal(t, expHistory, cInfo.ContractCodeHistory)
assert.Equal(t, expHistory, keeper.getContractHistory(ctx, contractAddr))
m := keeper.QueryRaw(ctx, addr, []byte("config"))
m := keeper.QueryRaw(ctx, contractAddr, []byte("config"))
require.Len(t, m, 1)
var stored map[string][]byte
require.NoError(t, json.Unmarshal(m[0].Value, &stored))

View File

@ -110,8 +110,7 @@ func TestInstantiateProposal(t *testing.T) {
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: src.InitMsg,
}}
assert.Equal(t, expHistory, cInfo.ContractCodeHistory)
assert.Equal(t, expHistory, wasmKeeper.getContractHistory(ctx, contractAddr))
}
func TestMigrateProposal(t *testing.T) {
@ -188,7 +187,7 @@ func TestMigrateProposal(t *testing.T) {
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: src.MigrateMsg,
}}
assert.Equal(t, expHistory, cInfo.ContractCodeHistory)
assert.Equal(t, expHistory, wasmKeeper.getContractHistory(ctx, contractAddr))
}

View File

@ -84,10 +84,6 @@ func queryContractInfo(ctx sdk.Context, bech string, req abci.RequestQuery, keep
// redact clears all fields not in the public api
func redact(info *types.ContractInfo) {
info.Created = nil
for i := range info.ContractCodeHistory {
info.ContractCodeHistory[i].Updated = nil
info.ContractCodeHistory[i].Msg = nil
}
}
func queryContractListByCode(ctx sdk.Context, codeIDstr string, req abci.RequestQuery, keeper Keeper) ([]byte, error) {

View File

@ -214,9 +214,5 @@ func TestListContractByCodeOrdering(t *testing.T) {
assert.NotEmpty(t, contract.Address)
// ensure these are not shown
assert.Nil(t, contract.Created)
for _, entry := range contract.ContractCodeHistory {
assert.Nil(t, entry.Updated)
assert.Nil(t, entry.Msg)
}
}
}

View File

@ -27,11 +27,6 @@ func FuzzContractInfo(m *types.ContractInfo, c fuzz.Continue) {
FuzzAddr(&m.Admin, c)
m.Label = c.RandString()
c.Fuzz(&m.Created)
historyElements := c.Int() % 128 // 128 should be enough for tests
m.ContractCodeHistory = make([]types.ContractCodeHistoryEntry, historyElements)
for i := range m.ContractCodeHistory {
c.Fuzz(&m.ContractCodeHistory[i])
}
}
func FuzzContractCodeHistory(m *types.ContractCodeHistoryEntry, c fuzz.Continue) {

View File

@ -86,9 +86,6 @@ func (c Contract) ValidateBasic() error {
if c.ContractInfo.Created != nil {
return sdkerrors.Wrap(ErrInvalid, "created must be empty")
}
if len(c.ContractInfo.ContractCodeHistory) != 0 {
return sdkerrors.Wrap(ErrInvalid, "history must be empty")
}
for i := range c.ContractState {
if err := c.ContractState[i].ValidateBasic(); err != nil {
return sdkerrors.Wrapf(err, "contract state %d", i)

View File

@ -127,12 +127,6 @@ func TestContractValidateBasic(t *testing.T) {
},
expError: true,
},
"contract with history set": {
srcMutator: func(c *Contract) {
c.ContractInfo.ContractCodeHistory = []ContractCodeHistoryEntry{{}}
},
expError: true,
},
"contract state invalid": {
srcMutator: func(c *Contract) {
c.ContractState = append(c.ContractState, Model{})

View File

@ -31,12 +31,14 @@ const ( // event attributes
// nolint
var (
KeyLastCodeID = []byte("lastCodeId")
KeyLastInstanceID = []byte("lastContractId")
CodeKeyPrefix = []byte{0x01}
ContractKeyPrefix = []byte{0x02}
ContractStorePrefix = []byte{0x03}
SequenceKeyPrefix = []byte{0x04}
ContractHistoryStorePrefix = []byte{0x05}
CodeKeyPrefix = []byte{0x01}
ContractKeyPrefix = []byte{0x02}
ContractStorePrefix = []byte{0x03}
KeyLastCodeID = append(SequenceKeyPrefix, []byte("lastCodeId")...)
KeyLastInstanceID = append(SequenceKeyPrefix, []byte("lastContractId")...)
)
// GetCodeKey constructs the key for retreiving the ID for the WASM code

View File

@ -88,7 +88,6 @@ func ContractFixture(mutators ...func(*Contract)) Contract {
func OnlyGenesisFields(info *ContractInfo) {
info.Created = nil
info.ContractCodeHistory = nil
}
func ContractInfoFixture(mutators ...func(*ContractInfo)) ContractInfo {

View File

@ -2,7 +2,6 @@ package types
import (
"encoding/json"
"sort"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
tmBytes "github.com/tendermint/tendermint/libs/bytes"
@ -93,24 +92,19 @@ type ContractInfo struct {
Label string `json:"label"`
// never show this in query results, just use for sorting
// (Note: when using json tag "-" amino refused to serialize it...)
Created *AbsoluteTxPosition `json:"created,omitempty"`
ContractCodeHistory []ContractCodeHistoryEntry `json:"contract_code_history,omitempty"`
Created *AbsoluteTxPosition `json:"created,omitempty"`
}
func (c *ContractInfo) AddMigration(ctx sdk.Context, codeID uint64, msg []byte) {
h := ContractCodeHistoryEntry{
Operation: MigrateContractCodeHistoryType,
CodeID: codeID,
Updated: NewAbsoluteTxPosition(ctx),
Msg: msg,
// NewContractInfo creates a new instance of a given WASM contract info
func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, label string, createdAt *AbsoluteTxPosition) ContractInfo {
return ContractInfo{
CodeID: codeID,
Creator: creator,
Admin: admin,
Label: label,
Created: createdAt,
}
c.ContractCodeHistory = append(c.ContractCodeHistory, h)
sort.Slice(c.ContractCodeHistory, func(i, j int) bool {
return c.ContractCodeHistory[i].Updated.LessThan(c.ContractCodeHistory[j].Updated)
})
c.CodeID = codeID
}
func (c *ContractInfo) ValidateBasic() error {
if c.CodeID == 0 {
return sdkerrors.Wrap(ErrEmpty, "code id")
@ -129,15 +123,34 @@ func (c *ContractInfo) ValidateBasic() error {
return nil
}
// ResetFromGenesis resets contracts timestamp and history.
func (c *ContractInfo) ResetFromGenesis(ctx sdk.Context) {
c.Created = NewAbsoluteTxPosition(ctx)
func (c ContractInfo) InitialHistory(initMsg []byte) ContractCodeHistoryEntry {
return ContractCodeHistoryEntry{
Operation: InitContractCodeHistoryType,
CodeID: c.CodeID,
Updated: c.Created,
Msg: initMsg,
}
}
func (c *ContractInfo) AddMigration(ctx sdk.Context, codeID uint64, msg []byte) ContractCodeHistoryEntry {
h := ContractCodeHistoryEntry{
Operation: MigrateContractCodeHistoryType,
CodeID: codeID,
Updated: NewAbsoluteTxPosition(ctx),
Msg: msg,
}
c.CodeID = codeID
return h
}
// ResetFromGenesis resets contracts timestamp and history.
func (c *ContractInfo) ResetFromGenesis(ctx sdk.Context) ContractCodeHistoryEntry {
c.Created = NewAbsoluteTxPosition(ctx)
return ContractCodeHistoryEntry{
Operation: GenesisContractCodeHistoryType,
CodeID: c.CodeID,
Updated: c.Created,
}
c.ContractCodeHistory = []ContractCodeHistoryEntry{h}
}
// AbsoluteTxPosition can be used to sort contracts
@ -173,23 +186,6 @@ func NewAbsoluteTxPosition(ctx sdk.Context) *AbsoluteTxPosition {
}
}
// NewContractInfo creates a new instance of a given WASM contract info
func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, createdAt *AbsoluteTxPosition) ContractInfo {
return ContractInfo{
CodeID: codeID,
Creator: creator,
Admin: admin,
Label: label,
Created: createdAt,
ContractCodeHistory: []ContractCodeHistoryEntry{{
Operation: InitContractCodeHistoryType,
CodeID: codeID,
Updated: createdAt,
Msg: initMsg,
}},
}
}
// NewEnv initializes the environment for a contract instance
func NewEnv(ctx sdk.Context, creator sdk.AccAddress, deposit sdk.Coins, contractAddr sdk.AccAddress) wasmTypes.Env {
// safety checks before casting below